From 178981551b73c42bec76891603db5dc21865de73 Mon Sep 17 00:00:00 2001 From: etienneINSA Date: Fri, 26 Apr 2024 11:29:12 +0200 Subject: [PATCH 1/5] fix kruskal and steiner tree --- src/spanningtrees/kruskal.jl | 1 + src/steinertree/steiner_tree.jl | 2 ++ test/spanningtrees/kruskal.jl | 5 +++++ test/steinertree/steiner_tree.jl | 5 +++++ 4 files changed, 13 insertions(+) diff --git a/src/spanningtrees/kruskal.jl b/src/spanningtrees/kruskal.jl index 52cb936ae..452fbf4f1 100644 --- a/src/spanningtrees/kruskal.jl +++ b/src/spanningtrees/kruskal.jl @@ -15,6 +15,7 @@ function kruskal_mst end connected_vs = IntDisjointSets(nv(g)) mst = Vector{edgetype(g)}() + nv(g) <= 1 && return mst sizehint!(mst, nv(g) - 1) weights = Vector{T}() diff --git a/src/steinertree/steiner_tree.jl b/src/steinertree/steiner_tree.jl index 6f7713cd1..96a19440f 100644 --- a/src/steinertree/steiner_tree.jl +++ b/src/steinertree/steiner_tree.jl @@ -44,6 +44,8 @@ function steiner_tree end g::AG::(!IsDirected), term_vert::Vector{<:Integer}, distmx::AbstractMatrix{U}=weights(g) ) where {U<:Real,T,AG<:AbstractGraph{T}} nvg = nv(g) + length(term_vert) == 0 && return SimpleGraph() + length(term_vert) == 1 && return SimpleGraph(first(term_vert)) term_to_actual = T.(term_vert) unique!(term_to_actual) diff --git a/test/spanningtrees/kruskal.jl b/test/spanningtrees/kruskal.jl index b45eb8cb6..94b534ec1 100644 --- a/test/spanningtrees/kruskal.jl +++ b/test/spanningtrees/kruskal.jl @@ -44,4 +44,9 @@ @test sort([(src(e), dst(e)) for e in mst2]) == sort([(src(e), dst(e)) for e in vec2]) @test sort([(src(e), dst(e)) for e in max_mst2]) == sort([(src(e), dst(e)) for e in max_vec2]) end + + # non regression test for #362 + g = Graph() + mst = @inferred(kruskal_mst(g)) + @test isempty(mst) end diff --git a/test/steinertree/steiner_tree.jl b/test/steinertree/steiner_tree.jl index c5ee13b90..caef7618d 100644 --- a/test/steinertree/steiner_tree.jl +++ b/test/steinertree/steiner_tree.jl @@ -19,6 +19,11 @@ g_copy = SimpleGraph(g) Graphs.filter_non_term_leaves!(g_copy, [2, 5]) @test ne(g_copy) == 2 # [Edge(2, 1), Edge(1, 5)] + + # non regression test for #362 + g_st = @inferred(steiner_tree(g, [2])) + @test ne(g_st) == 0 + end g4 = path_graph(11) From baae69bc0ef30613337c8b2befdf7d9c3c4fc88d Mon Sep 17 00:00:00 2001 From: etienneINSA Date: Fri, 26 Apr 2024 11:52:25 +0200 Subject: [PATCH 2/5] fix eltype --- src/steinertree/steiner_tree.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/steinertree/steiner_tree.jl b/src/steinertree/steiner_tree.jl index 96a19440f..d6a758b0b 100644 --- a/src/steinertree/steiner_tree.jl +++ b/src/steinertree/steiner_tree.jl @@ -44,8 +44,8 @@ function steiner_tree end g::AG::(!IsDirected), term_vert::Vector{<:Integer}, distmx::AbstractMatrix{U}=weights(g) ) where {U<:Real,T,AG<:AbstractGraph{T}} nvg = nv(g) - length(term_vert) == 0 && return SimpleGraph() - length(term_vert) == 1 && return SimpleGraph(first(term_vert)) + length(term_vert) == 0 && return SimpleGraph{T}() + length(term_vert) == 1 && return SimpleGraph{T}(first(term_vert)) term_to_actual = T.(term_vert) unique!(term_to_actual) From e89128a3dc6289acdd2645268d4d96281931ac3e Mon Sep 17 00:00:00 2001 From: etienneINSA Date: Fri, 26 Apr 2024 17:13:40 +0200 Subject: [PATCH 3/5] formatting --- src/spanningtrees/kruskal.jl | 42 ++++++------- src/steinertree/steiner_tree.jl | 102 +++++++++++++++---------------- test/spanningtrees/kruskal.jl | 92 ++++++++++++++-------------- test/steinertree/steiner_tree.jl | 102 +++++++++++++++---------------- 4 files changed, 169 insertions(+), 169 deletions(-) diff --git a/src/spanningtrees/kruskal.jl b/src/spanningtrees/kruskal.jl index 452fbf4f1..80b4221f6 100644 --- a/src/spanningtrees/kruskal.jl +++ b/src/spanningtrees/kruskal.jl @@ -1,5 +1,5 @@ """ - kruskal_mst(g, distmx=weights(g); minimize=true) + kruskal_mst(g, distmx=weights(g); minimize=true) Return a vector of edges representing the minimum (by default) spanning tree of a connected, undirected graph `g` with optional distance matrix `distmx` using [Kruskal's algorithm](https://en.wikipedia.org/wiki/Kruskal%27s_algorithm). @@ -10,28 +10,28 @@ undirected graph `g` with optional distance matrix `distmx` using [Kruskal's alg function kruskal_mst end # see https://github.com/mauro3/SimpleTraits.jl/issues/47#issuecomment-327880153 for syntax @traitfn function kruskal_mst( - g::AG::(!IsDirected), distmx::AbstractMatrix{T}=weights(g); minimize=true -) where {T<:Real,U,AG<:AbstractGraph{U}} - connected_vs = IntDisjointSets(nv(g)) + g::AG::(!IsDirected), distmx::AbstractMatrix{T} = weights(g); minimize = true, +) where {T <: Real, U, AG <: AbstractGraph{U}} + connected_vs = IntDisjointSets(nv(g)) - mst = Vector{edgetype(g)}() - nv(g) <= 1 && return mst - sizehint!(mst, nv(g) - 1) + mst = Vector{edgetype(g)}() + nv(g) <= 1 && return mst + sizehint!(mst, nv(g) - 1) - weights = Vector{T}() - sizehint!(weights, ne(g)) - edge_list = collect(edges(g)) - for e in edge_list - push!(weights, distmx[src(e), dst(e)]) - end + weights = Vector{T}() + sizehint!(weights, ne(g)) + edge_list = collect(edges(g)) + for e in edge_list + push!(weights, distmx[src(e), dst(e)]) + end - for e in edge_list[sortperm(weights; rev=!minimize)] - if !in_same_set(connected_vs, src(e), dst(e)) - union!(connected_vs, src(e), dst(e)) - push!(mst, e) - (length(mst) >= nv(g) - 1) && break - end - end + for e in edge_list[sortperm(weights; rev = !minimize)] + if !in_same_set(connected_vs, src(e), dst(e)) + union!(connected_vs, src(e), dst(e)) + push!(mst, e) + (length(mst) >= nv(g) - 1) && break + end + end - return mst + return mst end diff --git a/src/steinertree/steiner_tree.jl b/src/steinertree/steiner_tree.jl index d6a758b0b..2349ef076 100644 --- a/src/steinertree/steiner_tree.jl +++ b/src/steinertree/steiner_tree.jl @@ -1,26 +1,26 @@ """ - filter_non_term_leaves!(g, term_vert) + filter_non_term_leaves!(g, term_vert) Remove edges of `g` so that all non-isolated leaves of `g` are in the set `term_vert` """ function filter_non_term_leaves!( - g::AbstractGraph{T}, term_vert::Vector{<:Integer} -) where {T<:Integer} - is_term = falses(nv(g)) - is_term[term_vert] .= true - leaves = [v for v in vertices(g) if degree(g, v) == 1 && !is_term[v]] + g::AbstractGraph{T}, term_vert::Vector{<:Integer}, +) where {T <: Integer} + is_term = falses(nv(g)) + is_term[term_vert] .= true + leaves = [v for v in vertices(g) if degree(g, v) == 1 && !is_term[v]] - while !isempty(leaves) - leaf = pop!(leaves) - for v in neighbors(g, leaf) - rem_edge!(g, v, leaf) - if degree(g, v) == 1 && !is_term[v] - push!(leaves, v) - end - end - end + while !isempty(leaves) + leaf = pop!(leaves) + for v in neighbors(g, leaf) + rem_edge!(g, v, leaf) + if degree(g, v) == 1 && !is_term[v] + push!(leaves, v) + end + end + end - return g + return g end """ @@ -41,45 +41,45 @@ Approximation Factor: 2-2/t function steiner_tree end @traitfn function steiner_tree( - g::AG::(!IsDirected), term_vert::Vector{<:Integer}, distmx::AbstractMatrix{U}=weights(g) -) where {U<:Real,T,AG<:AbstractGraph{T}} - nvg = nv(g) - length(term_vert) == 0 && return SimpleGraph{T}() - length(term_vert) == 1 && return SimpleGraph{T}(first(term_vert)) - term_to_actual = T.(term_vert) - unique!(term_to_actual) + g::AG::(!IsDirected), term_vert::Vector{<:Integer}, distmx::AbstractMatrix{U} = weights(g), +) where {U <: Real, T, AG <: AbstractGraph{T}} + nvg = nv(g) + length(term_vert) == 0 && return SimpleGraph{T}() + length(term_vert) == 1 && return SimpleGraph{T}(first(term_vert)) + term_to_actual = T.(term_vert) + unique!(term_to_actual) - # Compute the graph formed by inducing the metric closure graph of g on term_vert - nvg_mc = length(term_to_actual) - distmx_mc = Matrix{U}(undef, nvg_mc, nvg_mc) - parents = Matrix{T}(undef, nvg, nvg_mc) + # Compute the graph formed by inducing the metric closure graph of g on term_vert + nvg_mc = length(term_to_actual) + distmx_mc = Matrix{U}(undef, nvg_mc, nvg_mc) + parents = Matrix{T}(undef, nvg, nvg_mc) - for (i, v) in enumerate(term_to_actual) - d_s = dijkstra_shortest_paths(g, v, distmx) - @inbounds parents[:, i] = d_s.parents - @inbounds distmx_mc[:, i] = @view d_s.dists[term_to_actual] - end + for (i, v) in enumerate(term_to_actual) + d_s = dijkstra_shortest_paths(g, v, distmx) + @inbounds parents[:, i] = d_s.parents + @inbounds distmx_mc[:, i] = @view d_s.dists[term_to_actual] + end - # Compute MST of Metric Closure graph - mst_mc = kruskal_mst(complete_graph(nvg_mc), distmx_mc) - expanded_mst = Vector{Edge{T}}() - sizehint!(expanded_mst, nvg) + # Compute MST of Metric Closure graph + mst_mc = kruskal_mst(complete_graph(nvg_mc), distmx_mc) + expanded_mst = Vector{Edge{T}}() + sizehint!(expanded_mst, nvg) - # Expand each edge in mst_mc into the path it represents - for e in mst_mc - i = src(e) - s = term_to_actual[i] - t = term_to_actual[dst(e)] - while s != t - t_next = parents[t, i] - push!(expanded_mst, Edge(min(t_next, t), max(t_next, t))) - t = t_next - end - end + # Expand each edge in mst_mc into the path it represents + for e in mst_mc + i = src(e) + s = term_to_actual[i] + t = term_to_actual[dst(e)] + while s != t + t_next = parents[t, i] + push!(expanded_mst, Edge(min(t_next, t), max(t_next, t))) + t = t_next + end + end - # Compute the MST of the expanded graph - mst_mst_mc = kruskal_mst(SimpleGraph(expanded_mst), distmx) + # Compute the MST of the expanded graph + mst_mst_mc = kruskal_mst(SimpleGraph(expanded_mst), distmx) - # Remove non-terminal leaves - return filter_non_term_leaves!(SimpleGraph(mst_mst_mc), term_to_actual) + # Remove non-terminal leaves + return filter_non_term_leaves!(SimpleGraph(mst_mst_mc), term_to_actual) end diff --git a/test/spanningtrees/kruskal.jl b/test/spanningtrees/kruskal.jl index 94b534ec1..32108094d 100644 --- a/test/spanningtrees/kruskal.jl +++ b/test/spanningtrees/kruskal.jl @@ -1,52 +1,52 @@ @testset "Kruskal" begin - g4 = complete_graph(4) + g4 = complete_graph(4) - distmx = [ - 0 1 5 6 - 1 0 4 10 - 5 4 0 3 - 6 10 3 0 - ] + distmx = [ + 0 1 5 6 + 1 0 4 10 + 5 4 0 3 + 6 10 3 0 + ] - vec_mst = Vector{Edge}([Edge(1, 2), Edge(3, 4), Edge(2, 3)]) - max_vec_mst = Vector{Edge}([Edge(2, 4), Edge(1, 4), Edge(1, 3)]) - for g in test_generic_graphs(g4) - # Testing Kruskal's algorithm - mst = @inferred(kruskal_mst(g, distmx)) - max_mst = @inferred(kruskal_mst(g, distmx, minimize=false)) - # GenericEdge currently does not implement any comparison operators - # so instead we compare tuples of source and target vertices - @test sort([(src(e), dst(e)) for e in mst]) == sort([(src(e), dst(e)) for e in vec_mst]) - @test sort([(src(e), dst(e)) for e in max_mst]) == sort([(src(e), dst(e)) for e in max_vec_mst]) - end - # second test - distmx_sec = [ - 0 0 0.26 0 0.38 0 0.58 0.16 - 0 0 0.36 0.29 0 0.32 0 0.19 - 0.26 0.36 0 0.17 0 0 0.4 0.34 - 0 0.29 0.17 0 0 0 0.52 0 - 0.38 0 0 0 0 0.35 0.93 0.37 - 0 0.32 0 0 0.35 0 0 0.28 - 0.58 0 0.4 0.52 0.93 0 0 0 - 0.16 0.19 0.34 0 0.37 0.28 0 0 - ] + vec_mst = Vector{Edge}([Edge(1, 2), Edge(3, 4), Edge(2, 3)]) + max_vec_mst = Vector{Edge}([Edge(2, 4), Edge(1, 4), Edge(1, 3)]) + for g in test_generic_graphs(g4) + # Testing Kruskal's algorithm + mst = @inferred(kruskal_mst(g, distmx)) + max_mst = @inferred(kruskal_mst(g, distmx, minimize = false)) + # GenericEdge currently does not implement any comparison operators + # so instead we compare tuples of source and target vertices + @test sort([(src(e), dst(e)) for e in mst]) == sort([(src(e), dst(e)) for e in vec_mst]) + @test sort([(src(e), dst(e)) for e in max_mst]) == sort([(src(e), dst(e)) for e in max_vec_mst]) + end + # second test + distmx_sec = [ + 0 0 0.26 0 0.38 0 0.58 0.16 + 0 0 0.36 0.29 0 0.32 0 0.19 + 0.26 0.36 0 0.17 0 0 0.4 0.34 + 0 0.29 0.17 0 0 0 0.52 0 + 0.38 0 0 0 0 0.35 0.93 0.37 + 0 0.32 0 0 0.35 0 0 0.28 + 0.58 0 0.4 0.52 0.93 0 0 0 + 0.16 0.19 0.34 0 0.37 0.28 0 0 + ] - gx = SimpleGraph(distmx_sec) - vec2 = Vector{Edge}([ - Edge(1, 8), Edge(3, 4), Edge(2, 8), Edge(1, 3), Edge(6, 8), Edge(5, 6), Edge(3, 7) - ]) - max_vec2 = Vector{Edge}([ - Edge(5, 7), Edge(1, 7), Edge(4, 7), Edge(3, 7), Edge(5, 8), Edge(2, 3), Edge(5, 6) - ]) - for g in test_generic_graphs(gx) - mst2 = @inferred(kruskal_mst(g, distmx_sec)) - max_mst2 = @inferred(kruskal_mst(g, distmx_sec, minimize=false)) - @test sort([(src(e), dst(e)) for e in mst2]) == sort([(src(e), dst(e)) for e in vec2]) - @test sort([(src(e), dst(e)) for e in max_mst2]) == sort([(src(e), dst(e)) for e in max_vec2]) - end + gx = SimpleGraph(distmx_sec) + vec2 = Vector{Edge}([ + Edge(1, 8), Edge(3, 4), Edge(2, 8), Edge(1, 3), Edge(6, 8), Edge(5, 6), Edge(3, 7), + ]) + max_vec2 = Vector{Edge}([ + Edge(5, 7), Edge(1, 7), Edge(4, 7), Edge(3, 7), Edge(5, 8), Edge(2, 3), Edge(5, 6), + ]) + for g in test_generic_graphs(gx) + mst2 = @inferred(kruskal_mst(g, distmx_sec)) + max_mst2 = @inferred(kruskal_mst(g, distmx_sec, minimize = false)) + @test sort([(src(e), dst(e)) for e in mst2]) == sort([(src(e), dst(e)) for e in vec2]) + @test sort([(src(e), dst(e)) for e in max_mst2]) == sort([(src(e), dst(e)) for e in max_vec2]) + end - # non regression test for #362 - g = Graph() - mst = @inferred(kruskal_mst(g)) - @test isempty(mst) + # non regression test for #362 + g = Graph() + mst = @inferred(kruskal_mst(g)) + @test isempty(mst) end diff --git a/test/steinertree/steiner_tree.jl b/test/steinertree/steiner_tree.jl index caef7618d..d2b0aba8e 100644 --- a/test/steinertree/steiner_tree.jl +++ b/test/steinertree/steiner_tree.jl @@ -1,66 +1,66 @@ @testset "Steiner Tree" begin - function sum_weight( - g::AbstractGraph{<:Integer}, distmx::AbstractMatrix{<:Integer}=weights(g) - ) - sum_wt = zero(eltype(g)) - for e in edges(g) - sum_wt += distmx[src(e), dst(e)] - end - return sum_wt - end + function sum_weight( + g::AbstractGraph{<:Integer}, distmx::AbstractMatrix{<:Integer} = weights(g), + ) + sum_wt = zero(eltype(g)) + for e in edges(g) + sum_wt += distmx[src(e), dst(e)] + end + return sum_wt + end - approx_factor(t::Integer) = 2 - 2 / t + approx_factor(t::Integer) = 2 - 2 / t - g3 = star_graph(5) - for g in testgraphs(g3) - g_st = @inferred(steiner_tree(g, [2, 5])) - @test ne(g_st) == 2 # [Edge(2, 1), Edge(1, 5)] + g3 = star_graph(5) + for g in testgraphs(g3) + g_st = @inferred(steiner_tree(g, [2, 5])) + @test ne(g_st) == 2 # [Edge(2, 1), Edge(1, 5)] - g_copy = SimpleGraph(g) - Graphs.filter_non_term_leaves!(g_copy, [2, 5]) - @test ne(g_copy) == 2 # [Edge(2, 1), Edge(1, 5)] + g_copy = SimpleGraph(g) + Graphs.filter_non_term_leaves!(g_copy, [2, 5]) + @test ne(g_copy) == 2 # [Edge(2, 1), Edge(1, 5)] - # non regression test for #362 - g_st = @inferred(steiner_tree(g, [2])) - @test ne(g_st) == 0 + # non regression test for #362 + g_st = @inferred(steiner_tree(g, [2])) + @test ne(g_st) == 0 - end + end - g4 = path_graph(11) - for g in testgraphs(g4) - g_st = @inferred(steiner_tree(g, [4, 8])) - @test ne(g_st) == 4 + g4 = path_graph(11) + for g in testgraphs(g4) + g_st = @inferred(steiner_tree(g, [4, 8])) + @test ne(g_st) == 4 - g_copy = SimpleGraph(g) - Graphs.filter_non_term_leaves!(g_copy, [4, 8]) - @test ne(g_copy) == 4 - end + g_copy = SimpleGraph(g) + Graphs.filter_non_term_leaves!(g_copy, [4, 8]) + @test ne(g_copy) == 4 + end - g5 = grid([5, 5]) - for g in testgraphs(g5) - g_st = @inferred(steiner_tree(g, [3, 11, 15, 23])) - @test sum_weight(g_st) == 8 - end + g5 = grid([5, 5]) + for g in testgraphs(g5) + g_st = @inferred(steiner_tree(g, [3, 11, 15, 23])) + @test sum_weight(g_st) == 8 + end - d = [ - 0 2 3 4 5 - 2 0 60 80 1 - 3 60 0 120 150 - 4 80 120 0 200 - 5 1 150 200 0 - ] + d = [ + 0 2 3 4 5 + 2 0 60 80 1 + 3 60 0 120 150 + 4 80 120 0 200 + 5 1 150 200 0 + ] - g6 = complete_graph(5) + g6 = complete_graph(5) - for g in testgraphs(g6) - g_st = @inferred(steiner_tree(g, [2, 4, 5], d)) - @test sum_weight(g_st, d) <= approx_factor(3) * (1 + 2 + 4) - end + for g in testgraphs(g6) + g_st = @inferred(steiner_tree(g, [2, 4, 5], d)) + @test sum_weight(g_st, d) <= approx_factor(3) * (1 + 2 + 4) + end - d[2, 5] = d[5, 2] = 100 + d[2, 5] = d[5, 2] = 100 - for g in testgraphs(g6) - g_st = @inferred(steiner_tree(g, [2, 4, 5], d)) - @test sum_weight(g_st, d) <= approx_factor(3) * (2 + 4 + 5) - end + for g in testgraphs(g6) + g_st = @inferred(steiner_tree(g, [2, 4, 5], d)) + @test sum_weight(g_st, d) <= approx_factor(3) * (2 + 4 + 5) + end end From 38b732d5dd7f2b8444db9963d03ecc59f0af97ba Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Thu, 2 May 2024 13:58:14 +0200 Subject: [PATCH 4/5] Apply formatter --- src/spanningtrees/kruskal.jl | 40 ++++++------ src/steinertree/steiner_tree.jl | 100 +++++++++++++++--------------- test/spanningtrees/kruskal.jl | 92 +++++++++++++-------------- test/steinertree/steiner_tree.jl | 103 +++++++++++++++---------------- 4 files changed, 167 insertions(+), 168 deletions(-) diff --git a/src/spanningtrees/kruskal.jl b/src/spanningtrees/kruskal.jl index 80b4221f6..883e39495 100644 --- a/src/spanningtrees/kruskal.jl +++ b/src/spanningtrees/kruskal.jl @@ -10,28 +10,28 @@ undirected graph `g` with optional distance matrix `distmx` using [Kruskal's alg function kruskal_mst end # see https://github.com/mauro3/SimpleTraits.jl/issues/47#issuecomment-327880153 for syntax @traitfn function kruskal_mst( - g::AG::(!IsDirected), distmx::AbstractMatrix{T} = weights(g); minimize = true, -) where {T <: Real, U, AG <: AbstractGraph{U}} - connected_vs = IntDisjointSets(nv(g)) + g::AG::(!IsDirected), distmx::AbstractMatrix{T}=weights(g); minimize=true +) where {T<:Real,U,AG<:AbstractGraph{U}} + connected_vs = IntDisjointSets(nv(g)) - mst = Vector{edgetype(g)}() - nv(g) <= 1 && return mst - sizehint!(mst, nv(g) - 1) + mst = Vector{edgetype(g)}() + nv(g) <= 1 && return mst + sizehint!(mst, nv(g) - 1) - weights = Vector{T}() - sizehint!(weights, ne(g)) - edge_list = collect(edges(g)) - for e in edge_list - push!(weights, distmx[src(e), dst(e)]) - end + weights = Vector{T}() + sizehint!(weights, ne(g)) + edge_list = collect(edges(g)) + for e in edge_list + push!(weights, distmx[src(e), dst(e)]) + end - for e in edge_list[sortperm(weights; rev = !minimize)] - if !in_same_set(connected_vs, src(e), dst(e)) - union!(connected_vs, src(e), dst(e)) - push!(mst, e) - (length(mst) >= nv(g) - 1) && break - end - end + for e in edge_list[sortperm(weights; rev=!minimize)] + if !in_same_set(connected_vs, src(e), dst(e)) + union!(connected_vs, src(e), dst(e)) + push!(mst, e) + (length(mst) >= nv(g) - 1) && break + end + end - return mst + return mst end diff --git a/src/steinertree/steiner_tree.jl b/src/steinertree/steiner_tree.jl index 2349ef076..9676b9ae6 100644 --- a/src/steinertree/steiner_tree.jl +++ b/src/steinertree/steiner_tree.jl @@ -4,23 +4,23 @@ Remove edges of `g` so that all non-isolated leaves of `g` are in the set `term_vert` """ function filter_non_term_leaves!( - g::AbstractGraph{T}, term_vert::Vector{<:Integer}, -) where {T <: Integer} - is_term = falses(nv(g)) - is_term[term_vert] .= true - leaves = [v for v in vertices(g) if degree(g, v) == 1 && !is_term[v]] + g::AbstractGraph{T}, term_vert::Vector{<:Integer} +) where {T<:Integer} + is_term = falses(nv(g)) + is_term[term_vert] .= true + leaves = [v for v in vertices(g) if degree(g, v) == 1 && !is_term[v]] - while !isempty(leaves) - leaf = pop!(leaves) - for v in neighbors(g, leaf) - rem_edge!(g, v, leaf) - if degree(g, v) == 1 && !is_term[v] - push!(leaves, v) - end - end - end + while !isempty(leaves) + leaf = pop!(leaves) + for v in neighbors(g, leaf) + rem_edge!(g, v, leaf) + if degree(g, v) == 1 && !is_term[v] + push!(leaves, v) + end + end + end - return g + return g end """ @@ -41,45 +41,45 @@ Approximation Factor: 2-2/t function steiner_tree end @traitfn function steiner_tree( - g::AG::(!IsDirected), term_vert::Vector{<:Integer}, distmx::AbstractMatrix{U} = weights(g), -) where {U <: Real, T, AG <: AbstractGraph{T}} - nvg = nv(g) - length(term_vert) == 0 && return SimpleGraph{T}() - length(term_vert) == 1 && return SimpleGraph{T}(first(term_vert)) - term_to_actual = T.(term_vert) - unique!(term_to_actual) + g::AG::(!IsDirected), term_vert::Vector{<:Integer}, distmx::AbstractMatrix{U}=weights(g) +) where {U<:Real,T,AG<:AbstractGraph{T}} + nvg = nv(g) + length(term_vert) == 0 && return SimpleGraph{T}() + length(term_vert) == 1 && return SimpleGraph{T}(first(term_vert)) + term_to_actual = T.(term_vert) + unique!(term_to_actual) - # Compute the graph formed by inducing the metric closure graph of g on term_vert - nvg_mc = length(term_to_actual) - distmx_mc = Matrix{U}(undef, nvg_mc, nvg_mc) - parents = Matrix{T}(undef, nvg, nvg_mc) + # Compute the graph formed by inducing the metric closure graph of g on term_vert + nvg_mc = length(term_to_actual) + distmx_mc = Matrix{U}(undef, nvg_mc, nvg_mc) + parents = Matrix{T}(undef, nvg, nvg_mc) - for (i, v) in enumerate(term_to_actual) - d_s = dijkstra_shortest_paths(g, v, distmx) - @inbounds parents[:, i] = d_s.parents - @inbounds distmx_mc[:, i] = @view d_s.dists[term_to_actual] - end + for (i, v) in enumerate(term_to_actual) + d_s = dijkstra_shortest_paths(g, v, distmx) + @inbounds parents[:, i] = d_s.parents + @inbounds distmx_mc[:, i] = @view d_s.dists[term_to_actual] + end - # Compute MST of Metric Closure graph - mst_mc = kruskal_mst(complete_graph(nvg_mc), distmx_mc) - expanded_mst = Vector{Edge{T}}() - sizehint!(expanded_mst, nvg) + # Compute MST of Metric Closure graph + mst_mc = kruskal_mst(complete_graph(nvg_mc), distmx_mc) + expanded_mst = Vector{Edge{T}}() + sizehint!(expanded_mst, nvg) - # Expand each edge in mst_mc into the path it represents - for e in mst_mc - i = src(e) - s = term_to_actual[i] - t = term_to_actual[dst(e)] - while s != t - t_next = parents[t, i] - push!(expanded_mst, Edge(min(t_next, t), max(t_next, t))) - t = t_next - end - end + # Expand each edge in mst_mc into the path it represents + for e in mst_mc + i = src(e) + s = term_to_actual[i] + t = term_to_actual[dst(e)] + while s != t + t_next = parents[t, i] + push!(expanded_mst, Edge(min(t_next, t), max(t_next, t))) + t = t_next + end + end - # Compute the MST of the expanded graph - mst_mst_mc = kruskal_mst(SimpleGraph(expanded_mst), distmx) + # Compute the MST of the expanded graph + mst_mst_mc = kruskal_mst(SimpleGraph(expanded_mst), distmx) - # Remove non-terminal leaves - return filter_non_term_leaves!(SimpleGraph(mst_mst_mc), term_to_actual) + # Remove non-terminal leaves + return filter_non_term_leaves!(SimpleGraph(mst_mst_mc), term_to_actual) end diff --git a/test/spanningtrees/kruskal.jl b/test/spanningtrees/kruskal.jl index 32108094d..94b534ec1 100644 --- a/test/spanningtrees/kruskal.jl +++ b/test/spanningtrees/kruskal.jl @@ -1,52 +1,52 @@ @testset "Kruskal" begin - g4 = complete_graph(4) + g4 = complete_graph(4) - distmx = [ - 0 1 5 6 - 1 0 4 10 - 5 4 0 3 - 6 10 3 0 - ] + distmx = [ + 0 1 5 6 + 1 0 4 10 + 5 4 0 3 + 6 10 3 0 + ] - vec_mst = Vector{Edge}([Edge(1, 2), Edge(3, 4), Edge(2, 3)]) - max_vec_mst = Vector{Edge}([Edge(2, 4), Edge(1, 4), Edge(1, 3)]) - for g in test_generic_graphs(g4) - # Testing Kruskal's algorithm - mst = @inferred(kruskal_mst(g, distmx)) - max_mst = @inferred(kruskal_mst(g, distmx, minimize = false)) - # GenericEdge currently does not implement any comparison operators - # so instead we compare tuples of source and target vertices - @test sort([(src(e), dst(e)) for e in mst]) == sort([(src(e), dst(e)) for e in vec_mst]) - @test sort([(src(e), dst(e)) for e in max_mst]) == sort([(src(e), dst(e)) for e in max_vec_mst]) - end - # second test - distmx_sec = [ - 0 0 0.26 0 0.38 0 0.58 0.16 - 0 0 0.36 0.29 0 0.32 0 0.19 - 0.26 0.36 0 0.17 0 0 0.4 0.34 - 0 0.29 0.17 0 0 0 0.52 0 - 0.38 0 0 0 0 0.35 0.93 0.37 - 0 0.32 0 0 0.35 0 0 0.28 - 0.58 0 0.4 0.52 0.93 0 0 0 - 0.16 0.19 0.34 0 0.37 0.28 0 0 - ] + vec_mst = Vector{Edge}([Edge(1, 2), Edge(3, 4), Edge(2, 3)]) + max_vec_mst = Vector{Edge}([Edge(2, 4), Edge(1, 4), Edge(1, 3)]) + for g in test_generic_graphs(g4) + # Testing Kruskal's algorithm + mst = @inferred(kruskal_mst(g, distmx)) + max_mst = @inferred(kruskal_mst(g, distmx, minimize=false)) + # GenericEdge currently does not implement any comparison operators + # so instead we compare tuples of source and target vertices + @test sort([(src(e), dst(e)) for e in mst]) == sort([(src(e), dst(e)) for e in vec_mst]) + @test sort([(src(e), dst(e)) for e in max_mst]) == sort([(src(e), dst(e)) for e in max_vec_mst]) + end + # second test + distmx_sec = [ + 0 0 0.26 0 0.38 0 0.58 0.16 + 0 0 0.36 0.29 0 0.32 0 0.19 + 0.26 0.36 0 0.17 0 0 0.4 0.34 + 0 0.29 0.17 0 0 0 0.52 0 + 0.38 0 0 0 0 0.35 0.93 0.37 + 0 0.32 0 0 0.35 0 0 0.28 + 0.58 0 0.4 0.52 0.93 0 0 0 + 0.16 0.19 0.34 0 0.37 0.28 0 0 + ] - gx = SimpleGraph(distmx_sec) - vec2 = Vector{Edge}([ - Edge(1, 8), Edge(3, 4), Edge(2, 8), Edge(1, 3), Edge(6, 8), Edge(5, 6), Edge(3, 7), - ]) - max_vec2 = Vector{Edge}([ - Edge(5, 7), Edge(1, 7), Edge(4, 7), Edge(3, 7), Edge(5, 8), Edge(2, 3), Edge(5, 6), - ]) - for g in test_generic_graphs(gx) - mst2 = @inferred(kruskal_mst(g, distmx_sec)) - max_mst2 = @inferred(kruskal_mst(g, distmx_sec, minimize = false)) - @test sort([(src(e), dst(e)) for e in mst2]) == sort([(src(e), dst(e)) for e in vec2]) - @test sort([(src(e), dst(e)) for e in max_mst2]) == sort([(src(e), dst(e)) for e in max_vec2]) - end + gx = SimpleGraph(distmx_sec) + vec2 = Vector{Edge}([ + Edge(1, 8), Edge(3, 4), Edge(2, 8), Edge(1, 3), Edge(6, 8), Edge(5, 6), Edge(3, 7) + ]) + max_vec2 = Vector{Edge}([ + Edge(5, 7), Edge(1, 7), Edge(4, 7), Edge(3, 7), Edge(5, 8), Edge(2, 3), Edge(5, 6) + ]) + for g in test_generic_graphs(gx) + mst2 = @inferred(kruskal_mst(g, distmx_sec)) + max_mst2 = @inferred(kruskal_mst(g, distmx_sec, minimize=false)) + @test sort([(src(e), dst(e)) for e in mst2]) == sort([(src(e), dst(e)) for e in vec2]) + @test sort([(src(e), dst(e)) for e in max_mst2]) == sort([(src(e), dst(e)) for e in max_vec2]) + end - # non regression test for #362 - g = Graph() - mst = @inferred(kruskal_mst(g)) - @test isempty(mst) + # non regression test for #362 + g = Graph() + mst = @inferred(kruskal_mst(g)) + @test isempty(mst) end diff --git a/test/steinertree/steiner_tree.jl b/test/steinertree/steiner_tree.jl index d2b0aba8e..35bd323dd 100644 --- a/test/steinertree/steiner_tree.jl +++ b/test/steinertree/steiner_tree.jl @@ -1,66 +1,65 @@ @testset "Steiner Tree" begin - function sum_weight( - g::AbstractGraph{<:Integer}, distmx::AbstractMatrix{<:Integer} = weights(g), - ) - sum_wt = zero(eltype(g)) - for e in edges(g) - sum_wt += distmx[src(e), dst(e)] - end - return sum_wt - end + function sum_weight( + g::AbstractGraph{<:Integer}, distmx::AbstractMatrix{<:Integer}=weights(g) + ) + sum_wt = zero(eltype(g)) + for e in edges(g) + sum_wt += distmx[src(e), dst(e)] + end + return sum_wt + end - approx_factor(t::Integer) = 2 - 2 / t + approx_factor(t::Integer) = 2 - 2 / t - g3 = star_graph(5) - for g in testgraphs(g3) - g_st = @inferred(steiner_tree(g, [2, 5])) - @test ne(g_st) == 2 # [Edge(2, 1), Edge(1, 5)] + g3 = star_graph(5) + for g in testgraphs(g3) + g_st = @inferred(steiner_tree(g, [2, 5])) + @test ne(g_st) == 2 # [Edge(2, 1), Edge(1, 5)] - g_copy = SimpleGraph(g) - Graphs.filter_non_term_leaves!(g_copy, [2, 5]) - @test ne(g_copy) == 2 # [Edge(2, 1), Edge(1, 5)] + g_copy = SimpleGraph(g) + Graphs.filter_non_term_leaves!(g_copy, [2, 5]) + @test ne(g_copy) == 2 # [Edge(2, 1), Edge(1, 5)] - # non regression test for #362 - g_st = @inferred(steiner_tree(g, [2])) - @test ne(g_st) == 0 + # non regression test for #362 + g_st = @inferred(steiner_tree(g, [2])) + @test ne(g_st) == 0 + end - end + g4 = path_graph(11) + for g in testgraphs(g4) + g_st = @inferred(steiner_tree(g, [4, 8])) + @test ne(g_st) == 4 - g4 = path_graph(11) - for g in testgraphs(g4) - g_st = @inferred(steiner_tree(g, [4, 8])) - @test ne(g_st) == 4 + g_copy = SimpleGraph(g) + Graphs.filter_non_term_leaves!(g_copy, [4, 8]) + @test ne(g_copy) == 4 + end - g_copy = SimpleGraph(g) - Graphs.filter_non_term_leaves!(g_copy, [4, 8]) - @test ne(g_copy) == 4 - end + g5 = grid([5, 5]) + for g in testgraphs(g5) + g_st = @inferred(steiner_tree(g, [3, 11, 15, 23])) + @test sum_weight(g_st) == 8 + end - g5 = grid([5, 5]) - for g in testgraphs(g5) - g_st = @inferred(steiner_tree(g, [3, 11, 15, 23])) - @test sum_weight(g_st) == 8 - end + d = [ + 0 2 3 4 5 + 2 0 60 80 1 + 3 60 0 120 150 + 4 80 120 0 200 + 5 1 150 200 0 + ] - d = [ - 0 2 3 4 5 - 2 0 60 80 1 - 3 60 0 120 150 - 4 80 120 0 200 - 5 1 150 200 0 - ] + g6 = complete_graph(5) - g6 = complete_graph(5) + for g in testgraphs(g6) + g_st = @inferred(steiner_tree(g, [2, 4, 5], d)) + @test sum_weight(g_st, d) <= approx_factor(3) * (1 + 2 + 4) + end - for g in testgraphs(g6) - g_st = @inferred(steiner_tree(g, [2, 4, 5], d)) - @test sum_weight(g_st, d) <= approx_factor(3) * (1 + 2 + 4) - end + d[2, 5] = d[5, 2] = 100 - d[2, 5] = d[5, 2] = 100 - - for g in testgraphs(g6) - g_st = @inferred(steiner_tree(g, [2, 4, 5], d)) - @test sum_weight(g_st, d) <= approx_factor(3) * (2 + 4 + 5) - end + for g in testgraphs(g6) + g_st = @inferred(steiner_tree(g, [2, 4, 5], d)) + @test sum_weight(g_st, d) <= approx_factor(3) * (2 + 4 + 5) + end end From 1c7f5b99846d22c2a69da9ea9e0f01d4fff34b79 Mon Sep 17 00:00:00 2001 From: etienneINSA Date: Sat, 4 May 2024 14:10:45 +0200 Subject: [PATCH 5/5] update test --- test/spanningtrees/kruskal.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/spanningtrees/kruskal.jl b/test/spanningtrees/kruskal.jl index 94b534ec1..ddf8fead8 100644 --- a/test/spanningtrees/kruskal.jl +++ b/test/spanningtrees/kruskal.jl @@ -49,4 +49,8 @@ g = Graph() mst = @inferred(kruskal_mst(g)) @test isempty(mst) + + g = Graph(1) + mst = @inferred(kruskal_mst(g)) + @test isempty(mst) end