diff --git a/Project.toml b/Project.toml index 2a77d65..ef2161c 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QXGraphDecompositions" uuid = "b15f1ded-d19a-4aca-a940-1991fcfc7750" authors = ["QuantEx team"] -version = "0.1.4" +version = "0.1.5" [deps] FlowCutterPACE17_jll = "008204e2-cd5c-5c6d-9360-d31f32b5f6c2" diff --git a/src/treewidth.jl b/src/treewidth.jl index fbd34ef..dee466f 100644 --- a/src/treewidth.jl +++ b/src/treewidth.jl @@ -15,10 +15,10 @@ export quickbb # **************************************************************************************** # """ - flow_cutter(G::lg.AbstractGraph; time::Integer=10) + flow_cutter(G::lg.AbstractGraph, time::Integer=10; seed::Integer=-1) -Use the flow cutter algorithm to find a tree decomposition for the graph `G` with minimal -treewidth. +Run the flow cutter algorithm for `time` seconds to find a tree decomposition for the graph +`G` with minimal treewidth. The tree decomposition is returned in a dictionary with the following key value pairs: - `:treewidth` => The treewidth of the tree decomposotion, @@ -27,9 +27,8 @@ The tree decomposition is returned in a dictionary with the following key value - `:b_n` => The n-th bag of the decomposition where n is an integer from 1 to the number of bags in the decomposition. A bag is an array of vertices of `G`. - `:edges` => An array of integer pairs indicating the edges of the tree decomposition. - -If a tree decomposition is not found within the allocated time then an empty dictionary is -returned. +- `:comments` => An array of comments created by flow cutter regarding heuristics used and if + it had enough time to find a decomposition. The flow cutter algorithm and how it is used to find tree decompositions is described by Michael Hamann and Ben Strasser in the following papers: @@ -38,9 +37,10 @@ Graph Bisection with Pareto Optimization - https://dl.acm.org/doi/10.1145/317304 Computing Tree Decompositions with FlowCutter - https://arxiv.org/abs/1709.08949 # Keywords -- `time::Integer=10`: The number of seconds to run flow cutter for. +- `seed::Integer=-1`: The seed used by flow cutter. Most be a non negative integer, + otherwise the seed is set by flow cutter. """ -function flow_cutter(G::lg.AbstractGraph; time::Integer=10) +function flow_cutter(G::lg.AbstractGraph, time::Integer=10; seed::Integer=-1) # Create a temporary directory with the input and output files for flow cutter. out = Pipe() flow_cutter_dir = dirname(FlowCutterPACE17_jll.flow_cutter_pace17_path) @@ -51,12 +51,15 @@ function flow_cutter(G::lg.AbstractGraph; time::Integer=10) # Call and run flow cutter for the specified duration of time. flow_cutter_pace17() do exe - flow_cutter_cmd = [exe, graph_file] + flow_cutter_cmd = [exe] + seed >= 0 && append!(flow_cutter_cmd, ["-s", string(0)]) flow_cutter_cmd = Cmd(flow_cutter_cmd) - flow_cutter_proc = run(pipeline(flow_cutter_cmd, td_file); wait=false) + flow_cutter_proc = run(pipeline(graph_file, flow_cutter_cmd, td_file); wait=false) while !process_running(flow_cutter_proc) # Wait until process has started. sleep(1) end + # TODO: Would be preferable to measure the execution time of the flow cutter + # process here. sleep(time) kill(flow_cutter_proc) end @@ -64,15 +67,16 @@ function flow_cutter(G::lg.AbstractGraph; time::Integer=10) # Read the output of flow cutter into a dictionary to be returned to the user. td = Dict{Symbol, Any}() - length(flow_cutter_output) > 0 && (td[:edges] = Tuple{Int, Int}[]) + td[:edges] = Tuple{Int, Int}[] + td[:comments] = String[] for line in flow_cutter_output words = split(line) if words[1] == "c" - continue + push!(td[:comments], line) elseif words[1] == "s" td[:num_bags] = parse(Int, words[3]) td[:treewidth] = parse(Int, words[4]) - 1 - td[:num_vertices] = parse(Int, words[4]) + td[:num_vertices] = parse(Int, words[5]) elseif words[1] == "b" td[Symbol("b_"*words[2])] = parse.(Int, words[3:end]) else @@ -83,7 +87,7 @@ function flow_cutter(G::lg.AbstractGraph; time::Integer=10) end end -flow_cutter(G::LabeledGraph; kwargs...) = flow_cutter(G.graph; kwargs...) +flow_cutter(G::LabeledGraph, time::Integer=10; kwargs...) = flow_cutter(G.graph, time; kwargs...) diff --git a/test/elimination_order_tests.jl b/test/elimination_order_tests.jl index 6a1f024..ea71941 100644 --- a/test/elimination_order_tests.jl +++ b/test/elimination_order_tests.jl @@ -43,7 +43,7 @@ # Get a tree decomposition of the square lattice graph and test converting it to an # elimination order. - tree_decomp = flow_cutter(G; time=15) + tree_decomp = flow_cutter(G, 15) td_order = order_from_tree_decomposition(tree_decomp) @test find_treewidth_from_order(G, td_order) == tree_decomp[:treewidth] @test length(td_order) == N*N diff --git a/test/flow_cutter_tests.jl b/test/flow_cutter_tests.jl index f464c80..93603b8 100644 --- a/test/flow_cutter_tests.jl +++ b/test/flow_cutter_tests.jl @@ -10,7 +10,7 @@ # Check if the treewidth and number of bags is correct and check that the tree # decomposition has no edges. - tree_decomp = flow_cutter(G; time=20) + tree_decomp = flow_cutter(G, 30; seed=42) @test tree_decomp[:treewidth] == 9 @test tree_decomp[:num_bags] == 1 @test length(tree_decomp[:edges]) == 0 @@ -32,12 +32,9 @@ end # Check if the treewidth, number of bags and number of edges is correct. - tree_decomp = flow_cutter(G; time=20) + tree_decomp = flow_cutter(G, 30) @test tree_decomp[:treewidth] == N-1 @test tree_decomp[:num_bags] == 2 + @test tree_decomp[:num_vertices] == N + n @test length(tree_decomp[:edges]) == 1 - - # Check that an empty dictionary is returned if no tree decomposition is found. - tree_decomp = flow_cutter(G; time=0) - @test isempty(tree_decomp) end \ No newline at end of file