From 34e4773715e2b18991ce509a804a1e8067bfb8ef Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Wed, 20 May 2026 09:59:41 -0400 Subject: [PATCH 01/10] Fix infinite method call in `forest_cover_edge_sequence`; add tests. --- src/abstractnamedgraph.jl | 5 ----- src/lib/GraphsExtensions/test/runtests.jl | 21 +++++++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/abstractnamedgraph.jl b/src/abstractnamedgraph.jl index 41fa82d..f97f84c 100644 --- a/src/abstractnamedgraph.jl +++ b/src/abstractnamedgraph.jl @@ -628,8 +628,3 @@ 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/test/runtests.jl b/src/lib/GraphsExtensions/test/runtests.jl index a6e8214..1c4fdd9 100644 --- a/src/lib/GraphsExtensions/test/runtests.jl +++ b/src/lib/GraphsExtensions/test/runtests.jl @@ -9,14 +9,14 @@ using NamedGraphs.GraphGenerators: binary_arborescence using NamedGraphs.GraphsExtensions: TreeGraph, add_edge, add_edges, add_edges!, add_vertices!, all_edges, arrange_edge, arranged_edges, child_edges, child_vertices, convert_vertextype, degrees, directed_graph, directed_graph_type, disjoint_union, - distance_to_leaves, has_edges, has_leaf_neighbor, has_vertices, incident_edges, - indegrees, is_arborescence, is_arranged, is_binary_arborescence, is_cycle_graph, - 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, ⊔ + distance_to_leaves, forest_cover_edge_sequence, has_edges, has_leaf_neighbor, + has_vertices, incident_edges, indegrees, is_arborescence, is_arranged, + is_binary_arborescence, is_cycle_graph, 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, ⊔ using NamedGraphs: NamedDiGraph, NamedEdge, NamedGraph using Test: @test, @test_broken, @test_throws, @testset @@ -539,6 +539,11 @@ 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 = NamedGraph(path_graph(2)) + @test forest_cover_edge_sequence(g; root_vertex = (g) -> 1) == + [NamedEdge(2, 1), NamedEdge(1, 2)] + @test forest_cover_edge_sequence(g; root_vertex = (g) -> 2) == + [NamedEdge(1, 2), NamedEdge(2, 1)] @testset "arrange" begin @testset "is_arranged, is_edge_arranged" begin for (a, b) in [ From 0a12bb56528966ef9fc65aa8cf2437334eb08442 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Wed, 20 May 2026 11:21:07 -0400 Subject: [PATCH 02/10] Version bump to v0.11.4 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 5743190..d5dab0f 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "NamedGraphs" uuid = "678767b0-92e7-4007-89e4-4527a8725b19" -version = "0.11.3" +version = "0.11.4" authors = ["Matthew Fishman , Joseph Tindall and contributors"] [workspace] From b8c3d8d1a31ac61008e1304cfdd4bef3b064a082 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Tue, 26 May 2026 17:55:46 -0400 Subject: [PATCH 03/10] Move `similar_graph` back to `GraphsExtensions`; add `similar_dataless_graph`. --- src/NamedGraphs.jl | 1 + src/abstractnamedgraph.jl | 68 +++--------------- src/lib/GraphsExtensions/src/abstractgraph.jl | 71 ++++++++++++++++--- src/lib/GraphsExtensions/src/simplegraph.jl | 21 ------ .../GraphsExtensions/src/trees_and_forests.jl | 9 ++- src/lib/GraphsExtensions/test/runtests.jl | 39 ++++++---- src/namedgraph.jl | 15 ++-- src/similar_graph.jl | 70 ++++++++++++++++++ test/test_abstractnamedgraph.jl | 33 +++++++-- test/test_trees_and_forests.jl | 3 +- 10 files changed, 212 insertions(+), 118 deletions(-) create mode 100644 src/similar_graph.jl diff --git a/src/NamedGraphs.jl b/src/NamedGraphs.jl index c3021b1..5a8bfd5 100644 --- a/src/NamedGraphs.jl +++ b/src/NamedGraphs.jl @@ -10,6 +10,7 @@ include("abstractnamededge.jl") include("namededge.jl") include("abstractnamedgraph.jl") include("abstractgraphindices.jl") +include("similar_graph.jl") include("decorate.jl") include("simplecycles.jl") include("shortestpaths.jl") diff --git a/src/abstractnamedgraph.jl b/src/abstractnamedgraph.jl index f97f84c..0e58c23 100644 --- a/src/abstractnamedgraph.jl +++ b/src/abstractnamedgraph.jl @@ -1,13 +1,12 @@ using .GraphsExtensions: GraphsExtensions, all_edges, directed_graph, incident_edges, - partition_vertices, rem_edges, rem_edges!, rem_vertices, rename_vertices, - similar_simplegraph, subgraph + partition_vertices, rem_edges, rem_edges!, rem_vertices, rename_vertices, similar_graph, + subgraph using Dictionaries: set! -using Graphs.SimpleGraphs: SimpleDiGraph, SimpleEdge -using Graphs: Graphs, AbstractGraph, AbstractSimpleGraph, IsDirected, a_star, add_edge!, - adjacency_matrix, bfs_parents, boruvka_mst, connected_components, degree, edges, - has_path, indegree, induced_subgraph, inneighbors, is_connected, is_cyclic, kruskal_mst, - ne, neighborhood, neighborhood_dists, nv, outdegree, prim_mst, rem_edge!, - spfa_shortest_paths, vertices, weights +using Graphs: Graphs, AbstractGraph, AbstractSimpleGraph, IsDirected, SimpleDiGraph, + SimpleEdge, SimpleGraph, a_star, add_edge!, adjacency_matrix, bfs_parents, boruvka_mst, + connected_components, degree, edges, has_path, indegree, induced_subgraph, inneighbors, + is_connected, is_cyclic, kruskal_mst, ne, neighborhood, neighborhood_dists, nv, + outdegree, prim_mst, rem_edge!, spfa_shortest_paths, vertices, weights using SimpleTraits: SimpleTraits, @traitfn, Not abstract type AbstractNamedGraph{V} <: AbstractGraph{V} end @@ -51,42 +50,6 @@ Graphs.edgetype(::Type{<:AbstractNamedGraph}) = not_implemented() GraphsExtensions.convert_vertextype(::Type{V}, g::AbstractNamedGraph{V}) where {V} = g GraphsExtensions.convert_vertextype(::Type, g::AbstractNamedGraph) = not_implemented() -function similar_graph(graph::AbstractGraph) - newgraph = similar_graph(graph, vertices(graph)) - add_edges!(newgraph, edges(graph)) - return newgraph -end - -# Construct `GenericNamedGraph` as a fallback. -@traitfn function similar_graph( - graph::AbstractGraph::(!IsDirected), - vertices - ) - V = eltype(vertices) - return NamedGraph{V}(vertices) -end -@traitfn function similar_graph( - graph::AbstractGraph::IsDirected, - vertices - ) - V = eltype(vertices) - 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}) = 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) - return similar_simplegraph(T, vertices) -end - -edgeless_graph(graph::AbstractGraph) = rem_edges(graph, edges(graph)) - -empty_graph(graph::AbstractGraph) = rem_vertices(graph, vertices(graph)) - Base.copy(graph::AbstractNamedGraph) = copyto!(similar_graph(graph), graph) function Graphs.merge_vertices!( @@ -585,7 +548,7 @@ end # TODO: Implement an edgelist version function induced_subgraph_from_vertices(graph::AbstractGraph, subvertices) - subgraph = similar_graph(graph, collect(subvertices)) + subgraph = similar_graph(graph, subvertices) add_edges!(subgraph, subgraph_edges(graph, subvertices)) return subgraph, nothing end @@ -613,18 +576,3 @@ function edge_subgraph_namedgraph(graph, edgelist) g = rem_edges!(g, setdiff(edges(g), edgelist)) return g 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::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) - end - return undigraph -end diff --git a/src/lib/GraphsExtensions/src/abstractgraph.jl b/src/lib/GraphsExtensions/src/abstractgraph.jl index b5bcc9d..415d26f 100644 --- a/src/lib/GraphsExtensions/src/abstractgraph.jl +++ b/src/lib/GraphsExtensions/src/abstractgraph.jl @@ -26,17 +26,26 @@ convert_vertextype(::Type, ::AbstractGraph) = not_implemented() convert_vertextype(::Type{V}, G::Type{<:AbstractGraph{V}}) where {V} = G convert_vertextype(::Type, ::Type{<:AbstractGraph}) = not_implemented() -# ==================================== similar_simplegraph =============================== # +# ==================================== similar_graph ===================================== # -function similar_simplegraph(graph::AbstractGraph) - newgraph = similar_simplegraph(graph, vertices(graph)) +# Generic for any `AbstractGraph`. +function similar_graph(graph::AbstractGraph) + newgraph = similar_graph(graph, vertices(graph)) add_edges!(newgraph, edges(graph)) return newgraph end +similar_graph(graph::AbstractGraph, vertices) = similar_simplegraph(graph, vertices) + +# Don't know a sensible fallback in `Graphs.jl` for a non-`Base.OneTo` vertex collection. +function similar_simplegraph(graph::AbstractGraph, vertices) + return throw(MethodError(similar_graph, (typeof(graph), typeof(vertices)))) +end + function similar_simplegraph(graph::AbstractGraph, vertices::Base.OneTo) - return similar_simplegraph(graph, length(vertices)) + return similar_graph(graph, length(vertices)) end + # To be specialized (optional, has following fallback) @traitfn function similar_simplegraph( graph::AbstractGraph::(!IsDirected), @@ -55,16 +64,60 @@ end return new_graph end -similar_simplegraph(T::Type{<:AbstractSimpleGraph}) = T() -similar_simplegraph(T::Type{<:AbstractSimpleGraph}, vertices) = T(vertices) -function similar_simplegraph(T::Type{<:AbstractSimpleGraph}, vertices::Base.OneTo) - return similar_simplegraph(T, length(vertices)) +# Type domain versions attempt to call a constructor. +similar_graph(T::Type{<:AbstractGraph}) = T() +similar_graph(T::Type{<:AbstractGraph}, vertices) = T(vertices) +function similar_graph(T::Type{<:AbstractSimpleGraph}, vertices::Base.OneTo) + return similar_graph(T, length(vertices)) +end +similar_graph(T::Type{<:AbstractSimpleGraph}) = similar_graph(T, 0) + +# =============================== `similar_dataless_grap` ================================ # +# This function behaves much the same as `similar_graph`, but should strictly return a +# a similar graph type that has no notion of data (in the abstract sense). + +function similar_dataless_graph(graph::AbstractGraph) + dataless_graph = similar_graph(graph, vertices(graph)) + add_edges!(dataless_graph, edges(graph)) + return dataless_graph +end +function similar_dataless_graph(graph::AbstractGraph, vertices) + return throw(MethodError(similar_dataless_graph, (typeof(graph), typeof(vertices)))) +end + +function similar_dataless_graph(graph::AbstractGraph, vertices::Base.OneTo) + return similar_graph(graph, length(vertices)) end -similar_simplegraph(T::Type{<:AbstractSimpleGraph}, nvertices::Int) = T(nvertices) + +# Fallbacks +@traitfn function similar_dataless_graph(graph::AbstractGraph::!(IsDirected), nv::Int) + return SimpleGraph(nv) +end + +@traitfn function similar_dataless_graph(graph::AbstractGraph::IsDirected, nv::Int) + return SimpleDiGraph(nv) +end + +edgeless_graph(graph::AbstractGraph) = rem_edges(graph, edges(graph)) +empty_graph(graph::AbstractGraph) = rem_vertices(graph, vertices(graph)) @traitfn directed_graph(graph::::IsDirected) = graph @traitfn undirected_graph(graph::::(!IsDirected)) = graph +@traitfn function directed_graph(graph::AbstractGraph::(!IsDirected)) + digraph = similar_graph(directed_graph_type(graph), vertices(graph)) + add_edges!(digraph, all_edges(graph)) + return digraph +end +@traitfn function undirected_graph(graph::AbstractGraph::IsDirected) + undigraph = similar_graph(undirected_graph_type(graph), vertices(graph)) + for e in edges(graph) + has_edge(undigraph, e) && continue + add_edge!(undigraph, e) + end + return undigraph +end + # Similar to `eltype`, but `eltype` doesn't work on types vertextype(::Type{<:AbstractGraph{V}}) where {V} = V vertextype(graph::AbstractGraph) = vertextype(typeof(graph)) diff --git a/src/lib/GraphsExtensions/src/simplegraph.jl b/src/lib/GraphsExtensions/src/simplegraph.jl index 4a7b391..25c17ce 100644 --- a/src/lib/GraphsExtensions/src/simplegraph.jl +++ b/src/lib/GraphsExtensions/src/simplegraph.jl @@ -20,28 +20,7 @@ function convert_vertextype(vertextype::Type, graph::SimpleDiGraph) end directed_graph_type(G::Type{<:SimpleGraph}) = SimpleDiGraph{vertextype(G)} -# TODO: Use traits to make this more general. undirected_graph_type(G::Type{<:SimpleGraph}) = G -# TODO: Use traits to make this more general. 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)) - add_edges!(digraph, all_edges(graph)) - return digraph -end - -# Must have the same argument name as: -# @traitfn undirected_graph(graph::::(!IsDirected)) -# to avoid method overwrite warnings, see: -# https://github.com/mauro3/SimpleTraits.jl#method-overwritten-warnings -@traitfn function undirected_graph(graph::AbstractSimpleGraph::IsDirected) - undigraph = similar_simplegraph(undirected_graph_type(graph), vertices(graph)) - for e in edges(graph) - has_edge(undigraph, e) && continue - add_edge!(undigraph, e) - end - return undigraph -end diff --git a/src/lib/GraphsExtensions/src/trees_and_forests.jl b/src/lib/GraphsExtensions/src/trees_and_forests.jl index ac070f0..b043cf4 100644 --- a/src/lib/GraphsExtensions/src/trees_and_forests.jl +++ b/src/lib/GraphsExtensions/src/trees_and_forests.jl @@ -1,5 +1,5 @@ using .GraphsExtensions: random_bfs_tree, rem_edges, undirected_graph -using Graphs: IsDirected, bfs_tree, connected_components, edges, edgetype +using Graphs: AbstractGraph, IsDirected, bfs_tree, connected_components, edges, edgetype using SimpleTraits: SimpleTraits, @traitfn, Not abstract type SpanningTreeAlgorithm end @@ -47,9 +47,12 @@ end # Given an undirected graph g with vertex set V, build a set of forests (each with vertex set V) which covers all edges in g # (see https://en.wikipedia.org/wiki/Arboricity) We do not find the minimum but our tests show this algorithm performs well function forest_cover(g::AbstractGraph; spanning_tree = spanning_tree) - edges_collected = edgetype(g)[] + g = similar_dataless_graph(g) + g_reduced = copy(g) + remaining_edges = edges(g) - g_reduced = rem_edges(g, edges_collected) + edges_collected = empty(remaining_edges) + forests = typeof(g)[] while !isempty(remaining_edges) g_reduced_spanning_forest = spanning_forest(g_reduced; spanning_tree) diff --git a/src/lib/GraphsExtensions/test/runtests.jl b/src/lib/GraphsExtensions/test/runtests.jl index 1c4fdd9..1192a5a 100644 --- a/src/lib/GraphsExtensions/test/runtests.jl +++ b/src/lib/GraphsExtensions/test/runtests.jl @@ -15,8 +15,8 @@ using NamedGraphs.GraphsExtensions: TreeGraph, add_edge, add_edges, add_edges!, 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, ⊔ + similar_dataless_graph, similar_graph, 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 @@ -482,20 +482,31 @@ using Test: @test, @test_broken, @test_throws, @testset @test_throws ErrorException root_vertex(g) @test_throws MethodError root_vertex(binary_tree(3)) - # similar_simplegraph + # similar_graph g = path_graph(4) - @test similar_simplegraph(g) isa typeof(g) - @test similar_simplegraph(g) == g - @test similar_simplegraph(typeof(g)) isa typeof(g) - @test similar_simplegraph(typeof(g)) == typeof(g)() - @test isempty(edges(similar_simplegraph(typeof(g)))) - @test isempty(vertices(similar_simplegraph(typeof(g)))) - - @test similar_simplegraph(g, vertices(g)) == typeof(g)(4) - @test similar_simplegraph(typeof(g), vertices(g)) == typeof(g)(4) - @test isempty(edges(similar_simplegraph(g, vertices(g)))) - @test isempty(edges(similar_simplegraph(typeof(g), vertices(g)))) + @test similar_graph(g) isa typeof(g) + @test similar_graph(g) == g + @test similar_graph(typeof(g)) isa typeof(g) + @test similar_graph(typeof(g)) == typeof(g)() + @test isempty(edges(similar_graph(typeof(g)))) + @test isempty(vertices(similar_graph(typeof(g)))) + + @test similar_graph(g, vertices(g)) == typeof(g)(4) + @test similar_graph(typeof(g), vertices(g)) == typeof(g)(4) + @test isempty(edges(similar_graph(g, vertices(g)))) + @test isempty(edges(similar_graph(typeof(g), vertices(g)))) + + # similar_dataless_graph + g = path_graph(4) + + @test similar_dataless_graph(g) isa SimpleGraph + @test similar_dataless_graph(SimpleDiGraph(4)) isa SimpleDiGraph + @test similar_dataless_graph(g) == g + + @test similar_dataless_graph(SimpleDiGraph(4), 2) isa SimpleDiGraph + @test similar_dataless_graph(g, vertices(g)) == typeof(g)(4) + @test isempty(edges(similar_dataless_graph(g, vertices(g)))) # add_edge g = SimpleGraph(4) diff --git a/src/namedgraph.jl b/src/namedgraph.jl index a20c152..66e9646 100644 --- a/src/namedgraph.jl +++ b/src/namedgraph.jl @@ -1,5 +1,5 @@ -using .GraphsExtensions: - GraphsExtensions, directed_graph_type, undirected_graph_type, vertextype +using .GraphsExtensions: GraphsExtensions, directed_graph_type, similar_graph, + similar_simplegraph, undirected_graph_type, vertextype using .OrderedDictionaries: OrderedDictionaries, OrderedIndices using .OrdinalIndexing: th using Dictionaries: Dictionary @@ -123,7 +123,7 @@ end # function GenericNamedGraph{V, G}(vertices) where {V, G <: AbstractSimpleGraph{Int}} - return GenericNamedGraph(G(length(to_vertices(vertices))), vertices) + return GenericNamedGraph{V, G}(G(length(to_vertices(vertices))), vertices) end function GenericNamedGraph{V}(vertices) where {V} @@ -209,7 +209,10 @@ end const NamedGraph{V} = GenericNamedGraph{V, SimpleGraph{Int}} const NamedDiGraph{V} = GenericNamedGraph{V, SimpleDiGraph{Int}} -function similar_graph( +function GraphsExtensions.similar_graph(graph::GenericNamedGraph, nv::Int) + return similar_simplegraph(graph, nv) +end +function GraphsExtensions.similar_graph( ::GenericNamedGraph{<:Any, G}, vertices ) where {G} @@ -219,4 +222,6 @@ function similar_graph( return graph::GenericNamedGraph{V, G} end -similar_graph(T::Type{<:GenericNamedGraph}, vertices) = T(vertices) +function GraphsExtensions.similar_graph(T::Type{<:GenericNamedGraph}, vertices) + return T(vertices) +end diff --git a/src/similar_graph.jl b/src/similar_graph.jl new file mode 100644 index 0000000..2d2bf2a --- /dev/null +++ b/src/similar_graph.jl @@ -0,0 +1,70 @@ +using .GraphsExtensions: rem_edges, rem_vertices, similar_graph, similar_simplegraph +using SimpleTraits: SimpleTraits, @traitfn, Not + +# =================================== `similar_graph` ==================================== # + +function GraphsExtensions.similar_graph(graph::AbstractNamedGraph, vertices) + return similar_namedgraph(graph, vertices) +end + +@traitfn function similar_namedgraph( + graph::AbstractGraph::(!IsDirected), + vertices + ) + V = eltype(vertices) + return NamedGraph{V}(vertices) +end +@traitfn function similar_namedgraph( + graph::AbstractGraph::IsDirected, + vertices + ) + V = eltype(vertices) + return NamedDiGraph{V}(vertices) +end + +similar_namedgraph(g::AbstractGraph, nv::Int) = similar_simplegraph(g, nv) +similar_namedgraph(g::AbstractGraph, ::Base.OneTo) = similar_graph(g, collect(vertices)) + +# 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. +function GraphsExtensions.similar_graph(T::Type{<:AbstractNamedGraph}) + return similar_graph(T, vertextype(T)[]) +end + +# =============================== `similar_dataless_graph` =============================== # +# This function behaves much the same as `similar_graph`, but should strictly return a +# a similar graph type that has no notion of data (in the abstract sense). + +# Dimambiguation with `AbstractGraph` method in `GraphsExtensions` +@traitfn function GraphsExtensions.similar_dataless_graph( + graph::AbstractNamedGraph::(!IsDirected), + nv::Int + ) + return SimpleGraph(nv) +end +@traitfn function GraphsExtensions.similar_dataless_graph( + graph::AbstractNamedGraph::(IsDirected), + nv::Int + ) + return SimpleDiGraph(nv) +end +function GraphsExtensions.similar_dataless_graph( + graph::AbstractNamedGraph, + vertices::Base.OneTo + ) + return similar_dataless_graph(graph, collect(vertices)) +end + +@traitfn function GraphsExtensions.similar_dataless_graph( + graph::AbstractNamedGraph::(!IsDirected), + vertices + ) + return NamedGraph(vertices) +end + +@traitfn function GraphsExtensions.similar_dataless_graph( + graph::AbstractNamedGraph::IsDirected, + vertices + ) + return NamedDiGraph(vertices) +end diff --git a/test/test_abstractnamedgraph.jl b/test/test_abstractnamedgraph.jl index ebdb9af..62f8eed 100644 --- a/test/test_abstractnamedgraph.jl +++ b/test/test_abstractnamedgraph.jl @@ -1,11 +1,11 @@ @eval module $(gensym()) using Dictionaries: Dictionary -using Graphs: Graphs, AbstractGraph, DiGraph, Graph, a_star, add_edge!, edges, edgetype, - grid, has_edge, has_vertex, ne, nv, rem_edge!, vertices -using NamedGraphs.GraphsExtensions: GraphsExtensions, rename_vertices +using Graphs: Graphs, AbstractGraph, DiGraph, Graph, SimpleDiGraph, SimpleGraph, a_star, + add_edge!, edges, edgetype, grid, has_edge, has_vertex, ne, nv, rem_edge!, vertices +using NamedGraphs.GraphsExtensions: GraphsExtensions, edgeless_graph, empty_graph, + rename_vertices, similar_dataless_graph, similar_graph using NamedGraphs.NamedGraphGenerators: named_grid, named_path_graph -using NamedGraphs: NamedGraphs, AbstractNamedGraph, NamedDiGraph, NamedGraph, - edgeless_graph, empty_graph, position_graph, similar_graph +using NamedGraphs: NamedGraphs, AbstractNamedGraph, NamedDiGraph, NamedGraph, position_graph using Test: @test, @testset struct TestGraph{V} <: AbstractNamedGraph{V} @@ -192,6 +192,11 @@ end @test isempty(edges(similar_graph(g, vertices(g)))) @test isempty(edges(similar_graph(typeof(g), vertices(g)))) + @test similar_graph(g, 2) isa SimpleGraph + @test nv(similar_graph(g, 2)) == 2 + @test similar_graph(NamedDiGraph([1, 2]), 2) isa SimpleDiGraph + @test nv(similar_graph(NamedDiGraph([1, 2]), 2)) == 2 + @test nv(empty_graph(ug)) == 0 @test ne(empty_graph(ug)) == 0 @@ -201,6 +206,24 @@ end # Make sure the TestGraph is unchanged. @test nv(g) == 4 @test ne(g) == 3 + + # similar_dataless_graph + g = named_path_graph(4) + + @test similar_dataless_graph(g) isa NamedGraph + @test similar_dataless_graph(g) == g + @test similar_dataless_graph(NamedDiGraph([1, 2, 3])) isa NamedDiGraph + + @test similar_dataless_graph(g, vertices(g)) == typeof(g)(4) + @test isempty(edges(similar_dataless_graph(g, vertices(g)))) + + sdg = similar_dataless_graph(g, 2) + @test sdg isa SimpleGraph + @test nv(sdg) == 2 + + sdg = similar_dataless_graph(NamedDiGraph([1, 2, 3]), vertices(g)) + @test similar_dataless_graph(sdg, [1, 2]) isa NamedDiGraph + @test similar_dataless_graph(sdg, 2) isa SimpleDiGraph end end diff --git a/test/test_trees_and_forests.jl b/test/test_trees_and_forests.jl index f88b306..da6cd82 100644 --- a/test/test_trees_and_forests.jl +++ b/test/test_trees_and_forests.jl @@ -1,6 +1,7 @@ @eval module $(gensym()) using Graphs: connected_components, edges, is_tree, vertices -using NamedGraphs.GraphsExtensions: GraphsExtensions, all_edges, forest_cover, spanning_tree +using NamedGraphs.GraphsExtensions: + GraphsExtensions, all_edges, forest_cover, spanning_tree, vertextype using NamedGraphs.NamedGraphGenerators: named_comb_tree, named_grid, named_hexagonal_lattice_graph, named_triangular_lattice_graph using NamedGraphs: NamedGraph, Vertices From c026e70875b2b39a8bdc0bba2268b0e059c8c17c Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Wed, 27 May 2026 14:56:13 -0400 Subject: [PATCH 04/10] Fix bug when checking for quotient vertices for cases where the fallback is used; add test --- src/lib/PartitionedGraphs/src/partitionedgraph.jl | 4 +--- src/lib/PartitionedGraphs/src/quotientvertex.jl | 4 ++-- test/test_partitionedgraph.jl | 4 ++++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/lib/PartitionedGraphs/src/partitionedgraph.jl b/src/lib/PartitionedGraphs/src/partitionedgraph.jl index 79278ec..66c420d 100644 --- a/src/lib/PartitionedGraphs/src/partitionedgraph.jl +++ b/src/lib/PartitionedGraphs/src/partitionedgraph.jl @@ -122,9 +122,7 @@ function Graphs.rem_vertex!(pg::PartitionedGraph{V}, vertex::V) where {V} rem_vertex!(pg.graph, vertex) # If the super-vertex is now empty, remove it from the quotient graph - if !haskey(pg.partitioned_vertices, qv) - rem_vertex!(pg.quotient_graph, qv) - end + qv ∈ keys(pg.partitioned_vertices) || rem_vertex!(pg.quotient_graph, qv) return pg end diff --git a/src/lib/PartitionedGraphs/src/quotientvertex.jl b/src/lib/PartitionedGraphs/src/quotientvertex.jl index a795cab..2e75ef7 100644 --- a/src/lib/PartitionedGraphs/src/quotientvertex.jl +++ b/src/lib/PartitionedGraphs/src/quotientvertex.jl @@ -79,7 +79,7 @@ function Graphs.vertices(g::AbstractGraph, quotientvertex::QuotientVertex) qv = parent(quotientvertex) pvs = partitioned_vertices(g) - haskey(pvs, qv) || throw(ArgumentError("Quotient vertex $quotientvertex not in graph")) + qv ∈ keys(pvs) || throw(ArgumentError("Quotient vertex $quotientvertex not in graph")) return pvs[qv] end @@ -88,7 +88,7 @@ function Graphs.vertices(g::AbstractGraph, quotientvertices::QuotientVertices) end function has_quotientvertex(g::AbstractGraph, quotientvertex::QuotientVertex) - return haskey(partitioned_vertices(g), parent(quotientvertex)) + return parent(quotientvertex) ∈ keys(partitioned_vertices(g)) end Graphs.nv(g::AbstractGraph, sv::QuotientVertex) = length(vertices(g, sv)) diff --git a/test/test_partitionedgraph.jl b/test/test_partitionedgraph.jl index 9d361ce..3083f87 100644 --- a/test/test_partitionedgraph.jl +++ b/test/test_partitionedgraph.jl @@ -144,6 +144,10 @@ end @testset "Test Partitioned Graph Subgraph Functionality" begin n, z = 12, 4 g = NamedGraph(random_regular_graph(n, z)) + + # test trivial case + @test g[QuotientVertex(1)] == g + partitions = dictionary( [ 1 => [1, 2, 3], 2 => [4, 5, 6], 3 => [7, 8, 9], 4 => [10, 11, 12], From 0b4938b9986e2e9f90f219a52e0d2473804ca442 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Wed, 27 May 2026 14:56:49 -0400 Subject: [PATCH 05/10] Adjust `similar_dataless_graph` to avoid method ambiguities. --- src/lib/GraphsExtensions/src/abstractgraph.jl | 9 +++--- src/similar_graph.jl | 28 ++++++------------- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/src/lib/GraphsExtensions/src/abstractgraph.jl b/src/lib/GraphsExtensions/src/abstractgraph.jl index 415d26f..98648c8 100644 --- a/src/lib/GraphsExtensions/src/abstractgraph.jl +++ b/src/lib/GraphsExtensions/src/abstractgraph.jl @@ -81,20 +81,21 @@ function similar_dataless_graph(graph::AbstractGraph) add_edges!(dataless_graph, edges(graph)) return dataless_graph end + function similar_dataless_graph(graph::AbstractGraph, vertices) - return throw(MethodError(similar_dataless_graph, (typeof(graph), typeof(vertices)))) + return similar_dataless_simplegraph(graph, vertices) end -function similar_dataless_graph(graph::AbstractGraph, vertices::Base.OneTo) +function similar_dataless_simplegraph(graph::AbstractGraph, vertices::Base.OneTo) return similar_graph(graph, length(vertices)) end # Fallbacks -@traitfn function similar_dataless_graph(graph::AbstractGraph::!(IsDirected), nv::Int) +@traitfn function similar_dataless_simplegraph(graph::AbstractGraph::!(IsDirected), nv::Int) return SimpleGraph(nv) end -@traitfn function similar_dataless_graph(graph::AbstractGraph::IsDirected, nv::Int) +@traitfn function similar_dataless_simplegraph(graph::AbstractGraph::IsDirected, nv::Int) return SimpleDiGraph(nv) end diff --git a/src/similar_graph.jl b/src/similar_graph.jl index 2d2bf2a..d91b8b3 100644 --- a/src/similar_graph.jl +++ b/src/similar_graph.jl @@ -35,36 +35,24 @@ end # This function behaves much the same as `similar_graph`, but should strictly return a # a similar graph type that has no notion of data (in the abstract sense). -# Dimambiguation with `AbstractGraph` method in `GraphsExtensions` -@traitfn function GraphsExtensions.similar_dataless_graph( - graph::AbstractNamedGraph::(!IsDirected), - nv::Int - ) - return SimpleGraph(nv) -end -@traitfn function GraphsExtensions.similar_dataless_graph( - graph::AbstractNamedGraph::(IsDirected), - nv::Int - ) - return SimpleDiGraph(nv) -end -function GraphsExtensions.similar_dataless_graph( - graph::AbstractNamedGraph, - vertices::Base.OneTo - ) - return similar_dataless_graph(graph, collect(vertices)) +function GraphsExtensions.similar_dataless_graph(graph::AbstractNamedGraph, vertices) + return similar_dataless_namedgraph(graph, vertices) end -@traitfn function GraphsExtensions.similar_dataless_graph( +@traitfn function similar_dataless_namedgraph( graph::AbstractNamedGraph::(!IsDirected), vertices ) return NamedGraph(vertices) end -@traitfn function GraphsExtensions.similar_dataless_graph( +@traitfn function similar_dataless_namedgraph( graph::AbstractNamedGraph::IsDirected, vertices ) return NamedDiGraph(vertices) end + +function similar_dataless_namedgraph(graph::AbstractNamedGraph, nv::Int) + return GraphsExtensions.similar_dataless_simplegraph(graph, nv) +end From cfc411c72744bd6b165334cadcc6db62ee0954da Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Wed, 27 May 2026 14:57:13 -0400 Subject: [PATCH 06/10] Add `empty_graph` to namespace of `NamedGraphs` for backwards compat. --- src/abstractnamedgraph.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/abstractnamedgraph.jl b/src/abstractnamedgraph.jl index 0e58c23..6d99927 100644 --- a/src/abstractnamedgraph.jl +++ b/src/abstractnamedgraph.jl @@ -1,6 +1,6 @@ -using .GraphsExtensions: GraphsExtensions, all_edges, directed_graph, incident_edges, - partition_vertices, rem_edges, rem_edges!, rem_vertices, rename_vertices, similar_graph, - subgraph +using .GraphsExtensions: GraphsExtensions, all_edges, directed_graph, empty_graph, + incident_edges, partition_vertices, rem_edges, rem_edges!, rem_vertices, + rename_vertices, similar_graph, subgraph using Dictionaries: set! using Graphs: Graphs, AbstractGraph, AbstractSimpleGraph, IsDirected, SimpleDiGraph, SimpleEdge, SimpleGraph, a_star, add_edge!, adjacency_matrix, bfs_parents, boruvka_mst, From 49c2dd7f2f4d94cd67a034fb82627c1156084405 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Thu, 28 May 2026 16:07:20 -0400 Subject: [PATCH 07/10] Add code comments explaining `haskey` -> `\in keys` change. --- src/lib/PartitionedGraphs/src/quotientvertex.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/PartitionedGraphs/src/quotientvertex.jl b/src/lib/PartitionedGraphs/src/quotientvertex.jl index 2e75ef7..8e0ab6d 100644 --- a/src/lib/PartitionedGraphs/src/quotientvertex.jl +++ b/src/lib/PartitionedGraphs/src/quotientvertex.jl @@ -79,6 +79,8 @@ function Graphs.vertices(g::AbstractGraph, quotientvertex::QuotientVertex) qv = parent(quotientvertex) pvs = partitioned_vertices(g) + + # Can't use `haskey` as not defined on vectors. qv ∈ keys(pvs) || throw(ArgumentError("Quotient vertex $quotientvertex not in graph")) return pvs[qv] @@ -88,6 +90,7 @@ function Graphs.vertices(g::AbstractGraph, quotientvertices::QuotientVertices) end function has_quotientvertex(g::AbstractGraph, quotientvertex::QuotientVertex) + # Can't use `haskey` as not defined on vectors. return parent(quotientvertex) ∈ keys(partitioned_vertices(g)) end From 8916958f0f90ec2cd1dca978c371438119988aab Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Thu, 28 May 2026 16:38:40 -0400 Subject: [PATCH 08/10] Fix comment wording regarding haskey usage --- src/lib/PartitionedGraphs/src/quotientvertex.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/PartitionedGraphs/src/quotientvertex.jl b/src/lib/PartitionedGraphs/src/quotientvertex.jl index 8e0ab6d..6e74c5a 100644 --- a/src/lib/PartitionedGraphs/src/quotientvertex.jl +++ b/src/lib/PartitionedGraphs/src/quotientvertex.jl @@ -80,7 +80,7 @@ function Graphs.vertices(g::AbstractGraph, quotientvertex::QuotientVertex) pvs = partitioned_vertices(g) - # Can't use `haskey` as not defined on vectors. + # Can't use `haskey` since it is not defined on vectors. qv ∈ keys(pvs) || throw(ArgumentError("Quotient vertex $quotientvertex not in graph")) return pvs[qv] @@ -90,7 +90,7 @@ function Graphs.vertices(g::AbstractGraph, quotientvertices::QuotientVertices) end function has_quotientvertex(g::AbstractGraph, quotientvertex::QuotientVertex) - # Can't use `haskey` as not defined on vectors. + # Can't use `haskey` since it is not defined on vectors. return parent(quotientvertex) ∈ keys(partitioned_vertices(g)) end From d3c20ad97d956f3625c7b2fa5041e98b9a698fb8 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Thu, 28 May 2026 16:43:03 -0400 Subject: [PATCH 09/10] Fix `similar_dataless_graph` definitions for `SimpleGraph`. --- src/lib/GraphsExtensions/src/abstractgraph.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/GraphsExtensions/src/abstractgraph.jl b/src/lib/GraphsExtensions/src/abstractgraph.jl index 98648c8..d8f70e7 100644 --- a/src/lib/GraphsExtensions/src/abstractgraph.jl +++ b/src/lib/GraphsExtensions/src/abstractgraph.jl @@ -77,7 +77,7 @@ similar_graph(T::Type{<:AbstractSimpleGraph}) = similar_graph(T, 0) # a similar graph type that has no notion of data (in the abstract sense). function similar_dataless_graph(graph::AbstractGraph) - dataless_graph = similar_graph(graph, vertices(graph)) + dataless_graph = similar_dataless_graph(graph, vertices(graph)) add_edges!(dataless_graph, edges(graph)) return dataless_graph end @@ -87,7 +87,7 @@ function similar_dataless_graph(graph::AbstractGraph, vertices) end function similar_dataless_simplegraph(graph::AbstractGraph, vertices::Base.OneTo) - return similar_graph(graph, length(vertices)) + return similar_dataless_simplegraph(graph, length(vertices)) end # Fallbacks From e1ab8b36f352183e1ba5212443079fbefb7e616d Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Thu, 28 May 2026 16:46:23 -0400 Subject: [PATCH 10/10] Remove unnecessary `copy` call in `forest_cover` definition. --- src/lib/GraphsExtensions/src/trees_and_forests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/GraphsExtensions/src/trees_and_forests.jl b/src/lib/GraphsExtensions/src/trees_and_forests.jl index b043cf4..1564ab5 100644 --- a/src/lib/GraphsExtensions/src/trees_and_forests.jl +++ b/src/lib/GraphsExtensions/src/trees_and_forests.jl @@ -48,7 +48,7 @@ end # (see https://en.wikipedia.org/wiki/Arboricity) We do not find the minimum but our tests show this algorithm performs well function forest_cover(g::AbstractGraph; spanning_tree = spanning_tree) g = similar_dataless_graph(g) - g_reduced = copy(g) + g_reduced = g remaining_edges = edges(g) edges_collected = empty(remaining_edges)