From fc884573e740b0e91b9fe2931c737b197c29adc0 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Thu, 14 May 2026 10:00:46 -0400 Subject: [PATCH 01/28] Add `AbstractVertex/EdgeDataGraph` const types. --- src/DataGraphs.jl | 1 + src/abstractdatagraph.jl | 23 +++- src/abstractindexdatagraph.jl | 199 ++++++++++++++++++++++++++++++++++ 3 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 src/abstractindexdatagraph.jl diff --git a/src/DataGraphs.jl b/src/DataGraphs.jl index 6f93972..04725f4 100644 --- a/src/DataGraphs.jl +++ b/src/DataGraphs.jl @@ -6,6 +6,7 @@ include("dataview.jl") include("abstractdatagraph.jl") include("indexing.jl") include("datagraph.jl") +include("abstractindexdatagraph.jl") # TODO: Turn into an extension once `PartitionedGraphs` is excised. include("lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl") diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl index 2fefa68..b94e392 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -6,7 +6,8 @@ using NamedGraphs.GraphsExtensions: GraphsExtensions, add_edges!, add_vertices!, using NamedGraphs.OrdinalIndexing: OrdinalSuffixedInteger using NamedGraphs.SimilarType: similar_type using NamedGraphs: NamedGraphs, AbstractEdges, AbstractNamedEdge, AbstractNamedGraph, - AbstractVertices, NamedDiGraph, NamedGraph, position_graph_type, similar_graph + AbstractVertices, NamedDiGraph, NamedGraph, Vertices, position_graph_type, + similar_graph, subgraph_edges using SimpleTraits: SimpleTraits, @traitfn, Not abstract type AbstractDataGraph{V, VD, ED} <: AbstractNamedGraph{V} end @@ -75,11 +76,27 @@ function NamedGraphs.position_graph_type(type::Type{<:AbstractDataGraph}) return position_graph_type(underlying_graph_type(type)) end +function Base.copy(graph::AbstractDataGraph) + copy_graph = similar_graph(graph) + copyto!(copy_graph, graph) + return graph +end + function Base.copyto!(dst_graph::AbstractDataGraph, src_graph::AbstractDataGraph) vertex_data(dst_graph) .= vertex_data(src_graph) edge_data(dst_graph) .= edge_data(src_graph) return dst_graph end +function Base.copyto!( + graph_dst::AbstractDataGraph, + dict_src::Union{Dict, AbstractDictionary}, + dimnames = keys(dict_src) + ) + for key in dimnames + graph_dst[key] = dict_src[key] + end + return graph_dst +end # Graphs overloads function Graphs.vertices(graph::AbstractDataGraph) @@ -110,8 +127,8 @@ GraphsExtensions.convert_vertextype(::Type, ::AbstractDataGraph) = not_implement function Base.:(==)(dg1::AbstractDataGraph, dg2::AbstractDataGraph) underlying_graph(dg1) == underlying_graph(dg2) || return false - vertex_data(dg1) == vertex_data(dg2) || return false - edge_data(dg1) == edge_data(dg2) || return false + assigned_vertex_data(dg1) == assigned_vertex_data(dg2) || return false + assigned_edge_data(dg1) == assigned_edge_data(dg2) || return false return true end diff --git a/src/abstractindexdatagraph.jl b/src/abstractindexdatagraph.jl new file mode 100644 index 0000000..1fd9b26 --- /dev/null +++ b/src/abstractindexdatagraph.jl @@ -0,0 +1,199 @@ +# ================================== vertex data graph =================================== # + +const AbstractVertexDataGraph{V, VD} = AbstractDataGraph{V, VD, Nothing} + +is_edge_assigned(::AbstractVertexDataGraph, _edge) = false + +# For ambiguity resolution. +function NamedGraphs.similar_graph( + graph::AbstractVertexDataGraph, + VD::Type, + ED::Type + ) + return similar_graph(graph, VD, ED, vertices(graph)) # goes to fallback +end + +function NamedGraphs.similar_graph( + graph::AbstractVertexDataGraph, + VD::Type, + ::Type{<:Nothing}, + vertices + ) + return similar_graph(graph, VD, vertices) +end + +function NamedGraphs.similar_graph( + graph::AbstractVertexDataGraph, + T::Type, + vertices::Vertices + ) + return DataGraph( + similar_graph(underlying_graph(graph), collect(vertices)); + vertex_data_type = Nothing, + edge_data_type = T + ) +end + +function NamedGraphs.induced_subgraph_from_vertices( + graph::AbstractVertexDataGraph, + subvertices + ) + subnetwork = similar_graph(graph, subvertices) + tensors = view(vertex_data(graph), Indices(subvertices)) + copyto!(subnetwork, tensors) + return subnetwork, subvertices +end + +# Internal +index_data(graph::AbstractVertexDataGraph) = vertex_data(graph) + +function Base.show(io::IO, mime::MIME"text/plain", graph::AbstractVertexDataGraph) + println(io, "$(typeof(graph)) with $(nv(graph)) vertices:") + show(io, mime, vertices(graph)) + println(io, "\n") + println(io, "and $(ne(graph)) edge(s):") + for e in edges(graph) + show(io, mime, e) + println(io) + end + println(io) + println(io, "with vertex data:") + show(io, mime, vertex_data(graph)) + return nothing +end + +# =================================== edge data graph ==================================== # + +const AbstractEdgeDataGraph{V, ED} = AbstractDataGraph{V, Nothing, ED} + +is_vertex_assigned(::AbstractEdgeDataGraph, _vertex) = false + +# For ambiguity resolution. +function NamedGraphs.similar_graph( + graph::AbstractEdgeDataGraph, + VD::Type, + ED::Type + ) + return similar_graph(graph, VD, ED, vertices(graph)) # goes to fallback +end + +function NamedGraphs.similar_graph( + graph::AbstractEdgeDataGraph, + ::Type{<:Nothing}, + ED::Type, + vertices + ) + return similar_graph(graph, ED, vertices) +end + +function NamedGraphs.similar_graph( + graph::AbstractEdgeDataGraph, + T::Type, + vertices::Vertices + ) + return DataGraph( + similar_graph(underlying_graph(graph), collect(vertices)); + vertex_data_type = Nothing, + edge_data_type = T + ) +end + +function NamedGraphs.induced_subgraph_from_vertices( + graph::AbstractEdgeDataGraph, + subvertices + ) + subnetwork = similar_graph(graph, subvertices) + subedges = subgraph_edges(graph, subvertices) + add_edges!(subnetwork, subedges) + + tensors = view(edge_data(graph), Indices(subedges)) + + copyto!(subnetwork, tensors) + + return subnetwork, subvertices +end + +# Internal +index_data(graph::AbstractEdgeDataGraph) = edge_data(graph) + +function Base.show(io::IO, mime::MIME"text/plain", graph::AbstractEdgeDataGraph) + println(io, "$(typeof(graph)) with $(nv(graph)) vertices:") + show(io, mime, vertices(graph)) + println(io, "\n") + println(io, "and $(ne(graph)) edge(s):") + for e in edges(graph) + show(io, mime, e) + println(io) + end + println(io) + println(io, "with edge data:") + show(io, mime, edge_data(graph)) + return nothing +end + +# ============================= index graph (vertex or edge) ============================= # + +const AbstractIndexDataGraph{V, T} = + Union{AbstractVertexDataGraph{V, T}, AbstractEdgeDataGraph{V, T}} + +function NamedGraphs.similar_graph( + graph::AbstractIndexDataGraph, + T::Type + ) + return similar_graph(graph, T, vertices(graph)) +end + +function NamedGraphs.similar_graph( + graph::AbstractIndexDataGraph, + T::Type, + vertices + ) + return similar_graph(graph, T, Vertices(vertices)) +end + +function Base.:(==)(dg1::AbstractIndexDataGraph, dg2::AbstractIndexDataGraph) + return underlying_graph(dg1) == underlying_graph(dg2) && + index_data(dg1) == index_data(dg2) +end + +function copyto!_indexdatagraph( + dst::AbstractIndexDataGraph, + src, # not a graph. + dimnames = nothing + ) + dimnames = isnothing(dimnames) ? Indices(keys(src)) : Indices(dimnames) + view(index_data(dst), dimnames) .= view(src, dimnames) + return dst +end + +function Base.copyto!( + graph_dst::G, + graph_src::G, + dimnames = nothing + ) where {G <: AbstractIndexDataGraph} + copyto!_indexdatagraph(graph_dst, index_data(graph_src), dimnames) + return graph_dst +end +function Base.copyto!( + graph_dst::AbstractIndexDataGraph, + dictionary_src::AbstractDictionary, + dimnames = nothing + ) + copyto!_indexdatagraph(graph_dst, dictionary_src, dimnames) + return graph_dst +end + +Base.iterate(graph::AbstractIndexDataGraph) = iterate(index_data(graph)) +Base.iterate(graph::AbstractIndexDataGraph, state) = iterate(index_data(graph), state) + +Base.keytype(::AbstractIndexDataGraph{V, T}) where {V, T} = V +Base.keytype(::Type{<:AbstractIndexDataGraph{V, T}}) where {V, T} = V + +Base.valtype(::AbstractIndexDataGraph{V, T}) where {V, T} = T +Base.valtype(::Type{<:AbstractIndexDataGraph{V, T}}) where {V, T} = T + +Base.eltype(::AbstractIndexDataGraph{V, T}) where {V, T} = T +Base.eltype(::Type{<:AbstractIndexDataGraph{V, T}}) where {V, T} = T + +Base.keys(graph::AbstractIndexDataGraph) = keys(index_data(graph)) +Base.length(graph::AbstractIndexDataGraph) = length(index_data(graph)) From 5b628ca4de3f658b70a1db70603f5797f399eb5a Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Fri, 15 May 2026 14:44:40 -0400 Subject: [PATCH 02/28] Add some code comments. --- src/abstractdatagraph.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl index b94e392..1bb200d 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -22,12 +22,15 @@ edge_data_type(::Type{<:AbstractDataGraph{V, VD, ED}}) where {V, VD, ED} = ED # TODO: Define for `AbstractGraph` as a `DataGraphInterface`. underlying_graph(::AbstractDataGraph) = not_implemented() +# `isassigned` is_vertex_assigned(::AbstractDataGraph, vertex) = not_implemented() is_edge_assigned(::AbstractDataGraph, edge) = not_implemented() +# `getindex` get_vertex_data(::AbstractDataGraph, vertex) = not_implemented() get_edge_data(::AbstractDataGraph, edge) = not_implemented() +# `setindex!` set_vertex_data!(::AbstractDataGraph, data, vertex) = not_implemented() set_edge_data!(::AbstractDataGraph, data, edge) = not_implemented() From fa19f5df7d40003f70a760f533e8be235026e6ad Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Fri, 15 May 2026 14:45:22 -0400 Subject: [PATCH 03/28] Make `AbstractVertexOrEdgeDataGraph` an abstract type; add concrete type. --- src/DataGraphs.jl | 2 + src/abstractindexdatagraph.jl | 247 +++++++++++++++++++++++++--------- src/edgedatagraph.jl | 97 +++++++++++++ src/vertexdatagraph.jl | 90 +++++++++++++ 4 files changed, 369 insertions(+), 67 deletions(-) create mode 100644 src/edgedatagraph.jl create mode 100644 src/vertexdatagraph.jl diff --git a/src/DataGraphs.jl b/src/DataGraphs.jl index 04725f4..e11d955 100644 --- a/src/DataGraphs.jl +++ b/src/DataGraphs.jl @@ -7,6 +7,8 @@ include("abstractdatagraph.jl") include("indexing.jl") include("datagraph.jl") include("abstractindexdatagraph.jl") +include("vertexdatagraph.jl") +include("edgedatagraph.jl") # TODO: Turn into an extension once `PartitionedGraphs` is excised. include("lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl") diff --git a/src/abstractindexdatagraph.jl b/src/abstractindexdatagraph.jl index 1fd9b26..cbb63e3 100644 --- a/src/abstractindexdatagraph.jl +++ b/src/abstractindexdatagraph.jl @@ -1,9 +1,137 @@ +abstract type AbstractVertexOrEdgeDataGraph{I, T, V} <: AbstractDataGraph{V, T, T} end + +function NamedGraphs.similar_graph( + graph::AbstractVertexOrEdgeDataGraph, + T::Type + ) + return similar_graph(graph, T, vertices(graph)) +end + +function NamedGraphs.similar_graph( + graph::AbstractVertexOrEdgeDataGraph, + T::Type, + vertices + ) + return similar_graph(graph, T, Vertices(vertices)) +end + +function Base.:(==)(dg1::AbstractVertexOrEdgeDataGraph, dg2::AbstractVertexOrEdgeDataGraph) + return underlying_graph(dg1) == underlying_graph(dg2) && + index_data(dg1) == index_data(dg2) +end + +function copyto!_indexdatagraph( + dst::AbstractVertexOrEdgeDataGraph, + src, # not a graph. + dimnames = nothing + ) + dimnames = isnothing(dimnames) ? Indices(keys(src)) : Indices(dimnames) + view(index_data(dst), dimnames) .= view(src, dimnames) + return dst +end + +function Base.copyto!( + graph_dst::G, + graph_src::G, + dimnames = nothing + ) where {G <: AbstractVertexOrEdgeDataGraph} + copyto!_indexdatagraph(graph_dst, index_data(graph_src), dimnames) + return graph_dst +end +function Base.copyto!( + graph_dst::AbstractVertexOrEdgeDataGraph, + dictionary_src::AbstractDictionary, + dimnames = nothing + ) + copyto!_indexdatagraph(graph_dst, dictionary_src, dimnames) + return graph_dst +end + +Base.iterate(graph::AbstractVertexOrEdgeDataGraph) = iterate(index_data(graph)) +function Base.iterate(graph::AbstractVertexOrEdgeDataGraph, state) + return iterate(index_data(graph), state) +end + +Base.keytype(graph::AbstractVertexOrEdgeDataGraph) = keytype(typeof(graph)) +Base.keytype(::Type{<:AbstractVertexOrEdgeDataGraph{I, T, V}}) where {I, T, V} = I + +Base.valtype(graph::AbstractVertexOrEdgeDataGraph) = valtype(typeof(graph)) +Base.valtype(::Type{<:AbstractVertexOrEdgeDataGraph{I, T, V}}) where {I, T, V} = T + +Base.eltype(graph::AbstractVertexOrEdgeDataGraph) = eltype(typeof(graph)) +Base.eltype(::Type{<:AbstractVertexOrEdgeDataGraph{I, T, V}}) where {I, T, V} = T + +Base.length(graph::AbstractVertexOrEdgeDataGraph) = length(index_data(graph)) +Base.keys(graph::AbstractVertexOrEdgeDataGraph) = keys(index_data(graph)) +Base.values(graph::AbstractVertexOrEdgeDataGraph) = values(index_data(graph)) + +Dictionaries.issettable(::AbstractVertexOrEdgeDataGraph) = true +Dictionaries.isinsertable(::AbstractVertexOrEdgeDataGraph) = true + +function Base.insert!( + graph::AbstractVertexOrEdgeDataGraph{I, T}, + ind::I, + data::T + ) where {I, T} + insert!_datagraph(graph, to_graph_index(graph, ind), data) + return graph +end +function Base.delete!(graph::AbstractVertexOrEdgeDataGraph{I, T}, ind::T) where {I, T} + delete!_datagraph(graph, to_graph_index(graph, ind)) + return graph +end +# function Dictionaries.set!(graph::AbstractVertexOrEdgeDataGraph, ind) +# set!_datagraph(graph, to_graph_index(graph, ind)) +# return graph +# end + # ================================== vertex data graph =================================== # -const AbstractVertexDataGraph{V, VD} = AbstractDataGraph{V, VD, Nothing} +abstract type AbstractVertexDataGraph{V, T} <: AbstractVertexOrEdgeDataGraph{V, T, V} end is_edge_assigned(::AbstractVertexDataGraph, _edge) = false +# `setindex!` +function set_index_data!(graph::AbstractVertexDataGraph, data, vertex) + if !has_vertex(graph, vertex) + throw(IndexError("Graph does not contain vertex $vertex")) + else + set_vertex_data!(graph, data, vertex) + end + return graph +end + +# `insert!` +function insert!_datagraph(graph::AbstractVertexDataGraph, vertex, data) + if has_vertex(graph, vertex) + throw(IndexError("Graph already contains vertex $vertex")) + else + add_vertex!(graph, vertex) + setindex!(graph, data, vertex) + end + return graph +end + +# `delete!` +function delete!_datagraph(graph::AbstractVertexDataGraph, vertex) + if !has_vertex(graph, vertex) + throw(IndexError("Graph does not contain vertex $vertex")) + else + rem_vertex!(graph, vertex) + end + return graph +end + +# # `set!` +# function set!_datagraph(graph::AbstractVertexDataGraph, data, vertex) +# if has_vertex(graph, vertex) +# set_vertex_data!(graph, vertex, data) +# else +# insert!_datagraph(graph, vertex, data) +# end +# return graph +# end + # For ambiguity resolution. function NamedGraphs.similar_graph( graph::AbstractVertexDataGraph, @@ -64,10 +192,51 @@ end # =================================== edge data graph ==================================== # -const AbstractEdgeDataGraph{V, ED} = AbstractDataGraph{V, Nothing, ED} +abstract type AbstractEdgeDataGraph{E, T, V} <: AbstractVertexOrEdgeDataGraph{E, T, V} end is_vertex_assigned(::AbstractEdgeDataGraph, _vertex) = false +# `setindex!` +function set_index_data!(graph::AbstractEdgeDataGraph, data, edge::AbstractEdge) + if !has_edge(graph, edge) + throw(IndexError("Graph does not contain edge $edge")) + else + set_edge_data!(graph, data, edge) + end + return graph +end + +# `insert!` +function insert!_datagraph(graph::AbstractEdgeDataGraph, edge::AbstractEdge, data) + if has_edge(graph, edge) + throw(IndexError("Graph already contains edge $edge")) + else + add_edge!(graph, edge) + setindex!(graph, data, edge) + end + return graph +end + +# `delete!` +function delete!_datagraph(graph::AbstractEdgeDataGraph, edge) + if !has_edge(graph, edge) + throw(IndexError("Graph does not contain edge $edge")) + else + rem_edge!(graph, edge) + end + return graph +end +# +# # `set!` +# function set!_datagraph(graph::AbstractEdgeDataGraph, data, edge::AbstractEdge) +# if has_edge(graph, edge) +# set_edge_data!(graph, edge, data) +# else +# insert!_datagraph(graph, edge, data) +# end +# return graph +# end + # For ambiguity resolution. function NamedGraphs.similar_graph( graph::AbstractEdgeDataGraph, @@ -131,69 +300,13 @@ function Base.show(io::IO, mime::MIME"text/plain", graph::AbstractEdgeDataGraph) return nothing end -# ============================= index graph (vertex or edge) ============================= # - -const AbstractIndexDataGraph{V, T} = - Union{AbstractVertexDataGraph{V, T}, AbstractEdgeDataGraph{V, T}} - -function NamedGraphs.similar_graph( - graph::AbstractIndexDataGraph, - T::Type - ) - return similar_graph(graph, T, vertices(graph)) -end - -function NamedGraphs.similar_graph( - graph::AbstractIndexDataGraph, - T::Type, - vertices - ) - return similar_graph(graph, T, Vertices(vertices)) -end - -function Base.:(==)(dg1::AbstractIndexDataGraph, dg2::AbstractIndexDataGraph) - return underlying_graph(dg1) == underlying_graph(dg2) && - index_data(dg1) == index_data(dg2) -end - -function copyto!_indexdatagraph( - dst::AbstractIndexDataGraph, - src, # not a graph. - dimnames = nothing - ) - dimnames = isnothing(dimnames) ? Indices(keys(src)) : Indices(dimnames) - view(index_data(dst), dimnames) .= view(src, dimnames) - return dst -end - -function Base.copyto!( - graph_dst::G, - graph_src::G, - dimnames = nothing - ) where {G <: AbstractIndexDataGraph} - copyto!_indexdatagraph(graph_dst, index_data(graph_src), dimnames) - return graph_dst -end -function Base.copyto!( - graph_dst::AbstractIndexDataGraph, - dictionary_src::AbstractDictionary, - dimnames = nothing - ) - copyto!_indexdatagraph(graph_dst, dictionary_src, dimnames) - return graph_dst -end - -Base.iterate(graph::AbstractIndexDataGraph) = iterate(index_data(graph)) -Base.iterate(graph::AbstractIndexDataGraph, state) = iterate(index_data(graph), state) - -Base.keytype(::AbstractIndexDataGraph{V, T}) where {V, T} = V -Base.keytype(::Type{<:AbstractIndexDataGraph{V, T}}) where {V, T} = V - -Base.valtype(::AbstractIndexDataGraph{V, T}) where {V, T} = T -Base.valtype(::Type{<:AbstractIndexDataGraph{V, T}}) where {V, T} = T - -Base.eltype(::AbstractIndexDataGraph{V, T}) where {V, T} = T -Base.eltype(::Type{<:AbstractIndexDataGraph{V, T}}) where {V, T} = T +# ============================== Dictionaries.jl interface =============================== # -Base.keys(graph::AbstractIndexDataGraph) = keys(index_data(graph)) -Base.length(graph::AbstractIndexDataGraph) = length(index_data(graph)) +# Dictionaries.isinsertable(::AbstractVertexOrEdgeDataGraph) = true +# function Dictionaries.insert!(graph::AbstractVertexOrEdgeDataGraph{I, T}, ind::I, value::T) +# insert_graph_index!(graph, ind, value) +# return graph +# end +# function Dictionaries.delete!(graph::AbstractVertexOrEdgeDataGraph{I, T}, ind::I, value::T) +# return graph +# end diff --git a/src/edgedatagraph.jl b/src/edgedatagraph.jl new file mode 100644 index 0000000..9c2a770 --- /dev/null +++ b/src/edgedatagraph.jl @@ -0,0 +1,97 @@ +using Graphs: dst, rem_edge!, rem_vertex!, src +using NamedGraphs: NamedEdge, NamedGraph, ordered_vertices, position_graph, vertex_positions + +struct EdgeDataGraph{E <: NamedEdge{V} where {V}, T, V} <: AbstractEdgeDataGraph{E, T, V} + underlying_graph::NamedGraph{V} + edge_data::Dictionary{E, T} + function EdgeDataGraph{E, T, V}( + ::UndefInitializer, + vertices + ) where {V, E <: NamedEdge{V}, T} + graph = NamedGraph{V}(vertices) + edge_data = Dictionary{E, T}() + return new{E, T, V}(graph, edge_data) + end +end + +EdgeDataGraph(data) = EdgeDataGraph{keytype(data)}(data) +EdgeDataGraph{I}(data) where {I} = EdgeDataGraph{I, valtype(data)}(data) +EdgeDataGraph{I, T}(data) where {I, T} = EdgeDataGraph{I, T, vertextype{I}}(data) + +Graphs.is_directed(::Type{<:EdgeDataGraph}) = false + +struct EdgeDataDiGraph{E <: NamedEdge{V} where {V}, T, V} <: AbstractEdgeDataGraph{E, T, V} + underlying_graph::NamedDiGraph{V} + edge_data::Dictionary{E, T} + function EdgeDataDiGraph{E, T, V}( + ::UndefInitializer, + vertices + ) where {V, E <: NamedEdge{V}, T} + graph = NamedDiGraph{V}(vertices) + edge_data = Dictionary{E, T}() + return new{E, T, V}(graph, edge_data) + end +end + +EdgeDataDiGraph(data) = EdgeDataDiGraph{keytype(data)}(data) +EdgeDataDiGraph{I}(data) where {I} = EdgeDataDiGraph{I, valtype(data)}(data) +EdgeDataDiGraph{I, T}(data) where {I, T} = EdgeDataDiGraph{I, T, vertextype{I}}(data) + +Graphs.is_directed(::Type{<:EdgeDataDiGraph}) = true + +const GenericEdgeDataGraph{I, T, V} = + Union{EdgeDataGraph{I, T, V}, EdgeDataDiGraph{I, T, V}} + +function (GType::Type{<:GenericEdgeDataGraph{I, T, V}})(data) where {I, T, V} + edges = keys(data) + vertices = union(src.(edges), dst.(edges)) + cache = GType(undef, vertices) + add_edges!(cache.underlying_graph, edges) + return copyto!(cache, data) +end + +# ====================================== Graphs.jl ======================================= # + +function Graphs.rem_edge!(graph::GenericEdgeDataGraph, edge) + rem_edge!(graph.underlying_graph, edge) + delete!(graph.edge_data, edge) + return graph +end + +function Graphs.rem_vertex!(graph::GenericEdgeDataGraph, vertex) + for edge in incident_edges(graph, vertex) + delete!(graph.edge_data, edge) + end + rem_vertex!(graph.underlying_graph, vertex) + return graph +end + +Graphs.vertices(graph::GenericEdgeDataGraph) = vertices(graph.underlying_graph) + +# ==================================== NamedGraphs.jl ==================================== # + +function NamedGraphs.vertex_positions(graph::GenericEdgeDataGraph) + return vertex_positions(graph.underlying_graph) +end + +function NamedGraphs.ordered_vertices(graph::GenericEdgeDataGraph) + return ordered_vertices(graph.underlying_graph) +end + +function NamedGraphs.position_graph(graph::GenericEdgeDataGraph) + return position_graph(graph.underlying_graph) +end + +# ==================================== DataGraphs.jl ===================================== # + +edge_data_type(::Type{<:GenericEdgeDataGraph{I, T}}) where {I, T} = T + +function set_edge_data!(graph::GenericEdgeDataGraph, data, edge) + graph.edge_data[edge] = data + return graph +end + +get_edge_data(graph::GenericEdgeDataGraph, edge) = graph.edge_data[edge] + +is_vertex_assigned(::GenericEdgeDataGraph, _vertex) = false +is_edge_assigned(graph::GenericEdgeDataGraph, edge) = isassigned(graph.edge_data, edge) diff --git a/src/vertexdatagraph.jl b/src/vertexdatagraph.jl new file mode 100644 index 0000000..494e5ff --- /dev/null +++ b/src/vertexdatagraph.jl @@ -0,0 +1,90 @@ +using Dictionaries: Dictionary, set! +using Graphs: Graphs, rem_vertex! +using NamedGraphs: + NamedDiGraph, NamedGraph, ordered_vertices, position_graph, vertex_positions + +struct VertexDataGraph{V, T} <: AbstractVertexDataGraph{V, T} + underlying_graph::NamedGraph{V} + vertex_data::Dictionary{V, T} + function VertexDataGraph{V, T}( + ::UndefInitializer, + vertices + ) where {V, T} + graph = NamedGraph{V}(vertices) + vertex_data = Dictionary{V, T}() + return new{V, T}(graph, vertex_data) + end +end + +VertexDataGraph(data) = VertexDataGraph{keytype(data)}(data) +VertexDataGraph{V}(data) where {V} = VertexDataGraph{V, valtype(data)}(data) + +struct VertexDataDiGraph{V, T} <: AbstractVertexDataGraph{V, T} + underlying_graph::NamedDiGraph{V} + vertex_data::Dictionary{V, T} + function VertexDataDiGraph{V, T}( + ::UndefInitializer, + vertices + ) where {V, T} + graph = NamedDiGraph{V}(vertices) + vertex_data = Dictionary{V, T}() + return new{V, T}(graph, vertex_data) + end +end + +VertexDataDiGraph(data) = VertexDataDiGraph{keytype(data)}(data) +VertexDataDiGraph{V}(data) where {V} = VertexDataDiGraph{V, valtype(data)}(data) + +const GenericVertexDataGraph{V, T} = Union{VertexDataGraph{V, T}, VertexDataDiGraph{V, T}} + +function (GType::Type{<:GenericVertexDataGraph{V, T}})(data) where {V, T} + vertices = keys(data) + cache = GType(undef, vertices) + return copyto!(cache, data) +end + +Graphs.is_directed(::Type{<:VertexDataGraph}) = false +Graphs.is_directed(::Type{<:VertexDataDiGraph}) = true + +# ====================================== Graphs.jl ======================================= # + +function Graphs.rem_vertex!(graph::GenericVertexDataGraph, vertex) + delete!(graph.vertex_data, vertex) + rem_vertex!(graph.underlying_graph, vertex) + return graph +end + +Graphs.vertices(graph::GenericVertexDataGraph) = vertices(graph.underlying_graph) + +# ==================================== NamedGraphs.jl ==================================== # + +function NamedGraphs.vertex_positions(graph::GenericVertexDataGraph) + return vertex_positions(graph.underlying_graph) +end + +function NamedGraphs.ordered_vertices(graph::GenericVertexDataGraph) + return ordered_vertices(graph.underlying_graph) +end + +function NamedGraphs.position_graph(graph::GenericVertexDataGraph) + return position_graph(graph.underlying_graph) +end + +# ==================================== DataGraphs.jl ===================================== # + +underlying_graph(graph::VertexDataGraph) = getfield(graph, :underlying_graph) +underlying_graph(graph::VertexDataDiGraph) = getfield(graph, :underlying_graph) + +vertex_data_type(::Type{<:GenericVertexDataGraph{V, T}}) where {V, T} = T + +function set_vertex_data!(graph::GenericVertexDataGraph, data, vertex) + graph.vertex_data[vertex] = data + return graph +end + +get_vertex_data(graph::GenericVertexDataGraph, vertex) = graph.vertex_data[vertex] + +function is_vertex_assigned(graph::GenericVertexDataGraph, vertex) + return isassigned(graph.vertex_data, vertex) +end +is_edge_assigned(::GenericVertexDataGraph, _edge) = false From 0afcc3580dc294254cd1a67dee051fa31832258b Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Fri, 15 May 2026 14:50:12 -0400 Subject: [PATCH 04/28] Rename file. --- src/DataGraphs.jl | 2 +- ...stractindexdatagraph.jl => abstractedgeorvertexdatagraph.jl} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{abstractindexdatagraph.jl => abstractedgeorvertexdatagraph.jl} (100%) diff --git a/src/DataGraphs.jl b/src/DataGraphs.jl index e11d955..e201e5e 100644 --- a/src/DataGraphs.jl +++ b/src/DataGraphs.jl @@ -6,7 +6,7 @@ include("dataview.jl") include("abstractdatagraph.jl") include("indexing.jl") include("datagraph.jl") -include("abstractindexdatagraph.jl") +include("abstractedgeorvertexdatagraph.jl") include("vertexdatagraph.jl") include("edgedatagraph.jl") # TODO: Turn into an extension once `PartitionedGraphs` is excised. diff --git a/src/abstractindexdatagraph.jl b/src/abstractedgeorvertexdatagraph.jl similarity index 100% rename from src/abstractindexdatagraph.jl rename to src/abstractedgeorvertexdatagraph.jl From b2e39d3e0818d5f271d661f04194752deece18f1 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Fri, 15 May 2026 16:13:47 -0400 Subject: [PATCH 05/28] Tests and bug fixes. --- src/abstractdatagraph.jl | 4 +- src/abstractedgeorvertexdatagraph.jl | 108 +++++----- src/edgedatagraph.jl | 38 +++- src/vertexdatagraph.jl | 33 ++- test/test_vertexoredgedatagraph.jl | 293 +++++++++++++++++++++++++++ 5 files changed, 403 insertions(+), 73 deletions(-) create mode 100644 test/test_vertexoredgedatagraph.jl diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl index 1bb200d..d55d57a 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -61,9 +61,7 @@ function assigned_edges(graph::AbstractGraph) return Indices(filter(e -> isassigned(graph, e), edges(graph))) end -function Graphs.edgetype(graph::AbstractDataGraph) - return Graphs.edgetype(underlying_graph(graph)) -end +Graphs.edgetype(graph::AbstractDataGraph) = edgetype(typeof(graph)) function Graphs.edgetype(graph_type::Type{<:AbstractDataGraph}) return edgetype(underlying_graph_type(graph_type)) end diff --git a/src/abstractedgeorvertexdatagraph.jl b/src/abstractedgeorvertexdatagraph.jl index cbb63e3..036b947 100644 --- a/src/abstractedgeorvertexdatagraph.jl +++ b/src/abstractedgeorvertexdatagraph.jl @@ -20,16 +20,6 @@ function Base.:(==)(dg1::AbstractVertexOrEdgeDataGraph, dg2::AbstractVertexOrEdg index_data(dg1) == index_data(dg2) end -function copyto!_indexdatagraph( - dst::AbstractVertexOrEdgeDataGraph, - src, # not a graph. - dimnames = nothing - ) - dimnames = isnothing(dimnames) ? Indices(keys(src)) : Indices(dimnames) - view(index_data(dst), dimnames) .= view(src, dimnames) - return dst -end - function Base.copyto!( graph_dst::G, graph_src::G, @@ -66,7 +56,7 @@ Base.keys(graph::AbstractVertexOrEdgeDataGraph) = keys(index_data(graph)) Base.values(graph::AbstractVertexOrEdgeDataGraph) = values(index_data(graph)) Dictionaries.issettable(::AbstractVertexOrEdgeDataGraph) = true -Dictionaries.isinsertable(::AbstractVertexOrEdgeDataGraph) = true +Dictionaries.isinsertable(::AbstractVertexOrEdgeDataGraph) = false function Base.insert!( graph::AbstractVertexOrEdgeDataGraph{I, T}, @@ -80,10 +70,11 @@ function Base.delete!(graph::AbstractVertexOrEdgeDataGraph{I, T}, ind::T) where delete!_datagraph(graph, to_graph_index(graph, ind)) return graph end -# function Dictionaries.set!(graph::AbstractVertexOrEdgeDataGraph, ind) -# set!_datagraph(graph, to_graph_index(graph, ind)) -# return graph -# end + +function Dictionaries.set!(graph::AbstractVertexOrEdgeDataGraph, ind, data) + set!_datagraph(graph, to_graph_index(graph, ind), data) + return graph +end # ================================== vertex data graph =================================== # @@ -103,12 +94,7 @@ end # `insert!` function insert!_datagraph(graph::AbstractVertexDataGraph, vertex, data) - if has_vertex(graph, vertex) - throw(IndexError("Graph already contains vertex $vertex")) - else - add_vertex!(graph, vertex) - setindex!(graph, data, vertex) - end + insert_vertex_data!(graph, vertex, data) return graph end @@ -122,15 +108,25 @@ function delete!_datagraph(graph::AbstractVertexDataGraph, vertex) return graph end -# # `set!` -# function set!_datagraph(graph::AbstractVertexDataGraph, data, vertex) -# if has_vertex(graph, vertex) -# set_vertex_data!(graph, vertex, data) -# else -# insert!_datagraph(graph, vertex, data) -# end -# return graph -# end +# `set!` +function set!_datagraph(graph::AbstractVertexDataGraph, vertex, data) + if has_vertex(graph, vertex) + set_vertex_data!(graph, data, vertex) + else + insert_vertex_data!(graph, vertex, data) + end + return graph +end + +function copyto!_indexdatagraph( + dst::AbstractVertexDataGraph, + src, # not a graph. + dimnames = nothing + ) + dimnames = isnothing(dimnames) ? Indices(keys(src)) : Indices(dimnames) + view(index_data(dst), dimnames) .= view(src, dimnames) + return dst +end # For ambiguity resolution. function NamedGraphs.similar_graph( @@ -208,12 +204,7 @@ end # `insert!` function insert!_datagraph(graph::AbstractEdgeDataGraph, edge::AbstractEdge, data) - if has_edge(graph, edge) - throw(IndexError("Graph already contains edge $edge")) - else - add_edge!(graph, edge) - setindex!(graph, data, edge) - end + insert_edge_data!(graph, edge, data) return graph end @@ -226,16 +217,29 @@ function delete!_datagraph(graph::AbstractEdgeDataGraph, edge) end return graph end -# -# # `set!` -# function set!_datagraph(graph::AbstractEdgeDataGraph, data, edge::AbstractEdge) -# if has_edge(graph, edge) -# set_edge_data!(graph, edge, data) -# else -# insert!_datagraph(graph, edge, data) -# end -# return graph -# end + +# `set!` +function set!_datagraph(graph::AbstractEdgeDataGraph, edge::AbstractEdge, data) + if has_edge(graph, edge) + set_edge_data!(graph, data, edge) + else + insert_edge_data!(graph, edge, data) + end + return graph +end + +function copyto!_indexdatagraph( + dst::AbstractEdgeDataGraph, + src, # not a graph. + dimnames = nothing + ) + dimnames = isnothing(dimnames) ? Indices(keys(src)) : Indices(dimnames) + # In analogy to SparseArrays, we allow `copyto!` to add in missing edges. + for edge in dimnames + set!(dst, edge, src[edge]) + end + return dst +end # For ambiguity resolution. function NamedGraphs.similar_graph( @@ -273,7 +277,6 @@ function NamedGraphs.induced_subgraph_from_vertices( ) subnetwork = similar_graph(graph, subvertices) subedges = subgraph_edges(graph, subvertices) - add_edges!(subnetwork, subedges) tensors = view(edge_data(graph), Indices(subedges)) @@ -299,14 +302,3 @@ function Base.show(io::IO, mime::MIME"text/plain", graph::AbstractEdgeDataGraph) show(io, mime, edge_data(graph)) return nothing end - -# ============================== Dictionaries.jl interface =============================== # - -# Dictionaries.isinsertable(::AbstractVertexOrEdgeDataGraph) = true -# function Dictionaries.insert!(graph::AbstractVertexOrEdgeDataGraph{I, T}, ind::I, value::T) -# insert_graph_index!(graph, ind, value) -# return graph -# end -# function Dictionaries.delete!(graph::AbstractVertexOrEdgeDataGraph{I, T}, ind::I, value::T) -# return graph -# end diff --git a/src/edgedatagraph.jl b/src/edgedatagraph.jl index 9c2a770..8d9b15b 100644 --- a/src/edgedatagraph.jl +++ b/src/edgedatagraph.jl @@ -1,4 +1,4 @@ -using Graphs: dst, rem_edge!, rem_vertex!, src +using Graphs: dst, has_edge, rem_edge!, rem_vertex!, src using NamedGraphs: NamedEdge, NamedGraph, ordered_vertices, position_graph, vertex_positions struct EdgeDataGraph{E <: NamedEdge{V} where {V}, T, V} <: AbstractEdgeDataGraph{E, T, V} @@ -16,7 +16,7 @@ end EdgeDataGraph(data) = EdgeDataGraph{keytype(data)}(data) EdgeDataGraph{I}(data) where {I} = EdgeDataGraph{I, valtype(data)}(data) -EdgeDataGraph{I, T}(data) where {I, T} = EdgeDataGraph{I, T, vertextype{I}}(data) +EdgeDataGraph{I, T}(data) where {I, T} = EdgeDataGraph{I, T, vertextype(I)}(data) Graphs.is_directed(::Type{<:EdgeDataGraph}) = false @@ -35,7 +35,7 @@ end EdgeDataDiGraph(data) = EdgeDataDiGraph{keytype(data)}(data) EdgeDataDiGraph{I}(data) where {I} = EdgeDataDiGraph{I, valtype(data)}(data) -EdgeDataDiGraph{I, T}(data) where {I, T} = EdgeDataDiGraph{I, T, vertextype{I}}(data) +EdgeDataDiGraph{I, T}(data) where {I, T} = EdgeDataDiGraph{I, T, vertextype(I)}(data) Graphs.is_directed(::Type{<:EdgeDataDiGraph}) = true @@ -45,22 +45,30 @@ const GenericEdgeDataGraph{I, T, V} = function (GType::Type{<:GenericEdgeDataGraph{I, T, V}})(data) where {I, T, V} edges = keys(data) vertices = union(src.(edges), dst.(edges)) - cache = GType(undef, vertices) - add_edges!(cache.underlying_graph, edges) - return copyto!(cache, data) + graph = GType(undef, vertices) + copyto!(graph, data) + return graph end # ====================================== Graphs.jl ======================================= # +Graphs.edgetype(::Type{<:GenericEdgeDataGraph{I, T}}) where {I, T} = I + +function Graphs.has_vertex(graph::GenericEdgeDataGraph, vertex) + return has_vertex(graph.underlying_graph, vertex) +end +function Graphs.has_edge(graph::GenericEdgeDataGraph, edge::NamedEdge) + return has_edge(graph.underlying_graph, edge) +end function Graphs.rem_edge!(graph::GenericEdgeDataGraph, edge) + unset!(graph.edge_data, edge) rem_edge!(graph.underlying_graph, edge) - delete!(graph.edge_data, edge) return graph end function Graphs.rem_vertex!(graph::GenericEdgeDataGraph, vertex) for edge in incident_edges(graph, vertex) - delete!(graph.edge_data, edge) + unset!(graph.edge_data, edge) end rem_vertex!(graph.underlying_graph, vertex) return graph @@ -95,3 +103,17 @@ get_edge_data(graph::GenericEdgeDataGraph, edge) = graph.edge_data[edge] is_vertex_assigned(::GenericEdgeDataGraph, _vertex) = false is_edge_assigned(graph::GenericEdgeDataGraph, edge) = isassigned(graph.edge_data, edge) + +# =================================== Dictionaries.jl ==================================== # + +Dictionaries.isinsertable(::Type{<:GenericEdgeDataGraph}, _edge) = true + +function insert_edge_data!(graph::AbstractEdgeDataGraph, edge::AbstractEdge, data) + if has_edge(graph, edge) + throw(IndexError("Graph already contains edge $edge")) + else + add_edge!(graph.underlying_graph, edge) + insert!(graph.edge_data, edge, data) + end + return graph +end diff --git a/src/vertexdatagraph.jl b/src/vertexdatagraph.jl index 494e5ff..cf5b65e 100644 --- a/src/vertexdatagraph.jl +++ b/src/vertexdatagraph.jl @@ -1,7 +1,7 @@ using Dictionaries: Dictionary, set! -using Graphs: Graphs, rem_vertex! +using Graphs: Graphs, has_edge, rem_vertex! using NamedGraphs: - NamedDiGraph, NamedGraph, ordered_vertices, position_graph, vertex_positions + NamedDiGraph, NamedEdge, NamedGraph, ordered_vertices, position_graph, vertex_positions struct VertexDataGraph{V, T} <: AbstractVertexDataGraph{V, T} underlying_graph::NamedGraph{V} @@ -48,8 +48,17 @@ Graphs.is_directed(::Type{<:VertexDataDiGraph}) = true # ====================================== Graphs.jl ======================================= # +Graphs.edgetype(::Type{<:GenericVertexDataGraph{V, T}}) where {V, T} = NamedEdge{V} + +function Graphs.has_vertex(graph::GenericVertexDataGraph, vertex) + return has_vertex(graph.underlying_graph, vertex) +end +function Graphs.has_edge(graph::GenericVertexDataGraph, edge::NamedEdge) + return has_edge(graph.underlying_graph, edge) +end + function Graphs.rem_vertex!(graph::GenericVertexDataGraph, vertex) - delete!(graph.vertex_data, vertex) + unset!(graph.vertex_data, vertex) rem_vertex!(graph.underlying_graph, vertex) return graph end @@ -78,7 +87,9 @@ underlying_graph(graph::VertexDataDiGraph) = getfield(graph, :underlying_graph) vertex_data_type(::Type{<:GenericVertexDataGraph{V, T}}) where {V, T} = T function set_vertex_data!(graph::GenericVertexDataGraph, data, vertex) - graph.vertex_data[vertex] = data + # We use an upsert here as we have already checked if the vertex (i.e. key) exists, + # but it might not exist in the internal `Dictionary`, so add it if not. + set!(graph.vertex_data, vertex, data) return graph end @@ -88,3 +99,17 @@ function is_vertex_assigned(graph::GenericVertexDataGraph, vertex) return isassigned(graph.vertex_data, vertex) end is_edge_assigned(::GenericVertexDataGraph, _edge) = false + +# =================================== Dictionaries.jl ==================================== # + +Dictionaries.isinsertable(::Type{<:GenericVertexDataGraph}, _edge) = true + +function insert_vertex_data!(graph::AbstractVertexDataGraph, vertex, data) + if has_vertex(graph, vertex) + throw(IndexError("Graph already contains vertex $vertex")) + else + add_vertex!(graph.underlying_graph, vertex) + insert!(graph.vertex_data, vertex, data) + end + return graph +end diff --git a/test/test_vertexoredgedatagraph.jl b/test/test_vertexoredgedatagraph.jl new file mode 100644 index 0000000..0e6fed8 --- /dev/null +++ b/test/test_vertexoredgedatagraph.jl @@ -0,0 +1,293 @@ +using DataGraphs: DataGraphs, EdgeDataDiGraph, EdgeDataGraph, EdgeDataView, + VertexDataDiGraph, VertexDataGraph, VertexDataView, edge_data, edge_data_type, + underlying_graph, vertex_data, vertex_data_type +using Dictionaries: AbstractDictionary, Dictionary, Indices +using Graphs: AbstractGraph, add_edge!, dst, edges, edgetype, has_edge, has_vertex, + is_directed, ne, nv, rem_edge!, rem_vertex!, src, vertices +using NamedGraphs.GraphsExtensions: vertextype +using NamedGraphs: + NamedDiGraph, NamedEdge, NamedGraph, ordered_vertices, position_graph, vertex_positions +using Test: @test, @testset + +@testset "VertexDataGraph and EdgeDataGraph" begin + @testset "VertexDataGraph" begin + @testset "undef constructor" begin + g = VertexDataGraph{Int, String}(undef, [1, 2, 3]) + @test g isa VertexDataGraph{Int, String} + @test nv(g) == 3 + @test ne(g) == 0 + @test has_vertex(g, 1) + @test has_vertex(g, 2) + @test has_vertex(g, 3) + @test !has_vertex(g, 4) + @test Set(collect(vertices(g))) == Set([1, 2, 3]) + end + + @testset "data constructor" begin + data = Dictionary([1, 2, 3], ["V1", "V2", "V3"]) + g = VertexDataGraph(data) + @test g isa VertexDataGraph{Int, String} + @test nv(g) == 3 + @test isassigned(g, 1) + @test isassigned(g, 2) + @test isassigned(g, 3) + @test g[1] == "V1" + @test g[2] == "V2" + @test g[3] == "V3" + end + + @testset "Graphs.jl interface" begin + g = VertexDataGraph{Int, String}(undef, [1, 2, 3]) + @test !is_directed(VertexDataGraph) + @test !is_directed(g) + @test vertextype(g) == Int + @test edgetype(g) == NamedEdge{Int} + + add_edge!(g, NamedEdge(1, 2)) + add_edge!(g, NamedEdge(2, 3)) + @test ne(g) == 2 + @test has_edge(g, NamedEdge(1, 2)) + @test has_edge(g, NamedEdge(2, 3)) + @test !has_edge(g, NamedEdge(1, 3)) + @test length(collect(edges(g))) == 2 + + rem_edge!(g, NamedEdge(1, 2)) + @test ne(g) == 1 + @test !has_edge(g, NamedEdge(1, 2)) + end + + @testset "rem_vertex!" begin + g = VertexDataGraph{Int, String}(undef, [1, 2, 3]) + add_edge!(g, NamedEdge(1, 2)) + rem_vertex!(g, 1) + @test !has_vertex(g, 1) + @test nv(g) == 2 + @test ne(g) == 0 + end + + @testset "DataGraphs interface" begin + g = VertexDataGraph{Int, String}(undef, [1, 2, 3]) + @test underlying_graph(g) isa NamedGraph{Int} + @test vertex_data_type(g) == String + @test vertex_data_type(VertexDataGraph{Int, String}) == String + @test !isassigned(g, 1) + @test !isassigned(g, 2) + @test !isassigned(g, 3) + end + + @testset "setindex! and getindex" begin + g = VertexDataGraph{Int, String}(undef, [1, 2, 3]) + g[1] = "V1" + g[2] = "V2" + g[3] = "V3" + @test isassigned(g, 1) + @test isassigned(g, 2) + @test isassigned(g, 3) + @test g[1] == "V1" + @test g[2] == "V2" + @test g[3] == "V3" + end + + @testset "NamedGraphs interface" begin + g = VertexDataGraph{Int, String}(undef, [1, 2, 3]) + @test underlying_graph(g) isa NamedGraph{Int} + @test position_graph(g) isa AbstractGraph + @test ordered_vertices(g) isa AbstractVector + @test vertex_positions(g) isa AbstractDictionary + end + + @testset "Dictionaries interface" begin + g = VertexDataGraph{Int, String}(undef, [1, 2, 3]) + @test keytype(g) == Int + @test valtype(g) == String + @test keys(g) isa Indices + @test length(g) == 3 + @test vertex_data(g) isa VertexDataView + end + end + + @testset "VertexDataDiGraph" begin + @testset "undef constructor" begin + g = VertexDataDiGraph{Int, String}(undef, [1, 2, 3]) + @test g isa VertexDataDiGraph{Int, String} + @test nv(g) == 3 + @test ne(g) == 0 + @test has_vertex(g, 1) + @test !has_vertex(g, 4) + end + + @testset "data constructor" begin + data = Dictionary([1, 2, 3], ["V1", "V2", "V3"]) + g = VertexDataDiGraph(data) + @test g isa VertexDataDiGraph{Int, String} + @test nv(g) == 3 + @test g[1] == "V1" + @test g[2] == "V2" + @test g[3] == "V3" + end + + @testset "directed graph" begin + g = VertexDataDiGraph{Int, String}(undef, [1, 2, 3]) + @test is_directed(VertexDataDiGraph) + @test is_directed(g) + @test underlying_graph(g) isa NamedDiGraph{Int} + end + + @testset "directed edges" begin + g = VertexDataDiGraph{Int, String}(undef, [1, 2, 3]) + add_edge!(g, NamedEdge(1, 2)) + @test has_edge(g, NamedEdge(1, 2)) + @test !has_edge(g, NamedEdge(2, 1)) + @test ne(g) == 1 + end + + @testset "DataGraphs interface" begin + g = VertexDataDiGraph{Int, String}(undef, [1, 2, 3]) + @test vertex_data_type(g) == String + @test vertex_data_type(VertexDataDiGraph{Int, String}) == String + @test vertextype(g) == Int + @test edgetype(g) == NamedEdge{Int} + end + + @testset "Dictionaries interface" begin + g = VertexDataDiGraph{Int, String}(undef, [1, 2, 3]) + @test keytype(g) == Int + @test valtype(g) == String + @test keys(g) isa Indices + @test length(g) == 3 + @test vertex_data(g) isa VertexDataView + end + end + + @testset "EdgeDataGraph" begin + E = NamedEdge{Int} + + @testset "undef constructor" begin + g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + @test g isa EdgeDataGraph{E, String, Int} + @test nv(g) == 3 + @test ne(g) == 0 + end + + @testset "data constructor" begin + data = Dictionary([NamedEdge(1, 2), NamedEdge(2, 3)], ["E12", "E23"]) + g = EdgeDataGraph(data) + @test g isa EdgeDataGraph{E, String, Int} + @test nv(g) == 3 + @test ne(g) == 2 + @test isassigned(g, NamedEdge(1, 2)) + @test isassigned(g, NamedEdge(2, 3)) + @test g[NamedEdge(1, 2)] == "E12" + @test g[NamedEdge(2, 3)] == "E23" + end + + @testset "Graphs.jl interface" begin + g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + @test !is_directed(EdgeDataGraph) + @test !is_directed(g) + @test has_vertex(g, 1) + @test has_vertex(g, 2) + @test !has_vertex(g, 4) + @test edgetype(g) == E + + add_edge!(g, NamedEdge(1, 2)) + add_edge!(g, NamedEdge(2, 3)) + @test ne(g) == 2 + @test has_edge(g, NamedEdge(1, 2)) + @test has_edge(g, NamedEdge(2, 3)) + @test !has_edge(g, NamedEdge(1, 3)) + end + + @testset "DataGraphs interface" begin + g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + @test edge_data_type(EdgeDataGraph{E, String, Int}) == String + @test edge_data_type(g) == String + @test !isassigned(g, 1) + @test !isassigned(g, NamedEdge(1, 2)) + end + + @testset "setindex! and getindex" begin + data = Dictionary([NamedEdge(1, 2), NamedEdge(2, 3)], ["E12", "E23"]) + g = EdgeDataGraph(data) + g[NamedEdge(1, 2)] = "E12_updated" + @test g[NamedEdge(1, 2)] == "E12_updated" + @test g[NamedEdge(2, 3)] == "E23" + end + + @testset "undirected edge access" begin + data = Dictionary([NamedEdge(1, 2)], ["E12"]) + g = EdgeDataGraph(data) + @test g[NamedEdge(1, 2)] == "E12" + @test g[NamedEdge(2, 1)] == "E12" + @test g[1 => 2] == "E12" + @test g[2 => 1] == "E12" + end + + @testset "rem_edge!" begin + data = Dictionary([NamedEdge(1, 2), NamedEdge(2, 3)], ["E12", "E23"]) + g = EdgeDataGraph(data) + rem_edge!(g, NamedEdge(1, 2)) + @test !has_edge(g, NamedEdge(1, 2)) + @test ne(g) == 1 + end + + @testset "NamedGraphs interface" begin + g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + @test Set(collect(vertices(g))) == Set([1, 2, 3]) + @test position_graph(g) isa AbstractGraph + @test ordered_vertices(g) isa AbstractVector + @test vertex_positions(g) isa AbstractDictionary + end + + @testset "Dictionaries interface" begin + g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + @test keytype(g) == E + @test valtype(g) == String + @test edge_data(g) isa EdgeDataView + end + end + + @testset "EdgeDataDiGraph" begin + E = NamedEdge{Int} + + @testset "undef constructor" begin + g = EdgeDataDiGraph{E, String, Int}(undef, [1, 2, 3]) + @test g isa EdgeDataDiGraph{E, String, Int} + @test nv(g) == 3 + @test ne(g) == 0 + end + + @testset "data constructor" begin + data = Dictionary([NamedEdge(1, 2), NamedEdge(2, 3)], ["E12", "E23"]) + g = EdgeDataDiGraph(data) + @test g isa EdgeDataDiGraph{E, String, Int} + @test nv(g) == 3 + @test ne(g) == 2 + @test g[NamedEdge(1, 2)] == "E12" + @test g[NamedEdge(2, 3)] == "E23" + end + + @testset "directed graph" begin + g = EdgeDataDiGraph{E, String, Int}(undef, [1, 2, 3]) + @test is_directed(EdgeDataDiGraph) + @test is_directed(g) + end + + @testset "directed edges" begin + data = Dictionary([NamedEdge(1, 2)], ["E12"]) + g = EdgeDataDiGraph(data) + @test has_edge(g, NamedEdge(1, 2)) + @test !has_edge(g, NamedEdge(2, 1)) + @test g[NamedEdge(1, 2)] == "E12" + end + + @testset "DataGraphs interface" begin + g = EdgeDataDiGraph{E, String, Int}(undef, [1, 2, 3]) + @test edge_data_type(EdgeDataDiGraph{E, String, Int}) == String + @test edge_data_type(g) == String + @test keytype(g) == E + @test valtype(g) == String + @test edge_data(g) isa EdgeDataView + end + end +end From f60063c1dd263ce0c210ef277b2e2596051afda2 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Fri, 15 May 2026 16:17:40 -0400 Subject: [PATCH 06/28] Version bump. --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 2775d4a..e8b9800 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "DataGraphs" uuid = "b5a273c3-7e6c-41f6-98bd-8d7f1525a36a" -version = "0.4.2" +version = "0.4.3" authors = ["Matthew Fishman and contributors"] [workspace] From f7040284de9e9bc3747c782218e30d1ce8f58041 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Fri, 15 May 2026 16:48:58 -0400 Subject: [PATCH 07/28] Fix regressions; add explicit imports. --- src/abstractdatagraph.jl | 2 +- src/abstractedgeorvertexdatagraph.jl | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl index d55d57a..c96d0dd 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -61,7 +61,7 @@ function assigned_edges(graph::AbstractGraph) return Indices(filter(e -> isassigned(graph, e), edges(graph))) end -Graphs.edgetype(graph::AbstractDataGraph) = edgetype(typeof(graph)) +Graphs.edgetype(graph::AbstractDataGraph) = edgetype(underlying_graph(graph)) function Graphs.edgetype(graph_type::Type{<:AbstractDataGraph}) return edgetype(underlying_graph_type(graph_type)) end diff --git a/src/abstractedgeorvertexdatagraph.jl b/src/abstractedgeorvertexdatagraph.jl index 036b947..47e1502 100644 --- a/src/abstractedgeorvertexdatagraph.jl +++ b/src/abstractedgeorvertexdatagraph.jl @@ -1,5 +1,11 @@ +using Dictionaries: Dictionaries, Indices, set! +using Graphs: edges, edgetype, has_edge, has_vertex, rem_edge!, rem_vertex!, vertices +using NamedGraphs: NamedGraphs, Vertices, similar_graph, subgraph_edges, to_graph_index + abstract type AbstractVertexOrEdgeDataGraph{I, T, V} <: AbstractDataGraph{V, T, T} end +Graphs.edgetype(graph::AbstractVertexOrEdgeDataGraph) = edgetype(typeof(graph)) + function NamedGraphs.similar_graph( graph::AbstractVertexOrEdgeDataGraph, T::Type From b09f65ff25ac9e9f295dc5a221c464467466882d Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Mon, 18 May 2026 09:26:31 -0400 Subject: [PATCH 08/28] Used `@eval` loop instead of union type to define Graph/DiGraph shared methods. --- src/edgedatagraph.jl | 112 ++++++++++++++++++++++++----------------- src/vertexdatagraph.jl | 103 +++++++++++++++++++++---------------- 2 files changed, 125 insertions(+), 90 deletions(-) diff --git a/src/edgedatagraph.jl b/src/edgedatagraph.jl index 8d9b15b..8036fb3 100644 --- a/src/edgedatagraph.jl +++ b/src/edgedatagraph.jl @@ -39,74 +39,92 @@ EdgeDataDiGraph{I, T}(data) where {I, T} = EdgeDataDiGraph{I, T, vertextype(I)}( Graphs.is_directed(::Type{<:EdgeDataDiGraph}) = true -const GenericEdgeDataGraph{I, T, V} = - Union{EdgeDataGraph{I, T, V}, EdgeDataDiGraph{I, T, V}} - -function (GType::Type{<:GenericEdgeDataGraph{I, T, V}})(data) where {I, T, V} - edges = keys(data) - vertices = union(src.(edges), dst.(edges)) - graph = GType(undef, vertices) - copyto!(graph, data) - return graph +for GType in (:EdgeDataGraph, :EdgeDataDiGraph) + @eval begin + function $GType{E, T, V}(data) where {V, E <: NamedEdge{V}, T} + edges = keys(data) + vertices = union(src.(edges), dst.(edges)) + graph = $GType{E, T, V}(undef, vertices) + copyto!(graph, data) + return graph + end + end end # ====================================== Graphs.jl ======================================= # -Graphs.edgetype(::Type{<:GenericEdgeDataGraph{I, T}}) where {I, T} = I - -function Graphs.has_vertex(graph::GenericEdgeDataGraph, vertex) - return has_vertex(graph.underlying_graph, vertex) -end -function Graphs.has_edge(graph::GenericEdgeDataGraph, edge::NamedEdge) - return has_edge(graph.underlying_graph, edge) -end -function Graphs.rem_edge!(graph::GenericEdgeDataGraph, edge) - unset!(graph.edge_data, edge) - rem_edge!(graph.underlying_graph, edge) - return graph -end - -function Graphs.rem_vertex!(graph::GenericEdgeDataGraph, vertex) - for edge in incident_edges(graph, vertex) - unset!(graph.edge_data, edge) +for GType in (:EdgeDataGraph, :EdgeDataDiGraph) + @eval begin + Graphs.edgetype(::Type{<:$GType{I, T, V}}) where {I, T, V} = I + + function Graphs.has_vertex(graph::$GType, vertex) + return has_vertex(graph.underlying_graph, vertex) + end + function Graphs.has_edge(graph::$GType, edge::NamedEdge) + return has_edge(graph.underlying_graph, edge) + end + + function Graphs.rem_edge!(graph::$GType, edge) + unset!(graph.edge_data, edge) + rem_edge!(graph.underlying_graph, edge) + return graph + end + + function Graphs.rem_vertex!(graph::$GType, vertex) + for edge in incident_edges(graph, vertex) + unset!(graph.edge_data, edge) + end + rem_vertex!(graph.underlying_graph, vertex) + return graph + end + + Graphs.vertices(graph::$GType) = vertices(graph.underlying_graph) end - rem_vertex!(graph.underlying_graph, vertex) - return graph end -Graphs.vertices(graph::GenericEdgeDataGraph) = vertices(graph.underlying_graph) - # ==================================== NamedGraphs.jl ==================================== # -function NamedGraphs.vertex_positions(graph::GenericEdgeDataGraph) - return vertex_positions(graph.underlying_graph) -end +for GType in (:EdgeDataGraph, :EdgeDataDiGraph) + @eval begin + function NamedGraphs.vertex_positions(graph::$GType) + return vertex_positions(graph.underlying_graph) + end -function NamedGraphs.ordered_vertices(graph::GenericEdgeDataGraph) - return ordered_vertices(graph.underlying_graph) -end + function NamedGraphs.ordered_vertices(graph::$GType) + return ordered_vertices(graph.underlying_graph) + end -function NamedGraphs.position_graph(graph::GenericEdgeDataGraph) - return position_graph(graph.underlying_graph) + function NamedGraphs.position_graph(graph::$GType) + return position_graph(graph.underlying_graph) + end + end end # ==================================== DataGraphs.jl ===================================== # -edge_data_type(::Type{<:GenericEdgeDataGraph{I, T}}) where {I, T} = T +for GType in (:EdgeDataGraph, :EdgeDataDiGraph) + @eval begin + edge_data_type(::Type{<:$GType{I, T, V}}) where {I, T, V} = T -function set_edge_data!(graph::GenericEdgeDataGraph, data, edge) - graph.edge_data[edge] = data - return graph -end + function set_edge_data!(graph::$GType, data, edge) + graph.edge_data[edge] = data + return graph + end -get_edge_data(graph::GenericEdgeDataGraph, edge) = graph.edge_data[edge] + get_edge_data(graph::$GType, edge) = graph.edge_data[edge] -is_vertex_assigned(::GenericEdgeDataGraph, _vertex) = false -is_edge_assigned(graph::GenericEdgeDataGraph, edge) = isassigned(graph.edge_data, edge) + is_vertex_assigned(::$GType, _vertex) = false + is_edge_assigned(graph::$GType, edge) = isassigned(graph.edge_data, edge) + end +end # =================================== Dictionaries.jl ==================================== # -Dictionaries.isinsertable(::Type{<:GenericEdgeDataGraph}, _edge) = true +for GType in (:EdgeDataGraph, :EdgeDataDiGraph) + @eval begin + Dictionaries.isinsertable(::Type{<:$GType}, _edge) = true + end +end function insert_edge_data!(graph::AbstractEdgeDataGraph, edge::AbstractEdge, data) if has_edge(graph, edge) diff --git a/src/vertexdatagraph.jl b/src/vertexdatagraph.jl index cf5b65e..8e0565a 100644 --- a/src/vertexdatagraph.jl +++ b/src/vertexdatagraph.jl @@ -35,74 +35,91 @@ end VertexDataDiGraph(data) = VertexDataDiGraph{keytype(data)}(data) VertexDataDiGraph{V}(data) where {V} = VertexDataDiGraph{V, valtype(data)}(data) -const GenericVertexDataGraph{V, T} = Union{VertexDataGraph{V, T}, VertexDataDiGraph{V, T}} - -function (GType::Type{<:GenericVertexDataGraph{V, T}})(data) where {V, T} - vertices = keys(data) - cache = GType(undef, vertices) - return copyto!(cache, data) -end - Graphs.is_directed(::Type{<:VertexDataGraph}) = false Graphs.is_directed(::Type{<:VertexDataDiGraph}) = true +for GType in (:VertexDataGraph, :VertexDataDiGraph) + @eval begin + function $GType{V, T}(data) where {V, T} + vertices = keys(data) + cache = $GType{V, T}(undef, vertices) + return copyto!(cache, data) + end + end +end + # ====================================== Graphs.jl ======================================= # -Graphs.edgetype(::Type{<:GenericVertexDataGraph{V, T}}) where {V, T} = NamedEdge{V} +for GType in (:VertexDataGraph, :VertexDataDiGraph) + @eval begin + Graphs.edgetype(::Type{<:$GType{V, T}}) where {V, T} = NamedEdge{V} -function Graphs.has_vertex(graph::GenericVertexDataGraph, vertex) - return has_vertex(graph.underlying_graph, vertex) -end -function Graphs.has_edge(graph::GenericVertexDataGraph, edge::NamedEdge) - return has_edge(graph.underlying_graph, edge) -end + function Graphs.has_vertex(graph::$GType, vertex) + return has_vertex(graph.underlying_graph, vertex) + end + function Graphs.has_edge(graph::$GType, edge::NamedEdge) + return has_edge(graph.underlying_graph, edge) + end -function Graphs.rem_vertex!(graph::GenericVertexDataGraph, vertex) - unset!(graph.vertex_data, vertex) - rem_vertex!(graph.underlying_graph, vertex) - return graph -end + function Graphs.rem_vertex!(graph::$GType, vertex) + unset!(graph.vertex_data, vertex) + rem_vertex!(graph.underlying_graph, vertex) + return graph + end -Graphs.vertices(graph::GenericVertexDataGraph) = vertices(graph.underlying_graph) + Graphs.vertices(graph::$GType) = vertices(graph.underlying_graph) + end +end # ==================================== NamedGraphs.jl ==================================== # -function NamedGraphs.vertex_positions(graph::GenericVertexDataGraph) - return vertex_positions(graph.underlying_graph) -end +for GType in (:VertexDataGraph, :VertexDataDiGraph) + @eval begin + function NamedGraphs.vertex_positions(graph::$GType) + return vertex_positions(graph.underlying_graph) + end -function NamedGraphs.ordered_vertices(graph::GenericVertexDataGraph) - return ordered_vertices(graph.underlying_graph) -end + function NamedGraphs.ordered_vertices(graph::$GType) + return ordered_vertices(graph.underlying_graph) + end -function NamedGraphs.position_graph(graph::GenericVertexDataGraph) - return position_graph(graph.underlying_graph) + function NamedGraphs.position_graph(graph::$GType) + return position_graph(graph.underlying_graph) + end + end end # ==================================== DataGraphs.jl ===================================== # -underlying_graph(graph::VertexDataGraph) = getfield(graph, :underlying_graph) -underlying_graph(graph::VertexDataDiGraph) = getfield(graph, :underlying_graph) +for GType in (:VertexDataGraph, :VertexDataDiGraph) + @eval begin + underlying_graph(graph::$GType) = getfield(graph, :underlying_graph) -vertex_data_type(::Type{<:GenericVertexDataGraph{V, T}}) where {V, T} = T + vertex_data_type(::Type{<:$GType{V, T}}) where {V, T} = T -function set_vertex_data!(graph::GenericVertexDataGraph, data, vertex) - # We use an upsert here as we have already checked if the vertex (i.e. key) exists, - # but it might not exist in the internal `Dictionary`, so add it if not. - set!(graph.vertex_data, vertex, data) - return graph -end + function set_vertex_data!(graph::$GType, data, vertex) + # We use an upsert here as we have already checked if the vertex (i.e. key) exists, + # but it might not exist in the internal `Dictionary`, so add it if not. + set!(graph.vertex_data, vertex, data) + return graph + end -get_vertex_data(graph::GenericVertexDataGraph, vertex) = graph.vertex_data[vertex] + get_vertex_data(graph::$GType, vertex) = graph.vertex_data[vertex] -function is_vertex_assigned(graph::GenericVertexDataGraph, vertex) - return isassigned(graph.vertex_data, vertex) + function is_vertex_assigned(graph::$GType, vertex) + return isassigned(graph.vertex_data, vertex) + end + is_edge_assigned(::$GType, _edge) = false + end end -is_edge_assigned(::GenericVertexDataGraph, _edge) = false # =================================== Dictionaries.jl ==================================== # -Dictionaries.isinsertable(::Type{<:GenericVertexDataGraph}, _edge) = true +for GType in (:VertexDataGraph, :VertexDataDiGraph) + @eval begin + Dictionaries.isinsertable(::Type{<:$GType}, _edge) = true + end +end function insert_vertex_data!(graph::AbstractVertexDataGraph, vertex, data) if has_vertex(graph, vertex) From 1a060f9a79d0e092de956fef2e74205555810ea4 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Mon, 18 May 2026 09:52:20 -0400 Subject: [PATCH 09/28] Fix bug in `has_edge` definition; improve test converage. --- src/abstractdatagraph.jl | 4 +-- src/abstractedgeorvertexdatagraph.jl | 6 ++++ test/test_vertexoredgedatagraph.jl | 53 ++++++++++++++++++++++++++-- 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl index c96d0dd..bacff06 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -46,10 +46,10 @@ function get_edges_data(g::AbstractGraph, edges) end Graphs.has_vertex(g::AbstractDataGraph, vertex) = has_vertex(underlying_graph(g), vertex) -Graphs.has_edge(g::AbstractDataGraph, edge) = has_edge(underlying_graph(g), edge) -function Graphs.has_edge(g::AbstractDataGraph, edge::AbstractNamedEdge) +function Graphs.has_edge(g::AbstractDataGraph, edge::AbstractNamedGraph) return has_edge(underlying_graph(g), edge) end +Graphs.has_edge(g::AbstractDataGraph, pair::Pair) = has_edge(g, to_graph_index(g, pair)) vertex_data(dg::AbstractGraph) = VertexDataView(dg) edge_data(dg::AbstractGraph) = EdgeDataView(dg) diff --git a/src/abstractedgeorvertexdatagraph.jl b/src/abstractedgeorvertexdatagraph.jl index 47e1502..79d9464 100644 --- a/src/abstractedgeorvertexdatagraph.jl +++ b/src/abstractedgeorvertexdatagraph.jl @@ -64,6 +64,11 @@ Base.values(graph::AbstractVertexOrEdgeDataGraph) = values(index_data(graph)) Dictionaries.issettable(::AbstractVertexOrEdgeDataGraph) = true Dictionaries.isinsertable(::AbstractVertexOrEdgeDataGraph) = false +function Base.insert!(graph::AbstractVertexOrEdgeDataGraph, ind, data) + insert!_datagraph(graph, to_graph_index(graph, ind), data) + return graph +end +# For ambiguity resolution. function Base.insert!( graph::AbstractVertexOrEdgeDataGraph{I, T}, ind::I, @@ -72,6 +77,7 @@ function Base.insert!( insert!_datagraph(graph, to_graph_index(graph, ind), data) return graph end + function Base.delete!(graph::AbstractVertexOrEdgeDataGraph{I, T}, ind::T) where {I, T} delete!_datagraph(graph, to_graph_index(graph, ind)) return graph diff --git a/test/test_vertexoredgedatagraph.jl b/test/test_vertexoredgedatagraph.jl index 0e6fed8..fe68af4 100644 --- a/test/test_vertexoredgedatagraph.jl +++ b/test/test_vertexoredgedatagraph.jl @@ -1,13 +1,13 @@ using DataGraphs: DataGraphs, EdgeDataDiGraph, EdgeDataGraph, EdgeDataView, VertexDataDiGraph, VertexDataGraph, VertexDataView, edge_data, edge_data_type, underlying_graph, vertex_data, vertex_data_type -using Dictionaries: AbstractDictionary, Dictionary, Indices +using Dictionaries: AbstractDictionary, Dictionary, IndexError, Indices, set! using Graphs: AbstractGraph, add_edge!, dst, edges, edgetype, has_edge, has_vertex, is_directed, ne, nv, rem_edge!, rem_vertex!, src, vertices using NamedGraphs.GraphsExtensions: vertextype using NamedGraphs: NamedDiGraph, NamedEdge, NamedGraph, ordered_vertices, position_graph, vertex_positions -using Test: @test, @testset +using Test: @test, @test_throws, @testset @testset "VertexDataGraph and EdgeDataGraph" begin @testset "VertexDataGraph" begin @@ -54,6 +54,9 @@ using Test: @test, @testset rem_edge!(g, NamedEdge(1, 2)) @test ne(g) == 1 @test !has_edge(g, NamedEdge(1, 2)) + + @test !is_directed(VertexDataGraph) + @test !is_directed(g) end @testset "rem_vertex!" begin @@ -73,6 +76,8 @@ using Test: @test, @testset @test !isassigned(g, 1) @test !isassigned(g, 2) @test !isassigned(g, 3) + add_edge!(g, NamedEdge(1, 2)) + @test !isassigned(g, 1 => 2) end @testset "setindex! and getindex" begin @@ -103,6 +108,25 @@ using Test: @test, @testset @test keys(g) isa Indices @test length(g) == 3 @test vertex_data(g) isa VertexDataView + + insert!(g, 4, "V4") + @test_throws IndexError insert!(g, 4, "V4_again") + @test has_vertex(g, 4) + @test isassigned(g, 4) + @test g[4] == "V4" + @test nv(g) == 4 + + @test_throws IndexError g[5] = "V5" + @test !has_vertex(g, 5) + @test !isassigned(g, 5) + + set!(g, 5, "V5") + @test has_vertex(g, 5) + @test g[5] == "V5" + + g[5] = "V5_again" + @test g[5] == "V5_again" + @test nv(g) == 5 end end @@ -196,6 +220,9 @@ using Test: @test, @testset @test has_edge(g, NamedEdge(1, 2)) @test has_edge(g, NamedEdge(2, 3)) @test !has_edge(g, NamedEdge(1, 3)) + + @test !is_directed(EdgeDataGraph) + @test !is_directed(g) end @testset "DataGraphs interface" begin @@ -244,6 +271,28 @@ using Test: @test, @testset @test keytype(g) == E @test valtype(g) == String @test edge_data(g) isa EdgeDataView + + insert!(g, 1 => 2, "E12") + @test_throws IndexError insert!(g, 1 => 2, "E12_again") + @test_throws IndexError insert!(g, 2 => 1, "E12_again") + @test_throws IndexError insert!(g, NamedEdge(1, 2), "E12_again") + @test_throws IndexError insert!(g, NamedEdge(2, 1), "E12_again") + @test has_edge(g, 1 => 2) + @test isassigned(g, 1 => 2) + @test g[1 => 2] == "E12" + @test ne(g) == 1 + + @test_throws IndexError g[2 => 3] = "E23" + @test !has_edge(g, 2 => 3) + @test !isassigned(g, 2 => 3) + + set!(g, 2 => 3, "E23") + @test has_edge(g, 2 => 3) + @test g[2 => 3] == "E23" + + g[2 => 3] = "E23_again" + @test g[2 => 3] == "E23_again" + @test ne(g) == 2 end end From 8a890c3c659403c97f1568b766f929dfe8a5f419 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Mon, 18 May 2026 12:02:50 -0400 Subject: [PATCH 10/28] Allow `setindex!` to upsert edges if vertices are present in `AbstractEdgeDataGraph`. --- src/abstractdatagraph.jl | 4 +- src/abstractedgeorvertexdatagraph.jl | 67 ++++++++++++---------------- src/edgedatagraph.jl | 17 +++---- src/vertexdatagraph.jl | 18 ++++---- test/test_vertexoredgedatagraph.jl | 54 +++++++++++++++------- 5 files changed, 84 insertions(+), 76 deletions(-) diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl index bacff06..9bf1da7 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -91,9 +91,9 @@ end function Base.copyto!( graph_dst::AbstractDataGraph, dict_src::Union{Dict, AbstractDictionary}, - dimnames = keys(dict_src) + keynames = keys(dict_src) ) - for key in dimnames + for key in keynames graph_dst[key] = dict_src[key] end return graph_dst diff --git a/src/abstractedgeorvertexdatagraph.jl b/src/abstractedgeorvertexdatagraph.jl index 79d9464..454c3e9 100644 --- a/src/abstractedgeorvertexdatagraph.jl +++ b/src/abstractedgeorvertexdatagraph.jl @@ -29,17 +29,11 @@ end function Base.copyto!( graph_dst::G, graph_src::G, - dimnames = nothing + keynames = nothing ) where {G <: AbstractVertexOrEdgeDataGraph} - copyto!_indexdatagraph(graph_dst, index_data(graph_src), dimnames) - return graph_dst -end -function Base.copyto!( - graph_dst::AbstractVertexOrEdgeDataGraph, - dictionary_src::AbstractDictionary, - dimnames = nothing - ) - copyto!_indexdatagraph(graph_dst, dictionary_src, dimnames) + dict_src = index_data(graph_src) + keynames = isnothing(keynames) ? keys(dict_src) : keynames + copyto!(graph_dst, dict_src, keynames) return graph_dst end @@ -130,16 +124,6 @@ function set!_datagraph(graph::AbstractVertexDataGraph, vertex, data) return graph end -function copyto!_indexdatagraph( - dst::AbstractVertexDataGraph, - src, # not a graph. - dimnames = nothing - ) - dimnames = isnothing(dimnames) ? Indices(keys(src)) : Indices(dimnames) - view(index_data(dst), dimnames) .= view(src, dimnames) - return dst -end - # For ambiguity resolution. function NamedGraphs.similar_graph( graph::AbstractVertexDataGraph, @@ -206,8 +190,12 @@ is_vertex_assigned(::AbstractEdgeDataGraph, _vertex) = false # `setindex!` function set_index_data!(graph::AbstractEdgeDataGraph, data, edge::AbstractEdge) - if !has_edge(graph, edge) - throw(IndexError("Graph does not contain edge $edge")) + v1 = src(edge) + v2 = dst(edge) + if !has_vertex(graph, v1) + throw(IndexError("Graph does not contain vertex $v1")) + elseif !has_vertex(graph, v2) + throw(IndexError("Graph does not contain vertex $v2")) else set_edge_data!(graph, data, edge) end @@ -216,7 +204,21 @@ end # `insert!` function insert!_datagraph(graph::AbstractEdgeDataGraph, edge::AbstractEdge, data) - insert_edge_data!(graph, edge, data) + v1 = src(edge) + v2 = dst(edge) + if has_vertex(graph, v1) && has_vertex(graph, v2) + throw(IndexError("Graph already contains vertices $v1 and $v2")) + else + insert_edge_data!(graph, edge, data) + end + return graph +end +function insert_edge_data!(graph::AbstractEdgeDataGraph, edge::AbstractEdge, data) + v1 = src(edge) + v2 = dst(edge) + has_vertex(graph, v1) || add_vertex!(graph, v1) + has_vertex(graph, v2) || add_vertex!(graph, v2) + set_edge_data!(graph, data, edge) return graph end @@ -232,27 +234,14 @@ end # `set!` function set!_datagraph(graph::AbstractEdgeDataGraph, edge::AbstractEdge, data) - if has_edge(graph, edge) - set_edge_data!(graph, data, edge) - else + if !has_vertex(graph, src(edge)) || !has_vertex(graph, dst(edge)) insert_edge_data!(graph, edge, data) + else + set_edge_data!(graph, data, edge) end return graph end -function copyto!_indexdatagraph( - dst::AbstractEdgeDataGraph, - src, # not a graph. - dimnames = nothing - ) - dimnames = isnothing(dimnames) ? Indices(keys(src)) : Indices(dimnames) - # In analogy to SparseArrays, we allow `copyto!` to add in missing edges. - for edge in dimnames - set!(dst, edge, src[edge]) - end - return dst -end - # For ambiguity resolution. function NamedGraphs.similar_graph( graph::AbstractEdgeDataGraph, diff --git a/src/edgedatagraph.jl b/src/edgedatagraph.jl index 8036fb3..11421a1 100644 --- a/src/edgedatagraph.jl +++ b/src/edgedatagraph.jl @@ -57,6 +57,9 @@ for GType in (:EdgeDataGraph, :EdgeDataDiGraph) @eval begin Graphs.edgetype(::Type{<:$GType{I, T, V}}) where {I, T, V} = I + function Graphs.add_vertex!(graph::$GType, vertex) + return add_vertex!(graph.underlying_graph, vertex) + end function Graphs.has_vertex(graph::$GType, vertex) return has_vertex(graph.underlying_graph, vertex) end @@ -107,7 +110,9 @@ for GType in (:EdgeDataGraph, :EdgeDataDiGraph) edge_data_type(::Type{<:$GType{I, T, V}}) where {I, T, V} = T function set_edge_data!(graph::$GType, data, edge) - graph.edge_data[edge] = data + # Edges `upsert` if vertices are present. + has_edge(graph, edge) || add_edge!(graph.underlying_graph, edge) + set!(graph.edge_data, edge, data) return graph end @@ -125,13 +130,3 @@ for GType in (:EdgeDataGraph, :EdgeDataDiGraph) Dictionaries.isinsertable(::Type{<:$GType}, _edge) = true end end - -function insert_edge_data!(graph::AbstractEdgeDataGraph, edge::AbstractEdge, data) - if has_edge(graph, edge) - throw(IndexError("Graph already contains edge $edge")) - else - add_edge!(graph.underlying_graph, edge) - insert!(graph.edge_data, edge, data) - end - return graph -end diff --git a/src/vertexdatagraph.jl b/src/vertexdatagraph.jl index 8e0565a..e896c4c 100644 --- a/src/vertexdatagraph.jl +++ b/src/vertexdatagraph.jl @@ -118,15 +118,15 @@ end for GType in (:VertexDataGraph, :VertexDataDiGraph) @eval begin Dictionaries.isinsertable(::Type{<:$GType}, _edge) = true - end -end -function insert_vertex_data!(graph::AbstractVertexDataGraph, vertex, data) - if has_vertex(graph, vertex) - throw(IndexError("Graph already contains vertex $vertex")) - else - add_vertex!(graph.underlying_graph, vertex) - insert!(graph.vertex_data, vertex, data) + function insert_vertex_data!(graph::$GType, vertex, data) + if has_vertex(graph, vertex) + throw(IndexError("Graph already contains vertex $vertex")) + else + add_vertex!(graph.underlying_graph, vertex) + insert!(graph.vertex_data, vertex, data) + end + return graph + end end - return graph end diff --git a/test/test_vertexoredgedatagraph.jl b/test/test_vertexoredgedatagraph.jl index fe68af4..db3a5da 100644 --- a/test/test_vertexoredgedatagraph.jl +++ b/test/test_vertexoredgedatagraph.jl @@ -272,27 +272,51 @@ using Test: @test, @test_throws, @testset @test valtype(g) == String @test edge_data(g) isa EdgeDataView - insert!(g, 1 => 2, "E12") - @test_throws IndexError insert!(g, 1 => 2, "E12_again") - @test_throws IndexError insert!(g, 2 => 1, "E12_again") - @test_throws IndexError insert!(g, NamedEdge(1, 2), "E12_again") - @test_throws IndexError insert!(g, NamedEdge(2, 1), "E12_again") + g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + insert!(g, 3 => 4, "E34") + @test has_vertex(g, 4) + @test has_edge(g, 3 => 4) + @test isassigned(g, 3 => 4) + @test g[3 => 4] == "E34" + + g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + insert!(g, 5 => 6, "E56") + @test has_vertex(g, 5) + @test has_vertex(g, 6) + @test has_edge(g, 5 => 6) + @test isassigned(g, 5 => 6) + @test g[5 => 6] == "E56" + + g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + @test_throws IndexError insert!(g, 2 => 3, "E23") + @test !has_edge(g, 2 => 3) + + g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + set!(g, 1 => 2, "E12") @test has_edge(g, 1 => 2) - @test isassigned(g, 1 => 2) @test g[1 => 2] == "E12" - @test ne(g) == 1 - @test_throws IndexError g[2 => 3] = "E23" - @test !has_edge(g, 2 => 3) - @test !isassigned(g, 2 => 3) + g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + set!(g, 3 => 4, "E34") + @test has_vertex(g, 4) + @test has_edge(g, 3 => 4) + @test g[3 => 4] == "E34" + + g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + set!(g, 4 => 5, "E45") + @test has_vertex(g, 4) + @test has_vertex(g, 5) + @test has_edge(g, 4 => 5) + @test g[4 => 5] == "E45" + set!(g, 4 => 5, "E45_again") + @test g[4 => 5] == "E45_again" - set!(g, 2 => 3, "E23") + g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + g[2 => 3] = "E23" @test has_edge(g, 2 => 3) @test g[2 => 3] == "E23" - - g[2 => 3] = "E23_again" - @test g[2 => 3] == "E23_again" - @test ne(g) == 2 + @test_throws IndexError g[3 => 4] = "E34" + @test_throws IndexError g[4 => 5] = "E45" end end From 1fc06be2fcff87ef9f6869bf598a4598e303a097 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Mon, 18 May 2026 12:58:31 -0400 Subject: [PATCH 11/28] Canonize `if ...; throw(); else ...; end` code patterns to `if ...; throw(); end; ...` --- src/abstractedgeorvertexdatagraph.jl | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/abstractedgeorvertexdatagraph.jl b/src/abstractedgeorvertexdatagraph.jl index 454c3e9..0fdb3f6 100644 --- a/src/abstractedgeorvertexdatagraph.jl +++ b/src/abstractedgeorvertexdatagraph.jl @@ -92,9 +92,8 @@ is_edge_assigned(::AbstractVertexDataGraph, _edge) = false function set_index_data!(graph::AbstractVertexDataGraph, data, vertex) if !has_vertex(graph, vertex) throw(IndexError("Graph does not contain vertex $vertex")) - else - set_vertex_data!(graph, data, vertex) end + set_vertex_data!(graph, data, vertex) return graph end @@ -108,9 +107,8 @@ end function delete!_datagraph(graph::AbstractVertexDataGraph, vertex) if !has_vertex(graph, vertex) throw(IndexError("Graph does not contain vertex $vertex")) - else - rem_vertex!(graph, vertex) end + rem_vertex!(graph, vertex) return graph end @@ -196,9 +194,8 @@ function set_index_data!(graph::AbstractEdgeDataGraph, data, edge::AbstractEdge) throw(IndexError("Graph does not contain vertex $v1")) elseif !has_vertex(graph, v2) throw(IndexError("Graph does not contain vertex $v2")) - else - set_edge_data!(graph, data, edge) end + set_edge_data!(graph, data, edge) return graph end @@ -208,9 +205,8 @@ function insert!_datagraph(graph::AbstractEdgeDataGraph, edge::AbstractEdge, dat v2 = dst(edge) if has_vertex(graph, v1) && has_vertex(graph, v2) throw(IndexError("Graph already contains vertices $v1 and $v2")) - else - insert_edge_data!(graph, edge, data) end + insert_edge_data!(graph, edge, data) return graph end function insert_edge_data!(graph::AbstractEdgeDataGraph, edge::AbstractEdge, data) @@ -226,9 +222,8 @@ end function delete!_datagraph(graph::AbstractEdgeDataGraph, edge) if !has_edge(graph, edge) throw(IndexError("Graph does not contain edge $edge")) - else - rem_edge!(graph, edge) end + rem_edge!(graph, edge) return graph end From cb16df38f7117e5bd46592ee11fd9e43b4d1b45f Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Mon, 18 May 2026 12:59:04 -0400 Subject: [PATCH 12/28] Change `Type{<:Nothing}` to Type{Nothing}`. --- src/abstractedgeorvertexdatagraph.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/abstractedgeorvertexdatagraph.jl b/src/abstractedgeorvertexdatagraph.jl index 0fdb3f6..45fd6f8 100644 --- a/src/abstractedgeorvertexdatagraph.jl +++ b/src/abstractedgeorvertexdatagraph.jl @@ -134,7 +134,7 @@ end function NamedGraphs.similar_graph( graph::AbstractVertexDataGraph, VD::Type, - ::Type{<:Nothing}, + ::Type{Nothing}, vertices ) return similar_graph(graph, VD, vertices) @@ -248,7 +248,7 @@ end function NamedGraphs.similar_graph( graph::AbstractEdgeDataGraph, - ::Type{<:Nothing}, + ::Type{Nothing}, ED::Type, vertices ) From 41a4b5d9783bd0bfaef8e161099b9187b889a33c Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Mon, 18 May 2026 13:00:09 -0400 Subject: [PATCH 13/28] Improve code comments explaining `similar_graph(graph, VD, ED)` behaviour. --- src/abstractedgeorvertexdatagraph.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/abstractedgeorvertexdatagraph.jl b/src/abstractedgeorvertexdatagraph.jl index 45fd6f8..e2f4e3c 100644 --- a/src/abstractedgeorvertexdatagraph.jl +++ b/src/abstractedgeorvertexdatagraph.jl @@ -128,7 +128,8 @@ function NamedGraphs.similar_graph( VD::Type, ED::Type ) - return similar_graph(graph, VD, ED, vertices(graph)) # goes to fallback + # Can't have edge data, so go to `AbstractDataGraph` fallback. + return similar_graph(graph, VD, ED, vertices(graph)) # -> DataGraph end function NamedGraphs.similar_graph( @@ -243,7 +244,8 @@ function NamedGraphs.similar_graph( VD::Type, ED::Type ) - return similar_graph(graph, VD, ED, vertices(graph)) # goes to fallback + # Can't have vertex data, so go to `AbstractDataGraph` fallback. + return similar_graph(graph, VD, ED, vertices(graph)) # -> DataGraph end function NamedGraphs.similar_graph( From 442deb98c731e961a3625b777684cdc5d27d78e8 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Mon, 18 May 2026 13:01:16 -0400 Subject: [PATCH 14/28] Fix `similar_graph` fallbacks for `AbstractVertex/EdgeDataGraph` to return similar types. --- src/abstractedgeorvertexdatagraph.jl | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/abstractedgeorvertexdatagraph.jl b/src/abstractedgeorvertexdatagraph.jl index e2f4e3c..14cb3cc 100644 --- a/src/abstractedgeorvertexdatagraph.jl +++ b/src/abstractedgeorvertexdatagraph.jl @@ -142,15 +142,12 @@ function NamedGraphs.similar_graph( end function NamedGraphs.similar_graph( - graph::AbstractVertexDataGraph, + ::AbstractVertexDataGraph, T::Type, vertices::Vertices ) - return DataGraph( - similar_graph(underlying_graph(graph), collect(vertices)); - vertex_data_type = Nothing, - edge_data_type = T - ) + V = eltype(vertices) + return similar_graph(VertexDataGraph{V, T}, vertices) end function NamedGraphs.induced_subgraph_from_vertices( @@ -262,11 +259,9 @@ function NamedGraphs.similar_graph( T::Type, vertices::Vertices ) - return DataGraph( - similar_graph(underlying_graph(graph), collect(vertices)); - vertex_data_type = Nothing, - edge_data_type = T - ) + V = eltype(vertices) + E = convert_vertextype(V, edgetype(graph)) + return similar_graph(EdgeDataGraph{E, T, V}, collect(vertices)) end function NamedGraphs.induced_subgraph_from_vertices( From afbd74779d0683efe7f4a695f4828e8874ecaf95 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Mon, 18 May 2026 13:35:27 -0400 Subject: [PATCH 15/28] Switch order of type params in `AbstractVertexOrEdgeDataGraph`. --- src/abstractedgeorvertexdatagraph.jl | 35 ++++------- src/edgedatagraph.jl | 48 +++++++------- src/vertexdatagraph.jl | 38 ++++++------ test/test_vertexoredgedatagraph.jl | 93 +++++++++++++++------------- 4 files changed, 106 insertions(+), 108 deletions(-) diff --git a/src/abstractedgeorvertexdatagraph.jl b/src/abstractedgeorvertexdatagraph.jl index 14cb3cc..92d9f0a 100644 --- a/src/abstractedgeorvertexdatagraph.jl +++ b/src/abstractedgeorvertexdatagraph.jl @@ -2,7 +2,7 @@ using Dictionaries: Dictionaries, Indices, set! using Graphs: edges, edgetype, has_edge, has_vertex, rem_edge!, rem_vertex!, vertices using NamedGraphs: NamedGraphs, Vertices, similar_graph, subgraph_edges, to_graph_index -abstract type AbstractVertexOrEdgeDataGraph{I, T, V} <: AbstractDataGraph{V, T, T} end +abstract type AbstractVertexOrEdgeDataGraph{T, V} <: AbstractDataGraph{V, T, T} end Graphs.edgetype(graph::AbstractVertexOrEdgeDataGraph) = edgetype(typeof(graph)) @@ -43,13 +43,12 @@ function Base.iterate(graph::AbstractVertexOrEdgeDataGraph, state) end Base.keytype(graph::AbstractVertexOrEdgeDataGraph) = keytype(typeof(graph)) -Base.keytype(::Type{<:AbstractVertexOrEdgeDataGraph{I, T, V}}) where {I, T, V} = I Base.valtype(graph::AbstractVertexOrEdgeDataGraph) = valtype(typeof(graph)) -Base.valtype(::Type{<:AbstractVertexOrEdgeDataGraph{I, T, V}}) where {I, T, V} = T +Base.valtype(::Type{<:AbstractVertexOrEdgeDataGraph{T}}) where {T} = T Base.eltype(graph::AbstractVertexOrEdgeDataGraph) = eltype(typeof(graph)) -Base.eltype(::Type{<:AbstractVertexOrEdgeDataGraph{I, T, V}}) where {I, T, V} = T +Base.eltype(::Type{<:AbstractVertexOrEdgeDataGraph{T}}) where {T} = T Base.length(graph::AbstractVertexOrEdgeDataGraph) = length(index_data(graph)) Base.keys(graph::AbstractVertexOrEdgeDataGraph) = keys(index_data(graph)) @@ -62,17 +61,8 @@ function Base.insert!(graph::AbstractVertexOrEdgeDataGraph, ind, data) insert!_datagraph(graph, to_graph_index(graph, ind), data) return graph end -# For ambiguity resolution. -function Base.insert!( - graph::AbstractVertexOrEdgeDataGraph{I, T}, - ind::I, - data::T - ) where {I, T} - insert!_datagraph(graph, to_graph_index(graph, ind), data) - return graph -end -function Base.delete!(graph::AbstractVertexOrEdgeDataGraph{I, T}, ind::T) where {I, T} +function Base.delete!(graph::AbstractVertexOrEdgeDataGraph, ind) delete!_datagraph(graph, to_graph_index(graph, ind)) return graph end @@ -84,7 +74,9 @@ end # ================================== vertex data graph =================================== # -abstract type AbstractVertexDataGraph{V, T} <: AbstractVertexOrEdgeDataGraph{V, T, V} end +abstract type AbstractVertexDataGraph{T, V} <: AbstractVertexOrEdgeDataGraph{T, V} end + +Base.keytype(::Type{<:AbstractVertexDataGraph{T, V}}) where {T, V} = V is_edge_assigned(::AbstractVertexDataGraph, _edge) = false @@ -146,8 +138,7 @@ function NamedGraphs.similar_graph( T::Type, vertices::Vertices ) - V = eltype(vertices) - return similar_graph(VertexDataGraph{V, T}, vertices) + return similar_graph(VertexDataGraph{T}, vertices) end function NamedGraphs.induced_subgraph_from_vertices( @@ -180,7 +171,9 @@ end # =================================== edge data graph ==================================== # -abstract type AbstractEdgeDataGraph{E, T, V} <: AbstractVertexOrEdgeDataGraph{E, T, V} end +abstract type AbstractEdgeDataGraph{T, V} <: AbstractVertexOrEdgeDataGraph{T, V} end + +Base.keytype(::Type{<:AbstractEdgeDataGraph{T, V}}) where {T, V} = NamedEdge{V} is_vertex_assigned(::AbstractEdgeDataGraph, _vertex) = false @@ -255,13 +248,11 @@ function NamedGraphs.similar_graph( end function NamedGraphs.similar_graph( - graph::AbstractEdgeDataGraph, + ::AbstractEdgeDataGraph, T::Type, vertices::Vertices ) - V = eltype(vertices) - E = convert_vertextype(V, edgetype(graph)) - return similar_graph(EdgeDataGraph{E, T, V}, collect(vertices)) + return similar_graph(EdgeDataGraph{T}, collect(vertices)) end function NamedGraphs.induced_subgraph_from_vertices( diff --git a/src/edgedatagraph.jl b/src/edgedatagraph.jl index 11421a1..fd3a57f 100644 --- a/src/edgedatagraph.jl +++ b/src/edgedatagraph.jl @@ -1,50 +1,50 @@ using Graphs: dst, has_edge, rem_edge!, rem_vertex!, src using NamedGraphs: NamedEdge, NamedGraph, ordered_vertices, position_graph, vertex_positions -struct EdgeDataGraph{E <: NamedEdge{V} where {V}, T, V} <: AbstractEdgeDataGraph{E, T, V} +struct EdgeDataGraph{T, V} <: AbstractEdgeDataGraph{T, V} underlying_graph::NamedGraph{V} - edge_data::Dictionary{E, T} - function EdgeDataGraph{E, T, V}( + edge_data::Dictionary{NamedEdge{V}, T} + function EdgeDataGraph{T, V}( ::UndefInitializer, vertices - ) where {V, E <: NamedEdge{V}, T} + ) where {T, V} graph = NamedGraph{V}(vertices) - edge_data = Dictionary{E, T}() - return new{E, T, V}(graph, edge_data) + edge_data = Dictionary{NamedEdge{V}, T}() + return new{T, V}(graph, edge_data) end end -EdgeDataGraph(data) = EdgeDataGraph{keytype(data)}(data) -EdgeDataGraph{I}(data) where {I} = EdgeDataGraph{I, valtype(data)}(data) -EdgeDataGraph{I, T}(data) where {I, T} = EdgeDataGraph{I, T, vertextype(I)}(data) - Graphs.is_directed(::Type{<:EdgeDataGraph}) = false -struct EdgeDataDiGraph{E <: NamedEdge{V} where {V}, T, V} <: AbstractEdgeDataGraph{E, T, V} +struct EdgeDataDiGraph{T, V} <: AbstractEdgeDataGraph{T, V} underlying_graph::NamedDiGraph{V} - edge_data::Dictionary{E, T} - function EdgeDataDiGraph{E, T, V}( + edge_data::Dictionary{NamedEdge{V}, T} + function EdgeDataDiGraph{T, V}( ::UndefInitializer, vertices - ) where {V, E <: NamedEdge{V}, T} + ) where {T, V} graph = NamedDiGraph{V}(vertices) - edge_data = Dictionary{E, T}() - return new{E, T, V}(graph, edge_data) + edge_data = Dictionary{NamedEdge{V}, T}() + return new{T, V}(graph, edge_data) end end -EdgeDataDiGraph(data) = EdgeDataDiGraph{keytype(data)}(data) -EdgeDataDiGraph{I}(data) where {I} = EdgeDataDiGraph{I, valtype(data)}(data) -EdgeDataDiGraph{I, T}(data) where {I, T} = EdgeDataDiGraph{I, T, vertextype(I)}(data) - Graphs.is_directed(::Type{<:EdgeDataDiGraph}) = true for GType in (:EdgeDataGraph, :EdgeDataDiGraph) @eval begin - function $GType{E, T, V}(data) where {V, E <: NamedEdge{V}, T} + $GType(::UndefInitializer, vertices) = $GType{Any}(undef, vertices) + function $GType{T}(::UndefInitializer, vertices) where {T} + return $GType{T, eltype(vertices)}(undef, vertices) + end + + $GType(data) = $GType{valtype(data)}(data) + $GType{T}(data) where {T} = $GType{T, vertextype(keytype(data))}(data) + + function $GType{T, V}(data) where {T, V} edges = keys(data) vertices = union(src.(edges), dst.(edges)) - graph = $GType{E, T, V}(undef, vertices) + graph = $GType{T, V}(undef, vertices) copyto!(graph, data) return graph end @@ -55,7 +55,7 @@ end for GType in (:EdgeDataGraph, :EdgeDataDiGraph) @eval begin - Graphs.edgetype(::Type{<:$GType{I, T, V}}) where {I, T, V} = I + Graphs.edgetype(::Type{<:$GType{T, V}}) where {T, V} = NamedEdge{V} function Graphs.add_vertex!(graph::$GType, vertex) return add_vertex!(graph.underlying_graph, vertex) @@ -107,7 +107,7 @@ end for GType in (:EdgeDataGraph, :EdgeDataDiGraph) @eval begin - edge_data_type(::Type{<:$GType{I, T, V}}) where {I, T, V} = T + edge_data_type(::Type{<:$GType{T}}) where {T} = T function set_edge_data!(graph::$GType, data, edge) # Edges `upsert` if vertices are present. diff --git a/src/vertexdatagraph.jl b/src/vertexdatagraph.jl index e896c4c..d464422 100644 --- a/src/vertexdatagraph.jl +++ b/src/vertexdatagraph.jl @@ -3,46 +3,48 @@ using Graphs: Graphs, has_edge, rem_vertex! using NamedGraphs: NamedDiGraph, NamedEdge, NamedGraph, ordered_vertices, position_graph, vertex_positions -struct VertexDataGraph{V, T} <: AbstractVertexDataGraph{V, T} +struct VertexDataGraph{T, V} <: AbstractVertexDataGraph{T, V} underlying_graph::NamedGraph{V} vertex_data::Dictionary{V, T} - function VertexDataGraph{V, T}( + function VertexDataGraph{T, V}( ::UndefInitializer, vertices - ) where {V, T} + ) where {T, V} graph = NamedGraph{V}(vertices) vertex_data = Dictionary{V, T}() - return new{V, T}(graph, vertex_data) + return new{T, V}(graph, vertex_data) end end -VertexDataGraph(data) = VertexDataGraph{keytype(data)}(data) -VertexDataGraph{V}(data) where {V} = VertexDataGraph{V, valtype(data)}(data) - -struct VertexDataDiGraph{V, T} <: AbstractVertexDataGraph{V, T} +struct VertexDataDiGraph{T, V} <: AbstractVertexDataGraph{T, V} underlying_graph::NamedDiGraph{V} vertex_data::Dictionary{V, T} - function VertexDataDiGraph{V, T}( + function VertexDataDiGraph{T, V}( ::UndefInitializer, vertices - ) where {V, T} + ) where {T, V} graph = NamedDiGraph{V}(vertices) vertex_data = Dictionary{V, T}() - return new{V, T}(graph, vertex_data) + return new{T, V}(graph, vertex_data) end end -VertexDataDiGraph(data) = VertexDataDiGraph{keytype(data)}(data) -VertexDataDiGraph{V}(data) where {V} = VertexDataDiGraph{V, valtype(data)}(data) - Graphs.is_directed(::Type{<:VertexDataGraph}) = false Graphs.is_directed(::Type{<:VertexDataDiGraph}) = true for GType in (:VertexDataGraph, :VertexDataDiGraph) @eval begin - function $GType{V, T}(data) where {V, T} + $GType(::UndefInitializer, vertices) = $GType{Any}(undef, vertices) + function $GType{T}(::UndefInitializer, vertices) where {T} + return $GType{T, eltype(vertices)}(undef, vertices) + end + + $GType(data) = $GType{valtype(data)}(data) + $GType{T}(data) where {T} = $GType{T, keytype(data)}(data) + + function $GType{T, V}(data) where {T, V} vertices = keys(data) - cache = $GType{V, T}(undef, vertices) + cache = $GType{T, V}(undef, vertices) return copyto!(cache, data) end end @@ -52,7 +54,7 @@ end for GType in (:VertexDataGraph, :VertexDataDiGraph) @eval begin - Graphs.edgetype(::Type{<:$GType{V, T}}) where {V, T} = NamedEdge{V} + Graphs.edgetype(::Type{<:$GType{T, V}}) where {T, V} = NamedEdge{V} function Graphs.has_vertex(graph::$GType, vertex) return has_vertex(graph.underlying_graph, vertex) @@ -95,7 +97,7 @@ for GType in (:VertexDataGraph, :VertexDataDiGraph) @eval begin underlying_graph(graph::$GType) = getfield(graph, :underlying_graph) - vertex_data_type(::Type{<:$GType{V, T}}) where {V, T} = T + vertex_data_type(::Type{<:$GType{T}}) where {T} = T function set_vertex_data!(graph::$GType, data, vertex) # We use an upsert here as we have already checked if the vertex (i.e. key) exists, diff --git a/test/test_vertexoredgedatagraph.jl b/test/test_vertexoredgedatagraph.jl index db3a5da..19e6819 100644 --- a/test/test_vertexoredgedatagraph.jl +++ b/test/test_vertexoredgedatagraph.jl @@ -12,8 +12,8 @@ using Test: @test, @test_throws, @testset @testset "VertexDataGraph and EdgeDataGraph" begin @testset "VertexDataGraph" begin @testset "undef constructor" begin - g = VertexDataGraph{Int, String}(undef, [1, 2, 3]) - @test g isa VertexDataGraph{Int, String} + g = VertexDataGraph{String, Int}(undef, [1, 2, 3]) + @test g isa VertexDataGraph{String, Int} @test nv(g) == 3 @test ne(g) == 0 @test has_vertex(g, 1) @@ -26,7 +26,7 @@ using Test: @test, @test_throws, @testset @testset "data constructor" begin data = Dictionary([1, 2, 3], ["V1", "V2", "V3"]) g = VertexDataGraph(data) - @test g isa VertexDataGraph{Int, String} + @test g isa VertexDataGraph{String, Int} @test nv(g) == 3 @test isassigned(g, 1) @test isassigned(g, 2) @@ -37,7 +37,7 @@ using Test: @test, @test_throws, @testset end @testset "Graphs.jl interface" begin - g = VertexDataGraph{Int, String}(undef, [1, 2, 3]) + g = VertexDataGraph{String, Int}(undef, [1, 2, 3]) @test !is_directed(VertexDataGraph) @test !is_directed(g) @test vertextype(g) == Int @@ -60,7 +60,7 @@ using Test: @test, @test_throws, @testset end @testset "rem_vertex!" begin - g = VertexDataGraph{Int, String}(undef, [1, 2, 3]) + g = VertexDataGraph{String, Int}(undef, [1, 2, 3]) add_edge!(g, NamedEdge(1, 2)) rem_vertex!(g, 1) @test !has_vertex(g, 1) @@ -69,10 +69,10 @@ using Test: @test, @test_throws, @testset end @testset "DataGraphs interface" begin - g = VertexDataGraph{Int, String}(undef, [1, 2, 3]) + g = VertexDataGraph{String, Int}(undef, [1, 2, 3]) @test underlying_graph(g) isa NamedGraph{Int} @test vertex_data_type(g) == String - @test vertex_data_type(VertexDataGraph{Int, String}) == String + @test vertex_data_type(VertexDataGraph{String, Int}) == String @test !isassigned(g, 1) @test !isassigned(g, 2) @test !isassigned(g, 3) @@ -81,7 +81,7 @@ using Test: @test, @test_throws, @testset end @testset "setindex! and getindex" begin - g = VertexDataGraph{Int, String}(undef, [1, 2, 3]) + g = VertexDataGraph{String, Int}(undef, [1, 2, 3]) g[1] = "V1" g[2] = "V2" g[3] = "V3" @@ -94,7 +94,7 @@ using Test: @test, @test_throws, @testset end @testset "NamedGraphs interface" begin - g = VertexDataGraph{Int, String}(undef, [1, 2, 3]) + g = VertexDataGraph{String, Int}(undef, [1, 2, 3]) @test underlying_graph(g) isa NamedGraph{Int} @test position_graph(g) isa AbstractGraph @test ordered_vertices(g) isa AbstractVector @@ -102,7 +102,7 @@ using Test: @test, @test_throws, @testset end @testset "Dictionaries interface" begin - g = VertexDataGraph{Int, String}(undef, [1, 2, 3]) + g = VertexDataGraph{String, Int}(undef, [1, 2, 3]) @test keytype(g) == Int @test valtype(g) == String @test keys(g) isa Indices @@ -132,8 +132,8 @@ using Test: @test, @test_throws, @testset @testset "VertexDataDiGraph" begin @testset "undef constructor" begin - g = VertexDataDiGraph{Int, String}(undef, [1, 2, 3]) - @test g isa VertexDataDiGraph{Int, String} + g = VertexDataDiGraph{String, Int}(undef, [1, 2, 3]) + @test g isa VertexDataDiGraph{String, Int} @test nv(g) == 3 @test ne(g) == 0 @test has_vertex(g, 1) @@ -143,7 +143,7 @@ using Test: @test, @test_throws, @testset @testset "data constructor" begin data = Dictionary([1, 2, 3], ["V1", "V2", "V3"]) g = VertexDataDiGraph(data) - @test g isa VertexDataDiGraph{Int, String} + @test g isa VertexDataDiGraph{String, Int} @test nv(g) == 3 @test g[1] == "V1" @test g[2] == "V2" @@ -151,14 +151,14 @@ using Test: @test, @test_throws, @testset end @testset "directed graph" begin - g = VertexDataDiGraph{Int, String}(undef, [1, 2, 3]) + g = VertexDataDiGraph{String, Int}(undef, [1, 2, 3]) @test is_directed(VertexDataDiGraph) @test is_directed(g) @test underlying_graph(g) isa NamedDiGraph{Int} end @testset "directed edges" begin - g = VertexDataDiGraph{Int, String}(undef, [1, 2, 3]) + g = VertexDataDiGraph{String, Int}(undef, [1, 2, 3]) add_edge!(g, NamedEdge(1, 2)) @test has_edge(g, NamedEdge(1, 2)) @test !has_edge(g, NamedEdge(2, 1)) @@ -166,15 +166,15 @@ using Test: @test, @test_throws, @testset end @testset "DataGraphs interface" begin - g = VertexDataDiGraph{Int, String}(undef, [1, 2, 3]) + g = VertexDataDiGraph{String, Int}(undef, [1, 2, 3]) @test vertex_data_type(g) == String - @test vertex_data_type(VertexDataDiGraph{Int, String}) == String + @test vertex_data_type(VertexDataDiGraph{String, Int}) == String @test vertextype(g) == Int @test edgetype(g) == NamedEdge{Int} end @testset "Dictionaries interface" begin - g = VertexDataDiGraph{Int, String}(undef, [1, 2, 3]) + g = VertexDataDiGraph{String, Int}(undef, [1, 2, 3]) @test keytype(g) == Int @test valtype(g) == String @test keys(g) isa Indices @@ -184,11 +184,9 @@ using Test: @test, @test_throws, @testset end @testset "EdgeDataGraph" begin - E = NamedEdge{Int} - @testset "undef constructor" begin - g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) - @test g isa EdgeDataGraph{E, String, Int} + g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) + @test g isa EdgeDataGraph{String, Int} @test nv(g) == 3 @test ne(g) == 0 end @@ -196,7 +194,7 @@ using Test: @test, @test_throws, @testset @testset "data constructor" begin data = Dictionary([NamedEdge(1, 2), NamedEdge(2, 3)], ["E12", "E23"]) g = EdgeDataGraph(data) - @test g isa EdgeDataGraph{E, String, Int} + @test g isa EdgeDataGraph{String, Int} @test nv(g) == 3 @test ne(g) == 2 @test isassigned(g, NamedEdge(1, 2)) @@ -206,13 +204,13 @@ using Test: @test, @test_throws, @testset end @testset "Graphs.jl interface" begin - g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) @test !is_directed(EdgeDataGraph) @test !is_directed(g) @test has_vertex(g, 1) @test has_vertex(g, 2) @test !has_vertex(g, 4) - @test edgetype(g) == E + @test edgetype(g) == NamedEdge{Int} add_edge!(g, NamedEdge(1, 2)) add_edge!(g, NamedEdge(2, 3)) @@ -226,8 +224,8 @@ using Test: @test, @test_throws, @testset end @testset "DataGraphs interface" begin - g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) - @test edge_data_type(EdgeDataGraph{E, String, Int}) == String + g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) + @test edge_data_type(EdgeDataGraph{String, Int}) == String @test edge_data_type(g) == String @test !isassigned(g, 1) @test !isassigned(g, NamedEdge(1, 2)) @@ -259,7 +257,7 @@ using Test: @test, @test_throws, @testset end @testset "NamedGraphs interface" begin - g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) @test Set(collect(vertices(g))) == Set([1, 2, 3]) @test position_graph(g) isa AbstractGraph @test ordered_vertices(g) isa AbstractVector @@ -267,19 +265,26 @@ using Test: @test, @test_throws, @testset end @testset "Dictionaries interface" begin - g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) - @test keytype(g) == E + g = EdgeDataGraph(undef, [1, 2, 3]) + @test keytype(g) == NamedEdge{Int} + @test valtype(g) == Any + + g = EdgeDataGraph{String}(undef, [1, 2, 3]) + @test keytype(g) == NamedEdge{Int} @test valtype(g) == String @test edge_data(g) isa EdgeDataView - g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + g = EdgeDataGraph{String, Float64}(undef, [1, 2, 3]) + @test keytype(g) == NamedEdge{Float64} + + g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) insert!(g, 3 => 4, "E34") @test has_vertex(g, 4) @test has_edge(g, 3 => 4) @test isassigned(g, 3 => 4) @test g[3 => 4] == "E34" - g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) insert!(g, 5 => 6, "E56") @test has_vertex(g, 5) @test has_vertex(g, 6) @@ -287,22 +292,22 @@ using Test: @test, @test_throws, @testset @test isassigned(g, 5 => 6) @test g[5 => 6] == "E56" - g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) @test_throws IndexError insert!(g, 2 => 3, "E23") @test !has_edge(g, 2 => 3) - g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) set!(g, 1 => 2, "E12") @test has_edge(g, 1 => 2) @test g[1 => 2] == "E12" - g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) set!(g, 3 => 4, "E34") @test has_vertex(g, 4) @test has_edge(g, 3 => 4) @test g[3 => 4] == "E34" - g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) set!(g, 4 => 5, "E45") @test has_vertex(g, 4) @test has_vertex(g, 5) @@ -311,7 +316,7 @@ using Test: @test, @test_throws, @testset set!(g, 4 => 5, "E45_again") @test g[4 => 5] == "E45_again" - g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) g[2 => 3] = "E23" @test has_edge(g, 2 => 3) @test g[2 => 3] == "E23" @@ -324,8 +329,8 @@ using Test: @test, @test_throws, @testset E = NamedEdge{Int} @testset "undef constructor" begin - g = EdgeDataDiGraph{E, String, Int}(undef, [1, 2, 3]) - @test g isa EdgeDataDiGraph{E, String, Int} + g = EdgeDataDiGraph{String, Int}(undef, [1, 2, 3]) + @test g isa EdgeDataDiGraph{String, Int} @test nv(g) == 3 @test ne(g) == 0 end @@ -333,7 +338,7 @@ using Test: @test, @test_throws, @testset @testset "data constructor" begin data = Dictionary([NamedEdge(1, 2), NamedEdge(2, 3)], ["E12", "E23"]) g = EdgeDataDiGraph(data) - @test g isa EdgeDataDiGraph{E, String, Int} + @test g isa EdgeDataDiGraph{String, Int} @test nv(g) == 3 @test ne(g) == 2 @test g[NamedEdge(1, 2)] == "E12" @@ -341,7 +346,7 @@ using Test: @test, @test_throws, @testset end @testset "directed graph" begin - g = EdgeDataDiGraph{E, String, Int}(undef, [1, 2, 3]) + g = EdgeDataDiGraph{String, Int}(undef, [1, 2, 3]) @test is_directed(EdgeDataDiGraph) @test is_directed(g) end @@ -355,10 +360,10 @@ using Test: @test, @test_throws, @testset end @testset "DataGraphs interface" begin - g = EdgeDataDiGraph{E, String, Int}(undef, [1, 2, 3]) - @test edge_data_type(EdgeDataDiGraph{E, String, Int}) == String + g = EdgeDataDiGraph{String, Int}(undef, [1, 2, 3]) + @test edge_data_type(EdgeDataDiGraph{String, Int}) == String @test edge_data_type(g) == String - @test keytype(g) == E + @test keytype(g) == NamedEdge{Int} @test valtype(g) == String @test edge_data(g) isa EdgeDataView end From 8b1044ee2f8fe1fd308138be6c57782f95c25b86 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Mon, 18 May 2026 14:24:32 -0400 Subject: [PATCH 16/28] Increase strictness of `DataView` key setting. --- src/dataview.jl | 3 +++ test/test_vertexoredgedatagraph.jl | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/dataview.jl b/src/dataview.jl index 8eb055f..cd7eb63 100644 --- a/src/dataview.jl +++ b/src/dataview.jl @@ -90,6 +90,9 @@ function Base.setindex!(view::EdgeDataView, vals, keys::Indices{<:Pair}) end function Base.setindex!(view::VertexOrEdgeDataView{K, V}, data::V, key::K) where {K, V} + if !haskey(view, key) + throw(IndexError("Dictionary does not contain index: $key")) + end setindex!(view.graph, data, key) return view end diff --git a/test/test_vertexoredgedatagraph.jl b/test/test_vertexoredgedatagraph.jl index 19e6819..f1a8df9 100644 --- a/test/test_vertexoredgedatagraph.jl +++ b/test/test_vertexoredgedatagraph.jl @@ -322,12 +322,13 @@ using Test: @test, @test_throws, @testset @test g[2 => 3] == "E23" @test_throws IndexError g[3 => 4] = "E34" @test_throws IndexError g[4 => 5] = "E45" + + g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) + @test_throws IndexError edge_data(g)[2 => 3] = "E23" end end @testset "EdgeDataDiGraph" begin - E = NamedEdge{Int} - @testset "undef constructor" begin g = EdgeDataDiGraph{String, Int}(undef, [1, 2, 3]) @test g isa EdgeDataDiGraph{String, Int} From 6fff2ce2fba9ae425113b71caf8954464b88429b Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Mon, 18 May 2026 15:44:44 -0400 Subject: [PATCH 17/28] Move vertex checking to `insert!_datagraph`. --- src/abstractedgeorvertexdatagraph.jl | 3 +++ src/vertexdatagraph.jl | 8 ++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/abstractedgeorvertexdatagraph.jl b/src/abstractedgeorvertexdatagraph.jl index 92d9f0a..5b66f48 100644 --- a/src/abstractedgeorvertexdatagraph.jl +++ b/src/abstractedgeorvertexdatagraph.jl @@ -91,6 +91,9 @@ end # `insert!` function insert!_datagraph(graph::AbstractVertexDataGraph, vertex, data) + if has_vertex(graph, vertex) + throw(IndexError("Graph already contains vertex $vertex")) + end insert_vertex_data!(graph, vertex, data) return graph end diff --git a/src/vertexdatagraph.jl b/src/vertexdatagraph.jl index d464422..019f01b 100644 --- a/src/vertexdatagraph.jl +++ b/src/vertexdatagraph.jl @@ -122,12 +122,8 @@ for GType in (:VertexDataGraph, :VertexDataDiGraph) Dictionaries.isinsertable(::Type{<:$GType}, _edge) = true function insert_vertex_data!(graph::$GType, vertex, data) - if has_vertex(graph, vertex) - throw(IndexError("Graph already contains vertex $vertex")) - else - add_vertex!(graph.underlying_graph, vertex) - insert!(graph.vertex_data, vertex, data) - end + add_vertex!(graph.underlying_graph, vertex) + insert!(graph.vertex_data, vertex, data) return graph end end From 9141efa3930f03521bcc51b684dba27ccefc115d Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Tue, 19 May 2026 09:30:04 -0400 Subject: [PATCH 18/28] Fix `isinsertable` definitions. --- src/edgedatagraph.jl | 2 +- src/vertexdatagraph.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/edgedatagraph.jl b/src/edgedatagraph.jl index fd3a57f..097977f 100644 --- a/src/edgedatagraph.jl +++ b/src/edgedatagraph.jl @@ -127,6 +127,6 @@ end for GType in (:EdgeDataGraph, :EdgeDataDiGraph) @eval begin - Dictionaries.isinsertable(::Type{<:$GType}, _edge) = true + Dictionaries.isinsertable(::$GType) = true end end diff --git a/src/vertexdatagraph.jl b/src/vertexdatagraph.jl index 019f01b..969e7f2 100644 --- a/src/vertexdatagraph.jl +++ b/src/vertexdatagraph.jl @@ -119,7 +119,7 @@ end for GType in (:VertexDataGraph, :VertexDataDiGraph) @eval begin - Dictionaries.isinsertable(::Type{<:$GType}, _edge) = true + Dictionaries.isinsertable(::$GType) = true function insert_vertex_data!(graph::$GType, vertex, data) add_vertex!(graph.underlying_graph, vertex) From fa73fc717045d32a56fa673bea323fcb6b331b32 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Tue, 19 May 2026 09:31:44 -0400 Subject: [PATCH 19/28] Add `forest_cover_edge_sequence` fallback for `AbstractDataGraph` --- src/abstractdatagraph.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl index 9bf1da7..5b511f6 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -444,3 +444,8 @@ function Base.show(io::IO, mime::MIME"text/plain", graph::AbstractDataGraph) end Base.show(io::IO, graph::AbstractDataGraph) = show(io, MIME"text/plain"(), graph) + +function GraphsExtensions.forest_cover_edge_sequence(graph::AbstractDataGraph; kwargs...) + dummy_graph = add_edges!(NamedGraph(vertices(graph)), edges(graph)) + return GraphsExtensions.forest_cover_edge_sequence(dummy_graph; kwargs...) +end From aae56ffe21c11fae409d150c7b1330de6e7dff0d Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Tue, 19 May 2026 15:15:56 -0400 Subject: [PATCH 20/28] Interface clean up; fixes; tests --- src/abstractdatagraph.jl | 47 ++++++++------- src/abstractedgeorvertexdatagraph.jl | 89 ++++++++++++++-------------- src/edgedatagraph.jl | 38 ++++++++---- src/vertexdatagraph.jl | 31 +++++++--- test/test_basics.jl | 2 - test/test_vertexoredgedatagraph.jl | 79 +++++++++++++++++------- 6 files changed, 180 insertions(+), 106 deletions(-) diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl index 5b511f6..d1c87c6 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -45,12 +45,6 @@ function get_edges_data(g::AbstractGraph, edges) return map(e -> getindex(g, e), Indices(edges)) end -Graphs.has_vertex(g::AbstractDataGraph, vertex) = has_vertex(underlying_graph(g), vertex) -function Graphs.has_edge(g::AbstractDataGraph, edge::AbstractNamedGraph) - return has_edge(underlying_graph(g), edge) -end -Graphs.has_edge(g::AbstractDataGraph, pair::Pair) = has_edge(g, to_graph_index(g, pair)) - vertex_data(dg::AbstractGraph) = VertexDataView(dg) edge_data(dg::AbstractGraph) = EdgeDataView(dg) @@ -79,26 +73,39 @@ end function Base.copy(graph::AbstractDataGraph) copy_graph = similar_graph(graph) - copyto!(copy_graph, graph) - return graph + # Allow copies of graphs with undefined data. + copyto!(copy_graph, graph, assigned_vertices(graph)) + copyto!(copy_graph, graph, assigned_edges(graph)) + return copy_graph end -function Base.copyto!(dst_graph::AbstractDataGraph, src_graph::AbstractDataGraph) - vertex_data(dst_graph) .= vertex_data(src_graph) - edge_data(dst_graph) .= edge_data(src_graph) - return dst_graph -end -function Base.copyto!( - graph_dst::AbstractDataGraph, - dict_src::Union{Dict, AbstractDictionary}, - keynames = keys(dict_src) - ) - for key in keynames - graph_dst[key] = dict_src[key] +# Base method to specialize on. +function Base.copyto!(graph_dst::AbstractDataGraph, src, keys) + for key in keys + graph_dst[key] = src[key] end return graph_dst end +function Base.copyto!(graph_dst::AbstractDataGraph, src) + copyto!_datagraph(graph_dst, src) + return graph_dst +end + +# To prevent method ambiguities +function copyto!_datagraph(dst::AbstractDataGraph, src::AbstractDataGraph) + if !issetequal(vertices(dst), vertices(src)) + throw(ArgumentError("destination and source graphs must have the same vertices")) + end + copyto!(dst, src, vertices(src)) + copyto!(dst, src, edges(src)) + return dst +end +function copyto!_datagraph(dst_graph::AbstractDataGraph, src) + copyto!(dst_graph, src, keys(src)) + return dst_graph +end + # Graphs overloads function Graphs.vertices(graph::AbstractDataGraph) return Graphs.vertices(underlying_graph(graph)) diff --git a/src/abstractedgeorvertexdatagraph.jl b/src/abstractedgeorvertexdatagraph.jl index 5b66f48..c9a220b 100644 --- a/src/abstractedgeorvertexdatagraph.jl +++ b/src/abstractedgeorvertexdatagraph.jl @@ -1,4 +1,4 @@ -using Dictionaries: Dictionaries, Indices, set! +using Dictionaries: Dictionaries, Indices, isinsertable, set! using Graphs: edges, edgetype, has_edge, has_vertex, rem_edge!, rem_vertex!, vertices using NamedGraphs: NamedGraphs, Vertices, similar_graph, subgraph_edges, to_graph_index @@ -7,12 +7,17 @@ abstract type AbstractVertexOrEdgeDataGraph{T, V} <: AbstractDataGraph{V, T, T} Graphs.edgetype(graph::AbstractVertexOrEdgeDataGraph) = edgetype(typeof(graph)) function NamedGraphs.similar_graph( - graph::AbstractVertexOrEdgeDataGraph, - T::Type + graph::AbstractVertexOrEdgeDataGraph ) - return similar_graph(graph, T, vertices(graph)) + return similar_graph(graph, valtype(graph)) end +function NamedGraphs.similar_graph( + graph::AbstractVertexOrEdgeDataGraph, + vertices + ) + return similar_graph(graph, valtype(graph), vertices) +end function NamedGraphs.similar_graph( graph::AbstractVertexOrEdgeDataGraph, T::Type, @@ -21,19 +26,31 @@ function NamedGraphs.similar_graph( return similar_graph(graph, T, Vertices(vertices)) end +# For ambiguity resolution only. +function NamedGraphs.similar_graph( + graph::AbstractVertexOrEdgeDataGraph, + VD::Type, + ED::Type + ) + # No notion of both vertex and edge data, will go to `AbstractDataGraph` fallback. + new_graph = similar_graph(graph, VD, ED, vertices(graph)) # -> DataGraph + add_edges!(new_graph, edges(graph)) + return new_graph +end + function Base.:(==)(dg1::AbstractVertexOrEdgeDataGraph, dg2::AbstractVertexOrEdgeDataGraph) return underlying_graph(dg1) == underlying_graph(dg2) && index_data(dg1) == index_data(dg2) end -function Base.copyto!( - graph_dst::G, - graph_src::G, - keynames = nothing - ) where {G <: AbstractVertexOrEdgeDataGraph} - dict_src = index_data(graph_src) - keynames = isnothing(keynames) ? keys(dict_src) : keynames - copyto!(graph_dst, dict_src, keynames) +function Base.copy(graph::AbstractVertexOrEdgeDataGraph) + graph_dst = similar_graph(graph) + # Allow copies of graphs with undefined data. + copyto!(graph_dst, src, filter(key -> isassigned(graph, key), keys(graph))) + return graph_dst +end +function Base.copyto!(graph_dst::AbstractVertexOrEdgeDataGraph, src) + copyto!(graph_dst, src, keys(src)) return graph_dst end @@ -58,6 +75,7 @@ Dictionaries.issettable(::AbstractVertexOrEdgeDataGraph) = true Dictionaries.isinsertable(::AbstractVertexOrEdgeDataGraph) = false function Base.insert!(graph::AbstractVertexOrEdgeDataGraph, ind, data) + isinsertable(graph) || throw(ArgumentError("Graph does not support insertion.")) insert!_datagraph(graph, to_graph_index(graph, ind), data) return graph end @@ -110,32 +128,24 @@ end # `set!` function set!_datagraph(graph::AbstractVertexDataGraph, vertex, data) if has_vertex(graph, vertex) - set_vertex_data!(graph, data, vertex) + graph[vertex] = data else - insert_vertex_data!(graph, vertex, data) + insert!(graph, vertex, data) end return graph end -# For ambiguity resolution. function NamedGraphs.similar_graph( graph::AbstractVertexDataGraph, - VD::Type, - ED::Type - ) - # Can't have edge data, so go to `AbstractDataGraph` fallback. - return similar_graph(graph, VD, ED, vertices(graph)) # -> DataGraph -end - -function NamedGraphs.similar_graph( - graph::AbstractVertexDataGraph, - VD::Type, - ::Type{Nothing}, - vertices + T::Type ) - return similar_graph(graph, VD, vertices) + new_graph = similar_graph(graph, T, vertices(graph)) + # we can add edges to a `AbstractVertexDataGraph`. + add_edges!(new_graph, edges(graph)) + return new_graph end +# Base method to overload. function NamedGraphs.similar_graph( ::AbstractVertexDataGraph, T::Type, @@ -224,32 +234,23 @@ end # `set!` function set!_datagraph(graph::AbstractEdgeDataGraph, edge::AbstractEdge, data) if !has_vertex(graph, src(edge)) || !has_vertex(graph, dst(edge)) - insert_edge_data!(graph, edge, data) + insert!(graph, edge, data) else - set_edge_data!(graph, data, edge) + graph[edge] = data end return graph end -# For ambiguity resolution. function NamedGraphs.similar_graph( graph::AbstractEdgeDataGraph, - VD::Type, - ED::Type - ) - # Can't have vertex data, so go to `AbstractDataGraph` fallback. - return similar_graph(graph, VD, ED, vertices(graph)) # -> DataGraph -end - -function NamedGraphs.similar_graph( - graph::AbstractEdgeDataGraph, - ::Type{Nothing}, - ED::Type, - vertices + T::Type ) - return similar_graph(graph, ED, vertices) + new_graph = similar_graph(graph, T, vertices(graph)) + # we can't generically add edges to an `AbstractEdgeDataGraph`. + return new_graph end +# Base method to overload. function NamedGraphs.similar_graph( ::AbstractEdgeDataGraph, T::Type, diff --git a/src/edgedatagraph.jl b/src/edgedatagraph.jl index 097977f..eb2ebf0 100644 --- a/src/edgedatagraph.jl +++ b/src/edgedatagraph.jl @@ -60,17 +60,15 @@ for GType in (:EdgeDataGraph, :EdgeDataDiGraph) function Graphs.add_vertex!(graph::$GType, vertex) return add_vertex!(graph.underlying_graph, vertex) end - function Graphs.has_vertex(graph::$GType, vertex) - return has_vertex(graph.underlying_graph, vertex) - end - function Graphs.has_edge(graph::$GType, edge::NamedEdge) - return has_edge(graph.underlying_graph, edge) - end - function Graphs.rem_edge!(graph::$GType, edge) - unset!(graph.edge_data, edge) - rem_edge!(graph.underlying_graph, edge) - return graph + function Graphs.add_edge!(graph::$GType, edge) + G = esc($GType) + throw( + ArgumentError( + "cannot add data-free edges to $G; use `insert!`, `setindex!` or `set!` instead" + ) + ) + return nothing end function Graphs.rem_vertex!(graph::$GType, vertex) @@ -81,6 +79,12 @@ for GType in (:EdgeDataGraph, :EdgeDataDiGraph) return graph end + function Graphs.rem_edge!(graph::$GType, edge) + unset!(graph.edge_data, edge) + rem_edge!(graph.underlying_graph, edge) + return graph + end + Graphs.vertices(graph::$GType) = vertices(graph.underlying_graph) end end @@ -100,6 +104,20 @@ for GType in (:EdgeDataGraph, :EdgeDataDiGraph) function NamedGraphs.position_graph(graph::$GType) return position_graph(graph.underlying_graph) end + + function NamedGraphs.similar_graph(graph::$GType, T::Type, vertices::Vertices) + new_graph = $GType{T}(undef, collect(vertices)) + return new_graph + end + + # We know how to add edges keys for these particurly concrete types + function NamedGraphs.similar_graph(graph::$GType, T::Type) + new_graph = similar_graph(graph, T, vertices(graph)) + add_edges!(new_graph.underlying_graph, edges(graph)) + return new_graph + end + + NamedGraphs.similar_graph(T::Type{<:$GType}, vertices) = T(undef, vertices) end end diff --git a/src/vertexdatagraph.jl b/src/vertexdatagraph.jl index 969e7f2..7b0be27 100644 --- a/src/vertexdatagraph.jl +++ b/src/vertexdatagraph.jl @@ -56,11 +56,16 @@ for GType in (:VertexDataGraph, :VertexDataDiGraph) @eval begin Graphs.edgetype(::Type{<:$GType{T, V}}) where {T, V} = NamedEdge{V} - function Graphs.has_vertex(graph::$GType, vertex) - return has_vertex(graph.underlying_graph, vertex) + function Graphs.add_vertex!(graph::$GType, vertex) + return throw( + ArgumentError( + "cannot add data-free vertices to $GType; use `insert!`, `setindex!` or `set!` instead" + ) + ) end - function Graphs.has_edge(graph::$GType, edge::NamedEdge) - return has_edge(graph.underlying_graph, edge) + + function Graphs.add_edge!(graph::$GType, edge::NamedEdge) + return add_edge!(graph.underlying_graph, edge) end function Graphs.rem_vertex!(graph::$GType, vertex) @@ -69,6 +74,11 @@ for GType in (:VertexDataGraph, :VertexDataDiGraph) return graph end + function Graphs.rem_edge!(graph::$GType, vertex) + rem_edge!(graph.underlying_graph, vertex) + return graph + end + Graphs.vertices(graph::$GType) = vertices(graph.underlying_graph) end end @@ -88,6 +98,13 @@ for GType in (:VertexDataGraph, :VertexDataDiGraph) function NamedGraphs.position_graph(graph::$GType) return position_graph(graph.underlying_graph) end + + function NamedGraphs.similar_graph(graph::$GType, T::Type, vertices::Vertices) + new_graph = $GType{T}(undef, collect(vertices)) + return new_graph + end + + NamedGraphs.similar_graph(T::Type{<:$GType}, vertices) = T(undef, vertices) end end @@ -95,8 +112,6 @@ end for GType in (:VertexDataGraph, :VertexDataDiGraph) @eval begin - underlying_graph(graph::$GType) = getfield(graph, :underlying_graph) - vertex_data_type(::Type{<:$GType{T}}) where {T} = T function set_vertex_data!(graph::$GType, data, vertex) @@ -108,9 +123,7 @@ for GType in (:VertexDataGraph, :VertexDataDiGraph) get_vertex_data(graph::$GType, vertex) = graph.vertex_data[vertex] - function is_vertex_assigned(graph::$GType, vertex) - return isassigned(graph.vertex_data, vertex) - end + is_vertex_assigned(graph::$GType, vertex) = isassigned(graph.vertex_data, vertex) is_edge_assigned(::$GType, _edge) = false end end diff --git a/test/test_basics.jl b/test/test_basics.jl index 727da8d..7d3fc77 100644 --- a/test/test_basics.jl +++ b/test/test_basics.jl @@ -620,8 +620,6 @@ using Test: @test, @test_broken, @testset @test g_copy["a" => "b"] == -1.0 @test g_copy["b" => "c"] == -2.0 - @test_throws IndexError copyto!(empty_graph(g_copy), g_copy) - g2 = similar_graph(g, ["u", "v"]) @test similar_graph(g2) isa typeof(g) @test has_vertex(g2, "u") diff --git a/test/test_vertexoredgedatagraph.jl b/test/test_vertexoredgedatagraph.jl index f1a8df9..a95cd17 100644 --- a/test/test_vertexoredgedatagraph.jl +++ b/test/test_vertexoredgedatagraph.jl @@ -2,11 +2,12 @@ using DataGraphs: DataGraphs, EdgeDataDiGraph, EdgeDataGraph, EdgeDataView, VertexDataDiGraph, VertexDataGraph, VertexDataView, edge_data, edge_data_type, underlying_graph, vertex_data, vertex_data_type using Dictionaries: AbstractDictionary, Dictionary, IndexError, Indices, set! -using Graphs: AbstractGraph, add_edge!, dst, edges, edgetype, has_edge, has_vertex, - is_directed, ne, nv, rem_edge!, rem_vertex!, src, vertices -using NamedGraphs.GraphsExtensions: vertextype -using NamedGraphs: - NamedDiGraph, NamedEdge, NamedGraph, ordered_vertices, position_graph, vertex_positions +using Graphs: AbstractGraph, AbstractSimpleGraph, add_edge!, add_vertex!, dst, edges, + edgetype, has_edge, has_vertex, is_directed, ne, nv, rem_edge!, rem_vertex!, src, + vertices +using NamedGraphs.GraphsExtensions: add_edge, vertextype +using NamedGraphs: NamedDiGraph, NamedEdge, NamedGraph, ordered_vertices, position_graph, + similar_graph, vertex_positions using Test: @test, @test_throws, @testset @testset "VertexDataGraph and EdgeDataGraph" begin @@ -70,7 +71,6 @@ using Test: @test, @test_throws, @testset @testset "DataGraphs interface" begin g = VertexDataGraph{String, Int}(undef, [1, 2, 3]) - @test underlying_graph(g) isa NamedGraph{Int} @test vertex_data_type(g) == String @test vertex_data_type(VertexDataGraph{String, Int}) == String @test !isassigned(g, 1) @@ -93,12 +93,53 @@ using Test: @test, @test_throws, @testset @test g[3] == "V3" end - @testset "NamedGraphs interface" begin - g = VertexDataGraph{String, Int}(undef, [1, 2, 3]) - @test underlying_graph(g) isa NamedGraph{Int} - @test position_graph(g) isa AbstractGraph - @test ordered_vertices(g) isa AbstractVector - @test vertex_positions(g) isa AbstractDictionary + for GType in (VertexDataGraph{String, Int}, VertexDataDiGraph{String, Int}) + @testset "NamedGraphs interface" begin + g = GType(undef, [1, 2, 3]) + @test position_graph(g) isa AbstractSimpleGraph{Int} + @test ordered_vertices(g) == [1, 2, 3] + @test keys(vertex_positions(g)) == vertices(g) + + g = add_edge(g, NamedEdge(1, 2)) + g[1] = "1" + + gs = similar_graph(g) + @test gs isa GType + @test has_vertex(gs, 1) + @test has_vertex(gs, 2) + @test has_vertex(gs, 3) + @test has_edge(gs, 1 => 2) + @test !isassigned(gs, 1) + + gs = similar_graph(g, vertices(g)) + @test vertices(gs) == vertices(g) + @test ne(gs) == 0 + @test !isassigned(gs, 1) + + gs = similar_graph(g, [1, 2, 4]) + @test has_vertex(gs, 1) + @test has_vertex(gs, 2) + @test has_vertex(gs, 4) + @test nv(gs) == 3 + @test ne(gs) == 0 + @test !isassigned(gs, 1) + + gs = similar_graph(g, Char) + @test vertex_data_type(gs) === Char + @test nv(gs) == 3 + @test ne(gs) == 1 + gs[1] = 'C' + @test gs[1] == 'C' + + gs = similar_graph(g, Float64, vertices(g)) + @test ne(gs) == 0 + + gs = similar_graph(GType, [1.0, 2.0]) + @test gs isa GType + @test has_vertex(gs, 1) + @test has_vertex(gs, 2) + @test ne(gs) == 0 + end end @testset "Dictionaries interface" begin @@ -154,7 +195,6 @@ using Test: @test, @test_throws, @testset g = VertexDataDiGraph{String, Int}(undef, [1, 2, 3]) @test is_directed(VertexDataDiGraph) @test is_directed(g) - @test underlying_graph(g) isa NamedDiGraph{Int} end @testset "directed edges" begin @@ -212,15 +252,12 @@ using Test: @test, @test_throws, @testset @test !has_vertex(g, 4) @test edgetype(g) == NamedEdge{Int} - add_edge!(g, NamedEdge(1, 2)) - add_edge!(g, NamedEdge(2, 3)) - @test ne(g) == 2 - @test has_edge(g, NamedEdge(1, 2)) - @test has_edge(g, NamedEdge(2, 3)) - @test !has_edge(g, NamedEdge(1, 3)) + @test_throws ArgumentError add_edge!(g, NamedEdge(1, 2)) + @test_throws ArgumentError add_edge!(g, 2 => 3) - @test !is_directed(EdgeDataGraph) - @test !is_directed(g) + add_vertex!(g, 4) + @test has_vertex(g, 4) + @test nv(g) == 4 end @testset "DataGraphs interface" begin From 407b83f898962c16f5b37ae50b40dc1833075b9d Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Tue, 19 May 2026 16:05:10 -0400 Subject: [PATCH 21/28] Improve test coverage --- src/edgedatagraph.jl | 2 +- test/test_vertexoredgedatagraph.jl | 358 ++++++++++++++--------------- 2 files changed, 179 insertions(+), 181 deletions(-) diff --git a/src/edgedatagraph.jl b/src/edgedatagraph.jl index eb2ebf0..3f22f4b 100644 --- a/src/edgedatagraph.jl +++ b/src/edgedatagraph.jl @@ -42,7 +42,7 @@ for GType in (:EdgeDataGraph, :EdgeDataDiGraph) $GType{T}(data) where {T} = $GType{T, vertextype(keytype(data))}(data) function $GType{T, V}(data) where {T, V} - edges = keys(data) + edges = NamedEdge{V}.(keys(data)) vertices = union(src.(edges), dst.(edges)) graph = $GType{T, V}(undef, vertices) copyto!(graph, data) diff --git a/test/test_vertexoredgedatagraph.jl b/test/test_vertexoredgedatagraph.jl index a95cd17..d93e151 100644 --- a/test/test_vertexoredgedatagraph.jl +++ b/test/test_vertexoredgedatagraph.jl @@ -11,10 +11,13 @@ using NamedGraphs: NamedDiGraph, NamedEdge, NamedGraph, ordered_vertices, positi using Test: @test, @test_throws, @testset @testset "VertexDataGraph and EdgeDataGraph" begin - @testset "VertexDataGraph" begin + @testset "$GType basics" for GType in ( + VertexDataGraph{String, Int}, + VertexDataDiGraph{String, Int}, + ) @testset "undef constructor" begin - g = VertexDataGraph{String, Int}(undef, [1, 2, 3]) - @test g isa VertexDataGraph{String, Int} + g = GType(undef, [1, 2, 3]) + @test g isa GType @test nv(g) == 3 @test ne(g) == 0 @test has_vertex(g, 1) @@ -26,8 +29,8 @@ using Test: @test, @test_throws, @testset @testset "data constructor" begin data = Dictionary([1, 2, 3], ["V1", "V2", "V3"]) - g = VertexDataGraph(data) - @test g isa VertexDataGraph{String, Int} + g = GType(data) + @test g isa GType @test nv(g) == 3 @test isassigned(g, 1) @test isassigned(g, 2) @@ -35,12 +38,14 @@ using Test: @test, @test_throws, @testset @test g[1] == "V1" @test g[2] == "V2" @test g[3] == "V3" + + data = Dictionary([1.0, 2.0, 3.0], SubString.(["V1", "V2", "V3"])) + g = GType(data) + @test g isa GType end @testset "Graphs.jl interface" begin - g = VertexDataGraph{String, Int}(undef, [1, 2, 3]) - @test !is_directed(VertexDataGraph) - @test !is_directed(g) + g = GType(undef, [1, 2, 3]) @test vertextype(g) == Int @test edgetype(g) == NamedEdge{Int} @@ -55,13 +60,10 @@ using Test: @test, @test_throws, @testset rem_edge!(g, NamedEdge(1, 2)) @test ne(g) == 1 @test !has_edge(g, NamedEdge(1, 2)) - - @test !is_directed(VertexDataGraph) - @test !is_directed(g) end @testset "rem_vertex!" begin - g = VertexDataGraph{String, Int}(undef, [1, 2, 3]) + g = GType(undef, [1, 2, 3]) add_edge!(g, NamedEdge(1, 2)) rem_vertex!(g, 1) @test !has_vertex(g, 1) @@ -70,9 +72,9 @@ using Test: @test, @test_throws, @testset end @testset "DataGraphs interface" begin - g = VertexDataGraph{String, Int}(undef, [1, 2, 3]) + g = GType(undef, [1, 2, 3]) @test vertex_data_type(g) == String - @test vertex_data_type(VertexDataGraph{String, Int}) == String + @test vertex_data_type(GType) == String @test !isassigned(g, 1) @test !isassigned(g, 2) @test !isassigned(g, 3) @@ -81,7 +83,7 @@ using Test: @test, @test_throws, @testset end @testset "setindex! and getindex" begin - g = VertexDataGraph{String, Int}(undef, [1, 2, 3]) + g = GType(undef, [1, 2, 3]) g[1] = "V1" g[2] = "V2" g[3] = "V3" @@ -93,57 +95,55 @@ using Test: @test, @test_throws, @testset @test g[3] == "V3" end - for GType in (VertexDataGraph{String, Int}, VertexDataDiGraph{String, Int}) - @testset "NamedGraphs interface" begin - g = GType(undef, [1, 2, 3]) - @test position_graph(g) isa AbstractSimpleGraph{Int} - @test ordered_vertices(g) == [1, 2, 3] - @test keys(vertex_positions(g)) == vertices(g) - - g = add_edge(g, NamedEdge(1, 2)) - g[1] = "1" - - gs = similar_graph(g) - @test gs isa GType - @test has_vertex(gs, 1) - @test has_vertex(gs, 2) - @test has_vertex(gs, 3) - @test has_edge(gs, 1 => 2) - @test !isassigned(gs, 1) - - gs = similar_graph(g, vertices(g)) - @test vertices(gs) == vertices(g) - @test ne(gs) == 0 - @test !isassigned(gs, 1) - - gs = similar_graph(g, [1, 2, 4]) - @test has_vertex(gs, 1) - @test has_vertex(gs, 2) - @test has_vertex(gs, 4) - @test nv(gs) == 3 - @test ne(gs) == 0 - @test !isassigned(gs, 1) - - gs = similar_graph(g, Char) - @test vertex_data_type(gs) === Char - @test nv(gs) == 3 - @test ne(gs) == 1 - gs[1] = 'C' - @test gs[1] == 'C' - - gs = similar_graph(g, Float64, vertices(g)) - @test ne(gs) == 0 - - gs = similar_graph(GType, [1.0, 2.0]) - @test gs isa GType - @test has_vertex(gs, 1) - @test has_vertex(gs, 2) - @test ne(gs) == 0 - end + @testset "NamedGraphs interface" begin + g = GType(undef, [1, 2, 3]) + @test position_graph(g) isa AbstractSimpleGraph{Int} + @test ordered_vertices(g) == [1, 2, 3] + @test keys(vertex_positions(g)) == vertices(g) + + g = add_edge(g, NamedEdge(1, 2)) + g[1] = "1" + + gs = similar_graph(g) + @test gs isa GType + @test has_vertex(gs, 1) + @test has_vertex(gs, 2) + @test has_vertex(gs, 3) + @test has_edge(gs, 1 => 2) + @test !isassigned(gs, 1) + + gs = similar_graph(g, vertices(g)) + @test vertices(gs) == vertices(g) + @test ne(gs) == 0 + @test !isassigned(gs, 1) + + gs = similar_graph(g, [1, 2, 4]) + @test has_vertex(gs, 1) + @test has_vertex(gs, 2) + @test has_vertex(gs, 4) + @test nv(gs) == 3 + @test ne(gs) == 0 + @test !isassigned(gs, 1) + + gs = similar_graph(g, Char) + @test vertex_data_type(gs) === Char + @test nv(gs) == 3 + @test ne(gs) == 1 + gs[1] = 'C' + @test gs[1] == 'C' + + gs = similar_graph(g, Float64, vertices(g)) + @test ne(gs) == 0 + + gs = similar_graph(GType, [1.0, 2.0]) + @test gs isa GType + @test has_vertex(gs, 1) + @test has_vertex(gs, 2) + @test ne(gs) == 0 end @testset "Dictionaries interface" begin - g = VertexDataGraph{String, Int}(undef, [1, 2, 3]) + g = GType(undef, [1, 2, 3]) @test keytype(g) == Int @test valtype(g) == String @test keys(g) isa Indices @@ -171,70 +171,30 @@ using Test: @test, @test_throws, @testset end end - @testset "VertexDataDiGraph" begin + @testset "$GType basics" for GType in + (EdgeDataGraph{String, Int}, EdgeDataDiGraph{String, Int}) @testset "undef constructor" begin - g = VertexDataDiGraph{String, Int}(undef, [1, 2, 3]) - @test g isa VertexDataDiGraph{String, Int} + g = GType(undef, [1, 2, 3]) + @test g isa GType @test nv(g) == 3 @test ne(g) == 0 - @test has_vertex(g, 1) - @test !has_vertex(g, 4) end @testset "data constructor" begin - data = Dictionary([1, 2, 3], ["V1", "V2", "V3"]) - g = VertexDataDiGraph(data) - @test g isa VertexDataDiGraph{String, Int} - @test nv(g) == 3 - @test g[1] == "V1" - @test g[2] == "V2" - @test g[3] == "V3" - end - - @testset "directed graph" begin - g = VertexDataDiGraph{String, Int}(undef, [1, 2, 3]) - @test is_directed(VertexDataDiGraph) - @test is_directed(g) - end - - @testset "directed edges" begin - g = VertexDataDiGraph{String, Int}(undef, [1, 2, 3]) - add_edge!(g, NamedEdge(1, 2)) - @test has_edge(g, NamedEdge(1, 2)) - @test !has_edge(g, NamedEdge(2, 1)) - @test ne(g) == 1 - end - - @testset "DataGraphs interface" begin - g = VertexDataDiGraph{String, Int}(undef, [1, 2, 3]) - @test vertex_data_type(g) == String - @test vertex_data_type(VertexDataDiGraph{String, Int}) == String - @test vertextype(g) == Int - @test edgetype(g) == NamedEdge{Int} - end - - @testset "Dictionaries interface" begin - g = VertexDataDiGraph{String, Int}(undef, [1, 2, 3]) - @test keytype(g) == Int - @test valtype(g) == String - @test keys(g) isa Indices - @test length(g) == 3 - @test vertex_data(g) isa VertexDataView - end - end - - @testset "EdgeDataGraph" begin - @testset "undef constructor" begin - g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) - @test g isa EdgeDataGraph{String, Int} + data = Dictionary([NamedEdge(1, 2), NamedEdge(2, 3)], ["E12", "E23"]) + g = GType(data) + @test g isa GType @test nv(g) == 3 - @test ne(g) == 0 - end + @test ne(g) == 2 + @test isassigned(g, NamedEdge(1, 2)) + @test isassigned(g, NamedEdge(2, 3)) + @test g[NamedEdge(1, 2)] == "E12" + @test g[NamedEdge(2, 3)] == "E23" - @testset "data constructor" begin - data = Dictionary([NamedEdge(1, 2), NamedEdge(2, 3)], ["E12", "E23"]) - g = EdgeDataGraph(data) - @test g isa EdgeDataGraph{String, Int} + # With pairs + data = Dictionary([1.0 => 2.0, 2.0 => 3.0], ["E12", "E23"]) + g = GType(data) + @test g isa GType @test nv(g) == 3 @test ne(g) == 2 @test isassigned(g, NamedEdge(1, 2)) @@ -244,9 +204,7 @@ using Test: @test, @test_throws, @testset end @testset "Graphs.jl interface" begin - g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) - @test !is_directed(EdgeDataGraph) - @test !is_directed(g) + g = GType(undef, [1, 2, 3]) @test has_vertex(g, 1) @test has_vertex(g, 2) @test !has_vertex(g, 4) @@ -261,8 +219,8 @@ using Test: @test, @test_throws, @testset end @testset "DataGraphs interface" begin - g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) - @test edge_data_type(EdgeDataGraph{String, Int}) == String + g = GType(undef, [1, 2, 3]) + @test edge_data_type(GType) == String @test edge_data_type(g) == String @test !isassigned(g, 1) @test !isassigned(g, NamedEdge(1, 2)) @@ -270,58 +228,94 @@ using Test: @test, @test_throws, @testset @testset "setindex! and getindex" begin data = Dictionary([NamedEdge(1, 2), NamedEdge(2, 3)], ["E12", "E23"]) - g = EdgeDataGraph(data) + g = GType(data) g[NamedEdge(1, 2)] = "E12_updated" @test g[NamedEdge(1, 2)] == "E12_updated" @test g[NamedEdge(2, 3)] == "E23" end - @testset "undirected edge access" begin - data = Dictionary([NamedEdge(1, 2)], ["E12"]) - g = EdgeDataGraph(data) - @test g[NamedEdge(1, 2)] == "E12" - @test g[NamedEdge(2, 1)] == "E12" - @test g[1 => 2] == "E12" - @test g[2 => 1] == "E12" - end - @testset "rem_edge!" begin data = Dictionary([NamedEdge(1, 2), NamedEdge(2, 3)], ["E12", "E23"]) - g = EdgeDataGraph(data) + g = GType(data) rem_edge!(g, NamedEdge(1, 2)) @test !has_edge(g, NamedEdge(1, 2)) @test ne(g) == 1 end + @testset "rem_vertex!" begin + data = Dictionary([NamedEdge(1, 2), NamedEdge(2, 3)], ["E12", "E23"]) + g = GType(data) + rem_vertex!(g, 1) + @test !has_vertex(g, 1) + @test !has_edge(g, NamedEdge(1, 2)) + @test !isassigned(g, NamedEdge(1, 2)) + @test ne(g) == 1 + @test g[NamedEdge(2, 3)] == "E23" + end + @testset "NamedGraphs interface" begin - g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) - @test Set(collect(vertices(g))) == Set([1, 2, 3]) - @test position_graph(g) isa AbstractGraph + g = GType(Dictionary([1 => 2, 2 => 3], ["E12", "E23"])) + @test issetequal(vertices(g), [1, 2, 3]) + @test position_graph(g) isa AbstractSimpleGraph{Int} @test ordered_vertices(g) isa AbstractVector @test vertex_positions(g) isa AbstractDictionary + + gs = similar_graph(g) + @test gs isa GType + @test has_vertex(gs, 1) + @test has_vertex(gs, 2) + @test has_vertex(gs, 3) + @test has_edge(gs, 1 => 2) + @test !isassigned(gs, 1 => 2) + + gs = similar_graph(g, vertices(g)) + @test vertices(gs) == vertices(g) + @test ne(gs) == 0 + @test !isassigned(gs, 1 => 2) + + gs = similar_graph(g, [1, 2, 4]) + @test has_vertex(gs, 1) + @test has_vertex(gs, 2) + @test has_vertex(gs, 4) + @test nv(gs) == 3 + @test ne(gs) == 0 + @test !isassigned(gs, 1 => 2) + + gs = similar_graph(g, Char) + @test vertex_data_type(gs) === Char + @test nv(gs) == 3 + @test ne(gs) == 2 + gs[3 => 1] = 'C' + @test gs[3 => 1] == 'C' + @test ne(gs) == 3 + + gs = similar_graph(g, Float64, vertices(g)) + @test ne(gs) == 0 + + gs = similar_graph(GType, [1.0, 2.0]) + @test gs isa GType + @test has_vertex(gs, 1) + @test has_vertex(gs, 2) + @test ne(gs) == 0 end @testset "Dictionaries interface" begin - g = EdgeDataGraph(undef, [1, 2, 3]) - @test keytype(g) == NamedEdge{Int} - @test valtype(g) == Any - - g = EdgeDataGraph{String}(undef, [1, 2, 3]) + g = GType(undef, [1, 2, 3]) @test keytype(g) == NamedEdge{Int} @test valtype(g) == String @test edge_data(g) isa EdgeDataView - g = EdgeDataGraph{String, Float64}(undef, [1, 2, 3]) - @test keytype(g) == NamedEdge{Float64} + g = GType(undef, [1.0, 2.0, 3.0]) + @test keytype(g) == NamedEdge{Int} - g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) + g = GType(undef, [1, 2, 3]) insert!(g, 3 => 4, "E34") @test has_vertex(g, 4) @test has_edge(g, 3 => 4) @test isassigned(g, 3 => 4) @test g[3 => 4] == "E34" - g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) + g = GType(undef, [1, 2, 3]) insert!(g, 5 => 6, "E56") @test has_vertex(g, 5) @test has_vertex(g, 6) @@ -329,22 +323,22 @@ using Test: @test, @test_throws, @testset @test isassigned(g, 5 => 6) @test g[5 => 6] == "E56" - g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) + g = GType(undef, [1, 2, 3]) @test_throws IndexError insert!(g, 2 => 3, "E23") @test !has_edge(g, 2 => 3) - g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) + g = GType(undef, [1, 2, 3]) set!(g, 1 => 2, "E12") @test has_edge(g, 1 => 2) @test g[1 => 2] == "E12" - g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) + g = GType(undef, [1, 2, 3]) set!(g, 3 => 4, "E34") @test has_vertex(g, 4) @test has_edge(g, 3 => 4) @test g[3 => 4] == "E34" - g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) + g = GType(undef, [1, 2, 3]) set!(g, 4 => 5, "E45") @test has_vertex(g, 4) @test has_vertex(g, 5) @@ -353,7 +347,7 @@ using Test: @test, @test_throws, @testset set!(g, 4 => 5, "E45_again") @test g[4 => 5] == "E45_again" - g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) + g = GType(undef, [1, 2, 3]) g[2 => 3] = "E23" @test has_edge(g, 2 => 3) @test g[2 => 3] == "E23" @@ -365,45 +359,49 @@ using Test: @test, @test_throws, @testset end end - @testset "EdgeDataDiGraph" begin - @testset "undef constructor" begin - g = EdgeDataDiGraph{String, Int}(undef, [1, 2, 3]) - @test g isa EdgeDataDiGraph{String, Int} - @test nv(g) == 3 - @test ne(g) == 0 - end + @testset "(un)directed graph specific" begin + @testset "basics" begin + g = VertexDataGraph(undef, [1, 2, 3]) + @test !is_directed(VertexDataGraph) + @test !is_directed(g) - @testset "data constructor" begin - data = Dictionary([NamedEdge(1, 2), NamedEdge(2, 3)], ["E12", "E23"]) - g = EdgeDataDiGraph(data) - @test g isa EdgeDataDiGraph{String, Int} - @test nv(g) == 3 + g = VertexDataDiGraph(undef, [1, 2, 3]) + @test is_directed(VertexDataDiGraph) + @test is_directed(g) + + add_edge!(g, NamedEdge(1, 2)) + @test has_edge(g, NamedEdge(1, 2)) + @test !has_edge(g, NamedEdge(2, 1)) + @test ne(g) == 1 + + add_edge!(g, NamedEdge(2, 1)) + @test has_edge(g, NamedEdge(2, 1)) @test ne(g) == 2 - @test g[NamedEdge(1, 2)] == "E12" - @test g[NamedEdge(2, 3)] == "E23" - end - @testset "directed graph" begin - g = EdgeDataDiGraph{String, Int}(undef, [1, 2, 3]) - @test is_directed(EdgeDataDiGraph) + g = EdgeDataGraph(undef, [1, 2, 3]) + @test !is_directed(VertexDataGraph) + @test !is_directed(g) + + g = EdgeDataDiGraph(undef, [1, 2, 3]) + @test is_directed(VertexDataDiGraph) @test is_directed(g) end - @testset "directed edges" begin + @testset "undirected edge access" begin + data = Dictionary([NamedEdge(1, 2)], ["E12"]) + g = EdgeDataGraph(data) + @test g[NamedEdge(1, 2)] == "E12" + @test g[NamedEdge(2, 1)] == "E12" + @test g[1 => 2] == "E12" + @test g[2 => 1] == "E12" + end + + @testset "directed edge access" begin data = Dictionary([NamedEdge(1, 2)], ["E12"]) g = EdgeDataDiGraph(data) @test has_edge(g, NamedEdge(1, 2)) @test !has_edge(g, NamedEdge(2, 1)) @test g[NamedEdge(1, 2)] == "E12" end - - @testset "DataGraphs interface" begin - g = EdgeDataDiGraph{String, Int}(undef, [1, 2, 3]) - @test edge_data_type(EdgeDataDiGraph{String, Int}) == String - @test edge_data_type(g) == String - @test keytype(g) == NamedEdge{Int} - @test valtype(g) == String - @test edge_data(g) isa EdgeDataView - end end end From 9d7ff7b912a4ba7ce0cc29a3dd1839df40714679 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Tue, 19 May 2026 16:32:45 -0400 Subject: [PATCH 22/28] Add tests for `copy`; deabstract `==` method. --- src/abstractedgeorvertexdatagraph.jl | 8 +--- src/edgedatagraph.jl | 5 +++ src/vertexdatagraph.jl | 5 +++ test/test_vertexoredgedatagraph.jl | 56 +++++++++++++++++++++++++++- 4 files changed, 67 insertions(+), 7 deletions(-) diff --git a/src/abstractedgeorvertexdatagraph.jl b/src/abstractedgeorvertexdatagraph.jl index c9a220b..be0b56f 100644 --- a/src/abstractedgeorvertexdatagraph.jl +++ b/src/abstractedgeorvertexdatagraph.jl @@ -38,17 +38,13 @@ function NamedGraphs.similar_graph( return new_graph end -function Base.:(==)(dg1::AbstractVertexOrEdgeDataGraph, dg2::AbstractVertexOrEdgeDataGraph) - return underlying_graph(dg1) == underlying_graph(dg2) && - index_data(dg1) == index_data(dg2) -end - function Base.copy(graph::AbstractVertexOrEdgeDataGraph) graph_dst = similar_graph(graph) # Allow copies of graphs with undefined data. - copyto!(graph_dst, src, filter(key -> isassigned(graph, key), keys(graph))) + copyto!(graph_dst, graph, filter(key -> isassigned(graph, key), keys(graph))) return graph_dst end + function Base.copyto!(graph_dst::AbstractVertexOrEdgeDataGraph, src) copyto!(graph_dst, src, keys(src)) return graph_dst diff --git a/src/edgedatagraph.jl b/src/edgedatagraph.jl index 3f22f4b..0dbaf41 100644 --- a/src/edgedatagraph.jl +++ b/src/edgedatagraph.jl @@ -48,6 +48,11 @@ for GType in (:EdgeDataGraph, :EdgeDataDiGraph) copyto!(graph, data) return graph end + + function Base.:(==)(dg1::$GType, dg2::$GType) + return dg1.underlying_graph == dg2.underlying_graph && + dg1.edge_data == dg2.edge_data + end end end diff --git a/src/vertexdatagraph.jl b/src/vertexdatagraph.jl index 7b0be27..0f83af5 100644 --- a/src/vertexdatagraph.jl +++ b/src/vertexdatagraph.jl @@ -47,6 +47,11 @@ for GType in (:VertexDataGraph, :VertexDataDiGraph) cache = $GType{T, V}(undef, vertices) return copyto!(cache, data) end + + function Base.:(==)(dg1::$GType, dg2::$GType) + return dg1.underlying_graph == dg2.underlying_graph && + dg1.vertex_data == dg2.vertex_data + end end end diff --git a/test/test_vertexoredgedatagraph.jl b/test/test_vertexoredgedatagraph.jl index d93e151..b214a21 100644 --- a/test/test_vertexoredgedatagraph.jl +++ b/test/test_vertexoredgedatagraph.jl @@ -1,7 +1,8 @@ using DataGraphs: DataGraphs, EdgeDataDiGraph, EdgeDataGraph, EdgeDataView, VertexDataDiGraph, VertexDataGraph, VertexDataView, edge_data, edge_data_type, underlying_graph, vertex_data, vertex_data_type -using Dictionaries: AbstractDictionary, Dictionary, IndexError, Indices, set! +using Dictionaries: + AbstractDictionary, Dictionary, IndexError, Indices, isinsertable, issettable, set! using Graphs: AbstractGraph, AbstractSimpleGraph, add_edge!, add_vertex!, dst, edges, edgetype, has_edge, has_vertex, is_directed, ne, nv, rem_edge!, rem_vertex!, src, vertices @@ -44,6 +45,31 @@ using Test: @test, @test_throws, @testset @test g isa GType end + @testset "copy" begin + data = Dictionary([1, 2, 3], ["V1", "V2", "V3"]) + g = GType(data) + add_edge!(g, NamedEdge(1, 2)) + g_copy = copy(g) + + @test g_copy == g + @test g_copy !== g + + # Test we can copy a graph with undefined data. + g = GType(undef, [1, 2, 3]) + g[1] = "V1" + add_edge!(g, NamedEdge(2, 3)) + g_copy = copy(g) + + @test has_vertex(g_copy, 1) + @test has_vertex(g_copy, 2) + @test has_vertex(g_copy, 3) + @test has_edge(g_copy, 2 => 3) + @test isassigned(g_copy, 1) + @test g_copy[1] == "V1" + @test !isassigned(g_copy, 2) + @test !isassigned(g_copy, 3) + end + @testset "Graphs.jl interface" begin g = GType(undef, [1, 2, 3]) @test vertextype(g) == Int @@ -150,6 +176,9 @@ using Test: @test, @test_throws, @testset @test length(g) == 3 @test vertex_data(g) isa VertexDataView + @test issettable(g) + @test isinsertable(g) + insert!(g, 4, "V4") @test_throws IndexError insert!(g, 4, "V4_again") @test has_vertex(g, 4) @@ -203,6 +232,28 @@ using Test: @test, @test_throws, @testset @test g[NamedEdge(2, 3)] == "E23" end + @testset "copy" begin + data = Dictionary([1 => 2, 2 => 3], ["E12", "E23"]) + g = GType(data) + g_copy = copy(g) + + @test g_copy == g + @test g_copy !== g + + # Test we can copy a graph with undefined data. + g = GType(undef, [1, 2, 3]) + g[1 => 2] = "E12" + g_copy = copy(g) + + @test has_vertex(g_copy, 1) + @test has_vertex(g_copy, 2) + @test has_vertex(g_copy, 3) + @test has_edge(g_copy, 1 => 2) + @test isassigned(g_copy, 1 => 2) + @test g_copy[1 => 2] == "E12" + @test !isassigned(g_copy, 2 => 3) + end + @testset "Graphs.jl interface" begin g = GType(undef, [1, 2, 3]) @test has_vertex(g, 1) @@ -305,6 +356,9 @@ using Test: @test, @test_throws, @testset @test valtype(g) == String @test edge_data(g) isa EdgeDataView + @test issettable(g) + @test isinsertable(g) + g = GType(undef, [1.0, 2.0, 3.0]) @test keytype(g) == NamedEdge{Int} From fae58768890f1715a0d3b8b1f26e4e8f4945a6d0 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Tue, 19 May 2026 16:49:50 -0400 Subject: [PATCH 23/28] More tests and fixes. --- src/abstractedgeorvertexdatagraph.jl | 1 + test/test_vertexoredgedatagraph.jl | 51 +++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/abstractedgeorvertexdatagraph.jl b/src/abstractedgeorvertexdatagraph.jl index be0b56f..c48a8e5 100644 --- a/src/abstractedgeorvertexdatagraph.jl +++ b/src/abstractedgeorvertexdatagraph.jl @@ -155,6 +155,7 @@ function NamedGraphs.induced_subgraph_from_vertices( subvertices ) subnetwork = similar_graph(graph, subvertices) + add_edges!(subnetwork, subgraph_edges(graph, subvertices)) tensors = view(vertex_data(graph), Indices(subvertices)) copyto!(subnetwork, tensors) return subnetwork, subvertices diff --git a/test/test_vertexoredgedatagraph.jl b/test/test_vertexoredgedatagraph.jl index b214a21..778fad6 100644 --- a/test/test_vertexoredgedatagraph.jl +++ b/test/test_vertexoredgedatagraph.jl @@ -6,7 +6,7 @@ using Dictionaries: using Graphs: AbstractGraph, AbstractSimpleGraph, add_edge!, add_vertex!, dst, edges, edgetype, has_edge, has_vertex, is_directed, ne, nv, rem_edge!, rem_vertex!, src, vertices -using NamedGraphs.GraphsExtensions: add_edge, vertextype +using NamedGraphs.GraphsExtensions: add_edge, subgraph, vertextype using NamedGraphs: NamedDiGraph, NamedEdge, NamedGraph, ordered_vertices, position_graph, similar_graph, vertex_positions using Test: @test, @test_throws, @testset @@ -166,6 +166,20 @@ using Test: @test, @test_throws, @testset @test has_vertex(gs, 1) @test has_vertex(gs, 2) @test ne(gs) == 0 + + g = GType(Dictionary([1, 2, 3], ["V1", "V2", "V3"])) + add_edge!(g, NamedEdge(1, 2)) + add_edge!(g, NamedEdge(2, 3)) + sg = subgraph(g, [1, 2]) + @test sg isa GType + @test has_vertex(sg, 1) + @test has_vertex(sg, 2) + @test !has_vertex(sg, 3) + @test has_edge(sg, 1 => 2) + @test !has_edge(sg, 2 => 3) + @test sg[1] == "V1" + @test sg[2] == "V2" + @test !isassigned(sg, 3) end @testset "Dictionaries interface" begin @@ -198,6 +212,19 @@ using Test: @test, @test_throws, @testset @test g[5] == "V5_again" @test nv(g) == 5 end + + @testset "show" begin + g = GType(Dictionary([1, 2, 3], ["V1", "V2", "V3"])) + io = IOBuffer() + show(io, g) + str = String(take!(io)) + @test occursin("$GType", str) + @test occursin("V1", str) + @test occursin("V2", str) + @test occursin("V3", str) + @test !occursin("edge data", str) + @test occursin("vertex data", str) + end end @testset "$GType basics" for GType in @@ -348,6 +375,16 @@ using Test: @test, @test_throws, @testset @test has_vertex(gs, 1) @test has_vertex(gs, 2) @test ne(gs) == 0 + + g = GType(Dictionary([1 => 2, 2 => 3], ["E12", "E23"])) + sg = subgraph(g, [1, 2]) + @test sg isa GType + @test has_vertex(sg, 1) + @test has_vertex(sg, 2) + @test !has_vertex(sg, 3) + @test has_edge(sg, 1 => 2) + @test !has_edge(sg, 2 => 3) + @test sg[1 => 2] == "E12" end @testset "Dictionaries interface" begin @@ -411,6 +448,18 @@ using Test: @test, @test_throws, @testset g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) @test_throws IndexError edge_data(g)[2 => 3] = "E23" end + + @testset "show" begin + g = GType(Dictionary([1 => 2, 2 => 3], ["E12", "E23"])) + io = IOBuffer() + show(io, g) + str = String(take!(io)) + @test occursin("$GType", str) + @test occursin("edge data", str) + @test occursin("E12", str) + @test occursin("E23", str) + @test !occursin("vertex data", str) + end end @testset "(un)directed graph specific" begin From f3930fa7817f25557bf060eff13b0c7b9c0d0ba4 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Tue, 26 May 2026 17:57:07 -0400 Subject: [PATCH 24/28] Simplify `similar_graph` via new struct `VertexEdgeDataTypes`. --- src/abstractdatagraph.jl | 54 ++++++++++++++-------------- src/abstractedgeorvertexdatagraph.jl | 30 +++++----------- src/datagraph.jl | 2 +- src/edgedatagraph.jl | 4 +-- src/vertexdatagraph.jl | 4 +-- test/test_basics.jl | 13 +++---- 6 files changed, 48 insertions(+), 59 deletions(-) diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl index d1c87c6..a913660 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -10,6 +10,11 @@ using NamedGraphs: NamedGraphs, AbstractEdges, AbstractNamedEdge, AbstractNamedG similar_graph, subgraph_edges using SimpleTraits: SimpleTraits, @traitfn, Not +struct VertexEdgeDataTypes{VD, ED} + vertex_data_type::VD + edge_data_type::ED +end + abstract type AbstractDataGraph{V, VD, ED} <: AbstractNamedGraph{V} end vertex_data_type(::Type{<:AbstractGraph}) = Any @@ -145,21 +150,19 @@ end """ similar_graph(datagraph::AbstractDataGraph, D::Type) similar_graph(datagraph::AbstractDataGraph, D::Type, vertices) - similar_graph(datagraph::AbstractDataGraph, VD::Type, ED::Type) - similar_graph(datagraph::AbstractDataGraph, VD::Type, ED::Type, vertices) + similar_graph(datagraph::AbstractDataGraph, D::VertexEdgeDataTypes) + similar_graph(datagraph::AbstractDataGraph, D::VertexEdgeDataTypes, vertices) Create an uninitialized data graph, similar to the provided `datagraph`, but with vertices defined by `vertices` and a vertex and edge data type `D`. One may also provide separate -vertex and edge data types `VD` and `ED`. +vertex and edge data types `VD` and `ED` by using the wrapper `VertexEdgeDataTypes(VD, ED)`. If vertices are not provided, then the graph is constructed with the same vertices and edges as the input graph. """ -function NamedGraphs.similar_graph( - graph::AbstractDataGraph - ) - VD = vertex_data_type(graph) - ED = edge_data_type(graph) - return similar_graph(graph, VD, ED) + +NamedGraphs.similar_graph(graph::AbstractDataGraph, D::Type) = similar_datagraph(graph, D) +function NamedGraphs.similar_graph(graph::AbstractDataGraph, D::VertexEdgeDataTypes) + return similar_datagraph(graph, D) end function NamedGraphs.similar_graph( @@ -168,37 +171,36 @@ function NamedGraphs.similar_graph( ) VD = vertex_data_type(graph) ED = edge_data_type(graph) - return similar_graph(graph, VD, ED, vertices) -end -function NamedGraphs.similar_graph( - graph::AbstractDataGraph, - D::Type - ) - return similar_graph(graph, D, D) + + return similar_graph(graph, VertexEdgeDataTypes(VD, ED), vertices) end +# Base case(s) (overload these if fallback not wanted). function NamedGraphs.similar_graph( graph::AbstractDataGraph, D::Type, vertices ) - return similar_graph(graph, D, D, vertices) + return similar_datagraph(graph, D, D, vertices) end - function NamedGraphs.similar_graph( graph::AbstractDataGraph, - VD::Type, - ED::Type + D::VertexEdgeDataTypes, + vertices ) - new_graph = similar_graph(graph, VD, ED, vertices(graph)) + return similar_datagraph(graph, D.vertex_data_type, D.edge_data_type, vertices) +end + +# Internal implementation functions, +function similar_datagraph(graph::AbstractGraph, D) + new_graph = similar_graph(graph, D, vertices(graph)) add_edges!(new_graph, edges(graph)) return new_graph end -# Base case(s) (overload these if fallback not wanted). -@traitfn function NamedGraphs.similar_graph( - graph::AbstractDataGraph::(!IsDirected), +@traitfn function similar_datagraph( + graph::AbstractGraph::(!IsDirected), VD::Type, ED::Type, vertices @@ -207,8 +209,8 @@ end return DataGraph(underlying_graph; vertex_data_type = VD, edge_data_type = ED) end -@traitfn function NamedGraphs.similar_graph( - graph::AbstractDataGraph::IsDirected, +@traitfn function similar_datagraph( + graph::AbstractGraph::IsDirected, VD::Type, ED::Type, vertices diff --git a/src/abstractedgeorvertexdatagraph.jl b/src/abstractedgeorvertexdatagraph.jl index c48a8e5..f73a8c1 100644 --- a/src/abstractedgeorvertexdatagraph.jl +++ b/src/abstractedgeorvertexdatagraph.jl @@ -18,25 +18,6 @@ function NamedGraphs.similar_graph( ) return similar_graph(graph, valtype(graph), vertices) end -function NamedGraphs.similar_graph( - graph::AbstractVertexOrEdgeDataGraph, - T::Type, - vertices - ) - return similar_graph(graph, T, Vertices(vertices)) -end - -# For ambiguity resolution only. -function NamedGraphs.similar_graph( - graph::AbstractVertexOrEdgeDataGraph, - VD::Type, - ED::Type - ) - # No notion of both vertex and edge data, will go to `AbstractDataGraph` fallback. - new_graph = similar_graph(graph, VD, ED, vertices(graph)) # -> DataGraph - add_edges!(new_graph, edges(graph)) - return new_graph -end function Base.copy(graph::AbstractVertexOrEdgeDataGraph) graph_dst = similar_graph(graph) @@ -94,6 +75,11 @@ Base.keytype(::Type{<:AbstractVertexDataGraph{T, V}}) where {T, V} = V is_edge_assigned(::AbstractVertexDataGraph, _edge) = false +# For method ambiguity resolution, +# TODO: remove this method once generic `AbstractDataGraph` method is upgraded. +function set_index_data!(graph::AbstractVertexDataGraph, data, vertex::AbstractEdge) + return throw(MethodError(graph, (data, vertex))) +end # `setindex!` function set_index_data!(graph::AbstractVertexDataGraph, data, vertex) if !has_vertex(graph, vertex) @@ -145,7 +131,7 @@ end function NamedGraphs.similar_graph( ::AbstractVertexDataGraph, T::Type, - vertices::Vertices + vertices ) return similar_graph(VertexDataGraph{T}, vertices) end @@ -251,9 +237,9 @@ end function NamedGraphs.similar_graph( ::AbstractEdgeDataGraph, T::Type, - vertices::Vertices + vertices ) - return similar_graph(EdgeDataGraph{T}, collect(vertices)) + return similar_graph(EdgeDataGraph{T}, vertices) end function NamedGraphs.induced_subgraph_from_vertices( diff --git a/src/datagraph.jl b/src/datagraph.jl index 4a6b2fd..35c980f 100644 --- a/src/datagraph.jl +++ b/src/datagraph.jl @@ -54,7 +54,7 @@ edge_data_type(G::Type{<:DataGraph}) = eltype(fieldtype(G, :edge_data)) # Extras # Overwrite the `AbstractDataGraph` fallback (even though they coincide for `DataGraph`) -function NamedGraphs.similar_graph( +function similar_datagraph( graph::DataGraph, vertex_data_type::Type, edge_data_type::Type, diff --git a/src/edgedatagraph.jl b/src/edgedatagraph.jl index 0dbaf41..3252d8e 100644 --- a/src/edgedatagraph.jl +++ b/src/edgedatagraph.jl @@ -110,8 +110,8 @@ for GType in (:EdgeDataGraph, :EdgeDataDiGraph) return position_graph(graph.underlying_graph) end - function NamedGraphs.similar_graph(graph::$GType, T::Type, vertices::Vertices) - new_graph = $GType{T}(undef, collect(vertices)) + function NamedGraphs.similar_graph(graph::$GType, T::Type, vertices) + new_graph = $GType{T}(undef, vertices) return new_graph end diff --git a/src/vertexdatagraph.jl b/src/vertexdatagraph.jl index 0f83af5..e7fdd6d 100644 --- a/src/vertexdatagraph.jl +++ b/src/vertexdatagraph.jl @@ -104,8 +104,8 @@ for GType in (:VertexDataGraph, :VertexDataDiGraph) return position_graph(graph.underlying_graph) end - function NamedGraphs.similar_graph(graph::$GType, T::Type, vertices::Vertices) - new_graph = $GType{T}(undef, collect(vertices)) + function NamedGraphs.similar_graph(graph::$GType, T::Type, vertices) + new_graph = $GType{T}(undef, vertices) return new_graph end diff --git a/test/test_basics.jl b/test/test_basics.jl index 7d3fc77..0e54777 100644 --- a/test/test_basics.jl +++ b/test/test_basics.jl @@ -1,15 +1,16 @@ -using DataGraphs: DataGraphs, DataGraph, EdgeDataView, VertexDataView, edge_data, - edge_data_type, underlying_graph, vertex_data, vertex_data_type +using DataGraphs: DataGraphs, DataGraph, EdgeDataView, VertexDataView, VertexEdgeDataTypes, + edge_data, edge_data_type, underlying_graph, vertex_data, vertex_data_type using Dictionaries: Dictionaries, AbstractDictionary, AbstractIndices, Dictionary, IndexError, Indices, dictionary, unset! using Graphs: SimpleDiGraph, a_star, add_edge!, bfs_tree, connected_components, degree, dfs_tree, dijkstra_shortest_paths, dst, edges, edgetype, grid, has_edge, has_vertex, indegree, is_directed, ne, nv, outdegree, path_graph, src, steiner_tree, vertices using GraphsFlows: GraphsFlows -using NamedGraphs.GraphsExtensions: directed_graph, rename_vertices, subgraph, vertextype, ⊔ +using NamedGraphs.GraphsExtensions: + directed_graph, empty_graph, rename_vertices, subgraph, vertextype, ⊔ using NamedGraphs.NamedGraphGenerators: named_grid, named_path_graph using NamedGraphs.OrdinalIndexing: nd, rd, st, th -using NamedGraphs: NamedDiGraph, NamedEdge, NamedGraph, empty_graph, similar_graph +using NamedGraphs: NamedDiGraph, NamedEdge, NamedGraph, similar_graph using Test: @test, @test_broken, @testset @testset "DataGraphs.jl" begin @@ -626,13 +627,13 @@ using Test: @test, @test_broken, @testset @test has_vertex(g2, "v") @test ne(g2) == 0 - g2 = similar_graph(g, Float64, Int) + g2 = similar_graph(g, VertexEdgeDataTypes(Float64, Int)) @test has_vertex(g2, "a") @test has_edge(g2, "a" => "b") @test vertex_data_type(g2) === Float64 @test edge_data_type(g2) === Int - g2 = similar_graph(g, String, Tuple, [:a, :b]) + g2 = similar_graph(g, VertexEdgeDataTypes(String, Tuple), [:a, :b]) @test vertex_data_type(g2) === String @test edge_data_type(g2) === Tuple @test has_vertex(g2, :a) From 9ad7be1c1cb99d8c650a27bbe70a2f13f05af138 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Tue, 26 May 2026 17:59:01 -0400 Subject: [PATCH 25/28] Adjust NamedGraphs compat entry. --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index e8b9800..093a963 100644 --- a/Project.toml +++ b/Project.toml @@ -25,6 +25,6 @@ Dictionaries = "0.4" Graphs = "1" GraphsFlows = "0.1.1" ITensorVisualizationBase = "0.1" -NamedGraphs = "0.11.1" +NamedGraphs = "0.11.4" SimpleTraits = "0.9" julia = "1.10" From e14b618e2e1257fe7571d30a7fb71ef11c54c7a3 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Wed, 27 May 2026 16:08:15 -0400 Subject: [PATCH 26/28] Fix incorrect test. --- test/test_vertexoredgedatagraph.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_vertexoredgedatagraph.jl b/test/test_vertexoredgedatagraph.jl index 778fad6..f59f839 100644 --- a/test/test_vertexoredgedatagraph.jl +++ b/test/test_vertexoredgedatagraph.jl @@ -360,7 +360,7 @@ using Test: @test, @test_throws, @testset @test !isassigned(gs, 1 => 2) gs = similar_graph(g, Char) - @test vertex_data_type(gs) === Char + @test edge_data_type(gs) === Char @test nv(gs) == 3 @test ne(gs) == 2 gs[3 => 1] = 'C' From 71fbb0137dc84265fb5cc0b724a03946253ad00f Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Wed, 27 May 2026 16:08:44 -0400 Subject: [PATCH 27/28] Make `AbstractVertexOrEdgeDataType` a union type instead of abstract type. --- src/abstractedgeorvertexdatagraph.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/abstractedgeorvertexdatagraph.jl b/src/abstractedgeorvertexdatagraph.jl index f73a8c1..92216fd 100644 --- a/src/abstractedgeorvertexdatagraph.jl +++ b/src/abstractedgeorvertexdatagraph.jl @@ -2,7 +2,11 @@ using Dictionaries: Dictionaries, Indices, isinsertable, set! using Graphs: edges, edgetype, has_edge, has_vertex, rem_edge!, rem_vertex!, vertices using NamedGraphs: NamedGraphs, Vertices, similar_graph, subgraph_edges, to_graph_index -abstract type AbstractVertexOrEdgeDataGraph{T, V} <: AbstractDataGraph{V, T, T} end +abstract type AbstractVertexDataGraph{T, V} <: AbstractDataGraph{V, T, Nothing} end +abstract type AbstractEdgeDataGraph{T, V} <: AbstractDataGraph{V, Nothing, T} end + +const AbstractVertexOrEdgeDataGraph{T, V} = + Union{AbstractVertexDataGraph{T, V}, AbstractEdgeDataGraph{T, V}} Graphs.edgetype(graph::AbstractVertexOrEdgeDataGraph) = edgetype(typeof(graph)) @@ -69,8 +73,6 @@ end # ================================== vertex data graph =================================== # -abstract type AbstractVertexDataGraph{T, V} <: AbstractVertexOrEdgeDataGraph{T, V} end - Base.keytype(::Type{<:AbstractVertexDataGraph{T, V}}) where {T, V} = V is_edge_assigned(::AbstractVertexDataGraph, _edge) = false @@ -167,8 +169,6 @@ end # =================================== edge data graph ==================================== # -abstract type AbstractEdgeDataGraph{T, V} <: AbstractVertexOrEdgeDataGraph{T, V} end - Base.keytype(::Type{<:AbstractEdgeDataGraph{T, V}}) where {T, V} = NamedEdge{V} is_vertex_assigned(::AbstractEdgeDataGraph, _vertex) = false From b91cd717422755d2c98e2d938af329d7065a13ad Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Wed, 27 May 2026 16:20:50 -0400 Subject: [PATCH 28/28] Add [source] for NamedGraphs revision with bug-fix. --- Project.toml | 4 ++++ test/Project.toml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Project.toml b/Project.toml index 093a963..29d2126 100644 --- a/Project.toml +++ b/Project.toml @@ -16,6 +16,10 @@ SimpleTraits = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" GraphsFlows = "06909019-6f44-4949-96fc-b9d9aaa02889" ITensorVisualizationBase = "cd2553d2-8bef-4d93-8a38-c62f17d5ad23" +[sources.NamedGraphs] +rev = "jd/fix-forest-cover-edge-sequence" +url = "https://www.github.com/ITensor/NamedGraphs.jl" + [extensions] DataGraphsGraphsFlowsExt = "GraphsFlows" DataGraphsITensorVisualizationBaseExt = "ITensorVisualizationBase" diff --git a/test/Project.toml b/test/Project.toml index 3463736..d87cefc 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -14,6 +14,10 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [sources.DataGraphs] path = ".." +[sources.NamedGraphs] +rev = "jd/fix-forest-cover-edge-sequence" +url = "https://www.github.com/ITensor/NamedGraphs.jl" + [compat] Aqua = "0.8.11" DataGraphs = "0.4"