From 7aee9937b1023e0571dab141046289ea7b816859 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Mon, 23 Mar 2026 15:56:23 -0400 Subject: [PATCH 01/11] Fix: The single argument `similar_graph` now makes sure to pass in a copy of its vertices and edges From a250ec6e0cf1d556aec34694e9e41f21ce50cdbb Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Mon, 23 Mar 2026 16:43:36 -0400 Subject: [PATCH 02/11] More tests From 1478ec3516069296f74cb5a5945dd2c81a455fc7 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Wed, 6 May 2026 11:00:13 -0400 Subject: [PATCH 03/11] Add `similar_tree` function for use in `steiner_tree`. --- src/abstractnamedgraph.jl | 13 +++++++++++++ src/lib/GraphsExtensions/src/trees_and_forests.jl | 11 ++++++++++- src/lib/GraphsExtensions/test/runtests.jl | 13 ++++++++++--- src/steiner_tree.jl | 2 +- test/test_abstractnamedgraph.jl | 12 +++++++++--- 5 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/abstractnamedgraph.jl b/src/abstractnamedgraph.jl index 8862feb2..64db60dd 100644 --- a/src/abstractnamedgraph.jl +++ b/src/abstractnamedgraph.jl @@ -647,3 +647,16 @@ end end return undigraph end + +# This function will return a similar tree graph for graphs that have e.g. immutable edges. +function similar_tree(graph::AbstractGraph, vertices, edges) + tree = NamedGraph(vertices) + add_edges!(tree, edges) + is_tree(tree) || throw(ArgumentError("The edges provided do not form a tree.")) + return tree +end + +# This function will return a similar tree graph for graphs that have e.g. immutable edges. +function similar_tree(graph::AbstractSimpleGraph, vertices, edges) + return GraphsExtensions.similar_simpletree(graph, vertices, edges) +end diff --git a/src/lib/GraphsExtensions/src/trees_and_forests.jl b/src/lib/GraphsExtensions/src/trees_and_forests.jl index a90a57a0..d113f8e3 100644 --- a/src/lib/GraphsExtensions/src/trees_and_forests.jl +++ b/src/lib/GraphsExtensions/src/trees_and_forests.jl @@ -1,5 +1,6 @@ using .GraphsExtensions: random_bfs_tree, rem_edges, undirected_graph -using Graphs: IsDirected, bfs_tree, connected_components, edges, edgetype +using Graphs: + AbstractGraph, IsDirected, SimpleGraph, bfs_tree, connected_components, edges, edgetype using SimpleTraits: SimpleTraits, @traitfn, Not abstract type SpanningTreeAlgorithm end @@ -62,5 +63,13 @@ function forest_cover(g::AbstractGraph; spanning_tree = spanning_tree) return identity.(forests) end +# This function will return a similar tree graph for graphs that have e.g. immutable edges. +function similar_simpletree(::AbstractGraph, vertices, edges) + tree = similar_simplegraph(SimpleGraph, vertices) + add_edges!(tree, edges) + is_tree(tree) || throw(ArgumentError("The edges provided do not form a tree.")) + return tree +end + # TODO: Define in `NamedGraphs.PartitionedGraphs`. # forest_cover(g::PartitionedGraph; kwargs...) = not_implemented() diff --git a/src/lib/GraphsExtensions/test/runtests.jl b/src/lib/GraphsExtensions/test/runtests.jl index ce321329..89cc5c35 100644 --- a/src/lib/GraphsExtensions/test/runtests.jl +++ b/src/lib/GraphsExtensions/test/runtests.jl @@ -14,9 +14,9 @@ using NamedGraphs.GraphsExtensions: TreeGraph, add_edge, add_edges, add_edges!, is_ditree, is_edge_arranged, is_leaf_edge, is_leaf_vertex, is_path_graph, is_root_vertex, is_rooted, is_self_loop, leaf_vertices, minimum_distance_to_leaves, next_nearest_neighbors, non_leaf_edges, outdegrees, permute_vertices, rem_edge, - rem_edges, rem_edges!, rename_vertices, root_vertex, similar_simplegraph, subgraph, - tree_graph_node, undirected_graph, undirected_graph_type, vertextype, - vertices_at_distance, ⊔ + rem_edges, rem_edges!, rename_vertices, root_vertex, similar_simplegraph, + similar_simpletree, subgraph, tree_graph_node, undirected_graph, undirected_graph_type, + vertextype, vertices_at_distance, ⊔ using NamedGraphs: NamedDiGraph, NamedEdge, NamedGraph using Test: @test, @test_broken, @test_throws, @testset @@ -543,6 +543,13 @@ using Test: @test, @test_broken, @test_throws, @testset @test only(next_nearest_neighbors(g, 1)) == 3 @test issetequal(vertices_at_distance(g, 5, 3), [2, 8]) + g = path_graph(4) + # similar_simpletree + @test similar_simpletree(g, vertices(g), edges(g)) == g + @test similar_simpletree(g, vertices(g), edges(g)) !== g + g = cycle_graph(4) + @test_throws ArgumentError similar_simpletree(g, vertices(g), edges(g)) + @testset "arrange" begin @testset "is_arranged, is_edge_arranged" begin for (a, b) in [ diff --git a/src/steiner_tree.jl b/src/steiner_tree.jl index ac449208..4356a078 100644 --- a/src/steiner_tree.jl +++ b/src/steiner_tree.jl @@ -25,7 +25,7 @@ function namedgraph_steiner_tree( push!(featured_vertices, dst(named_edge)) end - tree = similar_graph(g, featured_vertices, named_edges) + tree = similar_tree(g, featured_vertices, named_edges) return tree end diff --git a/test/test_abstractnamedgraph.jl b/test/test_abstractnamedgraph.jl index 531eec75..283ecbc1 100644 --- a/test/test_abstractnamedgraph.jl +++ b/test/test_abstractnamedgraph.jl @@ -5,8 +5,8 @@ using Graphs: Graphs, AbstractGraph, DiGraph, Graph, a_star, add_edge!, edges, e using NamedGraphs.GraphsExtensions: GraphsExtensions, rename_vertices using NamedGraphs.NamedGraphGenerators: named_grid, named_path_graph using NamedGraphs: NamedGraphs, AbstractNamedGraph, NamedDiGraph, NamedGraph, - edgeless_graph, empty_graph, position_graph, similar_graph -using Test: @test, @testset + edgeless_graph, empty_graph, position_graph, similar_graph, similar_tree +using Test: @test, @test_throws, @testset struct TestGraph{V} <: AbstractNamedGraph{V} graph::NamedGraph{V} @@ -175,7 +175,7 @@ end @test !has_edge(nddg_function, (2, "Y") => (2, "X")) end -@testset "AbstractNamedGraph `similar_graph`" begin +@testset "AbstractNamedGraph `similar_graph/tree`" begin ug = named_path_graph(4) g = TestGraph(ug) @@ -207,6 +207,12 @@ end # Make sure the TestGraph is unchanged. @test nv(g) == 4 @test ne(g) == 3 + + @test similar_tree(ug, vertices(ug), edges(ug)) == ug + @test similar_tree(ug, vertices(ug), edges(ug)) !== ug + @test similar_tree(g, vertices(g), edges(g)) == ug + ug_loopy = add_edge!(copy(ug), 1 => 4) + @test_throws ArgumentError similar_tree(ug, vertices(ug), edges(ug_loopy)) end end From 6020567bfd36ac4774f9d04c4a640c5dd163eb58 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Wed, 6 May 2026 11:02:17 -0400 Subject: [PATCH 04/11] Revert "Add `similar_tree` function for use in `steiner_tree`." This reverts commit deddcb84e56ac52d5406deeb5cfb39999dd2ae0d. --- src/abstractnamedgraph.jl | 13 ------------- src/lib/GraphsExtensions/src/trees_and_forests.jl | 11 +---------- src/lib/GraphsExtensions/test/runtests.jl | 13 +++---------- src/steiner_tree.jl | 2 +- test/test_abstractnamedgraph.jl | 12 +++--------- 5 files changed, 8 insertions(+), 43 deletions(-) diff --git a/src/abstractnamedgraph.jl b/src/abstractnamedgraph.jl index 64db60dd..8862feb2 100644 --- a/src/abstractnamedgraph.jl +++ b/src/abstractnamedgraph.jl @@ -647,16 +647,3 @@ end end return undigraph end - -# This function will return a similar tree graph for graphs that have e.g. immutable edges. -function similar_tree(graph::AbstractGraph, vertices, edges) - tree = NamedGraph(vertices) - add_edges!(tree, edges) - is_tree(tree) || throw(ArgumentError("The edges provided do not form a tree.")) - return tree -end - -# This function will return a similar tree graph for graphs that have e.g. immutable edges. -function similar_tree(graph::AbstractSimpleGraph, vertices, edges) - return GraphsExtensions.similar_simpletree(graph, vertices, edges) -end diff --git a/src/lib/GraphsExtensions/src/trees_and_forests.jl b/src/lib/GraphsExtensions/src/trees_and_forests.jl index d113f8e3..a90a57a0 100644 --- a/src/lib/GraphsExtensions/src/trees_and_forests.jl +++ b/src/lib/GraphsExtensions/src/trees_and_forests.jl @@ -1,6 +1,5 @@ using .GraphsExtensions: random_bfs_tree, rem_edges, undirected_graph -using Graphs: - AbstractGraph, IsDirected, SimpleGraph, bfs_tree, connected_components, edges, edgetype +using Graphs: IsDirected, bfs_tree, connected_components, edges, edgetype using SimpleTraits: SimpleTraits, @traitfn, Not abstract type SpanningTreeAlgorithm end @@ -63,13 +62,5 @@ function forest_cover(g::AbstractGraph; spanning_tree = spanning_tree) return identity.(forests) end -# This function will return a similar tree graph for graphs that have e.g. immutable edges. -function similar_simpletree(::AbstractGraph, vertices, edges) - tree = similar_simplegraph(SimpleGraph, vertices) - add_edges!(tree, edges) - is_tree(tree) || throw(ArgumentError("The edges provided do not form a tree.")) - return tree -end - # TODO: Define in `NamedGraphs.PartitionedGraphs`. # forest_cover(g::PartitionedGraph; kwargs...) = not_implemented() diff --git a/src/lib/GraphsExtensions/test/runtests.jl b/src/lib/GraphsExtensions/test/runtests.jl index 89cc5c35..ce321329 100644 --- a/src/lib/GraphsExtensions/test/runtests.jl +++ b/src/lib/GraphsExtensions/test/runtests.jl @@ -14,9 +14,9 @@ using NamedGraphs.GraphsExtensions: TreeGraph, add_edge, add_edges, add_edges!, is_ditree, is_edge_arranged, is_leaf_edge, is_leaf_vertex, is_path_graph, is_root_vertex, is_rooted, is_self_loop, leaf_vertices, minimum_distance_to_leaves, next_nearest_neighbors, non_leaf_edges, outdegrees, permute_vertices, rem_edge, - rem_edges, rem_edges!, rename_vertices, root_vertex, similar_simplegraph, - similar_simpletree, subgraph, tree_graph_node, undirected_graph, undirected_graph_type, - vertextype, vertices_at_distance, ⊔ + rem_edges, rem_edges!, rename_vertices, root_vertex, similar_simplegraph, subgraph, + tree_graph_node, undirected_graph, undirected_graph_type, vertextype, + vertices_at_distance, ⊔ using NamedGraphs: NamedDiGraph, NamedEdge, NamedGraph using Test: @test, @test_broken, @test_throws, @testset @@ -543,13 +543,6 @@ using Test: @test, @test_broken, @test_throws, @testset @test only(next_nearest_neighbors(g, 1)) == 3 @test issetequal(vertices_at_distance(g, 5, 3), [2, 8]) - g = path_graph(4) - # similar_simpletree - @test similar_simpletree(g, vertices(g), edges(g)) == g - @test similar_simpletree(g, vertices(g), edges(g)) !== g - g = cycle_graph(4) - @test_throws ArgumentError similar_simpletree(g, vertices(g), edges(g)) - @testset "arrange" begin @testset "is_arranged, is_edge_arranged" begin for (a, b) in [ diff --git a/src/steiner_tree.jl b/src/steiner_tree.jl index 4356a078..ac449208 100644 --- a/src/steiner_tree.jl +++ b/src/steiner_tree.jl @@ -25,7 +25,7 @@ function namedgraph_steiner_tree( push!(featured_vertices, dst(named_edge)) end - tree = similar_tree(g, featured_vertices, named_edges) + tree = similar_graph(g, featured_vertices, named_edges) return tree end diff --git a/test/test_abstractnamedgraph.jl b/test/test_abstractnamedgraph.jl index 283ecbc1..531eec75 100644 --- a/test/test_abstractnamedgraph.jl +++ b/test/test_abstractnamedgraph.jl @@ -5,8 +5,8 @@ using Graphs: Graphs, AbstractGraph, DiGraph, Graph, a_star, add_edge!, edges, e using NamedGraphs.GraphsExtensions: GraphsExtensions, rename_vertices using NamedGraphs.NamedGraphGenerators: named_grid, named_path_graph using NamedGraphs: NamedGraphs, AbstractNamedGraph, NamedDiGraph, NamedGraph, - edgeless_graph, empty_graph, position_graph, similar_graph, similar_tree -using Test: @test, @test_throws, @testset + edgeless_graph, empty_graph, position_graph, similar_graph +using Test: @test, @testset struct TestGraph{V} <: AbstractNamedGraph{V} graph::NamedGraph{V} @@ -175,7 +175,7 @@ end @test !has_edge(nddg_function, (2, "Y") => (2, "X")) end -@testset "AbstractNamedGraph `similar_graph/tree`" begin +@testset "AbstractNamedGraph `similar_graph`" begin ug = named_path_graph(4) g = TestGraph(ug) @@ -207,12 +207,6 @@ end # Make sure the TestGraph is unchanged. @test nv(g) == 4 @test ne(g) == 3 - - @test similar_tree(ug, vertices(ug), edges(ug)) == ug - @test similar_tree(ug, vertices(ug), edges(ug)) !== ug - @test similar_tree(g, vertices(g), edges(g)) == ug - ug_loopy = add_edge!(copy(ug), 1 => 4) - @test_throws ArgumentError similar_tree(ug, vertices(ug), edges(ug_loopy)) end end From 9db35794f5c151581e01c248a36fc206c5ccd903 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Wed, 6 May 2026 14:31:08 -0400 Subject: [PATCH 05/11] Add function `forest_cover_edge_sequence` to `GraphsExtensions`. Previously existed in `ITensorNetworksNext` --- src/abstractnamedgraph.jl | 5 +++++ src/lib/GraphsExtensions/src/trees_and_forests.jl | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/abstractnamedgraph.jl b/src/abstractnamedgraph.jl index 8862feb2..d9e504ec 100644 --- a/src/abstractnamedgraph.jl +++ b/src/abstractnamedgraph.jl @@ -647,3 +647,8 @@ end end return undigraph end + +function GraphsExtensions.forest_cover_edge_sequence(graph::AbstractNamedGraph; kwargs...) + dummy_graph = add_edges!(NamedGraph(vertices(graph)), edges(graph)) + return GraphsExtensions.forest_cover_edge_sequence(dummy_graph; kwargs...) +end diff --git a/src/lib/GraphsExtensions/src/trees_and_forests.jl b/src/lib/GraphsExtensions/src/trees_and_forests.jl index a90a57a0..bdb3e43b 100644 --- a/src/lib/GraphsExtensions/src/trees_and_forests.jl +++ b/src/lib/GraphsExtensions/src/trees_and_forests.jl @@ -62,5 +62,18 @@ function forest_cover(g::AbstractGraph; spanning_tree = spanning_tree) return identity.(forests) end +function forest_cover_edge_sequence(gi::AbstractGraph; root_vertex = default_root_vertex) + forests = forest_cover(g) + rv = edgetype(g)[] + for forest in forests + trees = [forest[Vertices(vs)] for vs in connected_components(forest)] + for tree in trees + tree_edges = post_order_dfs_edges(tree, root_vertex(tree)) + push!(rv, vcat(tree_edges, reverse(reverse.(tree_edges)))...) + end + end + return rv +end + # TODO: Define in `NamedGraphs.PartitionedGraphs`. # forest_cover(g::PartitionedGraph; kwargs...) = not_implemented() From d095b5f777feb22e568bfe7b6f515fdbf972d82f Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Wed, 6 May 2026 14:42:59 -0400 Subject: [PATCH 06/11] Remove `similar_graph` methods that take edges as an argument. This is to align with DataGraphs.jl and in analogy to SparseArrays.jl --- src/abstractnamedgraph.jl | 81 ++++++++----------- src/lib/GraphsExtensions/src/abstractgraph.jl | 25 ++---- src/lib/GraphsExtensions/src/simplegraph.jl | 6 +- src/lib/GraphsExtensions/test/runtests.jl | 4 - src/namedgraph.jl | 10 +-- test/test_abstractnamedgraph.jl | 12 +-- 6 files changed, 47 insertions(+), 91 deletions(-) diff --git a/src/abstractnamedgraph.jl b/src/abstractnamedgraph.jl index d9e504ec..f772a607 100644 --- a/src/abstractnamedgraph.jl +++ b/src/abstractnamedgraph.jl @@ -1,4 +1,4 @@ -using .GraphsExtensions: GraphsExtensions, directed_graph, incident_edges, +using .GraphsExtensions: GraphsExtensions, all_edges, directed_graph, incident_edges, partition_vertices, rem_edges, rem_edges!, rem_vertices, rename_vertices, similar_simplegraph, subgraph using Dictionaries: set! @@ -52,56 +52,39 @@ GraphsExtensions.convert_vertextype(::Type{V}, g::AbstractNamedGraph{V}) where { GraphsExtensions.convert_vertextype(::Type, g::AbstractNamedGraph) = not_implemented() function similar_graph(graph::AbstractGraph) - return similar_graph(graph, copy(vertices(graph)), copy(edges(graph))) -end - -function similar_graph(graph::AbstractGraph, vertices) - new_edge_type = convert_vertextype(eltype(vertices), edgetype(graph)) - return similar_graph(graph, vertices, new_edge_type[]) + newgraph = similar_graph(graph, copy(vertices(graph))) + add_edges!(newgraph, edges(graph)) + return newgraph end # Construct `GenericNamedGraph` as a fallback. @traitfn function similar_graph( - ::AbstractGraph::(!IsDirected), - vertices, - edges + graph::AbstractGraph::(!IsDirected), + vertices ) V = eltype(vertices) - graph = add_edges!(NamedGraph{V}(vertices), edges) - return graph + return NamedGraph{V}(vertices) end @traitfn function similar_graph( - ::AbstractGraph::IsDirected, - vertices, - edges + graph::AbstractGraph::IsDirected, + vertices ) V = eltype(vertices) - graph = add_edges!(NamedDiGraph{V}(vertices), edges) - return graph + return NamedDiGraph{V}(vertices) end # Passing a type as a first argument attempts to call a constructor. Should be overloaded # if the constructor doesnt exist for a given `AbstractGraph` concrete type. -similar_graph(T::Type{<:AbstractGraph}) = T() -similar_graph(T::Type{<:AbstractGraph}, vertices) = T(vertices) -function similar_graph(T::Type{<:AbstractGraph}, vertices, edges) - graph = similar_graph(T, vertices) - add_edges!(graph, edges) - return graph -end +similar_graph(T::Type{<:AbstractGraph}, vertices = vertextype(T)[]) = T(vertices) # If `T <: AbstractSimpleGraph`, then we defer to `GraphsExtensions.similar_simplegraph`. -function similar_graph(T::Type{<:AbstractSimpleGraph}, vertices = 0, edges = []) - return similar_simplegraph(T, vertices, edges) +function similar_graph(T::Type{<:AbstractSimpleGraph}, vertices = 0) + return similar_simplegraph(T, vertices) end edgeless_graph(graph::AbstractGraph) = rem_edges(graph, edges(graph)) -# The intention is this will fail if `T` cannot be edgeless. -edgeless_graph(T::Type{<:AbstractGraph}, vertices) = similar_graph(T, vertices, []) empty_graph(graph::AbstractGraph) = rem_vertices(graph, vertices(graph)) -# The intention is this will fail if `T` cannot be empty. -empty_graph(T::Type{<:AbstractGraph}) = similar_graph(T, vertextype(T)[], []) Base.copy(graph::AbstractNamedGraph) = copyto!(similar_graph(graph), graph) @@ -413,12 +396,12 @@ function Graphs.has_path( end function Base.union(graph1::AbstractNamedGraph, graph2::AbstractNamedGraph) - union_graph = promote_type(typeof(graph1), typeof(graph2))() + union_graph_type = promote_type(typeof(graph1), typeof(graph2)) + union_vertices = union(vertices(graph1), vertices(graph2)) - for v in union_vertices - add_vertex!(union_graph, v) - end + union_graph = similar_graph(union_graph_type, union_vertices) + for e in edges(graph1) add_edge!(union_graph, e) end @@ -448,7 +431,9 @@ Graphs.is_connected(graph::AbstractNamedGraph) = is_connected(position_graph(gra Graphs.is_cyclic(graph::AbstractNamedGraph) = is_cyclic(position_graph(graph)) @traitfn function Base.reverse(graph::AbstractNamedGraph::IsDirected) - return similar_graph(graph, vertices, map(reverse, collect(edges(graph)))) + newgraph = edgeless_graph(graph) + add_edges!(newgraph, map(reverse, edges(graph))) + return newgraph end # This wont be the most efficient way for a given graph type. @@ -603,15 +588,15 @@ end # TODO: Implement an edgelist version function induced_subgraph_from_vertices(graph::AbstractGraph, subvertices) subgraph = similar_graph(graph, collect(subvertices)) + add_edges!(subgraph, subgraph_edges(graph, subvertices)) + return subgraph, nothing +end + +function subgraph_edges(graph::AbstractGraph, subvertices) subvertices_set = Set(subvertices) - for src in subvertices - for dst in outneighbors(graph, src) - if dst in subvertices_set && has_edge(graph, src, dst) - add_edge!(subgraph, src => dst) - end - end + return Iterators.filter(edges(graph)) do edge + return src(edge) in subvertices_set && dst(edge) in subvertices_set end - return subgraph, nothing end function GraphsExtensions.edge_subgraph(graph::AbstractNamedGraph, edges) @@ -631,16 +616,14 @@ function edge_subgraph_namedgraph(graph, edgelist) return g end -@traitfn function GraphsExtensions.directed_graph(graph::AbstractGraph::(!IsDirected)) - digraph = similar_graph(directed_graph_type(graph), vertices(graph), edges(graph)) - for e in edges(graph) - add_edge!(digraph, reverse(e)) - end +@traitfn function GraphsExtensions.directed_graph(graph::AbstractNamedGraph::(!IsDirected)) + digraph = similar_graph(directed_graph_type(graph), vertices(graph)) + add_edges!(digraph, all_edges(graph)) return digraph end -@traitfn function GraphsExtensions.undirected_graph(graph::AbstractGraph::IsDirected) - undigraph = edgeless_graph(undirected_graph_type(graph), vertices(graph)) +@traitfn function GraphsExtensions.undirected_graph(graph::AbstractNamedGraph::IsDirected) + undigraph = similar_graph(undirected_graph_type(graph), vertices(graph)) for e in edges(graph) has_edge(undigraph, e) && continue add_edge!(undigraph, e) diff --git a/src/lib/GraphsExtensions/src/abstractgraph.jl b/src/lib/GraphsExtensions/src/abstractgraph.jl index 30655821..b5bcc9d7 100644 --- a/src/lib/GraphsExtensions/src/abstractgraph.jl +++ b/src/lib/GraphsExtensions/src/abstractgraph.jl @@ -29,36 +29,29 @@ convert_vertextype(::Type, ::Type{<:AbstractGraph}) = not_implemented() # ==================================== similar_simplegraph =============================== # function similar_simplegraph(graph::AbstractGraph) - return similar_simplegraph(graph, vertices(graph), edges(graph)) + newgraph = similar_simplegraph(graph, vertices(graph)) + add_edges!(newgraph, edges(graph)) + return newgraph end -function similar_simplegraph(graph::AbstractGraph, vertices) - new_edge_type = convert_vertextype(eltype(vertices), edgetype(graph)) - return similar_simplegraph(graph, vertices, new_edge_type[]) -end - -function similar_simplegraph(graph::AbstractGraph, vertices::Base.OneTo, edges) - return similar_simplegraph(graph, length(vertices), edges) +function similar_simplegraph(graph::AbstractGraph, vertices::Base.OneTo) + return similar_simplegraph(graph, length(vertices)) end # To be specialized (optional, has following fallback) @traitfn function similar_simplegraph( graph::AbstractGraph::(!IsDirected), - nvertices::Int, - edges + nvertices::Int ) new_graph = SimpleGraph(nvertices) - add_edges!(new_graph, edges) return new_graph end # To be specialized (optional, has following fallback) @traitfn function similar_simplegraph( graph::AbstractGraph::IsDirected, - nvertices::Int, - edges + nvertices::Int ) new_graph = SimpleDiGraph(nvertices) - add_edges!(new_graph, edges) return new_graph end @@ -68,10 +61,6 @@ function similar_simplegraph(T::Type{<:AbstractSimpleGraph}, vertices::Base.OneT return similar_simplegraph(T, length(vertices)) end similar_simplegraph(T::Type{<:AbstractSimpleGraph}, nvertices::Int) = T(nvertices) -function similar_simplegraph(T::Type{<:AbstractSimpleGraph}, vertices, edges) - new_graph = similar_simplegraph(T, vertices) - return add_edges!(new_graph, edges) -end @traitfn directed_graph(graph::::IsDirected) = graph @traitfn undirected_graph(graph::::(!IsDirected)) = graph diff --git a/src/lib/GraphsExtensions/src/simplegraph.jl b/src/lib/GraphsExtensions/src/simplegraph.jl index 4b89ed1f..4a7b3910 100644 --- a/src/lib/GraphsExtensions/src/simplegraph.jl +++ b/src/lib/GraphsExtensions/src/simplegraph.jl @@ -28,10 +28,8 @@ directed_graph_type(G::Type{<:SimpleDiGraph}) = G undirected_graph_type(G::Type{<:SimpleDiGraph}) = SimpleGraph{vertextype(G)} @traitfn function directed_graph(graph::AbstractSimpleGraph::(!IsDirected)) - digraph = similar_simplegraph(directed_graph_type(graph), vertices(graph), edges(graph)) - for e in edges(graph) - add_edge!(digraph, reverse(e)) - end + digraph = similar_simplegraph(directed_graph_type(graph), vertices(graph)) + add_edges!(digraph, all_edges(graph)) return digraph end diff --git a/src/lib/GraphsExtensions/test/runtests.jl b/src/lib/GraphsExtensions/test/runtests.jl index ce321329..a6e82140 100644 --- a/src/lib/GraphsExtensions/test/runtests.jl +++ b/src/lib/GraphsExtensions/test/runtests.jl @@ -497,10 +497,6 @@ using Test: @test, @test_broken, @test_throws, @testset @test isempty(edges(similar_simplegraph(g, vertices(g)))) @test isempty(edges(similar_simplegraph(typeof(g), vertices(g)))) - @test similar_simplegraph(g, vertices(g), edges(g)) == g - @test similar_simplegraph(typeof(g), vertices(g), edges(g)) == g - @test !(similar_simplegraph(g, vertices(g), edges(g)) === g) - # add_edge g = SimpleGraph(4) add_edge!(g, 1 => 2) diff --git a/src/namedgraph.jl b/src/namedgraph.jl index 0fa0335b..59b80c44 100644 --- a/src/namedgraph.jl +++ b/src/namedgraph.jl @@ -225,16 +225,12 @@ const NamedDiGraph{V} = GenericNamedGraph{V, SimpleDiGraph{Int}} function similar_graph( ::GenericNamedGraph{<:Any, G}, - vertices, - edges + vertices ) where {G} V = eltype(vertices) - graph = similar_graph(GenericNamedGraph{V, G}, vertices, edges) + graph = similar_graph(GenericNamedGraph{V, G}, vertices) # HACK: Unsure why this annotation is needed, but some type inference fails without it. return graph::GenericNamedGraph{V, G} end -function similar_graph(T::Type{<:GenericNamedGraph}, vertices, edges) - graph = add_edges!(T(vertices), edges) - return graph -end +similar_graph(T::Type{<:GenericNamedGraph}, vertices) = T(vertices) diff --git a/test/test_abstractnamedgraph.jl b/test/test_abstractnamedgraph.jl index 531eec75..ebdb9af2 100644 --- a/test/test_abstractnamedgraph.jl +++ b/test/test_abstractnamedgraph.jl @@ -15,8 +15,8 @@ end TestGraph{V}(vertices = V[]) where {V} = TestGraph(NamedGraph(vertices)) -function NamedGraphs.similar_graph(g::Type{<:TestGraph}, vertices, edges) - return TestGraph(similar_graph(NamedGraph, vertices, edges)) +function NamedGraphs.similar_graph(g::Type{<:TestGraph}, vertices) + return TestGraph(similar_graph(NamedGraph, vertices)) end Graphs.vertices(g::TestGraph) = vertices(g.graph) @@ -24,7 +24,6 @@ Graphs.edges(g::TestGraph) = edges(g.graph) Graphs.is_directed(::Type{<:TestGraph}) = false -Base.:(==)(g1::TestGraph, g2::AbstractGraph) = g1.graph == g2 Base.:(==)(g1::TestGraph, g2::TestGraph) = g1.graph == g2.graph Graphs.edgetype(::Type{<:TestGraph{V}}) where {V} = edgetype(NamedGraph{V}) @@ -181,7 +180,7 @@ end @test similar_graph(g) isa NamedGraph @test similar_graph(g) == ug - @test !(similar_graph(g) === ug) + @test similar_graph(g) !== ug @test similar_graph(typeof(g)) isa typeof(g) @test similar_graph(typeof(g)) == typeof(g)() @@ -193,11 +192,6 @@ end @test isempty(edges(similar_graph(g, vertices(g)))) @test isempty(edges(similar_graph(typeof(g), vertices(g)))) - @test similar_graph(g, vertices(g), edges(g)) == ug - @test !(similar_graph(g, vertices(g), edges(g)) === ug) - @test similar_graph(typeof(g), vertices(g), edges(g)) == g - @test !(similar_graph(typeof(g), vertices(g), edges(g)) === g) - @test nv(empty_graph(ug)) == 0 @test ne(empty_graph(ug)) == 0 From ba71723d826f447bb851e76d9f21a805ed253133 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Thu, 7 May 2026 14:02:41 -0400 Subject: [PATCH 07/11] Bug fixes --- src/lib/GraphsExtensions/src/trees_and_forests.jl | 4 ++-- src/steiner_tree.jl | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/GraphsExtensions/src/trees_and_forests.jl b/src/lib/GraphsExtensions/src/trees_and_forests.jl index bdb3e43b..ac070f0f 100644 --- a/src/lib/GraphsExtensions/src/trees_and_forests.jl +++ b/src/lib/GraphsExtensions/src/trees_and_forests.jl @@ -62,11 +62,11 @@ function forest_cover(g::AbstractGraph; spanning_tree = spanning_tree) return identity.(forests) end -function forest_cover_edge_sequence(gi::AbstractGraph; root_vertex = default_root_vertex) +function forest_cover_edge_sequence(g::AbstractGraph; root_vertex = default_root_vertex) forests = forest_cover(g) rv = edgetype(g)[] for forest in forests - trees = [forest[Vertices(vs)] for vs in connected_components(forest)] + trees = [subgraph(forest, vs) for vs in connected_components(forest)] for tree in trees tree_edges = post_order_dfs_edges(tree, root_vertex(tree)) push!(rv, vcat(tree_edges, reverse(reverse.(tree_edges)))...) diff --git a/src/steiner_tree.jl b/src/steiner_tree.jl index ac449208..1138194b 100644 --- a/src/steiner_tree.jl +++ b/src/steiner_tree.jl @@ -25,7 +25,8 @@ function namedgraph_steiner_tree( push!(featured_vertices, dst(named_edge)) end - tree = similar_graph(g, featured_vertices, named_edges) + tree = NamedGraph(featured_vertices) + add_edges!(tree, named_edges) return tree end From c19762a1d15a914e6ee38f4c893e474e62dfa1a6 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Thu, 14 May 2026 09:50:52 -0400 Subject: [PATCH 08/11] Version bump v0.11.3 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 11a9918f..5743190f 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "NamedGraphs" uuid = "678767b0-92e7-4007-89e4-4527a8725b19" -version = "0.11.2" +version = "0.11.3" authors = ["Matthew Fishman , Joseph Tindall and contributors"] [workspace] From c29d61868f5f25828dd8a2e5ce96537875f4bd7a Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Thu, 14 May 2026 10:40:27 -0400 Subject: [PATCH 09/11] In `Base.union`, output type now coincides with graph type of the first argument Previously, used `promote_type`. This is to align more with existing `Base.union` methods. --- src/abstractnamedgraph.jl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/abstractnamedgraph.jl b/src/abstractnamedgraph.jl index f772a607..11d392ba 100644 --- a/src/abstractnamedgraph.jl +++ b/src/abstractnamedgraph.jl @@ -396,11 +396,8 @@ function Graphs.has_path( end function Base.union(graph1::AbstractNamedGraph, graph2::AbstractNamedGraph) - union_graph_type = promote_type(typeof(graph1), typeof(graph2)) - union_vertices = union(vertices(graph1), vertices(graph2)) - - union_graph = similar_graph(union_graph_type, union_vertices) + union_graph = similar_graph(graph1, union_vertices) for e in edges(graph1) add_edge!(union_graph, e) From 4c5c63a0eb3cef620b7d11ef8469c1d45b7074c1 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Thu, 14 May 2026 10:42:13 -0400 Subject: [PATCH 10/11] Seperate a `similar_graph` optional argument definition. --- src/abstractnamedgraph.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/abstractnamedgraph.jl b/src/abstractnamedgraph.jl index 11d392ba..1ce9b06d 100644 --- a/src/abstractnamedgraph.jl +++ b/src/abstractnamedgraph.jl @@ -75,7 +75,8 @@ end # Passing a type as a first argument attempts to call a constructor. Should be overloaded # if the constructor doesnt exist for a given `AbstractGraph` concrete type. -similar_graph(T::Type{<:AbstractGraph}, vertices = vertextype(T)[]) = T(vertices) +similar_graph(T::Type{<:AbstractGraph}) = similar_graph(T, vertextype(T)[]) +similar_graph(T::Type{<:AbstractGraph}, vertices) = T(vertices) # If `T <: AbstractSimpleGraph`, then we defer to `GraphsExtensions.similar_simplegraph`. function similar_graph(T::Type{<:AbstractSimpleGraph}, vertices = 0) From d07aee605fda6d90a13febeb1f6465bd28c87517 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Thu, 14 May 2026 11:35:47 -0400 Subject: [PATCH 11/11] Constructing a `GenericNamedGraph` now copies its input data. - Removed the need to copy in `similar_graph` - Updated the inner constructor for `GenericNamedGraph` - Removed two outer constructors. --- src/abstractnamedgraph.jl | 2 +- .../OrderedDictionaries/src/orderedindices.jl | 2 -- src/namedgraph.jl | 28 +++++-------------- 3 files changed, 8 insertions(+), 24 deletions(-) diff --git a/src/abstractnamedgraph.jl b/src/abstractnamedgraph.jl index 1ce9b06d..41fa82d9 100644 --- a/src/abstractnamedgraph.jl +++ b/src/abstractnamedgraph.jl @@ -52,7 +52,7 @@ GraphsExtensions.convert_vertextype(::Type{V}, g::AbstractNamedGraph{V}) where { GraphsExtensions.convert_vertextype(::Type, g::AbstractNamedGraph) = not_implemented() function similar_graph(graph::AbstractGraph) - newgraph = similar_graph(graph, copy(vertices(graph))) + newgraph = similar_graph(graph, vertices(graph)) add_edges!(newgraph, edges(graph)) return newgraph end diff --git a/src/lib/OrderedDictionaries/src/orderedindices.jl b/src/lib/OrderedDictionaries/src/orderedindices.jl index 31188f66..77fcead2 100644 --- a/src/lib/OrderedDictionaries/src/orderedindices.jl +++ b/src/lib/OrderedDictionaries/src/orderedindices.jl @@ -25,8 +25,6 @@ struct OrderedIndices{I} <: AbstractIndices{I} end OrderedIndices(indices) = OrderedIndices{eltype(indices)}(indices) -OrderedIndices{I}(indices::OrderedIndices{I}) where {I} = copy(indices) - ordered_indices(indices::OrderedIndices) = getfield(indices, :ordered_indices) # TODO: Better name for this? index_positions(indices::OrderedIndices) = getfield(indices, :index_positions) diff --git a/src/namedgraph.jl b/src/namedgraph.jl index 59b80c44..a20c152f 100644 --- a/src/namedgraph.jl +++ b/src/namedgraph.jl @@ -10,9 +10,13 @@ using Graphs: Graphs, AbstractGraph, add_edge!, add_vertex!, edgetype, has_edge, struct GenericNamedGraph{V, G <: AbstractSimpleGraph{Int}} <: AbstractNamedGraph{V} position_graph::G vertices::OrderedIndices{V} - global function _GenericNamedGraph(position_graph, vertices) - @assert length(vertices) == nv(position_graph) - return new{eltype(vertices), typeof(position_graph)}(position_graph, vertices) + function GenericNamedGraph{V, G}(graph, vertices) where {V, G} + position_graph = G(graph) + vertices = OrderedIndices{V}(to_vertices(position_graph, vertices)) + + @assert length(vertices) == nv(graph) + + return new{V, G}(position_graph, vertices) end end @@ -80,24 +84,6 @@ to_vertices(vertices) = vertices to_vertices(vertices::AbstractArray) = vec(vertices) to_vertices(vertices::Integer) = Base.OneTo(vertices) -# Inner constructor -# TODO: Is this needed? -function GenericNamedGraph{V, G}( - position_graph::G, vertices::OrderedIndices{V} - ) where {V, G <: AbstractSimpleGraph{Int}} - return _GenericNamedGraph(position_graph, vertices) -end - -function GenericNamedGraph{V, G}( - position_graph::AbstractSimpleGraph, vertices - ) where {V, G <: AbstractSimpleGraph{Int}} - return GenericNamedGraph{V, G}( - convert(G, position_graph), OrderedIndices{V}( - to_vertices(position_graph, vertices) - ) - ) -end - function GenericNamedGraph{V}(position_graph::AbstractSimpleGraph, vertices) where {V} return GenericNamedGraph{V, typeof(position_graph)}(position_graph, vertices) end