From a06ca9c8488a2210ff86c1779567fa46d1e7e786 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Tue, 2 Dec 2025 19:12:43 -0500 Subject: [PATCH 01/30] Refactor interface to make more abstract; have `AbstractDataGraph` subtype `AbstractNamedGraph` - Interface now no longer assumes vertex and edge date stored as mutable field. Instead, requires overloading setters and getters. - Subtyping `AbstractNamedGraph` cuts down on the method forwarding almost entirely. --- src/abstractdatagraph.jl | 220 ++++++++++++++++----------------------- src/datagraph.jl | 77 ++++++++++++-- test/test_basics.jl | 12 ++- 3 files changed, 165 insertions(+), 144 deletions(-) diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl index 46430b4..7e05d4f 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -1,8 +1,7 @@ -using Dictionaries: set!, unset! +using Dictionaries: set!, unset!, Indices using Graphs: Graphs, AbstractEdge, - AbstractGraph, IsDirected, add_edge!, a_star, @@ -11,29 +10,67 @@ using Graphs: nv, steiner_tree, vertices +using NamedGraphs: NamedGraphs, AbstractNamedGraph, AbstractNamedEdge using NamedGraphs.GraphsExtensions: GraphsExtensions, arrange_edge, incident_edges, is_edge_arranged, vertextype using NamedGraphs.SimilarType: similar_type using SimpleTraits: SimpleTraits, Not, @traitfn -abstract type AbstractDataGraph{V, VD, ED} <: AbstractGraph{V} end +abstract type AbstractDataGraph{V, VD, ED} <: AbstractNamedGraph{V} end # Minimal interface # TODO: Define for `AbstractGraph` as a `DataGraphInterface`. underlying_graph(::AbstractDataGraph) = not_implemented() -underlying_graph_type(::Type{<:AbstractDataGraph}) = not_implemented() -vertex_data(::AbstractDataGraph) = not_implemented() -vertex_data_eltype(::Type{<:AbstractDataGraph}) = not_implemented() -edge_data(::AbstractDataGraph) = not_implemented() -edge_data_eltype(::Type{<:AbstractDataGraph}) = not_implemented() + +has_vertex_data(::AbstractDataGraph, vertex) = not_implemented() +has_edge_data(::AbstractDataGraph, edge) = not_implemented() + +get_vertex_data(::AbstractDataGraph, vertex) = not_implemented() +get_edge_data(::AbstractDataGraph, edge) = not_implemented() + +set_vertex_data!(::AbstractDataGraph, data, vertex) = not_implemented() +set_edge_data!(::AbstractDataGraph, data, edge) = not_implemented() + +unset_vertex_data!(::AbstractDataGraph, data, vertex) = not_implemented() +unset_edge_data!(::AbstractDataGraph, data, edge) = not_implemented() + +GraphsExtensions.rename_vertices(f, ::AbstractDataGraph) = not_implemented() + +# Quasi-derived interface; only required if inference fails + +underlying_graph_type(T::Type{<:AbstractDataGraph}) = Base.promote_op(underlying_graph, T) +vertex_data_eltype(T::Type{<:AbstractDataGraph}) = eltype(Base.promote_op(vertex_data, T)) +edge_data_eltype(T::Type{<:AbstractDataGraph}) = eltype(Base.promote_op(edge_data, T)) # Derived interface + +Graphs.has_vertex(g::AbstractDataGraph, vertex) = has_vertex(underlying_graph(g), vertex) +Graphs.has_edge(g::AbstractDataGraph, edge) = has_edge(underlying_graph(g), edge) + +# For method ambiguities +Graphs.has_vertex(g::AbstractDataGraph, vertex) = has_vertex(underlying_graph(g), vertex) +Graphs.has_edge(g::AbstractDataGraph, edge::AbstractNamedEdge) = has_edge(underlying_graph(g), edge) + +vertex_data(dg::AbstractDataGraph) = map(v -> dg[v], assigned_vertices(dg)) +edge_data(dg::AbstractDataGraph) = map(v -> dg[v], assigned_edges(dg)) + +function assigned_vertices(graph::AbstractDataGraph) + return Indices(filter(v -> isassigned(graph, v), vertices(graph))) +end +function assigned_edges(graph::AbstractDataGraph) + return Indices(filter(e -> isassigned(graph, e), edges(graph))) +end + +function Graphs.edgetype(graph::AbstractDataGraph) + return Graphs.edgetype(underlying_graph(graph)) +end function Graphs.edgetype(graph_type::Type{<:AbstractDataGraph}) - return Graphs.edgetype(underlying_graph_type(graph_type)) + return Base.promote_op(edgetype, graph_type) end function Graphs.is_directed(graph_type::Type{<:AbstractDataGraph}) return Graphs.is_directed(underlying_graph_type(graph_type)) end + underlying_graph_type(graph::AbstractDataGraph) = typeof(underlying_graph(graph)) vertex_data_eltype(graph::AbstractDataGraph) = eltype(vertex_data(graph)) edge_data_eltype(graph::AbstractDataGraph) = eltype(edge_data(graph)) @@ -44,75 +81,25 @@ Base.zero(graph_type::Type{<:AbstractDataGraph}) = graph_type() function Graphs.vertices(graph::AbstractDataGraph) return Graphs.vertices(underlying_graph(graph)) end +function Graphs.add_vertex!(graph::AbstractDataGraph, vertex) + return Graphs.add_vertex!(underlying_graph(graph), vertex) +end -# Graphs overloads +# Simple NamedGraphs overloads for f in [ - :(Graphs.a_star), - :(Graphs.add_edge!), - :(Graphs.add_vertex!), - :(Graphs.adjacency_matrix), - :(Graphs.bellman_ford_shortest_paths), - :(Graphs.bfs_parents), - :(Graphs.bfs_tree), - :(Graphs.boruvka_mst), - :(Graphs.center), - :(Graphs.common_neighbors), - :(Graphs.connected_components), - :(Graphs.degree), - :(Graphs.degree_histogram), - :(Graphs.desopo_pape_shortest_paths), - :(Graphs.dfs_parents), - :(Graphs.dfs_tree), - :(Graphs.diameter), - :(Graphs.dijkstra_shortest_paths), - :(Graphs.eccentricity), - :(Graphs.edges), - :(Graphs.edgetype), - :(Graphs.eltype), - :(Graphs.enumerate_paths), - :(Graphs.floyd_warshall_shortest_paths), - :(Graphs.has_edge), - :(Graphs.has_path), - :(Graphs.has_vertex), - :(Graphs.inneighbors), - :(Graphs.is_connected), - :(Graphs.is_cyclic), - :(Graphs.is_directed), - :(Graphs.is_strongly_connected), - :(Graphs.is_weakly_connected), - :(Graphs.mincut), - :(Graphs.ne), - :(Graphs.neighbors), - :(Graphs.neighborhood), - :(Graphs.neighborhood_dists), - :(Graphs.johnson_shortest_paths), - :(Graphs.spfa_shortest_paths), - :(Graphs.yen_k_shortest_paths), - :(Graphs.kruskal_mst), - :(Graphs.prim_mst), - :(Graphs.nv), - :(Graphs.outneighbors), - :(Graphs.periphery), - :(Graphs.radius), - :(Graphs.steiner_tree), - :(Graphs.topological_sort_by_dfs), - :(Graphs.tree), - :(GraphsExtensions.boundary_edges), - :(GraphsExtensions.boundary_vertices), - :(GraphsExtensions.eccentricities), - :(GraphsExtensions.inner_boundary_vertices), - :(GraphsExtensions.mincut_partitions), - :(GraphsExtensions.outer_boundary_vertices), - :(GraphsExtensions.symrcm_perm), - :(GraphsExtensions.symrcm_permute), + :(NamedGraphs.ordered_vertices), + :(NamedGraphs.vertex_positions), + :(NamedGraphs.position_graph), ] @eval begin - function $f(graph::AbstractDataGraph, args...; kwargs...) - return $f(underlying_graph(graph), args...; kwargs...) - end + $f(graph::AbstractDataGraph) = $f(underlying_graph(graph)) end end +# These cannot be known abstractly. +GraphsExtensions.directed_graph_type(::AbstractDataGraph) = not_implemented() +GraphsExtensions.undirected_graph_type(::AbstractDataGraph) = not_implemented() + # Fix for ambiguity error with `AbstractGraph` version function Graphs.degree(graph::AbstractDataGraph, vertex::Integer) return Graphs.degree(underlying_graph(graph), vertex) @@ -248,37 +235,18 @@ function Base.union( return union(union(graph1, graph2; kwargs...), graph3, graphs_tail...; kwargs...) end -function GraphsExtensions.rename_vertices(f::Function, graph::AbstractDataGraph) - renamed_underlying_graph = GraphsExtensions.rename_vertices(f, underlying_graph(graph)) - # TODO: Base the ouput type on `typeof(graph)`, for example: - # convert_vertextype(eltype(renamed_vertices), typeof(graph))(renamed_underlying_graph) - renamed_graph = DataGraph( - renamed_underlying_graph; - vertex_data_eltype = vertex_data_eltype(graph), - edge_data_eltype = edge_data_eltype(graph), - ) - for v in keys(vertex_data(graph)) - renamed_graph[f(v)] = graph[v] - end - for e in keys(edge_data(graph)) - renamed_graph[GraphsExtensions.rename_vertices(f, e)] = graph[e] - end - return renamed_graph -end - function Graphs.rem_vertex!(graph::AbstractDataGraph, vertex) neighbor_edges = incident_edges(graph, vertex) - # unset!(vertex_data(graph), to_vertex(graph, vertex...)) - unset!(vertex_data(graph), vertex) + unset_vertex_data!(vertex_data(graph), vertex) for neighbor_edge in neighbor_edges - unset!(edge_data(graph), neighbor_edge) + unset_edge_data!(edge_data(graph), neighbor_edge) end Graphs.rem_vertex!(underlying_graph(graph), vertex) return graph end function Graphs.rem_edge!(graph::AbstractDataGraph, edge) - unset!(edge_data(graph), edge) + unset_edge_data!(edge_data(graph), edge) Graphs.rem_edge!(underlying_graph(graph), edge) return graph end @@ -302,23 +270,23 @@ function Graphs.dfs_tree(graph::AbstractDataGraph, s::Integer; kwargs...) end function map_vertex_data(f, graph::AbstractDataGraph; vertices = nothing) - graph′ = copy(graph) + new_graph = copy(graph) vs = isnothing(vertices) ? Graphs.vertices(graph) : vertices for v in vs - graph′[v] = f(graph[v]) + new_graph[v] = f(graph[v]) end - return graph′ + return new_graph end function map_edge_data(f, graph::AbstractDataGraph; edges = nothing) - graph′ = copy(graph) + new_graph = copy(graph) es = isnothing(edges) ? Graphs.edges(graph) : edges for e in es if isassigned(graph, e) - graph′[e] = f(graph[e]) + new_graph[e] = f(graph[e]) end end - return graph′ + return new_graph end function map_data(f, graph::AbstractDataGraph; vertices = nothing, edges = nothing) @@ -326,20 +294,27 @@ function map_data(f, graph::AbstractDataGraph; vertices = nothing, edges = nothi return map_edge_data(f, graph; edges) end -function Base.getindex(graph::AbstractDataGraph, vertex) - return vertex_data(graph)[vertex] -end +Base.getindex(graph::AbstractDataGraph, vertex) = get_vertex_data(graph, vertex) -function Base.get(graph::AbstractDataGraph, vertex, default) - return get(vertex_data(graph), vertex, default) +Base.get!(graph::AbstractGraph, key, default) = get!(() -> default, graph, key) +function Base.get!(default::Base.Callable, graph::AbstractGraph, key) + if isassigned(graph, key) + return graph[key] + else + return graph[key] = default() + end end - -function Base.get!(graph::AbstractDataGraph, vertex, default) - return get!(vertex_data(graph), vertex, default) +Base.get(graph::AbstractGraph, key, default) = get(() -> default, graph, key) +function Base.get(default::Base.Callable, graph::AbstractGraph, key) + if isassigned(graph, key) + return graph[key] + else + return default() + end end function Base.getindex(graph::AbstractDataGraph, edge::AbstractEdge) - data = edge_data(graph)[arrange_edge(graph, edge)] + data = get_edge_data(graph, arrange_edge(graph, edge)) return reverse_data_direction(graph, edge, data) end @@ -348,50 +323,29 @@ function Base.getindex(graph::AbstractDataGraph, edge::Pair) return graph[edgetype(graph)(edge)] end -function Base.get(graph::AbstractDataGraph, edge::AbstractEdge, default) - data = get(edge_data(graph), arrange_edge(graph, edge), default) - return reverse_data_direction(graph, edge, data) -end - -function Base.get(graph::AbstractDataGraph, edge::Pair, default) - return get(graph, edgetype(graph)(edge), default) -end - -function Base.get!(graph::AbstractDataGraph, edge::AbstractEdge, default) - data = get!(edge_data(graph), arrange_edge(graph, edge), default) - return reverse_data_direction(graph, edge, data) -end - -function Base.get!(graph::AbstractDataGraph, edge::Pair, default) - return get!(graph, edgetype(graph)(edge), default) -end - # Support syntax `g[1, 2] = g[(1, 2)]` function Base.getindex(graph::AbstractDataGraph, i1, i2, i...) return graph[(i1, i2, i...)] end -function Base.isassigned(graph::AbstractDataGraph, vertex) - return isassigned(vertex_data(graph), vertex) -end +Base.isassigned(graph::AbstractDataGraph, vertex) = has_vertex_data(graph, vertex) function Base.isassigned(graph::AbstractDataGraph, edge::AbstractEdge) - return isassigned(edge_data(graph), arrange_edge(graph, edge)) + return has_edge_data(graph, arrange_edge(graph, edge)) end - function Base.isassigned(graph::AbstractDataGraph, edge::Pair) return isassigned(graph, edgetype(graph)(edge)) end function Base.setindex!(graph::AbstractDataGraph, data, vertex) - set!(vertex_data(graph), vertex, data) + set_vertex_data!(graph, data, vertex) return graph end function Base.setindex!(graph::AbstractDataGraph, data, edge::AbstractEdge) arranged_edge = arrange_edge(graph, edge) arranged_data = reverse_data_direction(graph, edge, data) - set!(edge_data(graph), arranged_edge, arranged_data) + set_edge_data!(graph, arranged_data, arranged_edge) return graph end diff --git a/src/datagraph.jl b/src/datagraph.jl index 6317224..29ba363 100644 --- a/src/datagraph.jl +++ b/src/datagraph.jl @@ -1,7 +1,14 @@ using Dictionaries: Dictionary -using Graphs: Graphs, edgetype +using Graphs: Graphs, edgetype, has_edge, has_vertex using Graphs.SimpleGraphs: SimpleGraph -using NamedGraphs.GraphsExtensions: convert_vertextype, directed_graph, vertextype +using NamedGraphs: GenericNamedGraph +using NamedGraphs.GraphsExtensions: + convert_vertextype, + directed_graph, + vertextype, + directed_graph_type, + graph_from_vertices, + rename_vertices # TODO: define VertexDataGraph, a graph with only data on the # vertices, and EdgeDataGraph, a graph with only data on the edges. @@ -26,15 +33,69 @@ struct DataGraph{V, VD, ED, G <: AbstractGraph, E <: AbstractEdge} <: AbstractDa ) end end + +# Interface +underlying_graph(graph::DataGraph) = getfield(graph, :underlying_graph) + +has_vertex_data(dg::DataGraph, vertex) = haskey(dg.vertex_data, vertex) +has_edge_data(dg::DataGraph, edge) = haskey(dg.edge_data, edge) + +get_vertex_data(dg::DataGraph, vertex) = dg.vertex_data[vertex] +get_edge_data(dg::DataGraph, edge) = dg.edge_data[edge] + +function set_vertex_data!(dg::DataGraph, data, vertex) + set!(dg.vertex_data, vertex, data) + return dg +end +function set_edge_data!(dg::DataGraph, data, edge) + set!(dg.edge_data, edge, data) + return dg +end + +function unset_vertex_data!(dg::DataGraph, vertex) + unset!(dg.vertex_data, vertex) + return dg +end +function unset_edge_data!(dg::DataGraph, edge) + unset!(dg.edge_data, edge) + return dg +end + underlying_graph_type(G::Type{<:DataGraph}) = fieldtype(G, :underlying_graph) vertex_data_eltype(G::Type{<:DataGraph}) = eltype(fieldtype(G, :vertex_data)) edge_data_eltype(G::Type{<:DataGraph}) = eltype(fieldtype(G, :edge_data)) -underlying_graph(graph::DataGraph) = getfield(graph, :underlying_graph) -vertex_data(graph::DataGraph) = getfield(graph, :vertex_data) -edge_data(graph::DataGraph) = getfield(graph, :edge_data) -# TODO: Is this needed? Maybe define a generic `AbstractDataGraph` version. -Graphs.is_directed(G::Type{<:DataGraph}) = Graphs.is_directed(underlying_graph_type(G)) +# Extras + +Graphs.edgetype(T::Type{<:DataGraph}) = keytype(fieldtype(T, :edge_data)) + +function GraphsExtensions.graph_from_vertices(T::Type{<:DataGraph}, vertices) + return T(graph_from_vertices(underlying_graph_type(T), vertices)) +end + +function GraphsExtensions.rename_vertices(f::Function, graph::DataGraph) + renamed_underlying_graph = GraphsExtensions.rename_vertices(f, underlying_graph(graph)) + + renamed_graph = DataGraph( + renamed_underlying_graph; + vertex_data_eltype = vertex_data_eltype(graph), + edge_data_eltype = edge_data_eltype(graph), + ) + + for v in vertices(graph) + if isassigned(graph, v) + renamed_graph[f(v)] = graph[v] + end + end + + for e in edges(graph) + if isassigned(graph, e) + renamed_graph[rename_vertices(f, e)] = graph[e] + end + end + + return renamed_graph +end # TODO: Implement in terms of `set_underlying_graph`, `set_vertex_data`, etc. # TODO: Use `https://github.com/JuliaObjects/Accessors.jl`? @@ -94,7 +155,7 @@ function GraphsExtensions.directed_graph_type(graph_type::Type{<:DataGraph}) return DataGraph{ vertextype(graph_type), vertex_data_eltype(graph_type), - edgetype(graph_type), + edge_data_eltype(graph_type), directed_graph_type(underlying_graph_type(graph_type)), edgetype(graph_type), } diff --git a/test/test_basics.jl b/test/test_basics.jl index 6270d6c..a1173fc 100644 --- a/test/test_basics.jl +++ b/test/test_basics.jl @@ -1,5 +1,11 @@ using DataGraphs: - DataGraphs, DataGraph, edge_data, edge_data_eltype, vertex_data, vertex_data_eltype + DataGraphs, + DataGraph, + edge_data, + edge_data_eltype, + vertex_data, + vertex_data_eltype, + underlying_graph using Dictionaries: AbstractIndices, Dictionary, Indices, dictionary using Graphs: add_edge!, @@ -392,7 +398,7 @@ using Test: @test, @test_broken, @testset (3, 1) => (4, 1), (4, 1) => (4, 2), ] - @test t isa NamedDiGraph{Tuple{Int, Int}} + @test underlying_graph(t) isa NamedDiGraph{Tuple{Int, Int}} @test nv(t) == nv(g) @test ne(t) == nv(g) - 1 @test all(e -> has_edge(t, e), es) @@ -407,7 +413,7 @@ using Test: @test, @test_broken, @testset (3, 2) => (2, 2), (2, 2) => (1, 2), ] - @test t isa NamedDiGraph{Tuple{Int, Int}} + @test underlying_graph(t) isa NamedDiGraph{Tuple{Int, Int}} @test nv(t) == nv(g) @test ne(t) == nv(g) - 1 @test all(e -> has_edge(t, e), es) From c9eaeb0eb72a5c35d41da4ed4600beb2b95d3852 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Wed, 3 Dec 2025 11:41:05 -0500 Subject: [PATCH 02/30] Fix method overwrites --- ext/DataGraphsNamedGraphsExt/DataGraphsNamedGraphsExt.jl | 8 -------- src/abstractdatagraph.jl | 3 --- 2 files changed, 11 deletions(-) diff --git a/ext/DataGraphsNamedGraphsExt/DataGraphsNamedGraphsExt.jl b/ext/DataGraphsNamedGraphsExt/DataGraphsNamedGraphsExt.jl index 8595bc3..0543ff1 100644 --- a/ext/DataGraphsNamedGraphsExt/DataGraphsNamedGraphsExt.jl +++ b/ext/DataGraphsNamedGraphsExt/DataGraphsNamedGraphsExt.jl @@ -4,14 +4,6 @@ using NamedGraphs: NamedGraphs, AbstractNamedGraph DataGraphs.is_underlying_graph(::Type{<:AbstractNamedGraph}) = true -for f in [:(NamedGraphs.position_graph), :(NamedGraphs.vertex_positions)] - @eval begin - function $f(graph::AbstractDataGraph) - return $f(underlying_graph(graph)) - end - end -end - using Graphs: edgetype, vertices using NamedGraphs.OrdinalIndexing: OrdinalSuffixedInteger # TODO: Define through some intermediate `to_vertex` function diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl index 7e05d4f..4ff3403 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -46,9 +46,6 @@ edge_data_eltype(T::Type{<:AbstractDataGraph}) = eltype(Base.promote_op(edge_dat Graphs.has_vertex(g::AbstractDataGraph, vertex) = has_vertex(underlying_graph(g), vertex) Graphs.has_edge(g::AbstractDataGraph, edge) = has_edge(underlying_graph(g), edge) - -# For method ambiguities -Graphs.has_vertex(g::AbstractDataGraph, vertex) = has_vertex(underlying_graph(g), vertex) Graphs.has_edge(g::AbstractDataGraph, edge::AbstractNamedEdge) = has_edge(underlying_graph(g), edge) vertex_data(dg::AbstractDataGraph) = map(v -> dg[v], assigned_vertices(dg)) From 692b7b657c203562ff3ce6d005bdaf0e7c8dd8da Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Wed, 3 Dec 2025 11:47:46 -0500 Subject: [PATCH 03/30] Remove `NamedGraphs` ext and move code to core module --- .../DataGraphsNamedGraphsExt.jl | 32 ------------------- src/DataGraphs.jl | 3 -- src/abstractdatagraph.jl | 30 ++++++++++++++++- 3 files changed, 29 insertions(+), 36 deletions(-) delete mode 100644 ext/DataGraphsNamedGraphsExt/DataGraphsNamedGraphsExt.jl diff --git a/ext/DataGraphsNamedGraphsExt/DataGraphsNamedGraphsExt.jl b/ext/DataGraphsNamedGraphsExt/DataGraphsNamedGraphsExt.jl deleted file mode 100644 index 0543ff1..0000000 --- a/ext/DataGraphsNamedGraphsExt/DataGraphsNamedGraphsExt.jl +++ /dev/null @@ -1,32 +0,0 @@ -module DataGraphsNamedGraphsExt -using DataGraphs: DataGraphs, AbstractDataGraph, underlying_graph -using NamedGraphs: NamedGraphs, AbstractNamedGraph - -DataGraphs.is_underlying_graph(::Type{<:AbstractNamedGraph}) = true - -using Graphs: edgetype, vertices -using NamedGraphs.OrdinalIndexing: OrdinalSuffixedInteger -# TODO: Define through some intermediate `to_vertex` function -# (analagous to Julia's `to_indices`) instead of through -# overloading `Base.getindex`. -function Base.getindex(graph::AbstractDataGraph, vertex::OrdinalSuffixedInteger) - return graph[vertices(graph)[vertex]] -end -function Base.getindex( - graph::AbstractDataGraph, edge::Pair{<:OrdinalSuffixedInteger, <:OrdinalSuffixedInteger} - ) - return graph[edgetype(graph)(vertices(graph)[edge[1]], vertices(graph)[edge[2]])] -end -function Base.setindex!(graph::AbstractDataGraph, value, vertex::OrdinalSuffixedInteger) - graph[vertices(graph)[vertex]] = value - return graph -end -function Base.setindex!( - graph::AbstractDataGraph, - value, - edge::Pair{<:OrdinalSuffixedInteger, <:OrdinalSuffixedInteger}, - ) - graph[edgetype(graph)(vertices(graph)[edge[1]], vertices(graph)[edge[2]])] = value - return graph -end -end diff --git a/src/DataGraphs.jl b/src/DataGraphs.jl index 7569491..d009b5d 100644 --- a/src/DataGraphs.jl +++ b/src/DataGraphs.jl @@ -4,9 +4,6 @@ include("utils.jl") include("traits/isunderlyinggraph.jl") include("abstractdatagraph.jl") include("datagraph.jl") -# TODO: Turn into a weak dependency once `GraphsExtensions` -# is split off from `NamedGraphs`. -include("../ext/DataGraphsNamedGraphsExt/DataGraphsNamedGraphsExt.jl") export AbstractDataGraph, DataGraph diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl index 4ff3403..ccafb69 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -14,8 +14,11 @@ using NamedGraphs: NamedGraphs, AbstractNamedGraph, AbstractNamedEdge using NamedGraphs.GraphsExtensions: GraphsExtensions, arrange_edge, incident_edges, is_edge_arranged, vertextype using NamedGraphs.SimilarType: similar_type +using NamedGraphs.OrdinalIndexing: OrdinalSuffixedInteger using SimpleTraits: SimpleTraits, Not, @traitfn +is_underlying_graph(::Type{<:AbstractNamedGraph}) = true + abstract type AbstractDataGraph{V, VD, ED} <: AbstractNamedGraph{V} end # Minimal interface @@ -291,7 +294,6 @@ function map_data(f, graph::AbstractDataGraph; vertices = nothing, edges = nothi return map_edge_data(f, graph; edges) end -Base.getindex(graph::AbstractDataGraph, vertex) = get_vertex_data(graph, vertex) Base.get!(graph::AbstractGraph, key, default) = get!(() -> default, graph, key) function Base.get!(default::Base.Callable, graph::AbstractGraph, key) @@ -310,6 +312,18 @@ function Base.get(default::Base.Callable, graph::AbstractGraph, key) end end +Base.getindex(graph::AbstractDataGraph, vertex) = get_vertex_data(graph, vertex) + +function Base.getindex(graph::AbstractDataGraph, vertex::OrdinalSuffixedInteger) + return graph[vertices(graph)[vertex]] +end + +function Base.getindex( + graph::AbstractDataGraph, edge::Pair{<:OrdinalSuffixedInteger, <:OrdinalSuffixedInteger} + ) + return graph[edgetype(graph)(vertices(graph)[edge[1]], vertices(graph)[edge[2]])] +end + function Base.getindex(graph::AbstractDataGraph, edge::AbstractEdge) data = get_edge_data(graph, arrange_edge(graph, edge)) return reverse_data_direction(graph, edge, data) @@ -357,6 +371,20 @@ function Base.setindex!(graph::AbstractDataGraph, x, i1, i2, i...) return graph end +function Base.setindex!(graph::AbstractDataGraph, value, vertex::OrdinalSuffixedInteger) + graph[vertices(graph)[vertex]] = value + return graph +end + +function Base.setindex!( + graph::AbstractDataGraph, + value, + edge::Pair{<:OrdinalSuffixedInteger, <:OrdinalSuffixedInteger}, + ) + graph[edgetype(graph)(vertices(graph)[edge[1]], vertices(graph)[edge[2]])] = value + return graph +end + function induced_subgraph_datagraph(graph::AbstractDataGraph, subvertices) underlying_subgraph, vlist = Graphs.induced_subgraph(underlying_graph(graph), subvertices) subgraph = similar_type(graph)(underlying_subgraph) From accfdcb05904dcda75e0f2f6bacbb165be2e16c2 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Thu, 4 Dec 2025 13:18:18 -0500 Subject: [PATCH 04/30] Make `rename_vertices` generic by making use of `similar_graph` from `NamedGraphs` --- src/abstractdatagraph.jl | 31 ++++++++++++++++++++++++++++--- src/datagraph.jl | 34 ++++------------------------------ 2 files changed, 32 insertions(+), 33 deletions(-) diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl index ccafb69..0978a33 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -12,7 +12,12 @@ using Graphs: vertices using NamedGraphs: NamedGraphs, AbstractNamedGraph, AbstractNamedEdge using NamedGraphs.GraphsExtensions: - GraphsExtensions, arrange_edge, incident_edges, is_edge_arranged, vertextype + GraphsExtensions, + arrange_edge, + incident_edges, + is_edge_arranged, + vertextype, + similar_graph using NamedGraphs.SimilarType: similar_type using NamedGraphs.OrdinalIndexing: OrdinalSuffixedInteger using SimpleTraits: SimpleTraits, Not, @traitfn @@ -37,8 +42,6 @@ set_edge_data!(::AbstractDataGraph, data, edge) = not_implemented() unset_vertex_data!(::AbstractDataGraph, data, vertex) = not_implemented() unset_edge_data!(::AbstractDataGraph, data, edge) = not_implemented() -GraphsExtensions.rename_vertices(f, ::AbstractDataGraph) = not_implemented() - # Quasi-derived interface; only required if inference fails underlying_graph_type(T::Type{<:AbstractDataGraph}) = Base.promote_op(underlying_graph, T) @@ -169,6 +172,28 @@ end return digraph end +function GraphsExtensions.rename_vertices(f::Function, graph::AbstractDataGraph) + + # Use the two-argument `similar_graph` method so the new graph has correct vertex type + renamed_graph = similar_graph(graph, map(f, vertices(graph))) + + for vertex in vertices(graph) + if isassigned(graph, vertex) + renamed_graph[f(vertex)] = graph[vertex] + end + end + + for edge in edges(graph) + renamed_edge = rename_vertices(f, edge) + add_edge!(renamed_graph, renamed_edge) + if isassigned(graph, edge) + renamed_graph[renamed_edge] = graph[edge] + end + end + + return renamed_graph +end + function Base.reverse(graph::AbstractDataGraph) reversed_graph = typeof(graph)(reverse(underlying_graph(graph))) for v in vertices(graph) diff --git a/src/datagraph.jl b/src/datagraph.jl index 29ba363..a4622d5 100644 --- a/src/datagraph.jl +++ b/src/datagraph.jl @@ -7,7 +7,7 @@ using NamedGraphs.GraphsExtensions: directed_graph, vertextype, directed_graph_type, - graph_from_vertices, + similar_graph, rename_vertices # TODO: define VertexDataGraph, a graph with only data on the @@ -69,36 +69,11 @@ edge_data_eltype(G::Type{<:DataGraph}) = eltype(fieldtype(G, :edge_data)) Graphs.edgetype(T::Type{<:DataGraph}) = keytype(fieldtype(T, :edge_data)) -function GraphsExtensions.graph_from_vertices(T::Type{<:DataGraph}, vertices) - return T(graph_from_vertices(underlying_graph_type(T), vertices)) +function GraphsExtensions.similar_graph(T::Type{<:DataGraph}) + similar_underlying_graph = similar_graph(underlying_graph_type(T)) + return T(similar_underlying_graph) end -function GraphsExtensions.rename_vertices(f::Function, graph::DataGraph) - renamed_underlying_graph = GraphsExtensions.rename_vertices(f, underlying_graph(graph)) - - renamed_graph = DataGraph( - renamed_underlying_graph; - vertex_data_eltype = vertex_data_eltype(graph), - edge_data_eltype = edge_data_eltype(graph), - ) - - for v in vertices(graph) - if isassigned(graph, v) - renamed_graph[f(v)] = graph[v] - end - end - - for e in edges(graph) - if isassigned(graph, e) - renamed_graph[rename_vertices(f, e)] = graph[e] - end - end - - return renamed_graph -end - -# TODO: Implement in terms of `set_underlying_graph`, `set_vertex_data`, etc. -# TODO: Use `https://github.com/JuliaObjects/Accessors.jl`? function Base.copy(graph::DataGraph) # Need to manually copy the keys of Dictionaries, see: # https://github.com/andyferris/Dictionaries.jl/issues/98 @@ -150,7 +125,6 @@ function GraphsExtensions.convert_vertextype(vertextype::Type, graph::DataGraph) return DataGraph{vertextype}(graph) end -# TODO: implement generic version in terms of `set_underlying_graph_type`. function GraphsExtensions.directed_graph_type(graph_type::Type{<:DataGraph}) return DataGraph{ vertextype(graph_type), From b282355cba605d01aa704438ec11c68594473eb8 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Thu, 4 Dec 2025 15:27:04 -0500 Subject: [PATCH 05/30] Get `vertex_data_eltype` and `edge_data_eltype` directly from type params of abstract type. --- src/abstractdatagraph.jl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl index 0978a33..edcf712 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -26,6 +26,9 @@ is_underlying_graph(::Type{<:AbstractNamedGraph}) = true abstract type AbstractDataGraph{V, VD, ED} <: AbstractNamedGraph{V} end +vertex_data_eltype(::Type{<:AbstractDataGraph{V, VD, ED}}) where {V, VD, ED} = VD +edge_data_eltype(::Type{<:AbstractDataGraph{V, VD, ED}}) where {V, VD, ED} = ED + # Minimal interface # TODO: Define for `AbstractGraph` as a `DataGraphInterface`. underlying_graph(::AbstractDataGraph) = not_implemented() @@ -45,8 +48,6 @@ unset_edge_data!(::AbstractDataGraph, data, edge) = not_implemented() # Quasi-derived interface; only required if inference fails underlying_graph_type(T::Type{<:AbstractDataGraph}) = Base.promote_op(underlying_graph, T) -vertex_data_eltype(T::Type{<:AbstractDataGraph}) = eltype(Base.promote_op(vertex_data, T)) -edge_data_eltype(T::Type{<:AbstractDataGraph}) = eltype(Base.promote_op(edge_data, T)) # Derived interface @@ -75,8 +76,8 @@ function Graphs.is_directed(graph_type::Type{<:AbstractDataGraph}) end underlying_graph_type(graph::AbstractDataGraph) = typeof(underlying_graph(graph)) -vertex_data_eltype(graph::AbstractDataGraph) = eltype(vertex_data(graph)) -edge_data_eltype(graph::AbstractDataGraph) = eltype(edge_data(graph)) +vertex_data_eltype(graph::AbstractDataGraph) = vertex_data_eltype(typeof(graph)) +edge_data_eltype(graph::AbstractDataGraph) = edge_data_eltype(typeof(graph)) Base.zero(graph_type::Type{<:AbstractDataGraph}) = graph_type() From 8e85d6b6b92d22aeac449686f153d50e7ea20df6 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Thu, 4 Dec 2025 15:29:01 -0500 Subject: [PATCH 06/30] Add `Vertex/EdgeDataView` wrappers that lazily act like Dictionaries --- src/DataGraphs.jl | 1 + src/abstractdatagraph.jl | 4 ++-- src/dataview.jl | 44 +++++++++++++++++++++++++++++++++++ test/test_basics.jl | 50 ++++++++++++++++++++++++++++++++++++---- 4 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 src/dataview.jl diff --git a/src/DataGraphs.jl b/src/DataGraphs.jl index d009b5d..dc3a60b 100644 --- a/src/DataGraphs.jl +++ b/src/DataGraphs.jl @@ -2,6 +2,7 @@ module DataGraphs include("utils.jl") include("traits/isunderlyinggraph.jl") +include("dataview.jl") include("abstractdatagraph.jl") include("datagraph.jl") diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl index edcf712..0aebb1a 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -55,8 +55,8 @@ Graphs.has_vertex(g::AbstractDataGraph, vertex) = has_vertex(underlying_graph(g) Graphs.has_edge(g::AbstractDataGraph, edge) = has_edge(underlying_graph(g), edge) Graphs.has_edge(g::AbstractDataGraph, edge::AbstractNamedEdge) = has_edge(underlying_graph(g), edge) -vertex_data(dg::AbstractDataGraph) = map(v -> dg[v], assigned_vertices(dg)) -edge_data(dg::AbstractDataGraph) = map(v -> dg[v], assigned_edges(dg)) +vertex_data(dg::AbstractDataGraph) = VertexDataView(dg) +edge_data(dg::AbstractDataGraph) = EdgeDataView(dg) function assigned_vertices(graph::AbstractDataGraph) return Indices(filter(v -> isassigned(graph, v), vertices(graph))) diff --git a/src/dataview.jl b/src/dataview.jl new file mode 100644 index 0000000..0850f6c --- /dev/null +++ b/src/dataview.jl @@ -0,0 +1,44 @@ +using Dictionaries: Dictionaries, AbstractDictionary, gettokenvalue + +struct VertexDataView{V, VD, G <: AbstractGraph{V}} <: AbstractDictionary{V, VD} + graph::G + function VertexDataView(graph) + V = vertextype(graph) + VD = vertex_data_eltype(graph) + G = typeof(graph) + return new{V, VD, G}(graph) + end +end + +Base.keys(view::VertexDataView) = assigned_vertices(view.graph) + +struct EdgeDataView{E, ED, G <: AbstractGraph} <: AbstractDictionary{E, ED} + graph::G + function EdgeDataView(graph) + E = edgetype(graph) + ED = edge_data_eltype(graph) + G = typeof(graph) + return new{E, ED, G}(graph) + end +end + +Base.keys(view::EdgeDataView) = assigned_edges(view.graph) + +const VertexOrEdgeDataView{K, V, G} = Union{VertexDataView{K, V, G}, EdgeDataView{K, V, G}} + +Base.isassigned(view::VertexOrEdgeDataView{K}, key::K) where {K} = isassigned(view.graph, key) + +Base.getindex(view::VertexOrEdgeDataView, key) = view.graph[key] +# Method ambiguity +Base.getindex(view::VertexOrEdgeDataView{K}, key::K) where {K} = view.graph[key] + +Dictionaries.istokenizable(::Type{<:VertexOrEdgeDataView}) = true +Dictionaries.istokenassigned(view::VertexOrEdgeDataView, token) = istokenassigned(keys(view), token) +function Dictionaries.gettokenvalue(view::VertexOrEdgeDataView, token) + return view[gettokenvalue(keys(view), token)] +end + +function Base.setindex!(view::VertexOrEdgeDataView{K, V}, data::V, key::K) where {K, V} + setindex!(view.graph, data, key) + return view +end diff --git a/test/test_basics.jl b/test/test_basics.jl index a1173fc..e34c6de 100644 --- a/test/test_basics.jl +++ b/test/test_basics.jl @@ -1,11 +1,13 @@ using DataGraphs: - DataGraphs, DataGraph, + DataGraphs, + EdgeDataView, + VertexDataView, edge_data, edge_data_eltype, + underlying_graph, vertex_data, - vertex_data_eltype, - underlying_graph + vertex_data_eltype using Dictionaries: AbstractIndices, Dictionary, Indices, dictionary using Graphs: add_edge!, @@ -250,11 +252,9 @@ using Test: @test, @test_broken, @testset @test has_edge(dg, 2.0 => 3.0) @test has_edge(dg, 3.0 => 4.0) @test vertex_data(dg) == Dictionary{Float64, String}() - @test vertex_data(dg) isa Dictionary{Float64, String} @test keytype(vertex_data(dg)) === Float64 @test eltype(vertex_data(dg)) === String @test edge_data(dg) == Dictionary{NamedEdge{Float64}, Symbol}() - @test edge_data(dg) isa Dictionary{NamedEdge{Float64}, Symbol} @test keytype(edge_data(dg)) === NamedEdge{Float64} @test eltype(edge_data(dg)) === Symbol end @@ -474,4 +474,44 @@ using Test: @test, @test_broken, @testset @test g[2nd => 3rd] === :e_bc @test g[3rd => 2nd] === :e_bc end + + @testset "Data views" begin + g = DataGraph( + NamedGraph(path_graph(3), ["a", "b", "c"]); + vertex_data_eltype = Int, + edge_data_eltype = Float64 + ) + g["b"] = 2 + g["c"] = 3 + g["a" => "b"] = -1.0 + g["b" => "c"] = -2.0 + + vdata = vertex_data(g) + @test vdata isa VertexDataView + + @test keytype(VertexDataView(g)) === String + @test eltype(VertexDataView(g)) === Int + + @test collect(keys(vdata)) == ["b", "c"] + @test collect(vdata) == [2, 3] + + @test !haskey(vdata, "a") + @test haskey(vdata, "b") + @test haskey(vdata, "c") + + g["a"] = 1 + vdata = vertex_data(g) + @test collect(keys(vdata)) == collect(vertices(g)) + vdata["a"] = 4 + @test g["a"] == 4 + @test vdata["a"] == 4 + + @test length(keys(EdgeDataView(g))) == 2 + @test keytype(EdgeDataView(g)) === NamedEdge{String} + @test eltype(EdgeDataView(g)) === Float64 + + edata = edge_data(g) + @test edata["a" => "b"] == -1.0 + @test edata[edgetype(g)("b" => "c")] == -2.0 + end end From e5e8860eb475f325f52cbe42d79d006e4da059ee Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Thu, 4 Dec 2025 16:45:26 -0500 Subject: [PATCH 07/30] The underlying graph type of `DataGraph` now must be a `AbstractNamedGraph` Tests have been adjusted accordingly. --- examples/datagraph.jl | 16 +++++++++------- src/datagraph.jl | 3 +-- test/test_basics.jl | 29 ++++++++++++++--------------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/examples/datagraph.jl b/examples/datagraph.jl index 433ba80..4ba6ddb 100644 --- a/examples/datagraph.jl +++ b/examples/datagraph.jl @@ -1,11 +1,13 @@ using DataGraphs: DataGraph -using Graphs: Edge, grid, has_edge, has_vertex +using Graphs: has_edge, has_vertex +using NamedGraphs: NamedEdge +using NamedGraphs.NamedGraphGenerators: named_grid -g = grid((4,)) +g = named_grid((4)) dg = DataGraph(g; vertex_data_eltype = String, edge_data_eltype = Symbol) -@show !isassigned(dg, Edge(1, 2)) +@show !isassigned(dg, NamedEdge(1, 2)) @show !isassigned(dg, 1 => 2) -@show !isassigned(dg, Edge(1 => 2)) +@show !isassigned(dg, NamedEdge(1 => 2)) @show !isassigned(dg, 1 => 3) @show !isassigned(dg, 1) @show !isassigned(dg, 2) @@ -33,10 +35,10 @@ dg[4] = "V4" dg[1 => 2] = :E12 dg[2 => 3] = :E23 -dg[Edge(3, 4)] = :E34 +dg[NamedEdge(3, 4)] = :E34 #@show isassigned(dg, (1, 2)) -@show isassigned(dg, Edge(2, 3)) +@show isassigned(dg, NamedEdge(2, 3)) @show isassigned(dg, 3 => 4) -@show dg[Edge(1, 2)] == :E12 +@show dg[NamedEdge(1, 2)] == :E12 @show dg[2 => 3] == :E23 @show dg[3 => 4] == :E34 diff --git a/src/datagraph.jl b/src/datagraph.jl index a4622d5..e10fab1 100644 --- a/src/datagraph.jl +++ b/src/datagraph.jl @@ -1,6 +1,5 @@ using Dictionaries: Dictionary using Graphs: Graphs, edgetype, has_edge, has_vertex -using Graphs.SimpleGraphs: SimpleGraph using NamedGraphs: GenericNamedGraph using NamedGraphs.GraphsExtensions: convert_vertextype, @@ -15,7 +14,7 @@ using NamedGraphs.GraphsExtensions: # TODO: Use https://github.com/vtjnash/ComputedFieldTypes.jl to # automatically determine `E` from `G` from `edgetype(G)` # and `V` from `G` as `vertextype(G)`. -struct DataGraph{V, VD, ED, G <: AbstractGraph, E <: AbstractEdge} <: AbstractDataGraph{V, VD, ED} +struct DataGraph{V, VD, ED, G <: AbstractNamedGraph, E <: AbstractEdge} <: AbstractDataGraph{V, VD, ED} underlying_graph::G vertex_data::Dictionary{V, VD} edge_data::Dictionary{E, ED} diff --git a/test/test_basics.jl b/test/test_basics.jl index e34c6de..f826dd0 100644 --- a/test/test_basics.jl +++ b/test/test_basics.jl @@ -31,7 +31,6 @@ using Graphs: src, steiner_tree, vertices -using Graphs.SimpleGraphs: SimpleDiGraph, SimpleEdge, SimpleGraph using GraphsFlows: GraphsFlows using NamedGraphs: NamedDiGraph, NamedEdge, NamedGraph using NamedGraphs.GraphsExtensions: ⊔, rename_vertices, subgraph, vertextype @@ -55,11 +54,11 @@ using Test: @test, @test_broken, @testset end @testset "Basics" begin - g = grid((4,)) + g = named_grid(4) dg = DataGraph(g; vertex_data_eltype = String, edge_data_eltype = Symbol) - @test !isassigned(dg, SimpleEdge(1, 2)) + @test !isassigned(dg, NamedEdge(1, 2)) @test !isassigned(dg, 1 => 2) - @test !isassigned(dg, SimpleEdge(1 => 2)) + @test !isassigned(dg, NamedEdge(1 => 2)) @test !isassigned(dg, 1 => 3) @test !isassigned(dg, 1) @test !isassigned(dg, 2) @@ -94,11 +93,11 @@ using Test: @test, @test_broken, @testset dg[1 => 2] = :E12 dg[2 => 3] = :E23 - dg[SimpleEdge(3, 4)] = :E34 + dg[NamedEdge(3, 4)] = :E34 #@test isassigned(dg, (1, 2)) - @test isassigned(dg, SimpleEdge(2, 3)) + @test isassigned(dg, NamedEdge(2, 3)) @test isassigned(dg, 3 => 4) - @test dg[SimpleEdge(1, 2)] == :E12 + @test dg[NamedEdge(1, 2)] == :E12 @test dg[2 => 3] == :E23 @test dg[3 => 4] == :E34 @@ -109,7 +108,7 @@ using Test: @test, @test_broken, @testset @test dg[(1, 1) => (1, (1, 1))] == "X" vdata = map(v -> "V$v", Indices(1:4)) - edata = map(e -> "E$(src(e))$(dst(e))", Indices(SimpleEdge.([1 => 2, 2 => 3, 3 => 4]))) + edata = map(e -> "E$(src(e))$(dst(e))", Indices(NamedEdge.([1 => 2, 2 => 3, 3 => 4]))) # TODO: Make a more compact constructor that directly accepts # vertex and edge data? Maybe `DataGraph(g; vertex_data=vdata, edge_data=edata)` # or `DataGraph(g; vertex_data=v -> "V$v", edge_data=e -> "E$(src(e))$(dst(e))")`. @@ -129,13 +128,13 @@ using Test: @test, @test_broken, @testset @test dg[2 => 3] == "E23" @test dg[3 => 4] == "E34" - @test DataGraph(g) isa DataGraph{Int, Any, Any, SimpleGraph{Int}, SimpleEdge{Int}} + @test DataGraph(g) isa DataGraph{Int, Any, Any, NamedGraph{Int}, NamedEdge{Int}} dg_uint16 = DataGraph{UInt16}(dg) @test dg_uint16 isa - DataGraph{UInt16, String, String, SimpleGraph{UInt16}, SimpleEdge{UInt16}} + DataGraph{UInt16, String, String, NamedGraph{UInt16}, NamedEdge{UInt16}} @test vertextype(dg_uint16) === UInt16 - @test edgetype(dg_uint16) === SimpleEdge{UInt16} + @test edgetype(dg_uint16) === NamedEdge{UInt16} @test vertex_data_eltype(dg_uint16) === String @test edge_data_eltype(dg_uint16) === String @test dg_uint16[1] == "V1" @@ -181,7 +180,7 @@ using Test: @test, @test_broken, @testset end @testset "get and get! functions" begin - g = grid((4,)) + g = named_grid(4) dg = DataGraph(g; vertex_data_eltype = String, edge_data_eltype = Symbol) # Test for vertices @@ -303,11 +302,11 @@ using Test: @test, @test_broken, @testset end @testset "union" begin - g1 = DataGraph(grid((4,))) + g1 = DataGraph(named_grid(4)) g1[1] = ["A", "B", "C"] g1[1 => 2] = ["E", "F"] - g2 = DataGraph(SimpleGraph(5)) + g2 = DataGraph(NamedGraph(5)) add_edge!(g2, 1 => 5) g2[1] = ["C", "D", "E"] @@ -355,7 +354,7 @@ using Test: @test, @test_broken, @testset @test issetequal(comps[2], [4, 5, 6]) end @testset "reverse" begin - g = DataGraph(SimpleDiGraph(4)) + g = DataGraph(NamedDiGraph(4)) add_edge!(g, 1 => 2) add_edge!(g, 3 => 4) g[1 => 2] = :A From 7c07b7f9dadf46287e5aade516f23cbe8933ff1a Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Thu, 4 Dec 2025 16:46:01 -0500 Subject: [PATCH 08/30] Make `Base.reverse` method `AbstractDataGraph` more generic. --- src/abstractdatagraph.jl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl index 0aebb1a..ab0b47f 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -4,6 +4,7 @@ using Graphs: AbstractEdge, IsDirected, add_edge!, + add_vertex!, a_star, edges, ne, @@ -175,7 +176,7 @@ end function GraphsExtensions.rename_vertices(f::Function, graph::AbstractDataGraph) - # Use the two-argument `similar_graph` method so the new graph has correct vertex type + # Uses the two-argument `similar_graph` method so the new graph has correct vertex type renamed_graph = similar_graph(graph, map(f, vertices(graph))) for vertex in vertices(graph) @@ -196,13 +197,15 @@ function GraphsExtensions.rename_vertices(f::Function, graph::AbstractDataGraph) end function Base.reverse(graph::AbstractDataGraph) - reversed_graph = typeof(graph)(reverse(underlying_graph(graph))) + reversed_graph = similar_graph(graph) for v in vertices(graph) + add_vertex!(reversed_graph, v) if isassigned(graph, v) reversed_graph[v] = graph[v] end end for e in edges(graph) + add_edge!(reversed_graph, reverse(e)) if isassigned(graph, e) reversed_graph[reverse(e)] = graph[e] end @@ -210,6 +213,7 @@ function Base.reverse(graph::AbstractDataGraph) return reversed_graph end + function Graphs.merge_vertices( graph::AbstractDataGraph, merge_vertices; From 6b4826873e6f922c3cc7ad394eca48665122c10f Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Thu, 4 Dec 2025 17:01:54 -0500 Subject: [PATCH 09/30] Remove surplus code --- src/abstractdatagraph.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl index ab0b47f..010ebe3 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -222,7 +222,6 @@ function Graphs.merge_vertices( merge_edge_data = merge_data, kwargs..., ) - underlying_merged_graph = Graphs.merge_vertices(underlying_graph(graph); kwargs...) return not_implemented() end @@ -234,8 +233,6 @@ function Graphs.merge_vertices!( merge_edge_data = merge_data, kwargs..., ) - underlying_merged_graph = copy(underlying_graph(graph)) - Graphs.merge_vertices!(underlying_merged_graph; kwargs...) return not_implemented() end From f0e5bce94b5ff7dbf3045a2c6869311aeeb86bbd Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Mon, 24 Nov 2025 17:53:52 -0500 Subject: [PATCH 10/30] `PartitionedGraphs` extension for `DataGraphs`. --- src/DataGraphs.jl | 2 + src/abstractdatagraph.jl | 4 ++ .../src/DataGraphsPartitionedGraphsExt.jl | 59 +++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl diff --git a/src/DataGraphs.jl b/src/DataGraphs.jl index dc3a60b..3b0c10b 100644 --- a/src/DataGraphs.jl +++ b/src/DataGraphs.jl @@ -5,6 +5,8 @@ include("traits/isunderlyinggraph.jl") include("dataview.jl") include("abstractdatagraph.jl") include("datagraph.jl") +# TODO: Turn into an extension once `PartitionedGraphs` is excised. +include("lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl") export AbstractDataGraph, DataGraph diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl index 010ebe3..02b30cb 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -80,6 +80,10 @@ underlying_graph_type(graph::AbstractDataGraph) = typeof(underlying_graph(graph) vertex_data_eltype(graph::AbstractDataGraph) = vertex_data_eltype(typeof(graph)) edge_data_eltype(graph::AbstractDataGraph) = edge_data_eltype(typeof(graph)) +function NamedGraphs.position_graph_type(type::Type{<:AbstractDataGraph}) + return position_graph_type(underlying_graph_type(type)) +end + Base.zero(graph_type::Type{<:AbstractDataGraph}) = graph_type() # Graphs overloads diff --git a/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl b/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl new file mode 100644 index 0000000..644a533 --- /dev/null +++ b/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl @@ -0,0 +1,59 @@ +module DataGraphsPartitionedGraphsExt +using Graphs: Graphs, AbstractGraph, AbstractEdge, vertices, edges +using ..DataGraphs: + AbstractDataGraph, + DataGraphs, + edge_data, + underlying_graph, + vertex_data +using NamedGraphs.PartitionedGraphs: + PartitionedGraphs, + QuotientEdge, + QuotientVertex, + QuotientView, + partitioned_vertices +using Dictionaries: Dictionary, Indices + +""" + to_quotient_vertex_data(g::AbstractGraph{V}, data::Pair{QuotientVertex, Dictionary{V}}) + +Transform `data` obtained from the vertices in the quotient vertex of the graph `g` to a format +on the quotient graph of `g`. By default, this function returns `value` of the pair +`key, value = data` and should be specialized for specific graph types as needed. +""" +to_quotient_vertex_data(::AbstractGraph, data) = last(data) +""" + to_quotient_edge_data(g::AbstractGraph, data::Pair{QuotientEdge, Dictionary{AbstractEdge}}) + +Transform `data` obtained from the edges in the quotient edge of the graph `g` to a format +on the quotient graph of `g`. By default, this function returns `value` of the pair +`key, value = data` and should be specialized for specific graph types as needed. +""" +to_quotient_edge_data(::AbstractGraph, data) = last(data) + +function Base.getindex(g::AbstractGraph, qv::QuotientVertex) + vs = Indices(vertices(g, qv)) + return to_quotient_vertex_data(g, qv => map(v -> g[v], vs)) +end +function Base.getindex(g::AbstractGraph, qe::QuotientEdge) + es = Indices(edges(g, qe)) + return to_quotient_edge_data(g, qe => map(e -> g[e], es)) +end + +# QuotientView +Base.getindex(qv::QuotientView, v) = _getindex(qv, v) + +_getindex(qv::QuotientView, v) = parent(qv)[QuotientVertex(v)] +_getindex(qv::QuotientView, e::Union{Pair, AbstractEdge}) = parent(qv)[QuotientEdge(e)] + +DataGraphs.vertex_data(qv::QuotientView) = Dictionary(map(v -> qv[v], Indices(vertices(qv)))) +DataGraphs.edge_data(qv::QuotientView) = Dictionary(map(e -> qv[e], Indices(edges(qv)))) + +# PartitionedGraphs interface +function PartitionedGraphs.partitioned_vertices(dg::AbstractDataGraph) + return partitioned_vertices(underlying_graph(dg)) +end + +PartitionedGraphs.partitionedgraph(::AbstractDataGraph, parts) = not_implemented() + +end From 1fcb8e62cec11a9e32a6cc9937409fab309f507d Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Tue, 25 Nov 2025 12:26:35 -0500 Subject: [PATCH 11/30] Write interface in terms of `vertex_data` and `edge_data` rather than `getindex` This now correctly mirrors `DataGraphs` interface. --- .../src/DataGraphsPartitionedGraphsExt.jl | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl b/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl index 644a533..212645e 100644 --- a/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl +++ b/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl @@ -31,23 +31,43 @@ on the quotient graph of `g`. By default, this function returns `value` of the p """ to_quotient_edge_data(::AbstractGraph, data) = last(data) -function Base.getindex(g::AbstractGraph, qv::QuotientVertex) +# Ambiguity resolution +function Base.getindex(g::AbstractGraph, qve::Union{QuotientVertex, QuotientEdge}) + return _getindex(g, qve) +end +function Base.getindex(g::AbstractDataGraph, qve::Union{QuotientVertex, QuotientEdge}) + return _getindex(g, qve) +end +function _getindex(g::AbstractGraph, qv::QuotientVertex) vs = Indices(vertices(g, qv)) return to_quotient_vertex_data(g, qv => map(v -> g[v], vs)) end -function Base.getindex(g::AbstractGraph, qe::QuotientEdge) +function _getindex(g::AbstractGraph, qe::QuotientEdge) es = Indices(edges(g, qe)) return to_quotient_edge_data(g, qe => map(e -> g[e], es)) end -# QuotientView -Base.getindex(qv::QuotientView, v) = _getindex(qv, v) +# QuotientView; make sure views of data graphs do data graph indexing. +function Base.getindex(qv::QuotientView{V, <:AbstractDataGraph}, v) where {V} + return _getindex(qv, v) +end + +_getindex(qv::QuotientView, v) = vertex_data(qv)[v] +_getindex(qv::QuotientView, e::Union{Pair, AbstractEdge}) = edge_data(qv)[e] + +# QuotientView DataGraphs interface +function DataGraphs.vertex_data(qv::QuotientView) + return Dictionary(map(v -> parent(qv)[QuotientVertex(v)], Indices(vertices(qv)))) +end +function DataGraphs.edge_data(qv::QuotientView) + return Dictionary(map(e -> parent(qv)[QuotientEdge(e)], Indices(edges(qv)))) +end -_getindex(qv::QuotientView, v) = parent(qv)[QuotientVertex(v)] -_getindex(qv::QuotientView, e::Union{Pair, AbstractEdge}) = parent(qv)[QuotientEdge(e)] +DataGraphs.vertex_data_eltype(qv::QuotientView) = DataGraphs.vertex_data_eltype(typeof(qv)) +DataGraphs.vertex_data_eltype(T::Type{<:QuotientView}) = eltype(Base.promote_op(vertex_data, T)) -DataGraphs.vertex_data(qv::QuotientView) = Dictionary(map(v -> qv[v], Indices(vertices(qv)))) -DataGraphs.edge_data(qv::QuotientView) = Dictionary(map(e -> qv[e], Indices(edges(qv)))) +DataGraphs.edge_data_eltype(qv::QuotientView) = DataGraphs.edge_data_eltype(typeof(qv)) +DataGraphs.edge_data_eltype(T::Type{<:QuotientView}) = eltype(Base.promote_op(edge_data, T)) # PartitionedGraphs interface function PartitionedGraphs.partitioned_vertices(dg::AbstractDataGraph) @@ -55,5 +75,6 @@ function PartitionedGraphs.partitioned_vertices(dg::AbstractDataGraph) end PartitionedGraphs.partitionedgraph(::AbstractDataGraph, parts) = not_implemented() +PartitionedGraphs.departition(::AbstractDataGraph) = not_implemented() end From 925e77b59f487caae4a71fe880c27b006e08ba6d Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Mon, 1 Dec 2025 17:19:23 -0500 Subject: [PATCH 12/30] Interface is now defined in terms of `getindex`. This is in anticipation of making this change in `DataGraphs` --- .../src/DataGraphsPartitionedGraphsExt.jl | 70 +++++++------------ 1 file changed, 27 insertions(+), 43 deletions(-) diff --git a/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl b/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl index 212645e..9d6d8d7 100644 --- a/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl +++ b/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl @@ -11,57 +11,29 @@ using NamedGraphs.PartitionedGraphs: QuotientEdge, QuotientVertex, QuotientView, - partitioned_vertices + partitioned_vertices, + quotientvertices, + quotientedges using Dictionaries: Dictionary, Indices -""" - to_quotient_vertex_data(g::AbstractGraph{V}, data::Pair{QuotientVertex, Dictionary{V}}) +# Fallbacks +get_quotient_vertex_data(g::AbstractGraph, qv) = map(v -> g[v], Indices(vertices(g, qv))) +get_quotient_edge_data(g::AbstractGraph, qe) = map(e -> g[e], Indices(edges(g, qe))) -Transform `data` obtained from the vertices in the quotient vertex of the graph `g` to a format -on the quotient graph of `g`. By default, this function returns `value` of the pair -`key, value = data` and should be specialized for specific graph types as needed. -""" -to_quotient_vertex_data(::AbstractGraph, data) = last(data) -""" - to_quotient_edge_data(g::AbstractGraph, data::Pair{QuotientEdge, Dictionary{AbstractEdge}}) +# Interface functions +Base.getindex(g::AbstractGraph, qv::QuotientVertex) = get_quotient_vertex_data(g, qv) +Base.getindex(g::AbstractGraph, qe::QuotientEdge) = get_quotient_edge_data(g, qe) -Transform `data` obtained from the edges in the quotient edge of the graph `g` to a format -on the quotient graph of `g`. By default, this function returns `value` of the pair -`key, value = data` and should be specialized for specific graph types as needed. -""" -to_quotient_edge_data(::AbstractGraph, data) = last(data) +# For ambiguity resolution +Base.getindex(g::AbstractDataGraph, qv::QuotientVertex) = get_quotient_vertex_data(g, qv) +Base.getindex(g::AbstractDataGraph, qe::QuotientEdge) = get_quotient_edge_data(g, qe) -# Ambiguity resolution -function Base.getindex(g::AbstractGraph, qve::Union{QuotientVertex, QuotientEdge}) - return _getindex(g, qve) -end -function Base.getindex(g::AbstractDataGraph, qve::Union{QuotientVertex, QuotientEdge}) - return _getindex(g, qve) -end -function _getindex(g::AbstractGraph, qv::QuotientVertex) - vs = Indices(vertices(g, qv)) - return to_quotient_vertex_data(g, qv => map(v -> g[v], vs)) -end -function _getindex(g::AbstractGraph, qe::QuotientEdge) - es = Indices(edges(g, qe)) - return to_quotient_edge_data(g, qe => map(e -> g[e], es)) -end - -# QuotientView; make sure views of data graphs do data graph indexing. +# QuotientView; make sure quotient views of data graphs do data graph indexing. function Base.getindex(qv::QuotientView{V, <:AbstractDataGraph}, v) where {V} return _getindex(qv, v) end - -_getindex(qv::QuotientView, v) = vertex_data(qv)[v] -_getindex(qv::QuotientView, e::Union{Pair, AbstractEdge}) = edge_data(qv)[e] - -# QuotientView DataGraphs interface -function DataGraphs.vertex_data(qv::QuotientView) - return Dictionary(map(v -> parent(qv)[QuotientVertex(v)], Indices(vertices(qv)))) -end -function DataGraphs.edge_data(qv::QuotientView) - return Dictionary(map(e -> parent(qv)[QuotientEdge(e)], Indices(edges(qv)))) -end +_getindex(qv::QuotientView, v) = parent(qv)[QuotientVertex(v)] +_getindex(qv::QuotientView, e::Union{Pair, AbstractEdge}) = parent(qv)[QuotientEdge(e)] DataGraphs.vertex_data_eltype(qv::QuotientView) = DataGraphs.vertex_data_eltype(typeof(qv)) DataGraphs.vertex_data_eltype(T::Type{<:QuotientView}) = eltype(Base.promote_op(vertex_data, T)) @@ -69,6 +41,8 @@ DataGraphs.vertex_data_eltype(T::Type{<:QuotientView}) = eltype(Base.promote_op( DataGraphs.edge_data_eltype(qv::QuotientView) = DataGraphs.edge_data_eltype(typeof(qv)) DataGraphs.edge_data_eltype(T::Type{<:QuotientView}) = eltype(Base.promote_op(edge_data, T)) +DataGraphs.underlying_graph(qv::QuotientView) = underlying_graph(copy(qv)) + # PartitionedGraphs interface function PartitionedGraphs.partitioned_vertices(dg::AbstractDataGraph) return partitioned_vertices(underlying_graph(dg)) @@ -77,4 +51,14 @@ end PartitionedGraphs.partitionedgraph(::AbstractDataGraph, parts) = not_implemented() PartitionedGraphs.departition(::AbstractDataGraph) = not_implemented() +# TODO: These methods wont be necessary once DataGraphs pivots to a `get/setindex`-based interface. +function DataGraphs.vertex_data(g::QuotientView) + vs = Indices(vertices(g)) + return map(v -> getindex(parent(g), QuotientVertex(v)), vs) +end +function DataGraphs.edge_data(g::QuotientView) + es = Indices(edges(g)) + return map(e -> getindex(parent(g), QuotientEdge(e)), es) +end + end From b48acabfc72ddb6f3e71f4269872e9e628828f19 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Tue, 2 Dec 2025 10:17:05 -0500 Subject: [PATCH 13/30] Add `setindex!` fallback methods. --- .../src/DataGraphsPartitionedGraphsExt.jl | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl b/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl index 9d6d8d7..593fae1 100644 --- a/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl +++ b/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl @@ -16,24 +16,47 @@ using NamedGraphs.PartitionedGraphs: quotientedges using Dictionaries: Dictionary, Indices +#====INTERFACE FUNCTIONS====# +Base.getindex(g::AbstractGraph, qv::QuotientVertex) = get_quotient_vertex_data(g, qv) +Base.getindex(g::AbstractGraph, qe::QuotientEdge) = get_quotient_edge_data(g, qe) + +# The type of `val` should be the return type of the corresponding `getindex` method. +Base.setindex!(g::AbstractGraph, val, qv::QuotientVertex) = set_quotient_vertex_data!(g, val, qv) +Base.setindex!(g::AbstractGraph, val, qe::QuotientEdge) = set_quotient_edge_data!(g, val, qe) +#====# + # Fallbacks get_quotient_vertex_data(g::AbstractGraph, qv) = map(v -> g[v], Indices(vertices(g, qv))) get_quotient_edge_data(g::AbstractGraph, qe) = map(e -> g[e], Indices(edges(g, qe))) -# Interface functions -Base.getindex(g::AbstractGraph, qv::QuotientVertex) = get_quotient_vertex_data(g, qv) -Base.getindex(g::AbstractGraph, qe::QuotientEdge) = get_quotient_edge_data(g, qe) +function set_quotient_vertex_data!(g::AbstractGraph, val, qv) + for v in vertices(g, qv) + g[v] = val[v] + end + return g +end +function set_quotient_edge_data!(g::AbstractGraph, val, qe) + for e in edges(g, qe) + g[e] = val[e] + end + return g +end # For ambiguity resolution Base.getindex(g::AbstractDataGraph, qv::QuotientVertex) = get_quotient_vertex_data(g, qv) Base.getindex(g::AbstractDataGraph, qe::QuotientEdge) = get_quotient_edge_data(g, qe) +Base.setindex!(g::AbstractDataGraph, val, qv::QuotientVertex) = set_quotient_vertex_data!(g, val, qv) +Base.setindex!(g::AbstractDataGraph, val, qe::QuotientEdge) = set_quotient_edge_data!(g, val, qe) + # QuotientView; make sure quotient views of data graphs do data graph indexing. -function Base.getindex(qv::QuotientView{V, <:AbstractDataGraph}, v) where {V} - return _getindex(qv, v) -end -_getindex(qv::QuotientView, v) = parent(qv)[QuotientVertex(v)] -_getindex(qv::QuotientView, e::Union{Pair, AbstractEdge}) = parent(qv)[QuotientEdge(e)] +Base.getindex(qv::QuotientView{V, <:AbstractDataGraph}, v) where {V} = _getindex(qv, v) +_getindex(qv::QuotientView, v) = getindex(parent(qv), QuotientVertex(v)) +_getindex(qv::QuotientView, e::AbstractEdge) = getindex(parent(qv), QuotientEdge(e)) + +Base.setindex!(qv::QuotientView, ve) = _getindex(qv, ve) +_setindex!(qv::QuotientView, val, v) = setindex!(parent(qv), val, QuotientEdge(v)) +_setindex!(qv::QuotientView, val, e::AbstractEdge) = setindex!(parent(qv), val, QuotientEdge(e)) DataGraphs.vertex_data_eltype(qv::QuotientView) = DataGraphs.vertex_data_eltype(typeof(qv)) DataGraphs.vertex_data_eltype(T::Type{<:QuotientView}) = eltype(Base.promote_op(vertex_data, T)) From ae97f779f924c84630ebab744b9a1d1fc717ac20 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Thu, 11 Dec 2025 15:55:37 -0500 Subject: [PATCH 14/30] DataViews now use a lazy filter to construct keys --- src/dataview.jl | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/dataview.jl b/src/dataview.jl index 0850f6c..5ab8a89 100644 --- a/src/dataview.jl +++ b/src/dataview.jl @@ -1,4 +1,4 @@ -using Dictionaries: Dictionaries, AbstractDictionary, gettokenvalue +using Dictionaries: Dictionaries, AbstractDictionary, gettokenvalue, filterview struct VertexDataView{V, VD, G <: AbstractGraph{V}} <: AbstractDictionary{V, VD} graph::G @@ -10,7 +10,7 @@ struct VertexDataView{V, VD, G <: AbstractGraph{V}} <: AbstractDictionary{V, VD} end end -Base.keys(view::VertexDataView) = assigned_vertices(view.graph) +_keys(view::VertexDataView) = Indices(vertices(view.graph)) struct EdgeDataView{E, ED, G <: AbstractGraph} <: AbstractDictionary{E, ED} graph::G @@ -22,23 +22,42 @@ struct EdgeDataView{E, ED, G <: AbstractGraph} <: AbstractDictionary{E, ED} end end -Base.keys(view::EdgeDataView) = assigned_edges(view.graph) +_keys(view::EdgeDataView) = Indices(edges(view.graph)) const VertexOrEdgeDataView{K, V, G} = Union{VertexDataView{K, V, G}, EdgeDataView{K, V, G}} -Base.isassigned(view::VertexOrEdgeDataView{K}, key::K) where {K} = isassigned(view.graph, key) +function Base.keys(view::VertexOrEdgeDataView) + return filterview(k -> isassigned(view.graph, k), _keys(view)) +end -Base.getindex(view::VertexOrEdgeDataView, key) = view.graph[key] -# Method ambiguity +Base.isassigned(view::VertexOrEdgeDataView{K}, key::K) where {K} = isassigned(view.graph, key) Base.getindex(view::VertexOrEdgeDataView{K}, key::K) where {K} = view.graph[key] +Base.getindex(view::EdgeDataView, key::Pair) = view.graph[key] Dictionaries.istokenizable(::Type{<:VertexOrEdgeDataView}) = true -Dictionaries.istokenassigned(view::VertexOrEdgeDataView, token) = istokenassigned(keys(view), token) +function Dictionaries.istokenassigned(view::VertexOrEdgeDataView, token) + ind = gettokenvalue(keys(view), token) + return isassigned(view, ind) +end function Dictionaries.gettokenvalue(view::VertexOrEdgeDataView, token) - return view[gettokenvalue(keys(view), token)] + ind = gettokenvalue(keys(view), token) + return view[ind] end function Base.setindex!(view::VertexOrEdgeDataView{K, V}, data::V, key::K) where {K, V} setindex!(view.graph, data, key) return view end +function Dictionaries.settokenvalue!(view::VertexOrEdgeDataView{<:Any, T}, token, value::T) where {T} + setindex!(view, value, Dictionaries.gettokenvalue(view, token)) + return view +end + +function Dictionaries.gettoken!(view::VertexOrEdgeDataView{K}, ind::K) where {K} + # This will intentially error if `ind` is not assigned in `view`. + return Dictionaries.gettoken(keys(view), ind) +end +function Dictionaries.deletetoken!(view::VertexOrEdgeDataView, token) + Dictionaries.unset!(view.graph, gettokenvalue(keys(view), token)) + return view +end From bb9e47c976191e4d53f2ac5d5a3dabfb16350659 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Thu, 11 Dec 2025 15:57:13 -0500 Subject: [PATCH 15/30] Remove redundant methods These are defined on the abstract type --- src/datagraph.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/datagraph.jl b/src/datagraph.jl index e10fab1..ea959a2 100644 --- a/src/datagraph.jl +++ b/src/datagraph.jl @@ -66,8 +66,6 @@ edge_data_eltype(G::Type{<:DataGraph}) = eltype(fieldtype(G, :edge_data)) # Extras -Graphs.edgetype(T::Type{<:DataGraph}) = keytype(fieldtype(T, :edge_data)) - function GraphsExtensions.similar_graph(T::Type{<:DataGraph}) similar_underlying_graph = similar_graph(underlying_graph_type(T)) return T(similar_underlying_graph) @@ -119,7 +117,6 @@ function DataGraph{V}(graph::DataGraph) where {V} return _DataGraph(converted_underlying_graph, converted_vertex_data, converted_edge_data) end -GraphsExtensions.convert_vertextype(::Type{V}, graph::DataGraph{V}) where {V} = graph function GraphsExtensions.convert_vertextype(vertextype::Type, graph::DataGraph) return DataGraph{vertextype}(graph) end From f51f127a0d13ffb6d428695cb6d4c486812acc39 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Thu, 11 Dec 2025 15:58:02 -0500 Subject: [PATCH 16/30] Improve and reorganise indexing interface --- src/DataGraphs.jl | 1 + src/abstractdatagraph.jl | 175 +++++++++++++++++++-------------------- src/indexing.jl | 68 +++++++++++++++ 3 files changed, 155 insertions(+), 89 deletions(-) create mode 100644 src/indexing.jl diff --git a/src/DataGraphs.jl b/src/DataGraphs.jl index 3b0c10b..6f93972 100644 --- a/src/DataGraphs.jl +++ b/src/DataGraphs.jl @@ -4,6 +4,7 @@ include("utils.jl") include("traits/isunderlyinggraph.jl") include("dataview.jl") include("abstractdatagraph.jl") +include("indexing.jl") include("datagraph.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 02b30cb..2271330 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -11,14 +11,21 @@ using Graphs: nv, steiner_tree, vertices -using NamedGraphs: NamedGraphs, AbstractNamedGraph, AbstractNamedEdge +using NamedGraphs: + NamedGraphs, + AbstractNamedGraph, + AbstractNamedEdge, + AbstractVertices, + AbstractEdges, + position_graph_type using NamedGraphs.GraphsExtensions: GraphsExtensions, arrange_edge, incident_edges, is_edge_arranged, vertextype, - similar_graph + similar_graph, + add_vertices! using NamedGraphs.SimilarType: similar_type using NamedGraphs.OrdinalIndexing: OrdinalSuffixedInteger using SimpleTraits: SimpleTraits, Not, @traitfn @@ -27,6 +34,9 @@ is_underlying_graph(::Type{<:AbstractNamedGraph}) = true abstract type AbstractDataGraph{V, VD, ED} <: AbstractNamedGraph{V} end +vertex_data_eltype(::Type{<:AbstractGraph}) = Any +edge_data_eltype(::Type{<:AbstractGraph}) = Any + vertex_data_eltype(::Type{<:AbstractDataGraph{V, VD, ED}}) where {V, VD, ED} = VD edge_data_eltype(::Type{<:AbstractDataGraph{V, VD, ED}}) where {V, VD, ED} = ED @@ -48,21 +58,64 @@ unset_edge_data!(::AbstractDataGraph, data, edge) = not_implemented() # Quasi-derived interface; only required if inference fails -underlying_graph_type(T::Type{<:AbstractDataGraph}) = Base.promote_op(underlying_graph, T) +underlying_graph_type(T::Type{<:AbstractGraph}) = Base.promote_op(underlying_graph, T) # Derived interface +has_vertices_data(g::AbstractGraph, vertices) = all(v -> has_vertex_data(g, v), vertices) +has_edges_data(g::AbstractGraph, edges) = all(e -> has_edge_data(g, e), edges) + +function get_vertices_data(g::AbstractGraph, vertices) + return map(v -> get_vertex_data(g, v), Indices(vertices)) +end +function vertices_data_eltype(G::Type{<:AbstractGraph}, V::Type{<:AbstractVertices}) + return Dictionary{eltype(V), vertex_data_eltype(G)} +end + +function get_edges_data(g::AbstractGraph, edges) + return map(e -> get_edge_data(g, e), Indices(edges)) +end +function edges_data_eltype(G::Type{<:AbstractGraph}, E::Type{<:AbstractEdges{V, ET} where {V, ET}}) + return Dictionary{eltype(E), edge_data_eltype(G)} +end + +function set_vertices_data!(g::AbstractGraph, val, vertices) + for v in vertices + g[v] = val[v] + end + return g +end +function set_edges_data!(g::AbstractGraph, val, edges) + for e in edges + g[e] = val[e] + end + return g +end + +function unset_vertices_data!(g::AbstractGraph, vertices) + for v in vertices + unset_vertex_data!(g, v) + end + return g +end +function unset_edges_data!(g::AbstractGraph, edges) + for e in edges + unset_edge_data!(g, e) + end + return g +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) Graphs.has_edge(g::AbstractDataGraph, edge::AbstractNamedEdge) = has_edge(underlying_graph(g), edge) -vertex_data(dg::AbstractDataGraph) = VertexDataView(dg) -edge_data(dg::AbstractDataGraph) = EdgeDataView(dg) +vertex_data(dg::AbstractGraph) = VertexDataView(dg) +edge_data(dg::AbstractGraph) = EdgeDataView(dg) -function assigned_vertices(graph::AbstractDataGraph) +function assigned_vertices(graph::AbstractGraph) return Indices(filter(v -> isassigned(graph, v), vertices(graph))) end -function assigned_edges(graph::AbstractDataGraph) +function assigned_edges(graph::AbstractGraph) return Indices(filter(e -> isassigned(graph, e), edges(graph))) end @@ -70,21 +123,21 @@ function Graphs.edgetype(graph::AbstractDataGraph) return Graphs.edgetype(underlying_graph(graph)) end function Graphs.edgetype(graph_type::Type{<:AbstractDataGraph}) - return Base.promote_op(edgetype, graph_type) + return edgetype(underlying_graph_type(graph_type)) end function Graphs.is_directed(graph_type::Type{<:AbstractDataGraph}) return Graphs.is_directed(underlying_graph_type(graph_type)) end -underlying_graph_type(graph::AbstractDataGraph) = typeof(underlying_graph(graph)) -vertex_data_eltype(graph::AbstractDataGraph) = vertex_data_eltype(typeof(graph)) -edge_data_eltype(graph::AbstractDataGraph) = edge_data_eltype(typeof(graph)) +underlying_graph_type(graph::AbstractGraph) = typeof(underlying_graph(graph)) +vertex_data_eltype(graph::AbstractGraph) = vertex_data_eltype(typeof(graph)) +edge_data_eltype(graph::AbstractGraph) = edge_data_eltype(typeof(graph)) function NamedGraphs.position_graph_type(type::Type{<:AbstractDataGraph}) return position_graph_type(underlying_graph_type(type)) end -Base.zero(graph_type::Type{<:AbstractDataGraph}) = graph_type() +Base.zero(graph_type::Type{<:AbstractDataGraph}) = similar_graph(graph_type) # Graphs overloads function Graphs.vertices(graph::AbstractDataGraph) @@ -127,12 +180,12 @@ function Graphs.eccentricity(graph::AbstractDataGraph, distmx::AbstractMatrix) end # Fix for ambiguity error with `AbstractGraph` version -function indegree(graph::AbstractDataGraph, vertex::Integer) +function Graphs.indegree(graph::AbstractDataGraph, vertex::Integer) return indegree(underlying_graph(graph), vertex) end # Fix for ambiguity error with `AbstractGraph` version -function outdegree(graph::AbstractDataGraph, vertex::Integer) +function Graphs.outdegree(graph::AbstractDataGraph, vertex::Integer) return outdegree(underlying_graph(graph), vertex) end @@ -181,7 +234,10 @@ end function GraphsExtensions.rename_vertices(f::Function, graph::AbstractDataGraph) # Uses the two-argument `similar_graph` method so the new graph has correct vertex type - renamed_graph = similar_graph(graph, map(f, vertices(graph))) + renamed_vertices = map(f, vertices(graph)) + renamed_graph = similar_graph(graph, eltype(renamed_vertices)) + + add_vertices!(renamed_graph, renamed_vertices) for vertex in vertices(graph) if isassigned(graph, vertex) @@ -300,7 +356,7 @@ function Graphs.dfs_tree(graph::AbstractDataGraph, s::Integer; kwargs...) return Graphs.dfs_tree(underlying_graph(graph), s; kwargs...) end -function map_vertex_data(f, graph::AbstractDataGraph; vertices = nothing) +function map_vertex_data(f, graph::AbstractGraph; vertices = nothing) new_graph = copy(graph) vs = isnothing(vertices) ? Graphs.vertices(graph) : vertices for v in vs @@ -309,7 +365,7 @@ function map_vertex_data(f, graph::AbstractDataGraph; vertices = nothing) return new_graph end -function map_edge_data(f, graph::AbstractDataGraph; edges = nothing) +function map_edge_data(f, graph::AbstractGraph; edges = nothing) new_graph = copy(graph) es = isnothing(edges) ? Graphs.edges(graph) : edges for e in es @@ -320,22 +376,22 @@ function map_edge_data(f, graph::AbstractDataGraph; edges = nothing) return new_graph end -function map_data(f, graph::AbstractDataGraph; vertices = nothing, edges = nothing) +function map_data(f, graph::AbstractGraph; vertices = nothing, edges = nothing) graph = map_vertex_data(f, graph; vertices) return map_edge_data(f, graph; edges) end -Base.get!(graph::AbstractGraph, key, default) = get!(() -> default, graph, key) -function Base.get!(default::Base.Callable, graph::AbstractGraph, key) +Base.get!(graph::AbstractDataGraph, key, default) = get!(() -> default, graph, key) +function Base.get!(default::Base.Callable, graph::AbstractDataGraph, key) if isassigned(graph, key) return graph[key] else return graph[key] = default() end end -Base.get(graph::AbstractGraph, key, default) = get(() -> default, graph, key) -function Base.get(default::Base.Callable, graph::AbstractGraph, key) +Base.get(graph::AbstractDataGraph, key, default) = get(() -> default, graph, key) +function Base.get(default::Base.Callable, graph::AbstractDataGraph, key) if isassigned(graph, key) return graph[key] else @@ -343,77 +399,18 @@ function Base.get(default::Base.Callable, graph::AbstractGraph, key) end end -Base.getindex(graph::AbstractDataGraph, vertex) = get_vertex_data(graph, vertex) - -function Base.getindex(graph::AbstractDataGraph, vertex::OrdinalSuffixedInteger) - return graph[vertices(graph)[vertex]] -end - -function Base.getindex( - graph::AbstractDataGraph, edge::Pair{<:OrdinalSuffixedInteger, <:OrdinalSuffixedInteger} - ) - return graph[edgetype(graph)(vertices(graph)[edge[1]], vertices(graph)[edge[2]])] -end - -function Base.getindex(graph::AbstractDataGraph, edge::AbstractEdge) - data = get_edge_data(graph, arrange_edge(graph, edge)) - return reverse_data_direction(graph, edge, data) +function Base.isassigned(graph::AbstractDataGraph, index) + return _isassigned(graph, to_graph_indices(graph, index)) end - -# Support syntax `g[v1 => v2]` -function Base.getindex(graph::AbstractDataGraph, edge::Pair) - return graph[edgetype(graph)(edge)] -end - -# Support syntax `g[1, 2] = g[(1, 2)]` -function Base.getindex(graph::AbstractDataGraph, i1, i2, i...) - return graph[(i1, i2, i...)] -end - -Base.isassigned(graph::AbstractDataGraph, vertex) = has_vertex_data(graph, vertex) - -function Base.isassigned(graph::AbstractDataGraph, edge::AbstractEdge) +_isassigned(graph::AbstractDataGraph, vertex) = has_vertex_data(graph, vertex) +function _isassigned(graph::AbstractDataGraph, edge::AbstractEdge) return has_edge_data(graph, arrange_edge(graph, edge)) end -function Base.isassigned(graph::AbstractDataGraph, edge::Pair) - return isassigned(graph, edgetype(graph)(edge)) +function _isassigned(graph::AbstractDataGraph, edges::AbstractEdges) + return has_edges_data(graph, edges) end - -function Base.setindex!(graph::AbstractDataGraph, data, vertex) - set_vertex_data!(graph, data, vertex) - return graph -end - -function Base.setindex!(graph::AbstractDataGraph, data, edge::AbstractEdge) - arranged_edge = arrange_edge(graph, edge) - arranged_data = reverse_data_direction(graph, edge, data) - set_edge_data!(graph, arranged_data, arranged_edge) - return graph -end - -function Base.setindex!(graph::AbstractDataGraph, data, edge::Pair) - graph[edgetype(graph)(edge)] = data - return graph -end - -# Support syntax `g[1, 2] = g[(1, 2)]` -function Base.setindex!(graph::AbstractDataGraph, x, i1, i2, i...) - graph[(i1, i2, i...)] = x - return graph -end - -function Base.setindex!(graph::AbstractDataGraph, value, vertex::OrdinalSuffixedInteger) - graph[vertices(graph)[vertex]] = value - return graph -end - -function Base.setindex!( - graph::AbstractDataGraph, - value, - edge::Pair{<:OrdinalSuffixedInteger, <:OrdinalSuffixedInteger}, - ) - graph[edgetype(graph)(vertices(graph)[edge[1]], vertices(graph)[edge[2]])] = value - return graph +function _isassigned(graph::AbstractDataGraph, vertices::AbstractVertices) + return has_vertices_data(graph, vertices) end function induced_subgraph_datagraph(graph::AbstractDataGraph, subvertices) diff --git a/src/indexing.jl b/src/indexing.jl new file mode 100644 index 0000000..f1169da --- /dev/null +++ b/src/indexing.jl @@ -0,0 +1,68 @@ +using NamedGraphs: to_graph_indices, AbstractEdges, AbstractVertices + +function Base.getindex(graph::AbstractDataGraph, indices) + return _getindex(graph, to_graph_indices(graph, indices)) +end + +_getindex(graph::AbstractGraph, vertex) = get_vertex_data(graph, vertex) + +function _getindex(graph::AbstractGraph, edge::AbstractEdge) + data = get_edge_data(graph, arrange_edge(graph, edge)) + return reverse_data_direction(graph, edge, data) +end + +function _getindex(graph::AbstractGraph, vertices::AbstractVertices) + return get_vertices_data(graph, vertices) +end +function _getindex(graph::AbstractGraph, edges::AbstractEdges) + return get_edges_data(graph, edges) +end + +# Support syntax `g[1, 2] = g[(1, 2)]` +function Base.getindex(graph::AbstractDataGraph, i1, i2, i...) + return graph[(i1, i2, i...)] +end + +function Base.setindex!(graph::AbstractDataGraph, data, index) + _setindex!(graph, data, to_graph_indices(graph, index)) + return graph +end + +function _setindex!(graph::AbstractGraph, data, vertex) + set_vertex_data!(graph, data, vertex) + return graph +end + +function _setindex!(graph::AbstractGraph, data, edge::AbstractEdge) + arranged_edge = arrange_edge(graph, edge) + arranged_data = reverse_data_direction(graph, edge, data) + set_edge_data!(graph, arranged_data, arranged_edge) + return graph +end + +function _setindex!(graph::AbstractGraph, val, vertices::AbstractVertices) + return set_vertices_data!(graph, val, vertices) +end +function _setindex!(graph::AbstractGraph, val, edges::AbstractEdges) + return set_edges_data!(graph, val, edges) +end + +# Support syntax `g[1, 2] = g[(1, 2)]` +function Base.setindex!(graph::AbstractDataGraph, x, i1, i2, i...) + graph[(i1, i2, i...)] = x + return graph +end + +# Ordinal Indexing + +function NamedGraphs.to_graph_indices( + graph::AbstractGraph, + pair::Pair{<:OrdinalSuffixedInteger, <:OrdinalSuffixedInteger} + ) + vs = vertices(graph) + v1, v2 = pair + return to_graph_indices(graph, vs[v1] => vs[v2]) +end +function NamedGraphs.to_graph_indices(graph::AbstractGraph, vertex::OrdinalSuffixedInteger) + return to_graph_indices(graph, vertices(graph)[vertex]) +end From 58f0c89f279bd8bf6aa74ad3db0364f746b7a26a Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Thu, 11 Dec 2025 15:59:14 -0500 Subject: [PATCH 17/30] Apply new interface to PartitionedGraphs extension --- .../src/DataGraphsPartitionedGraphsExt.jl | 151 ++++++++++++------ 1 file changed, 104 insertions(+), 47 deletions(-) diff --git a/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl b/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl index 593fae1..c3ea5de 100644 --- a/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl +++ b/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl @@ -5,67 +5,108 @@ using ..DataGraphs: DataGraphs, edge_data, underlying_graph, - vertex_data + vertex_data, + get_vertex_data, + get_edge_data, + set_vertex_data!, + set_edge_data!, + has_vertex_data, + has_edge_data, + has_vertices_data, + has_edges_data, + unset_vertex_data!, + unset_edge_data!, + unset_vertices_data!, + unset_edges_data!, + vertices_data_eltype, + edges_data_eltype +using NamedGraphs: to_graph_indices using NamedGraphs.PartitionedGraphs: PartitionedGraphs, QuotientEdge, + QuotientEdgeEdges, QuotientVertex, + QuotientVertexVertices, QuotientView, partitioned_vertices, quotientvertices, - quotientedges + quotientedges, + parent_graph_type using Dictionaries: Dictionary, Indices -#====INTERFACE FUNCTIONS====# -Base.getindex(g::AbstractGraph, qv::QuotientVertex) = get_quotient_vertex_data(g, qv) -Base.getindex(g::AbstractGraph, qe::QuotientEdge) = get_quotient_edge_data(g, qe) +# Methods to overload if you dont want to use the defaults. +# DataGraphs.has_vertices_data(g::MyGraph, v::QuotientVertexVertices) +# DataGraphs.has_edges_data(g::MyGraph, v::QuotientEdgeEdges) +# +# DataGraphs.get_vertices_data(g::MyGraph, v::QuotientVertexVertices) +# DataGraphs.get_edges_data(g::MyGraph, v::QuotientEdgeEdges) +# +# DataGraphs.set_vertices_data!(g::MyGraph, v::QuotientVertexVertices) +# DataGraphs.set_edges_data!(g::MyGraph, v::QuotientEdgeEdges) +# +# DataGraphs.unset_vertices_data!(g::MyGraph, v::QuotientVertexVertices) +# DataGraphs.unset_edges_data!(g::MyGraph, v::QuotientEdgeEdges) +# +# DataGraphs.vertices_data_eltype(::Type{<:MyGraph}, ::Type{<:QuotientVertexVertices}) +# DataGraphs.edges_data_eltype(::Type{<:MyGraph}, ::Type{<:QuotientVertexVertices}) -# The type of `val` should be the return type of the corresponding `getindex` method. -Base.setindex!(g::AbstractGraph, val, qv::QuotientVertex) = set_quotient_vertex_data!(g, val, qv) -Base.setindex!(g::AbstractGraph, val, qe::QuotientEdge) = set_quotient_edge_data!(g, val, qe) -#====# +to_quotient_indices(vertex) = QuotientVertex(vertex) +to_quotient_indices(edge::AbstractEdge) = QuotientEdge(edge) -# Fallbacks -get_quotient_vertex_data(g::AbstractGraph, qv) = map(v -> g[v], Indices(vertices(g, qv))) -get_quotient_edge_data(g::AbstractGraph, qe) = map(e -> g[e], Indices(edges(g, qe))) - -function set_quotient_vertex_data!(g::AbstractGraph, val, qv) - for v in vertices(g, qv) - g[v] = val[v] - end - return g -end -function set_quotient_edge_data!(g::AbstractGraph, val, qe) - for e in edges(g, qe) - g[e] = val[e] - end - return g +# QuotientView; make sure quotient views of data graphs do data graph indexing. +function Base.getindex(qv::QuotientView{V, <:AbstractDataGraph}, ind) where {V} + return getindex(parent(qv), to_quotient_indices(to_graph_indices(qv, ind))) end -# For ambiguity resolution -Base.getindex(g::AbstractDataGraph, qv::QuotientVertex) = get_quotient_vertex_data(g, qv) -Base.getindex(g::AbstractDataGraph, qe::QuotientEdge) = get_quotient_edge_data(g, qe) +function Base.setindex!(qv::QuotientView{V, <:AbstractDataGraph}, val, ind) where {V} + return setindex!(parent(qv), val, to_quotient_indices(to_graph_indices(qv, ind))) +end -Base.setindex!(g::AbstractDataGraph, val, qv::QuotientVertex) = set_quotient_vertex_data!(g, val, qv) -Base.setindex!(g::AbstractDataGraph, val, qe::QuotientEdge) = set_quotient_edge_data!(g, val, qe) +function DataGraphs.get_vertex_data(qv::QuotientView, v) + return getindex(parent(qv), QuotientVertex(v)) +end +function DataGraphs.get_edge_data(qv::QuotientView, e) + return getindex(parent(qv), QuotientEdge(e)) +end -# QuotientView; make sure quotient views of data graphs do data graph indexing. -Base.getindex(qv::QuotientView{V, <:AbstractDataGraph}, v) where {V} = _getindex(qv, v) -_getindex(qv::QuotientView, v) = getindex(parent(qv), QuotientVertex(v)) -_getindex(qv::QuotientView, e::AbstractEdge) = getindex(parent(qv), QuotientEdge(e)) +function DataGraphs.set_vertex_data!(qv::QuotientView, val, v) + return setindex!(parent(qv), val, QuotientVertex(v)) +end +function DataGraphs.set_edge_data!(qv::QuotientView, val, e) + return setindex!(parent(qv), val, QuotientEdge(e)) +end -Base.setindex!(qv::QuotientView, ve) = _getindex(qv, ve) -_setindex!(qv::QuotientView, val, v) = setindex!(parent(qv), val, QuotientEdge(v)) -_setindex!(qv::QuotientView, val, e::AbstractEdge) = setindex!(parent(qv), val, QuotientEdge(e)) +function DataGraphs.has_vertex_data(qv::QuotientView, v) + pg = parent(qv) + return has_vertices_data(pg, to_graph_indices(pg, QuotientVertex(v))) +end +function DataGraphs.has_edge_data(qv::QuotientView, e) + pg = parent(qv) + return has_edges_data(pg, to_graph_indices(pg, QuotientEdge(e))) +end -DataGraphs.vertex_data_eltype(qv::QuotientView) = DataGraphs.vertex_data_eltype(typeof(qv)) -DataGraphs.vertex_data_eltype(T::Type{<:QuotientView}) = eltype(Base.promote_op(vertex_data, T)) +function DataGraphs.unset_vertex_data!(qv::QuotientView, v) + pg = parent(qv) + return unset_vertices_data!(pg, to_graph_indices(pg, QuotientVertex(v))) +end +function DataGraphs.unset_edge_data!(qv::QuotientView, e) + pg = parent(qv) + return unset_edges_data!(pg, to_graph_indices(pg, QuotientEdge(e))) +end -DataGraphs.edge_data_eltype(qv::QuotientView) = DataGraphs.edge_data_eltype(typeof(qv)) -DataGraphs.edge_data_eltype(T::Type{<:QuotientView}) = eltype(Base.promote_op(edge_data, T)) +function DataGraphs.vertex_data_eltype(T::Type{<:QuotientView}) + return vertices_data_eltype(parent_graph_type(T), QuotientVertexVertices) +end +function DataGraphs.edge_data_eltype(T::Type{<:QuotientView}) + return edges_data_eltype(parent_graph_type(T), QuotientEdgeEdges) +end DataGraphs.underlying_graph(qv::QuotientView) = underlying_graph(copy(qv)) +function Base.isassigned(qv::QuotientView, ind) + return isassigned(parent(qv), to_quotient_indices(to_graph_indices(qv, ind))) +end + # PartitionedGraphs interface function PartitionedGraphs.partitioned_vertices(dg::AbstractDataGraph) return partitioned_vertices(underlying_graph(dg)) @@ -74,14 +115,30 @@ end PartitionedGraphs.partitionedgraph(::AbstractDataGraph, parts) = not_implemented() PartitionedGraphs.departition(::AbstractDataGraph) = not_implemented() -# TODO: These methods wont be necessary once DataGraphs pivots to a `get/setindex`-based interface. -function DataGraphs.vertex_data(g::QuotientView) - vs = Indices(vertices(g)) - return map(v -> getindex(parent(g), QuotientVertex(v)), vs) +function DataGraphs.vertices_data_eltype( + T::Type{<:AbstractGraph}, + ::Type{<:QuotientVertexVertices} + ) + return eltype(Base.promote_op(quotientvertices_data, T)) end -function DataGraphs.edge_data(g::QuotientView) - es = Indices(edges(g)) - return map(e -> getindex(parent(g), QuotientEdge(e)), es) + +function quotientvertices_data(g::AbstractGraph) + qvs = PartitionedGraphs.quotientvertices(g) + data = map(qv -> DataGraphs.get_vertices_data(g, to_graph_indices(g, qv)), qvs) + return data +end + +function DataGraphs.edges_data_eltype( + T::Type{<:AbstractGraph}, + ::Type{<:QuotientEdgeEdges} + ) + return eltype(Base.promote_op(quotientedges_data, T)) +end + +function quotientedges_data(g::AbstractGraph) + qes = PartitionedGraphs.quotientedges(g) + data = map(qe -> DataGraphs.get_edges_data(g, to_graph_indices(g, qe)), qes) + return data end end From 9406bf006a61a25e9392340e3befb406e788f958 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Fri, 12 Dec 2025 13:22:43 -0500 Subject: [PATCH 18/30] Use `quotient_index` function to wrap vertices and edges --- .../src/DataGraphsPartitionedGraphsExt.jl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl b/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl index c3ea5de..ce20037 100644 --- a/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl +++ b/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl @@ -31,7 +31,8 @@ using NamedGraphs.PartitionedGraphs: partitioned_vertices, quotientvertices, quotientedges, - parent_graph_type + parent_graph_type, + quotient_index using Dictionaries: Dictionary, Indices # Methods to overload if you dont want to use the defaults. @@ -50,16 +51,16 @@ using Dictionaries: Dictionary, Indices # DataGraphs.vertices_data_eltype(::Type{<:MyGraph}, ::Type{<:QuotientVertexVertices}) # DataGraphs.edges_data_eltype(::Type{<:MyGraph}, ::Type{<:QuotientVertexVertices}) -to_quotient_indices(vertex) = QuotientVertex(vertex) -to_quotient_indices(edge::AbstractEdge) = QuotientEdge(edge) +PartitionedGraphs.quotient_index(vertex) = QuotientVertex(vertex) +PartitionedGraphs.quotient_index(edge::AbstractEdge) = QuotientEdge(edge) # QuotientView; make sure quotient views of data graphs do data graph indexing. function Base.getindex(qv::QuotientView{V, <:AbstractDataGraph}, ind) where {V} - return getindex(parent(qv), to_quotient_indices(to_graph_indices(qv, ind))) + return getindex(parent(qv), quotient_index(to_graph_indices(qv, ind))) end function Base.setindex!(qv::QuotientView{V, <:AbstractDataGraph}, val, ind) where {V} - return setindex!(parent(qv), val, to_quotient_indices(to_graph_indices(qv, ind))) + return setindex!(parent(qv), val, quotient_index(to_graph_indices(qv, ind))) end function DataGraphs.get_vertex_data(qv::QuotientView, v) @@ -104,7 +105,7 @@ end DataGraphs.underlying_graph(qv::QuotientView) = underlying_graph(copy(qv)) function Base.isassigned(qv::QuotientView, ind) - return isassigned(parent(qv), to_quotient_indices(to_graph_indices(qv, ind))) + return isassigned(parent(qv), quotient_index(to_graph_indices(qv, ind))) end # PartitionedGraphs interface From 3fd47f4dbc40bf1406a348cccc0a70076506cdc0 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Mon, 5 Jan 2026 16:58:10 -0500 Subject: [PATCH 19/30] Move non-abstract function to `abstractdatagraph.jl` --- src/abstractdatagraph.jl | 15 --------------- src/datagraph.jl | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl index 2271330..3139f4c 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -413,21 +413,6 @@ function _isassigned(graph::AbstractDataGraph, vertices::AbstractVertices) return has_vertices_data(graph, vertices) end -function induced_subgraph_datagraph(graph::AbstractDataGraph, subvertices) - underlying_subgraph, vlist = Graphs.induced_subgraph(underlying_graph(graph), subvertices) - subgraph = similar_type(graph)(underlying_subgraph) - for v in vertices(subgraph) - if isassigned(graph, v) - subgraph[v] = graph[v] - end - end - for e in edges(subgraph) - if isassigned(graph, e) - subgraph[e] = graph[e] - end - end - return subgraph, vlist -end function Graphs.induced_subgraph(graph::AbstractDataGraph, subvertices) return induced_subgraph_datagraph(graph, subvertices) end diff --git a/src/datagraph.jl b/src/datagraph.jl index ea959a2..b19d6de 100644 --- a/src/datagraph.jl +++ b/src/datagraph.jl @@ -130,3 +130,23 @@ function GraphsExtensions.directed_graph_type(graph_type::Type{<:DataGraph}) edgetype(graph_type), } end + +function induced_subgraph_datagraph(graph::DataGraph, subvertices) + underlying_subgraph, vlist = Graphs.induced_subgraph(underlying_graph(graph), subvertices) + subgraph = DataGraph( + underlying_subgraph; + vertex_data_eltype = vertex_data_eltype(graph), + edge_data_eltype = edge_data_eltype(graph), + ) + for v in vertices(subgraph) + if isassigned(graph, v) + subgraph[v] = graph[v] + end + end + for e in edges(subgraph) + if isassigned(graph, e) + subgraph[e] = graph[e] + end + end + return subgraph, vlist +end From 05ed524254db3676eff7e0b3e4b207350218cf14 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Mon, 5 Jan 2026 16:59:14 -0500 Subject: [PATCH 20/30] Update PartitionedGraphs extensions --- src/abstractdatagraph.jl | 18 ++-- src/indexing.jl | 14 +-- .../src/DataGraphsPartitionedGraphsExt.jl | 85 +++++++++++-------- 3 files changed, 71 insertions(+), 46 deletions(-) diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl index 3139f4c..3f3334f 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -65,16 +65,24 @@ underlying_graph_type(T::Type{<:AbstractGraph}) = Base.promote_op(underlying_gra has_vertices_data(g::AbstractGraph, vertices) = all(v -> has_vertex_data(g, v), vertices) has_edges_data(g::AbstractGraph, edges) = all(e -> has_edge_data(g, e), edges) -function get_vertices_data(g::AbstractGraph, vertices) - return map(v -> get_vertex_data(g, v), Indices(vertices)) +# Interface function +get_vertices_data(g::AbstractGraph, vertices) = _get_vertices_data(g, vertices) + +# For defining default behaviour +function _get_vertices_data(g::AbstractGraph, vertices) + return map(v -> getindex(g, v), Indices(vertices)) end + function vertices_data_eltype(G::Type{<:AbstractGraph}, V::Type{<:AbstractVertices}) return Dictionary{eltype(V), vertex_data_eltype(G)} end -function get_edges_data(g::AbstractGraph, edges) - return map(e -> get_edge_data(g, e), Indices(edges)) +get_edges_data(g::AbstractGraph, edges) = _get_edges_data(g, edges) + +function _get_edges_data(g::AbstractGraph, edges) + return map(e -> getindex(g, e), Indices(edges)) end + function edges_data_eltype(G::Type{<:AbstractGraph}, E::Type{<:AbstractEdges{V, ET} where {V, ET}}) return Dictionary{eltype(E), edge_data_eltype(G)} end @@ -400,7 +408,7 @@ function Base.get(default::Base.Callable, graph::AbstractDataGraph, key) end function Base.isassigned(graph::AbstractDataGraph, index) - return _isassigned(graph, to_graph_indices(graph, index)) + return _isassigned(graph, to_graph_index(graph, index)) end _isassigned(graph::AbstractDataGraph, vertex) = has_vertex_data(graph, vertex) function _isassigned(graph::AbstractDataGraph, edge::AbstractEdge) diff --git a/src/indexing.jl b/src/indexing.jl index f1169da..dcecc43 100644 --- a/src/indexing.jl +++ b/src/indexing.jl @@ -1,7 +1,7 @@ -using NamedGraphs: to_graph_indices, AbstractEdges, AbstractVertices +using NamedGraphs: to_graph_index, AbstractEdges, AbstractVertices function Base.getindex(graph::AbstractDataGraph, indices) - return _getindex(graph, to_graph_indices(graph, indices)) + return _getindex(graph, to_graph_index(graph, indices)) end _getindex(graph::AbstractGraph, vertex) = get_vertex_data(graph, vertex) @@ -24,7 +24,7 @@ function Base.getindex(graph::AbstractDataGraph, i1, i2, i...) end function Base.setindex!(graph::AbstractDataGraph, data, index) - _setindex!(graph, data, to_graph_indices(graph, index)) + _setindex!(graph, data, to_graph_index(graph, index)) return graph end @@ -55,14 +55,14 @@ end # Ordinal Indexing -function NamedGraphs.to_graph_indices( +function NamedGraphs.to_graph_index( graph::AbstractGraph, pair::Pair{<:OrdinalSuffixedInteger, <:OrdinalSuffixedInteger} ) vs = vertices(graph) v1, v2 = pair - return to_graph_indices(graph, vs[v1] => vs[v2]) + return to_graph_index(graph, vs[v1] => vs[v2]) end -function NamedGraphs.to_graph_indices(graph::AbstractGraph, vertex::OrdinalSuffixedInteger) - return to_graph_indices(graph, vertices(graph)[vertex]) +function NamedGraphs.to_graph_index(graph::AbstractGraph, vertex::OrdinalSuffixedInteger) + return to_graph_index(graph, vertices(graph)[vertex]) end diff --git a/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl b/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl index ce20037..c7509fa 100644 --- a/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl +++ b/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl @@ -1,13 +1,18 @@ module DataGraphsPartitionedGraphsExt +using NamedGraphs.PartitionedGraphs: quotient_graph_vertextype using Graphs: Graphs, AbstractGraph, AbstractEdge, vertices, edges using ..DataGraphs: + _getindex, AbstractDataGraph, DataGraphs, edge_data, + edgetype, underlying_graph, vertex_data, get_vertex_data, get_edge_data, + get_vertices_data, + get_edges_data, set_vertex_data!, set_edge_data!, has_vertex_data, @@ -20,13 +25,19 @@ using ..DataGraphs: unset_edges_data!, vertices_data_eltype, edges_data_eltype -using NamedGraphs: to_graph_indices +using NamedGraphs: to_graph_index, parent_graph_indices, Vertices, Edges +using NamedGraphs.GraphsExtensions: vertextype using NamedGraphs.PartitionedGraphs: PartitionedGraphs, QuotientEdge, + QuotientEdgeEdge, + QuotientEdges, QuotientEdgeEdges, QuotientVertex, + QuotientVertexVertex, QuotientVertexVertices, + QuotientVerticesVertices, + QuotientVertices, QuotientView, partitioned_vertices, quotientvertices, @@ -51,25 +62,37 @@ using Dictionaries: Dictionary, Indices # DataGraphs.vertices_data_eltype(::Type{<:MyGraph}, ::Type{<:QuotientVertexVertices}) # DataGraphs.edges_data_eltype(::Type{<:MyGraph}, ::Type{<:QuotientVertexVertices}) -PartitionedGraphs.quotient_index(vertex) = QuotientVertex(vertex) -PartitionedGraphs.quotient_index(edge::AbstractEdge) = QuotientEdge(edge) - -# QuotientView; make sure quotient views of data graphs do data graph indexing. +# QuotientView; make sure quotient views of data graphs do data graph indexing +# and not subgraph indexing. function Base.getindex(qv::QuotientView{V, <:AbstractDataGraph}, ind) where {V} - return getindex(parent(qv), quotient_index(to_graph_indices(qv, ind))) + return DataGraphs._getindex(qv, to_graph_index(qv, ind)) end function Base.setindex!(qv::QuotientView{V, <:AbstractDataGraph}, val, ind) where {V} - return setindex!(parent(qv), val, quotient_index(to_graph_indices(qv, ind))) + return DataGraphs._setindex!(qv, val, to_graph_index(qv, ind)) +end + +DataGraphs.get_vertex_data(qv::QuotientView, v) = getindex(parent(qv), QuotientVertex(v)) +DataGraphs.get_edge_data(qv::QuotientView, e) = getindex(parent(qv), QuotientEdge(e)) + +function DataGraphs._get_vertices_data(g::AbstractGraph, v::QuotientVertexVertices) + return getindex(g, Vertices(parent_graph_indices(v))) end -function DataGraphs.get_vertex_data(qv::QuotientView, v) - return getindex(parent(qv), QuotientVertex(v)) +function DataGraphs._get_vertices_data(g::AbstractGraph, v::QuotientVerticesVertices) + return getindex(g, Vertices(parent_graph_indices(v))) end -function DataGraphs.get_edge_data(qv::QuotientView, e) - return getindex(parent(qv), QuotientEdge(e)) + +function DataGraphs._get_edges_data(g::AbstractGraph, e::QuotientEdgeEdges) + return getindex(g, Edges(parent_graph_indices(e))) end + +# function DataGraphs.get_vertices_data(qv::QuotientView, vertices::AbstractGraphIndices) +# inds = Indices(parent_graph_indices(vertices)) +# return map(v -> getindex(qv, v), inds) +# end + function DataGraphs.set_vertex_data!(qv::QuotientView, val, v) return setindex!(parent(qv), val, QuotientVertex(v)) end @@ -79,33 +102,39 @@ end function DataGraphs.has_vertex_data(qv::QuotientView, v) pg = parent(qv) - return has_vertices_data(pg, to_graph_indices(pg, QuotientVertex(v))) + return has_vertices_data(pg, to_graph_index(pg, QuotientVertex(v))) end function DataGraphs.has_edge_data(qv::QuotientView, e) pg = parent(qv) - return has_edges_data(pg, to_graph_indices(pg, QuotientEdge(e))) + return has_edges_data(pg, to_graph_index(pg, QuotientEdge(e))) end function DataGraphs.unset_vertex_data!(qv::QuotientView, v) pg = parent(qv) - return unset_vertices_data!(pg, to_graph_indices(pg, QuotientVertex(v))) + return unset_vertices_data!(pg, to_graph_index(pg, QuotientVertex(v))) end function DataGraphs.unset_edge_data!(qv::QuotientView, e) pg = parent(qv) - return unset_edges_data!(pg, to_graph_indices(pg, QuotientEdge(e))) + return unset_edges_data!(pg, to_graph_index(pg, QuotientEdge(e))) end +# TODO: eltype -> type? function DataGraphs.vertex_data_eltype(T::Type{<:QuotientView}) - return vertices_data_eltype(parent_graph_type(T), QuotientVertexVertices) + PGT = parent_graph_type(T) + return Base.promote_op(getindex, PGT, QuotientVertex{vertextype(T)}) end function DataGraphs.edge_data_eltype(T::Type{<:QuotientView}) - return edges_data_eltype(parent_graph_type(T), QuotientEdgeEdges) + PGT = parent_graph_type(T) + return Base.promote_op(getindex, PGT, QuotientEdge{vertextype(T), edgetype(T)}) end +get_quotient_vertices_data(g::AbstractGraph) = get_vertices_data(g, QuotientVertices(g)) +get_quotient_edges_data(g::AbstractGraph) = get_edges_data(g, QuotientEdges(g)) + DataGraphs.underlying_graph(qv::QuotientView) = underlying_graph(copy(qv)) function Base.isassigned(qv::QuotientView, ind) - return isassigned(parent(qv), quotient_index(to_graph_indices(qv, ind))) + return isassigned(parent(qv), quotient_index(to_graph_index(qv, ind))) end # PartitionedGraphs interface @@ -118,28 +147,16 @@ PartitionedGraphs.departition(::AbstractDataGraph) = not_implemented() function DataGraphs.vertices_data_eltype( T::Type{<:AbstractGraph}, - ::Type{<:QuotientVertexVertices} + QV::Type{<:QuotientVertices} ) - return eltype(Base.promote_op(quotientvertices_data, T)) -end - -function quotientvertices_data(g::AbstractGraph) - qvs = PartitionedGraphs.quotientvertices(g) - data = map(qv -> DataGraphs.get_vertices_data(g, to_graph_indices(g, qv)), qvs) - return data + return eltype(Base.promote_op(get_vertices_data, T, QV)) end function DataGraphs.edges_data_eltype( T::Type{<:AbstractGraph}, - ::Type{<:QuotientEdgeEdges} + QE::Type{<:QuotientEdges} ) - return eltype(Base.promote_op(quotientedges_data, T)) -end - -function quotientedges_data(g::AbstractGraph) - qes = PartitionedGraphs.quotientedges(g) - data = map(qe -> DataGraphs.get_edges_data(g, to_graph_indices(g, qe)), qes) - return data + return eltype(Base.promote_op(get_edges_data, T, QE)) end end From be9c19a2df1b68951f2fd51edb7ddcfc19dce005 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Fri, 9 Jan 2026 14:27:46 -0500 Subject: [PATCH 21/30] Fix tokenized setindex! for data view objects. --- src/dataview.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dataview.jl b/src/dataview.jl index 5ab8a89..fffbfae 100644 --- a/src/dataview.jl +++ b/src/dataview.jl @@ -49,7 +49,7 @@ function Base.setindex!(view::VertexOrEdgeDataView{K, V}, data::V, key::K) where return view end function Dictionaries.settokenvalue!(view::VertexOrEdgeDataView{<:Any, T}, token, value::T) where {T} - setindex!(view, value, Dictionaries.gettokenvalue(view, token)) + setindex!(view, value, gettokenvalue(keys(view), token)) return view end From 1cad908d5a81c3ab57218630fb91bd19c1b79734 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Fri, 9 Jan 2026 16:44:34 -0500 Subject: [PATCH 22/30] Version bump and NamedGraphs compat. --- Project.toml | 4 ++-- docs/Project.toml | 2 +- examples/Project.toml | 4 ++-- test/Project.toml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Project.toml b/Project.toml index 54865da..e17be8c 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "DataGraphs" uuid = "b5a273c3-7e6c-41f6-98bd-8d7f1525a36a" authors = ["Matthew Fishman and contributors"] -version = "0.2.13" +version = "0.3.0" [deps] Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4" @@ -19,7 +19,7 @@ DataGraphsGraphsFlowsExt = "GraphsFlows" Dictionaries = "0.4" Graphs = "1" GraphsFlows = "0.1.1" -NamedGraphs = "0.8.2" +NamedGraphs = "0.9.0" SimpleTraits = "0.9" julia = "1.7" diff --git a/docs/Project.toml b/docs/Project.toml index a1ebe66..7fb8a13 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -4,6 +4,6 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306" [compat] -DataGraphs = "0.2.6" +DataGraphs = "0.3.0" Documenter = "1.10.0" Literate = "2.20.1" diff --git a/examples/Project.toml b/examples/Project.toml index e293680..8fe8f5e 100644 --- a/examples/Project.toml +++ b/examples/Project.toml @@ -4,6 +4,6 @@ Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" NamedGraphs = "678767b0-92e7-4007-89e4-4527a8725b19" [compat] -DataGraphs = "0.2.6" +DataGraphs = "0.3.0" Graphs = "1.12" -NamedGraphs = "0.6.5, 0.7, 0.8" +NamedGraphs = "0.9.0" diff --git a/test/Project.toml b/test/Project.toml index 2504a50..0163485 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -11,11 +11,11 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] Aqua = "0.8.11" -DataGraphs = "0.2.6" +DataGraphs = "0.3.0" Dictionaries = "0.4.4" Graphs = "1.12" GraphsFlows = "0.1.1" -NamedGraphs = "0.8.2" +NamedGraphs = "0.9.0" SafeTestsets = "0.1" Suppressor = "0.2.8" Test = "1.10" From 260035a8b4d79684b8b054f5b493a3e6992373e5 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Mon, 12 Jan 2026 17:45:20 -0500 Subject: [PATCH 23/30] Use correct `to_quotient_index` instead of `quotient_index`. --- .../src/DataGraphsPartitionedGraphsExt.jl | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl b/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl index c7509fa..56cf19b 100644 --- a/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl +++ b/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl @@ -43,7 +43,7 @@ using NamedGraphs.PartitionedGraphs: quotientvertices, quotientedges, parent_graph_type, - quotient_index + to_quotient_index using Dictionaries: Dictionary, Indices # Methods to overload if you dont want to use the defaults. @@ -87,12 +87,6 @@ function DataGraphs._get_edges_data(g::AbstractGraph, e::QuotientEdgeEdges) return getindex(g, Edges(parent_graph_indices(e))) end - -# function DataGraphs.get_vertices_data(qv::QuotientView, vertices::AbstractGraphIndices) -# inds = Indices(parent_graph_indices(vertices)) -# return map(v -> getindex(qv, v), inds) -# end - function DataGraphs.set_vertex_data!(qv::QuotientView, val, v) return setindex!(parent(qv), val, QuotientVertex(v)) end @@ -134,7 +128,7 @@ get_quotient_edges_data(g::AbstractGraph) = get_edges_data(g, QuotientEdges(g)) DataGraphs.underlying_graph(qv::QuotientView) = underlying_graph(copy(qv)) function Base.isassigned(qv::QuotientView, ind) - return isassigned(parent(qv), quotient_index(to_graph_index(qv, ind))) + return isassigned(parent(qv), to_quotient_index(to_graph_index(qv, ind))) end # PartitionedGraphs interface From 61e2f1d2d4e4eb582571fb8e52ff1b452d4e6b3a Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Mon, 12 Jan 2026 17:45:56 -0500 Subject: [PATCH 24/30] Indexing with `AbstractGraphIndices` now returns a subgraph. --- src/indexing.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/indexing.jl b/src/indexing.jl index dcecc43..c0eab22 100644 --- a/src/indexing.jl +++ b/src/indexing.jl @@ -1,4 +1,5 @@ using NamedGraphs: to_graph_index, AbstractEdges, AbstractVertices +using NamedGraphs.GraphsExtensions: subgraph function Base.getindex(graph::AbstractDataGraph, indices) return _getindex(graph, to_graph_index(graph, indices)) @@ -12,10 +13,10 @@ function _getindex(graph::AbstractGraph, edge::AbstractEdge) end function _getindex(graph::AbstractGraph, vertices::AbstractVertices) - return get_vertices_data(graph, vertices) + return subgraph(graph, vertices) end function _getindex(graph::AbstractGraph, edges::AbstractEdges) - return get_edges_data(graph, edges) + return subgraph(graph, edges) end # Support syntax `g[1, 2] = g[(1, 2)]` From f70e95d3435584af798c32bb15bb73d228f8e389 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Mon, 12 Jan 2026 17:50:50 -0500 Subject: [PATCH 25/30] Fix: `getindex` now constructs a `edge_subgraph` when given edges --- src/indexing.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/indexing.jl b/src/indexing.jl index c0eab22..341716a 100644 --- a/src/indexing.jl +++ b/src/indexing.jl @@ -1,5 +1,5 @@ using NamedGraphs: to_graph_index, AbstractEdges, AbstractVertices -using NamedGraphs.GraphsExtensions: subgraph +using NamedGraphs.GraphsExtensions: subgraph, edge_subgraph function Base.getindex(graph::AbstractDataGraph, indices) return _getindex(graph, to_graph_index(graph, indices)) @@ -16,7 +16,7 @@ function _getindex(graph::AbstractGraph, vertices::AbstractVertices) return subgraph(graph, vertices) end function _getindex(graph::AbstractGraph, edges::AbstractEdges) - return subgraph(graph, edges) + return edge_subgraph(graph, edges) end # Support syntax `g[1, 2] = g[(1, 2)]` From 446a0e202518dd6ca88fa96a4f65b4641ef86ffa Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Tue, 13 Jan 2026 09:40:13 -0500 Subject: [PATCH 26/30] Indexing with multiple edges is now undefined, throwing a method error This is to be enabled once `edges_subgraph` is defined for data graphs. --- src/indexing.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/indexing.jl b/src/indexing.jl index 341716a..4217942 100644 --- a/src/indexing.jl +++ b/src/indexing.jl @@ -16,7 +16,7 @@ function _getindex(graph::AbstractGraph, vertices::AbstractVertices) return subgraph(graph, vertices) end function _getindex(graph::AbstractGraph, edges::AbstractEdges) - return edge_subgraph(graph, edges) + return throw(MethodError(_getindex, (graph, edges))) end # Support syntax `g[1, 2] = g[(1, 2)]` From ebe0e933d9860b1303f137a8419d7cfb30769a4b Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Tue, 13 Jan 2026 09:42:37 -0500 Subject: [PATCH 27/30] Remove duplicate method by using union type QuotientVertexOrVerticesVertices --- .../src/DataGraphsPartitionedGraphsExt.jl | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl b/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl index 56cf19b..cbfb0ff 100644 --- a/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl +++ b/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl @@ -37,6 +37,7 @@ using NamedGraphs.PartitionedGraphs: QuotientVertexVertex, QuotientVertexVertices, QuotientVerticesVertices, + QuotientVertexOrVerticesVertices, QuotientVertices, QuotientView, partitioned_vertices, @@ -75,11 +76,7 @@ end DataGraphs.get_vertex_data(qv::QuotientView, v) = getindex(parent(qv), QuotientVertex(v)) DataGraphs.get_edge_data(qv::QuotientView, e) = getindex(parent(qv), QuotientEdge(e)) -function DataGraphs._get_vertices_data(g::AbstractGraph, v::QuotientVertexVertices) - return getindex(g, Vertices(parent_graph_indices(v))) -end - -function DataGraphs._get_vertices_data(g::AbstractGraph, v::QuotientVerticesVertices) +function DataGraphs._get_vertices_data(g::AbstractGraph, v::QuotientVertexOrVerticesVertices) return getindex(g, Vertices(parent_graph_indices(v))) end From f25331d8bfcff6fa56a686f439eb86140ab82773 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Tue, 13 Jan 2026 09:50:21 -0500 Subject: [PATCH 28/30] Rename `vertex_data_eltype` -> `vertex_data_type`, and similarly for `edge_data_eltype` --- examples/datagraph.jl | 2 +- examples/disjoint_union.jl | 2 +- examples/multidimdatagraph_1d.jl | 2 +- examples/multidimdatagraph_2d.jl | 2 +- examples/slicing.jl | 2 +- src/abstractdatagraph.jl | 16 +++++----- src/datagraph.jl | 18 +++++------ src/dataview.jl | 4 +-- .../src/DataGraphsPartitionedGraphsExt.jl | 4 +-- test/test_basics.jl | 30 +++++++++---------- 10 files changed, 41 insertions(+), 41 deletions(-) diff --git a/examples/datagraph.jl b/examples/datagraph.jl index 4ba6ddb..c1becea 100644 --- a/examples/datagraph.jl +++ b/examples/datagraph.jl @@ -4,7 +4,7 @@ using NamedGraphs: NamedEdge using NamedGraphs.NamedGraphGenerators: named_grid g = named_grid((4)) -dg = DataGraph(g; vertex_data_eltype = String, edge_data_eltype = Symbol) +dg = DataGraph(g; vertex_data_type = String, edge_data_type = Symbol) @show !isassigned(dg, NamedEdge(1, 2)) @show !isassigned(dg, 1 => 2) @show !isassigned(dg, NamedEdge(1 => 2)) diff --git a/examples/disjoint_union.jl b/examples/disjoint_union.jl index 0a5dce7..034867f 100644 --- a/examples/disjoint_union.jl +++ b/examples/disjoint_union.jl @@ -3,7 +3,7 @@ using NamedGraphs.GraphsExtensions: ⊔ using NamedGraphs.NamedGraphGenerators: named_grid using DataGraphs: DataGraph -g = DataGraph(named_grid((2, 2)); vertex_data_eltype = String, edge_data_eltype = String) +g = DataGraph(named_grid((2, 2)); vertex_data_type = String, edge_data_type = String) for v in vertices(g) g[v] = "V$v" diff --git a/examples/multidimdatagraph_1d.jl b/examples/multidimdatagraph_1d.jl index 91eedd9..ed90e2e 100644 --- a/examples/multidimdatagraph_1d.jl +++ b/examples/multidimdatagraph_1d.jl @@ -3,7 +3,7 @@ using Graphs: grid, has_edge, has_vertex using NamedGraphs: NamedGraph, NamedEdge g = NamedGraph(grid((4,)), ["A", "B", "C", "D"]) -dg = DataGraph(g; vertex_data_eltype = String, edge_data_eltype = Symbol) +dg = DataGraph(g; vertex_data_type = String, edge_data_type = Symbol) @show has_vertex(dg, "A") @show has_vertex(dg, "D") diff --git a/examples/multidimdatagraph_2d.jl b/examples/multidimdatagraph_2d.jl index 6134bbb..ef689e9 100644 --- a/examples/multidimdatagraph_2d.jl +++ b/examples/multidimdatagraph_2d.jl @@ -3,7 +3,7 @@ using NamedGraphs: NamedEdge using NamedGraphs.NamedGraphGenerators: named_grid g = named_grid((2, 2)) -dg = DataGraph(g; vertex_data_eltype = String, edge_data_eltype = String) +dg = DataGraph(g; vertex_data_type = String, edge_data_type = String) dg[1, 1] = "X11" diff --git a/examples/slicing.jl b/examples/slicing.jl index b4d7cbd..a652e28 100644 --- a/examples/slicing.jl +++ b/examples/slicing.jl @@ -4,7 +4,7 @@ using NamedGraphs.NamedGraphGenerators: named_grid using Graphs: ne, nv g = named_grid((2, 2)) -dg = DataGraph(g; vertex_data_eltype = String, edge_data_eltype = String) +dg = DataGraph(g; vertex_data_type = String, edge_data_type = String) dg[1, 1] = "V11" dg[1, 2] = "V12" diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl index 3f3334f..ebda1a6 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -34,11 +34,11 @@ is_underlying_graph(::Type{<:AbstractNamedGraph}) = true abstract type AbstractDataGraph{V, VD, ED} <: AbstractNamedGraph{V} end -vertex_data_eltype(::Type{<:AbstractGraph}) = Any -edge_data_eltype(::Type{<:AbstractGraph}) = Any +vertex_data_type(::Type{<:AbstractGraph}) = Any +edge_data_type(::Type{<:AbstractGraph}) = Any -vertex_data_eltype(::Type{<:AbstractDataGraph{V, VD, ED}}) where {V, VD, ED} = VD -edge_data_eltype(::Type{<:AbstractDataGraph{V, VD, ED}}) where {V, VD, ED} = ED +vertex_data_type(::Type{<:AbstractDataGraph{V, VD, ED}}) where {V, VD, ED} = VD +edge_data_type(::Type{<:AbstractDataGraph{V, VD, ED}}) where {V, VD, ED} = ED # Minimal interface # TODO: Define for `AbstractGraph` as a `DataGraphInterface`. @@ -74,7 +74,7 @@ function _get_vertices_data(g::AbstractGraph, vertices) end function vertices_data_eltype(G::Type{<:AbstractGraph}, V::Type{<:AbstractVertices}) - return Dictionary{eltype(V), vertex_data_eltype(G)} + return Dictionary{eltype(V), vertex_data_type(G)} end get_edges_data(g::AbstractGraph, edges) = _get_edges_data(g, edges) @@ -84,7 +84,7 @@ function _get_edges_data(g::AbstractGraph, edges) end function edges_data_eltype(G::Type{<:AbstractGraph}, E::Type{<:AbstractEdges{V, ET} where {V, ET}}) - return Dictionary{eltype(E), edge_data_eltype(G)} + return Dictionary{eltype(E), edge_data_type(G)} end function set_vertices_data!(g::AbstractGraph, val, vertices) @@ -138,8 +138,8 @@ function Graphs.is_directed(graph_type::Type{<:AbstractDataGraph}) end underlying_graph_type(graph::AbstractGraph) = typeof(underlying_graph(graph)) -vertex_data_eltype(graph::AbstractGraph) = vertex_data_eltype(typeof(graph)) -edge_data_eltype(graph::AbstractGraph) = edge_data_eltype(typeof(graph)) +vertex_data_type(graph::AbstractGraph) = vertex_data_type(typeof(graph)) +edge_data_type(graph::AbstractGraph) = edge_data_type(typeof(graph)) function NamedGraphs.position_graph_type(type::Type{<:AbstractDataGraph}) return position_graph_type(underlying_graph_type(type)) diff --git a/src/datagraph.jl b/src/datagraph.jl index b19d6de..60df6ad 100644 --- a/src/datagraph.jl +++ b/src/datagraph.jl @@ -61,8 +61,8 @@ function unset_edge_data!(dg::DataGraph, edge) end underlying_graph_type(G::Type{<:DataGraph}) = fieldtype(G, :underlying_graph) -vertex_data_eltype(G::Type{<:DataGraph}) = eltype(fieldtype(G, :vertex_data)) -edge_data_eltype(G::Type{<:DataGraph}) = eltype(fieldtype(G, :edge_data)) +vertex_data_type(G::Type{<:DataGraph}) = eltype(fieldtype(G, :vertex_data)) +edge_data_type(G::Type{<:DataGraph}) = eltype(fieldtype(G, :edge_data)) # Extras @@ -80,13 +80,13 @@ function Base.copy(graph::DataGraph) end function DataGraph{V}( - underlying_graph::AbstractGraph; vertex_data_eltype::Type = Any, edge_data_eltype::Type = Any + underlying_graph::AbstractGraph; vertex_data_type::Type = Any, edge_data_type::Type = Any ) where {V} converted_underlying_graph = convert_vertextype(V, underlying_graph) return _DataGraph( converted_underlying_graph, - Dictionary{vertextype(converted_underlying_graph), vertex_data_eltype}(), - Dictionary{edgetype(converted_underlying_graph), edge_data_eltype}(), + Dictionary{vertextype(converted_underlying_graph), vertex_data_type}(), + Dictionary{edgetype(converted_underlying_graph), edge_data_type}(), ) end @@ -124,8 +124,8 @@ end function GraphsExtensions.directed_graph_type(graph_type::Type{<:DataGraph}) return DataGraph{ vertextype(graph_type), - vertex_data_eltype(graph_type), - edge_data_eltype(graph_type), + vertex_data_type(graph_type), + edge_data_type(graph_type), directed_graph_type(underlying_graph_type(graph_type)), edgetype(graph_type), } @@ -135,8 +135,8 @@ function induced_subgraph_datagraph(graph::DataGraph, subvertices) underlying_subgraph, vlist = Graphs.induced_subgraph(underlying_graph(graph), subvertices) subgraph = DataGraph( underlying_subgraph; - vertex_data_eltype = vertex_data_eltype(graph), - edge_data_eltype = edge_data_eltype(graph), + vertex_data_type = vertex_data_type(graph), + edge_data_type = edge_data_type(graph), ) for v in vertices(subgraph) if isassigned(graph, v) diff --git a/src/dataview.jl b/src/dataview.jl index fffbfae..e61379e 100644 --- a/src/dataview.jl +++ b/src/dataview.jl @@ -4,7 +4,7 @@ struct VertexDataView{V, VD, G <: AbstractGraph{V}} <: AbstractDictionary{V, VD} graph::G function VertexDataView(graph) V = vertextype(graph) - VD = vertex_data_eltype(graph) + VD = vertex_data_type(graph) G = typeof(graph) return new{V, VD, G}(graph) end @@ -16,7 +16,7 @@ struct EdgeDataView{E, ED, G <: AbstractGraph} <: AbstractDictionary{E, ED} graph::G function EdgeDataView(graph) E = edgetype(graph) - ED = edge_data_eltype(graph) + ED = edge_data_type(graph) G = typeof(graph) return new{E, ED, G}(graph) end diff --git a/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl b/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl index cbfb0ff..bf65530 100644 --- a/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl +++ b/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl @@ -110,11 +110,11 @@ function DataGraphs.unset_edge_data!(qv::QuotientView, e) end # TODO: eltype -> type? -function DataGraphs.vertex_data_eltype(T::Type{<:QuotientView}) +function DataGraphs.vertex_data_type(T::Type{<:QuotientView}) PGT = parent_graph_type(T) return Base.promote_op(getindex, PGT, QuotientVertex{vertextype(T)}) end -function DataGraphs.edge_data_eltype(T::Type{<:QuotientView}) +function DataGraphs.edge_data_type(T::Type{<:QuotientView}) PGT = parent_graph_type(T) return Base.promote_op(getindex, PGT, QuotientEdge{vertextype(T), edgetype(T)}) end diff --git a/test/test_basics.jl b/test/test_basics.jl index f826dd0..705deea 100644 --- a/test/test_basics.jl +++ b/test/test_basics.jl @@ -4,10 +4,10 @@ using DataGraphs: EdgeDataView, VertexDataView, edge_data, - edge_data_eltype, + edge_data_type, underlying_graph, vertex_data, - vertex_data_eltype + vertex_data_type using Dictionaries: AbstractIndices, Dictionary, Indices, dictionary using Graphs: add_edge!, @@ -55,7 +55,7 @@ using Test: @test, @test_broken, @testset @testset "Basics" begin g = named_grid(4) - dg = DataGraph(g; vertex_data_eltype = String, edge_data_eltype = Symbol) + dg = DataGraph(g; vertex_data_type = String, edge_data_type = Symbol) @test !isassigned(dg, NamedEdge(1, 2)) @test !isassigned(dg, 1 => 2) @test !isassigned(dg, NamedEdge(1 => 2)) @@ -112,7 +112,7 @@ using Test: @test, @test_broken, @testset # TODO: Make a more compact constructor that directly accepts # vertex and edge data? Maybe `DataGraph(g; vertex_data=vdata, edge_data=edata)` # or `DataGraph(g; vertex_data=v -> "V$v", edge_data=e -> "E$(src(e))$(dst(e))")`. - dg = DataGraph(g; vertex_data_eltype = eltype(vdata), edge_data_eltype = eltype(edata)) + dg = DataGraph(g; vertex_data_type = eltype(vdata), edge_data_type = eltype(edata)) for v in vertices(dg) dg[v] = vdata[v] end @@ -135,8 +135,8 @@ using Test: @test, @test_broken, @testset DataGraph{UInt16, String, String, NamedGraph{UInt16}, NamedEdge{UInt16}} @test vertextype(dg_uint16) === UInt16 @test edgetype(dg_uint16) === NamedEdge{UInt16} - @test vertex_data_eltype(dg_uint16) === String - @test edge_data_eltype(dg_uint16) === String + @test vertex_data_type(dg_uint16) === String + @test edge_data_type(dg_uint16) === String @test dg_uint16[1] == "V1" @test dg_uint16[2] == "V2" @test dg_uint16[3] == "V3" @@ -181,7 +181,7 @@ using Test: @test, @test_broken, @testset @testset "get and get! functions" begin g = named_grid(4) - dg = DataGraph(g; vertex_data_eltype = String, edge_data_eltype = Symbol) + dg = DataGraph(g; vertex_data_type = String, edge_data_type = Symbol) # Test for vertices @test get(dg, 1, "default") == "default" @@ -236,14 +236,14 @@ using Test: @test, @test_broken, @testset @testset "Constructors specifying vertex type" begin dg = DataGraph{Float64}( - named_path_graph(4); vertex_data_eltype = String, edge_data_eltype = Symbol + named_path_graph(4); vertex_data_type = String, edge_data_type = Symbol ) @test nv(dg) == 4 @test ne(dg) == 3 @test edgetype(dg) === NamedEdge{Float64} @test vertextype(dg) === Float64 - @test vertex_data_eltype(dg) === String - @test edge_data_eltype(dg) === Symbol + @test vertex_data_type(dg) === String + @test edge_data_type(dg) === Symbol @test issetequal(vertices(dg), Float64.(1:4)) @test vertices(dg) isa AbstractIndices{Float64} @test eltype(vertices(dg)) === Float64 @@ -259,7 +259,7 @@ using Test: @test, @test_broken, @testset end @testset "Disjoint unions" begin - g = DataGraph(named_grid((2, 2)); vertex_data_eltype = String, edge_data_eltype = String) + g = DataGraph(named_grid((2, 2)); vertex_data_type = String, edge_data_type = String) for v in vertices(g) g[v] = "V$v" @@ -447,8 +447,8 @@ using Test: @test, @test_broken, @testset @testset "OrdinalIndexing" begin g = DataGraph( NamedGraph(path_graph(3), ["a", "b", "c"]); - vertex_data_eltype = String, - edge_data_eltype = Symbol, + vertex_data_type = String, + edge_data_type = Symbol, ) g[1st] = "v_a" g[2nd] = "v_b" @@ -477,8 +477,8 @@ using Test: @test, @test_broken, @testset @testset "Data views" begin g = DataGraph( NamedGraph(path_graph(3), ["a", "b", "c"]); - vertex_data_eltype = Int, - edge_data_eltype = Float64 + vertex_data_type = Int, + edge_data_type = Float64 ) g["b"] = 2 g["c"] = 3 From 90cf068914ca1e0e2a44a26c0949b790cf479244 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Tue, 13 Jan 2026 12:32:31 -0500 Subject: [PATCH 29/30] Can now assign to `VertexDataView` and `EdgeDataView` using `.=` syntax. This will update the underlying graph. --- src/dataview.jl | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/dataview.jl b/src/dataview.jl index e61379e..6dcd1cd 100644 --- a/src/dataview.jl +++ b/src/dataview.jl @@ -61,3 +61,20 @@ function Dictionaries.deletetoken!(view::VertexOrEdgeDataView, token) Dictionaries.unset!(view.graph, gettokenvalue(keys(view), token)) return view end + +Base.size(view::VertexDataView) = (nv(view.graph),) +Base.size(view::EdgeDataView) = (ne(view.graph),) + +function Base.copyto!(dest::VertexDataView, bc::Base.Broadcast.Broadcasted) + for (i, vertex) in enumerate(vertices(dest.graph)) + dest[vertex] = bc[i] + end + return dest +end + +function Base.copyto!(dest::EdgeDataView, bc::Base.Broadcast.Broadcasted) + for (i, edge) in enumerate(edges(dest.graph)) + dest[edge] = bc[i] + end + return dest +end From cccea6cfa9419c5e0c7f29caf3cc98144e58b415 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Tue, 27 Jan 2026 09:46:01 -0500 Subject: [PATCH 30/30] Interface overhaul --- src/abstractdatagraph.jl | 99 +++---- src/datagraph.jl | 27 +- src/dataview.jl | 52 +++- src/indexing.jl | 183 ++++++++++-- .../src/DataGraphsPartitionedGraphsExt.jl | 182 +++++++++--- test/test_basics.jl | 6 + test/test_partitionedgraphs.jl | 280 ++++++++++++++++++ 7 files changed, 678 insertions(+), 151 deletions(-) create mode 100644 test/test_partitionedgraphs.jl diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl index ebda1a6..92b3c05 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -53,66 +53,40 @@ get_edge_data(::AbstractDataGraph, edge) = not_implemented() set_vertex_data!(::AbstractDataGraph, data, vertex) = not_implemented() set_edge_data!(::AbstractDataGraph, data, edge) = not_implemented() -unset_vertex_data!(::AbstractDataGraph, data, vertex) = not_implemented() -unset_edge_data!(::AbstractDataGraph, data, edge) = not_implemented() +unset_vertex_data!(::AbstractDataGraph, vertex) = not_implemented() +unset_edge_data!(::AbstractDataGraph, edge) = not_implemented() # Quasi-derived interface; only required if inference fails underlying_graph_type(T::Type{<:AbstractGraph}) = Base.promote_op(underlying_graph, T) -# Derived interface -has_vertices_data(g::AbstractGraph, vertices) = all(v -> has_vertex_data(g, v), vertices) -has_edges_data(g::AbstractGraph, edges) = all(e -> has_edge_data(g, e), edges) +# Devirved Interface functions with defaults +# has_vertices_data(g::AbstractGraph, vertices) = _has_vertices_data(g, vertices) +# has_edges_data(g::AbstractGraph, edges) = _has_edges_data(g, edges) + +# get_vertices_data(g::AbstractGraph, vertices) = _get_vertices_data(g, vertices) +# get_edges_data(g::AbstractGraph, edges) = _get_edges_data(g, edges) -# Interface function -get_vertices_data(g::AbstractGraph, vertices) = _get_vertices_data(g, vertices) +# The defaults +has_edges_data(g::AbstractGraph, edges) = all(e -> has_edge_data(g, e), edges) +has_vertices_data(g::AbstractGraph, vertices) = all(v -> has_vertex_data(g, v), vertices) -# For defining default behaviour -function _get_vertices_data(g::AbstractGraph, vertices) - return map(v -> getindex(g, v), Indices(vertices)) +function get_vertices_data(g::AbstractGraph, vertices) + return map(v -> get_graph_data(g, v), Indices(vertices)) +end +function get_edges_data(g::AbstractGraph, edges) + return map(e -> get_graph_data(g, e), Indices(edges)) end function vertices_data_eltype(G::Type{<:AbstractGraph}, V::Type{<:AbstractVertices}) return Dictionary{eltype(V), vertex_data_type(G)} end -get_edges_data(g::AbstractGraph, edges) = _get_edges_data(g, edges) - -function _get_edges_data(g::AbstractGraph, edges) - return map(e -> getindex(g, e), Indices(edges)) -end - function edges_data_eltype(G::Type{<:AbstractGraph}, E::Type{<:AbstractEdges{V, ET} where {V, ET}}) return Dictionary{eltype(E), edge_data_type(G)} end -function set_vertices_data!(g::AbstractGraph, val, vertices) - for v in vertices - g[v] = val[v] - end - return g -end -function set_edges_data!(g::AbstractGraph, val, edges) - for e in edges - g[e] = val[e] - end - return g -end - -function unset_vertices_data!(g::AbstractGraph, vertices) - for v in vertices - unset_vertex_data!(g, v) - end - return g -end -function unset_edges_data!(g::AbstractGraph, edges) - for e in edges - unset_edge_data!(g, e) - end - return g -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) Graphs.has_edge(g::AbstractDataGraph, edge::AbstractNamedEdge) = has_edge(underlying_graph(g), edge) @@ -170,6 +144,11 @@ end GraphsExtensions.directed_graph_type(::AbstractDataGraph) = not_implemented() GraphsExtensions.undirected_graph_type(::AbstractDataGraph) = not_implemented() +# Thase canot be implemented abstractly. +function GraphsExtensions.convert_vertextype(vertextype::Type, graph::AbstractDataGraph) + return not_implemented() +end + # Fix for ambiguity error with `AbstractGraph` version function Graphs.degree(graph::AbstractDataGraph, vertex::Integer) return Graphs.degree(underlying_graph(graph), vertex) @@ -407,28 +386,26 @@ function Base.get(default::Base.Callable, graph::AbstractDataGraph, key) end end -function Base.isassigned(graph::AbstractDataGraph, index) - return _isassigned(graph, to_graph_index(graph, index)) -end -_isassigned(graph::AbstractDataGraph, vertex) = has_vertex_data(graph, vertex) -function _isassigned(graph::AbstractDataGraph, edge::AbstractEdge) - return has_edge_data(graph, arrange_edge(graph, edge)) -end -function _isassigned(graph::AbstractDataGraph, edges::AbstractEdges) - return has_edges_data(graph, edges) -end -function _isassigned(graph::AbstractDataGraph, vertices::AbstractVertices) - return has_vertices_data(graph, vertices) -end -function Graphs.induced_subgraph(graph::AbstractDataGraph, subvertices) +function NamedGraphs.induced_subgraph_from_vertices(graph::AbstractDataGraph, subvertices) return induced_subgraph_datagraph(graph, subvertices) end -# Fix ambiguity with Graphs.jl for integer `subvertices`. -function Graphs.induced_subgraph( - graph::AbstractDataGraph, subvertices::AbstractVector{<:Integer} - ) - return induced_subgraph_datagraph(graph, subvertices) +function induced_subgraph_datagraph(graph::AbstractDataGraph, subvertices) + underlying_subgraph, vlist = Graphs.induced_subgraph(underlying_graph(graph), subvertices) + + subgraph = similar_graph(graph, underlying_subgraph) + + for v in vertices(subgraph) + if isassigned(graph, v) + subgraph[v] = graph[v] + end + end + for e in edges(subgraph) + if isassigned(graph, e) + subgraph[e] = graph[e] + end + end + return subgraph, vlist end # diff --git a/src/datagraph.jl b/src/datagraph.jl index 60df6ad..df662db 100644 --- a/src/datagraph.jl +++ b/src/datagraph.jl @@ -70,6 +70,13 @@ function GraphsExtensions.similar_graph(T::Type{<:DataGraph}) similar_underlying_graph = similar_graph(underlying_graph_type(T)) return T(similar_underlying_graph) end +function GraphsExtensions.similar_graph(dg::DataGraph, underlying_graph::AbstractGraph) + return DataGraph( + underlying_graph; + vertex_data_type = vertex_data_type(dg), + edge_data_type = edge_data_type(dg) + ) +end function Base.copy(graph::DataGraph) # Need to manually copy the keys of Dictionaries, see: @@ -130,23 +137,3 @@ function GraphsExtensions.directed_graph_type(graph_type::Type{<:DataGraph}) edgetype(graph_type), } end - -function induced_subgraph_datagraph(graph::DataGraph, subvertices) - underlying_subgraph, vlist = Graphs.induced_subgraph(underlying_graph(graph), subvertices) - subgraph = DataGraph( - underlying_subgraph; - vertex_data_type = vertex_data_type(graph), - edge_data_type = edge_data_type(graph), - ) - for v in vertices(subgraph) - if isassigned(graph, v) - subgraph[v] = graph[v] - end - end - for e in edges(subgraph) - if isassigned(graph, e) - subgraph[e] = graph[e] - end - end - return subgraph, vlist -end diff --git a/src/dataview.jl b/src/dataview.jl index 6dcd1cd..e6cb1da 100644 --- a/src/dataview.jl +++ b/src/dataview.jl @@ -1,4 +1,12 @@ -using Dictionaries: Dictionaries, AbstractDictionary, gettokenvalue, filterview +using Dictionaries: + Dictionaries, + AbstractDictionary, + gettokenvalue, + filterview, + IndexError, + Indices, + getindices +using NamedGraphs: to_graph_index, to_vertices, to_edges struct VertexDataView{V, VD, G <: AbstractGraph{V}} <: AbstractDictionary{V, VD} graph::G @@ -31,8 +39,29 @@ function Base.keys(view::VertexOrEdgeDataView) end Base.isassigned(view::VertexOrEdgeDataView{K}, key::K) where {K} = isassigned(view.graph, key) -Base.getindex(view::VertexOrEdgeDataView{K}, key::K) where {K} = view.graph[key] -Base.getindex(view::EdgeDataView, key::Pair) = view.graph[key] + +Base.getindex(view::VertexOrEdgeDataView{K}, key::K) where {K} = _getindex(view, key) +function Base.getindex(view::VertexOrEdgeDataView, key) + return _getindex(view, to_graph_index(view.graph, key)) +end + +function _getindex(view::VertexDataView, key) + if key in keys(view) + return get_vertex_data(view.graph, key) + else + throw(IndexError("VertexDataView does not contain index: $key")) + end +end +function _getindex(view::EdgeDataView, key) + if key in keys(view) + return get_edge_data(view.graph, key) + else + throw(IndexError("EdgeDataView does not contain index: $key")) + end +end + +# Support indexing with `Indices`. +Base.getindex(view::VertexOrEdgeDataView, keys::Indices) = getindices(view, keys) Dictionaries.istokenizable(::Type{<:VertexOrEdgeDataView}) = true function Dictionaries.istokenassigned(view::VertexOrEdgeDataView, token) @@ -62,19 +91,16 @@ function Dictionaries.deletetoken!(view::VertexOrEdgeDataView, token) return view end -Base.size(view::VertexDataView) = (nv(view.graph),) -Base.size(view::EdgeDataView) = (ne(view.graph),) - -function Base.copyto!(dest::VertexDataView, bc::Base.Broadcast.Broadcasted) - for (i, vertex) in enumerate(vertices(dest.graph)) - dest[vertex] = bc[i] +function Base.copyto!(dest::VertexOrEdgeDataView, bc::Dictionaries.BroadcastedDictionary) + for (key, val) in pairs(bc) + dest[to_graph_index(dest.graph, key)] = val end return dest end -function Base.copyto!(dest::EdgeDataView, bc::Base.Broadcast.Broadcasted) - for (i, edge) in enumerate(edges(dest.graph)) - dest[edge] = bc[i] +function Base.fill!(view::VertexOrEdgeDataView{<:Any, T}, value::T) where {T} + for key in keys(view) + view[key] = value end - return dest + return view end diff --git a/src/indexing.jl b/src/indexing.jl index 4217942..7fb20e2 100644 --- a/src/indexing.jl +++ b/src/indexing.jl @@ -1,51 +1,195 @@ -using NamedGraphs: to_graph_index, AbstractEdges, AbstractVertices -using NamedGraphs.GraphsExtensions: subgraph, edge_subgraph +using NamedGraphs: + to_graph_index, + to_graph_indices, + AbstractEdges, + AbstractVertices, + to_vertices, + to_edges, + AbstractGraphIndices +using NamedGraphs.GraphsExtensions: subgraph -function Base.getindex(graph::AbstractDataGraph, indices) - return _getindex(graph, to_graph_index(graph, indices)) +# ====================================== getindex! ======================================= # + +NamedGraphs.get_graph_index(graph::AbstractDataGraph, index) = get_index_data(graph, index) +# If unknown, treat like single vertex +get_index_data(graph::AbstractGraph, vertex) = _get_index_data(graph, vertex) +function _get_index_data(graph::AbstractGraph, vertex) + if isassigned(graph, vertex) + return get_vertex_data(graph, vertex) + else + throw(IndexError("Vertex $vertex not assigned")) + end +end +function _get_index_data(graph::AbstractGraph, edge::AbstractEdge) + if isassigned(graph, edge) + data = get_edge_data(graph, arrange_edge(graph, edge)) + return reverse_data_direction(graph, edge, data) + else + throw(IndexError("Edge $edge not assigned")) + end end -_getindex(graph::AbstractGraph, vertex) = get_vertex_data(graph, vertex) +# Can force data retrivial instead of subgraphing by using `Indices`. +function NamedGraphs.get_graph_indices(graph::AbstractDataGraph, inds::Indices) + return get_indices_data(graph, to_graph_indices(graph, parent_graph_indices(inds))) +end -function _getindex(graph::AbstractGraph, edge::AbstractEdge) - data = get_edge_data(graph, arrange_edge(graph, edge)) - return reverse_data_direction(graph, edge, data) +function get_indices_data(graph::AbstractGraph, vertices::AbstractVertices) + return get_vertices_data(graph, vertices) end -function _getindex(graph::AbstractGraph, vertices::AbstractVertices) - return subgraph(graph, vertices) +function get_indices_data(graph::AbstractGraph, edges::AbstractEdges) + return get_edges_data(graph, edges) end -function _getindex(graph::AbstractGraph, edges::AbstractEdges) - return throw(MethodError(_getindex, (graph, edges))) + +# ====================================== isassigned ====================================== # + +function Base.isassigned(graph::AbstractDataGraph, index) + return _isassigned(graph, to_graph_index(graph, index)) end -# Support syntax `g[1, 2] = g[(1, 2)]` -function Base.getindex(graph::AbstractDataGraph, i1, i2, i...) - return graph[(i1, i2, i...)] +_isassigned(graph::AbstractGraph, ind) = has_index_data(graph, ind) +_isassigned(graph::AbstractGraph, inds::AbstractGraphIndices) = has_indices_data(graph, inds) + +has_index_data(graph::AbstractGraph, vertex) = has_vertex_data(graph, vertex) + +function has_index_data(graph::AbstractGraph, edge::AbstractEdge) + return has_edge_data(graph, arrange_edge(graph, edge)) +end +function has_indices_data(graph::AbstractGraph, edges::AbstractEdges) + return has_edges_data(graph, edges) +end +function has_indices_data(graph::AbstractGraph, vertices::AbstractVertices) + return has_vertices_data(graph, vertices) end +# ====================================== setindex! ======================================= # + function Base.setindex!(graph::AbstractDataGraph, data, index) _setindex!(graph, data, to_graph_index(graph, index)) return graph end function _setindex!(graph::AbstractGraph, data, vertex) - set_vertex_data!(graph, data, vertex) + set_index_data!(graph, data, vertex) return graph end function _setindex!(graph::AbstractGraph, data, edge::AbstractEdge) arranged_edge = arrange_edge(graph, edge) arranged_data = reverse_data_direction(graph, edge, data) - set_edge_data!(graph, arranged_data, arranged_edge) + set_index_data!(graph, arranged_data, arranged_edge) return graph end function _setindex!(graph::AbstractGraph, val, vertices::AbstractVertices) - return set_vertices_data!(graph, val, vertices) + return set_indices_data!(graph, val, vertices) end function _setindex!(graph::AbstractGraph, val, edges::AbstractEdges) - return set_edges_data!(graph, val, edges) + return set_indices_data!(graph, val, edges) +end + +function set_index_data!(g::AbstractGraph, val, vertex) + if !has_vertex(g, vertex) + add_vertex!(g, vertex) + end + set_vertex_data!(g, val, vertex) + return g +end +function set_index_data!(g::AbstractGraph, val, edge::AbstractEdge) + if !has_edge(g, edge) + add_edge!(g, edge) + end + set_edge_data!(g, val, edge) + return g +end + +function set_indices_data!(g::AbstractGraph, val, vertices::AbstractVertices) + set_vertices_data!(g, val, vertices) + return g +end +function set_indices_data!(g::AbstractGraph, val, edges::AbstractEdges) + set_edges_data!(g, val, edges) + return g +end +function set_vertices_data!(g::AbstractGraph, val, vertices) + for v in vertices + g[v] = val[v] + end + return g +end +function set_edges_data!(g::AbstractGraph, val, edges) + for e in edges + g[e] = val[e] + end + return g +end + +# ===================================== unsetindex! ====================================== # + +function unsetindex!(graph::AbstractDataGraph, index) + return _unsetindex!(graph, to_graph_index(graph, index)) +end + +_unsetindex!(graph::AbstractGraph, vertex) = unset_index_data!(graph, vertex) + +function _unsetindex!(graph::AbstractGraph, edge::AbstractEdge) + arranged_edge = arrange_edge(graph, edge) + unset_index_data!(graph, arranged_edge) + return graph +end + +function _unsetindex!(graph::AbstractGraph, vertices::AbstractVertices) + return unset_indices_data!(graph, vertices) +end +function _unsetindex!(graph::AbstractGraph, edges::AbstractEdges) + return unset_indices_data!(graph, edges) +end + +function unset_index_data!(g::AbstractGraph, vertex) + if has_vertex(g, vertex) + unset_vertex_data!(g, vertex) + return g + else + throw(ArgumentError("Cannot unset data as vertex $vertex not in graph")) + end +end +function unset_index_data!(g::AbstractGraph, edge::AbstractEdge) + if has_edge(g, edge) + unset_edge_data!(g, edge) + return g + else + throw(ArgumentError("Cannot unset data as edge $edge not in graph")) + end +end + +function unset_indices_data!(g::AbstractGraph, vertices::AbstractVertices) + unset_vertices_data!(g, vertices) + return g +end +function unset_indices_data!(g::AbstractGraph, edges::AbstractEdges) + unset_edges_data!(g, edges) + return g +end + +function unset_vertices_data!(g::AbstractGraph, vertices) + for v in vertices + unset_vertex_data!(g, v) + end + return g +end +function unset_edges_data!(g::AbstractGraph, edges) + for e in edges + unset_edge_data!(g, e) + end + return g +end + +# ======================================== Other ========================================= # + +# Support syntax `g[1, 2] = g[(1, 2)]` +function Base.getindex(graph::AbstractDataGraph, i1, i2, i...) + return graph[(i1, i2, i...)] end # Support syntax `g[1, 2] = g[(1, 2)]` @@ -55,7 +199,6 @@ function Base.setindex!(graph::AbstractDataGraph, x, i1, i2, i...) end # Ordinal Indexing - function NamedGraphs.to_graph_index( graph::AbstractGraph, pair::Pair{<:OrdinalSuffixedInteger, <:OrdinalSuffixedInteger} diff --git a/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl b/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl index bf65530..9a914c5 100644 --- a/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl +++ b/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl @@ -2,6 +2,8 @@ module DataGraphsPartitionedGraphsExt using NamedGraphs.PartitionedGraphs: quotient_graph_vertextype using Graphs: Graphs, AbstractGraph, AbstractEdge, vertices, edges using ..DataGraphs: + DataGraph, + _DataGraph, _getindex, AbstractDataGraph, DataGraphs, @@ -13,20 +15,39 @@ using ..DataGraphs: get_edge_data, get_vertices_data, get_edges_data, + set_index_data!, + set_indices_data!, set_vertex_data!, set_edge_data!, + set_vertices_data!, + set_edges_data!, + has_index_data, + has_indices_data, has_vertex_data, has_edge_data, has_vertices_data, has_edges_data, + unset_index_data!, + unset_indices_data!, unset_vertex_data!, unset_edge_data!, unset_vertices_data!, unset_edges_data!, vertices_data_eltype, - edges_data_eltype -using NamedGraphs: to_graph_index, parent_graph_indices, Vertices, Edges -using NamedGraphs.GraphsExtensions: vertextype + edges_data_eltype, + get_index_data +using NamedGraphs: NamedGraphs, + to_graph_index, + parent_graph_indices, + Vertices, + Edges, + to_edges, + to_vertices, + VertexSlice, + EdgeSlice, + to_graph_indices, + get_graph_index +using NamedGraphs.GraphsExtensions: vertextype, subgraph, edge_subgraph using NamedGraphs.PartitionedGraphs: PartitionedGraphs, QuotientEdge, @@ -34,54 +55,69 @@ using NamedGraphs.PartitionedGraphs: QuotientEdges, QuotientEdgeEdges, QuotientVertex, + QuotientVertexOrEdge, QuotientVertexVertex, QuotientVertexVertices, QuotientVerticesVertices, - QuotientVertexOrVerticesVertices, QuotientVertices, QuotientView, partitioned_vertices, quotientvertices, quotientedges, parent_graph_type, - to_quotient_index -using Dictionaries: Dictionary, Indices + to_quotient_index, + quotient_index, + unpartitioned_graph, + quotient_graph, + partitionedgraph, + departition, + has_quotientvertex, + has_quotientedge +using Dictionaries: Dictionary, Indices, IndexError, unset! # Methods to overload if you dont want to use the defaults. # DataGraphs.has_vertices_data(g::MyGraph, v::QuotientVertexVertices) # DataGraphs.has_edges_data(g::MyGraph, v::QuotientEdgeEdges) +# use `isassigned` # # DataGraphs.get_vertices_data(g::MyGraph, v::QuotientVertexVertices) # DataGraphs.get_edges_data(g::MyGraph, v::QuotientEdgeEdges) +# use `get_graph_data` # # DataGraphs.set_vertices_data!(g::MyGraph, v::QuotientVertexVertices) # DataGraphs.set_edges_data!(g::MyGraph, v::QuotientEdgeEdges) +# use `set!` # # DataGraphs.unset_vertices_data!(g::MyGraph, v::QuotientVertexVertices) # DataGraphs.unset_edges_data!(g::MyGraph, v::QuotientEdgeEdges) +# use `unset!` # # DataGraphs.vertices_data_eltype(::Type{<:MyGraph}, ::Type{<:QuotientVertexVertices}) # DataGraphs.edges_data_eltype(::Type{<:MyGraph}, ::Type{<:QuotientVertexVertices}) # QuotientView; make sure quotient views of data graphs do data graph indexing # and not subgraph indexing. -function Base.getindex(qv::QuotientView{V, <:AbstractDataGraph}, ind) where {V} - return DataGraphs._getindex(qv, to_graph_index(qv, ind)) + +# ======================== DataGraphs interface for QuotientView ========================= # + +function NamedGraphs.get_graph_index(qv::QuotientView{V, <:AbstractDataGraph}, ind) where {V} + return DataGraphs.get_index_data(qv, ind) end -function Base.setindex!(qv::QuotientView{V, <:AbstractDataGraph}, val, ind) where {V} - return DataGraphs._setindex!(qv, val, to_graph_index(qv, ind)) +function DataGraphs.get_vertex_data(qv::QuotientView, v) + return get_graph_index(parent(qv), QuotientVertex(v)) end -DataGraphs.get_vertex_data(qv::QuotientView, v) = getindex(parent(qv), QuotientVertex(v)) -DataGraphs.get_edge_data(qv::QuotientView, e) = getindex(parent(qv), QuotientEdge(e)) +function DataGraphs.get_edge_data(qv::QuotientView, v) + return get_graph_index(parent(qv), QuotientEdge(v)) +end -function DataGraphs._get_vertices_data(g::AbstractGraph, v::QuotientVertexOrVerticesVertices) - return getindex(g, Vertices(parent_graph_indices(v))) +function DataGraphs.has_vertex_data(qv::QuotientView, v) + return isassigned(parent(qv), QuotientVertex(v)) end -function DataGraphs._get_edges_data(g::AbstractGraph, e::QuotientEdgeEdges) - return getindex(g, Edges(parent_graph_indices(e))) +function DataGraphs.has_edge_data(qv::QuotientView, v) + return isassigned(parent(qv), QuotientEdge(v)) end function DataGraphs.set_vertex_data!(qv::QuotientView, val, v) @@ -91,42 +127,63 @@ function DataGraphs.set_edge_data!(qv::QuotientView, val, e) return setindex!(parent(qv), val, QuotientEdge(e)) end -function DataGraphs.has_vertex_data(qv::QuotientView, v) - pg = parent(qv) - return has_vertices_data(pg, to_graph_index(pg, QuotientVertex(v))) +function DataGraphs.unset_vertex_data!(qv::QuotientView, v) + return unset!(parent(qv), QuotientVertex(v)) end -function DataGraphs.has_edge_data(qv::QuotientView, e) - pg = parent(qv) - return has_edges_data(pg, to_graph_index(pg, QuotientEdge(e))) +function DataGraphs.unset_edge_data!(qe::QuotientView, e) + return unset!(parent(qe), QuotientEdge(e)) end -function DataGraphs.unset_vertex_data!(qv::QuotientView, v) - pg = parent(qv) - return unset_vertices_data!(pg, to_graph_index(pg, QuotientVertex(v))) +# =========================== Quotient indexing for DataGraphs =========================== # + +function DataGraphs.get_index_data(graph::AbstractGraph, ind::QuotientVertexOrEdge) + return _get_index_data(graph, ind) +end +function _get_index_data(graph::AbstractGraph, vertex::QuotientVertex) + if !isassigned(graph, vertex) + return subgraph(graph, vertex) + else + throw(MethodError(get_index_data, (graph, vertex))) + end end -function DataGraphs.unset_edge_data!(qv::QuotientView, e) - pg = parent(qv) - return unset_edges_data!(pg, to_graph_index(pg, QuotientEdge(e))) +function _get_index_data(graph::AbstractGraph, edge::QuotientEdge) + if !isassigned(graph, edge) + return edge_subgraph(graph, edge) + else + throw(MethodError(get_index_data, (graph, edge))) + end end +DataGraphs.has_index_data(graph::AbstractGraph, ind::QuotientVertexOrEdge) = false +function DataGraphs.set_index_data!(graph, ::AbstractGraph, value, ind::QuotientVertexOrEdge) + return MethodError(set_index_data!, (graph, value, ind)) +end +function DataGraphs.unset_index_data!(graph::AbstractGraph, ind::QuotientVertexOrEdge) + return MethodError(unset_index_data!, (graph, ind)) +end + +# Special case + +function DataGraphs.get_vertices_data(g::AbstractGraph, vertices::QuotientVerticesVertices) + + inds = Indices(quotient_index(v) for v in vertices.quotientvertices) + data = [get_vertices_data(g, to_vertices(g, v)) for v in vertices.quotientvertices] + + return Dictionary(inds, data) +end + -# TODO: eltype -> type? function DataGraphs.vertex_data_type(T::Type{<:QuotientView}) PGT = parent_graph_type(T) - return Base.promote_op(getindex, PGT, QuotientVertex{vertextype(T)}) + return Base.promote_op(get_index_data, PGT, QuotientVertex{vertextype(T)}) end function DataGraphs.edge_data_type(T::Type{<:QuotientView}) PGT = parent_graph_type(T) - return Base.promote_op(getindex, PGT, QuotientEdge{vertextype(T), edgetype(T)}) + return Base.promote_op(get_index_data, PGT, QuotientEdge{vertextype(T), edgetype(T)}) end -get_quotient_vertices_data(g::AbstractGraph) = get_vertices_data(g, QuotientVertices(g)) -get_quotient_edges_data(g::AbstractGraph) = get_edges_data(g, QuotientEdges(g)) - DataGraphs.underlying_graph(qv::QuotientView) = underlying_graph(copy(qv)) -function Base.isassigned(qv::QuotientView, ind) - return isassigned(parent(qv), to_quotient_index(to_graph_index(qv, ind))) -end +Base.isassigned(qv::QuotientView, ind) = DataGraphs._isassigned(qv, to_graph_index(qv, ind)) # PartitionedGraphs interface function PartitionedGraphs.partitioned_vertices(dg::AbstractDataGraph) @@ -136,6 +193,7 @@ end PartitionedGraphs.partitionedgraph(::AbstractDataGraph, parts) = not_implemented() PartitionedGraphs.departition(::AbstractDataGraph) = not_implemented() + function DataGraphs.vertices_data_eltype( T::Type{<:AbstractGraph}, QV::Type{<:QuotientVertices} @@ -150,4 +208,54 @@ function DataGraphs.edges_data_eltype( return eltype(Base.promote_op(get_edges_data, T, QE)) end +# ================================== DataGraph specific ================================== # + +function PartitionedGraphs.partitionedgraph(dg::DataGraph, parts) + pg = partitionedgraph(underlying_graph(dg), parts) + vd = copy(vertex_data(dg)) + ed = copy(edge_data(dg)) + return _DataGraph(pg, vd, ed) +end + +function PartitionedGraphs.departition(dg::DataGraph) + ug = underlying_graph(dg) + upg = departition(underlying_graph(dg)) + if upg === ug + return dg + else + vd = copy(vertex_data(dg)) + ed = copy(edge_data(dg)) + return _DataGraph(upg, vd, ed) + end +end + +function quotient_graph_vertex_data(f, dg) + ug = underlying_graph(dg) + qvs = VertexSlice(QuotientVertices(ug)) + return map(v -> f(dg[QuotientVertex(v)]), Indices(qvs)) +end + +function quotient_graph_edge_data(f, dg) + ug = underlying_graph(dg) + qes = EdgeSlice(QuotientEdges(ug)) + return map(e -> f(dg[QuotientEdge(e)]), Indices(qes)) +end + +function PartitionedGraphs.quotient_graph( + dg::DataGraph; + vertex_data_transform = identity, + edge_data_transform = identity + ) + + vertex_data = quotient_graph_vertex_data(vertex_data_transform, dg) + edge_data = quotient_graph_edge_data(edge_data_transform, dg) + + dg = _DataGraph( + copy(quotient_graph(underlying_graph(dg))), + vertex_data, + edge_data + ) + return dg +end + end diff --git a/test/test_basics.jl b/test/test_basics.jl index 705deea..7e64fe9 100644 --- a/test/test_basics.jl +++ b/test/test_basics.jl @@ -512,5 +512,11 @@ using Test: @test, @test_broken, @testset edata = edge_data(g) @test edata["a" => "b"] == -1.0 @test edata[edgetype(g)("b" => "c")] == -2.0 + + vertex_data(g) .= dictionary(["a" => 1, "b" => 2, "c" => 3]) + @test collect(vertex_data(g)) == [1, 2, 3] + + edge_data(g) .= dictionary([("a" => "b") => 1, ("b" => "c") => 2]) + @test collect(edge_data(g)) == [1.0, 2.0] end end diff --git a/test/test_partitionedgraphs.jl b/test/test_partitionedgraphs.jl new file mode 100644 index 0000000..55146e9 --- /dev/null +++ b/test/test_partitionedgraphs.jl @@ -0,0 +1,280 @@ +module TestModule + +using Graphs: AbstractGraph, vertices, edges, src, dst, has_vertex, has_edge, edgetype +using NamedGraphs: NamedGraph, Vertices, Edges +using NamedGraphs.PartitionedGraphs: + QuotientView, + QuotientVertex, + QuotientVertexVertex, + QuotientVertexVertices, + QuotientVertices, + QuotientVerticesVertices, + QuotientEdge, + QuotientEdges, + partitionedgraph, + QuotientVertexOrEdge, + PartitionedGraphs, + partitionedgraph, + PartitionedGraph, + departition, + unpartition, + quotient_graph +using DataGraphs: + DataGraphs, + DataGraph, + AbstractDataGraph, + vertex_data, + edge_data, + underlying_graph, + vertex_data_type, + edge_data_type +using NamedGraphs.GraphsExtensions: GraphsExtensions, similar_graph, subgraph, vertextype +using NamedGraphs.NamedGraphGenerators: named_path_graph +using Test: @testset, @test, @test_throws +using Dictionaries: Dictionary, IndexError + +struct TestDataGraph{V, VD, ED, DG <: DataGraph{V, VD, ED}, QDG} <: AbstractDataGraph{V, VD, ED} + graph::DG + quotientgraph::QDG +end + +function TestDataGraph(graph) + + vertex_data_transform = subgraph -> collect(vertex_data(subgraph)) + edge_data_transform = subgraph -> collect(edge_data(subgraph)) + + quotientgraph = quotient_graph(graph; vertex_data_transform, edge_data_transform) + + return TestDataGraph(graph, quotientgraph) +end + +DataGraphs.underlying_graph(graph::TestDataGraph) = underlying_graph(graph.graph) +PartitionedGraphs.quotient_graph(graph::TestDataGraph) = graph.quotientgraph + +function GraphsExtensions.similar_graph(dg::TestDataGraph, graph::AbstractGraph) + dg = similar_graph(dg.graph, graph) + return TestDataGraph(dg) +end + +for f in [ + :(DataGraphs.has_vertex_data), + :(DataGraphs.has_edge_data), + :(DataGraphs.get_vertex_data), + :(DataGraphs.get_edge_data), + :(DataGraphs.unset_vertex_data!), + :(DataGraphs.unset_edge_data!), + ] + @eval $(f)(graph::TestDataGraph, ind) = $(f)(graph.graph, ind) +end + +for f in [ + :(DataGraphs.set_vertex_data!), + :(DataGraphs.set_edge_data!), + ] + @eval $(f)(graph::TestDataGraph, val, ind) = $(f)(graph.graph, val, ind) +end + +function DataGraphs.get_index_data(graph::TestDataGraph, ind::QuotientVertexOrEdge) + return graph.quotientgraph[parent(ind)] +end +function DataGraphs.has_index_data(graph::TestDataGraph, ind::QuotientVertexOrEdge) + return isassigned(graph.quotientgraph, parent(ind)) +end + +@testset "DataGraphsPartitionedGraphsExt.jl" begin + g = named_path_graph(6) + dg = DataGraph(g; vertex_data_type = String, edge_data_type = Tuple{String, String}) + + for vertex in vertices(g) + dg[vertex] = string(vertex) + end + for edge in edges(g) + dg[edge] = (string(src(edge)), string(dst(edge))) + end + + pg = partitionedgraph(dg, Dict(:a => [1], :b => [2, 3], :c => [4, 5, 6])) + + tpg = TestDataGraph(pg) + + @testset "Basics" begin + + @test pg isa DataGraph + @test underlying_graph(pg) isa PartitionedGraph + @test departition(pg) == dg + @test unpartition(pg) == dg + @test quotient_graph(pg) isa DataGraph + @test has_vertex(quotient_graph(pg), :a) + @test has_vertex(quotient_graph(pg), :b) + @test has_vertex(quotient_graph(pg), :c) + + subgraph_type = DataGraph{vertextype(dg), String, Tuple{String, String}, <:NamedGraph} + + @test vertex_data_type(quotient_graph(pg)) <: subgraph_type + @test edge_data_type(quotient_graph(pg)) <: subgraph_type + @test vertex_data_type(QuotientView(pg)) <: subgraph_type + @test edge_data_type(QuotientView(pg)) <: subgraph_type + + @test vertex_data_type(quotient_graph(tpg)) <: Vector{String} + @test edge_data_type(quotient_graph(tpg)) <: Vector{Tuple{String, String}} + @test vertex_data_type(QuotientView(tpg)) <: Vector{String} + @test edge_data_type(QuotientView(tpg)) <: Vector{Tuple{String, String}} + end + + @testset "Scalar indexing" begin + + @testset "Default data graph indexing" begin + @test pg[1] == "1" + @test pg[QuotientVertex(:a)[2]] == "2" + @test pg[1 => 2] == ("1", "2") + @test pg[QuotientEdge(:b => :c)[4 => 5]] == ("4", "5") + + qv = QuotientView(pg) + @test qv[:a] isa DataGraph + @test underlying_graph(qv[:a]) isa NamedGraph + @test has_vertex(qv[:a], 1) + @test has_vertex(qv[:b], 2) + @test has_vertex(qv[:b], 3) + @test has_vertex(qv[:c], 4) + @test has_vertex(qv[:c], 5) + @test has_vertex(qv[:c], 6) + @test !has_vertex(qv[:c], 1) + @test qv[:a][1] == "1" + @test qv[:b][2] == "2" + @test qv[:b][3] == "3" + @test_throws IndexError qv[:c][1] + end + + @testset "Custom data graph indexing" begin + @test tpg[1] == "1" + @test tpg[QuotientVertex(:a)[2]] == "2" + @test tpg[1 => 2] == ("1", "2") + @test tpg[QuotientEdge(:b => :c)[4 => 5]] == ("4", "5") + + @test tpg[QuotientVertex(:a)] == ["1"] + @test tpg[QuotientVertex(:b)] == ["2", "3"] + @test tpg[QuotientVertex(:c)] == ["4", "5", "6"] + + @test tpg[QuotientEdge(:a => :b)] == [("1", "2")] + @test tpg[QuotientEdge(:b => :a)] == [("1", "2")] + @test tpg[QuotientEdge(:b => :c)] == [("3", "4")] + @test tpg[QuotientEdge(:c => :b)] == [("3", "4")] + + tqv = QuotientView(tpg) + + @test tqv[:a] isa Vector{String} + @test tqv[:a => :b] isa Vector{Tuple{String, String}} + + @test tqv[:a] == ["1"] + @test tqv[:b] == ["2", "3"] + @test tqv[:c] == ["4", "5", "6"] + + @test tqv[:a => :b] == [("1", "2")] + @test tqv[:b => :a] == [("1", "2")] + @test tqv[:b => :c] == [("3", "4")] + @test tqv[:c => :b] == [("3", "4")] + end + + end + + @testset "Partition non-preserving indexing" begin + sg1 = tpg[Vertices([4, 5])] + sg2 = tpg[QuotientVertex(:c)[Vertices([4, 5])]] + sg3 = subgraph(tpg, [QuotientVertex(:c)[4], QuotientVertex(:c)[5]]) + + sg4 = pg[QuotientVertex(:c)] + + @test sg1 isa TestDataGraph + @test sg2 isa TestDataGraph + @test sg3 isa TestDataGraph + + @test sg4 isa DataGraph + + function test_partition_non_preserving_indexing(sg) + @test !(underlying_graph(sg) isa PartitionedGraph) + + @test has_vertex(sg, 4) + @test has_vertex(sg, 5) + @test !has_vertex(sg, 1) + @test !has_vertex(sg, 2) + @test !has_vertex(sg, 3) + + @test vertex_data(sg)[4] == "4" + @test vertex_data(sg)[5] == "5" + + @test has_edge(sg, 4 => 5) + @test !has_edge(sg, 1 => 2) + @test !has_edge(sg, 2 => 3) + @test !has_edge(sg, 3 => 4) + + @test edge_data(sg)[4 => 5] == ("4", "5") + + end + + for (type, sg) in zip( + ("Vertices", "QuotientVertexVertices", "Vector{QuotientVertexVertex}"), + (sg1, sg2, sg3) + ) + @testset "$type" test_partition_non_preserving_indexing(sg) + @test sg == subgraph(dg, [4, 5]) + end + + @testset "QuotientVertex" begin + test_partition_non_preserving_indexing(sg4) + @test has_vertex(sg4, 6) + @test has_edge(sg4, 5 => 6) + @test vertex_data(sg4)[5] == "5" + @test vertex_data(sg4)[6] == "6" + @test edge_data(sg4)[5 => 6] == ("5", "6") + @test sg4 == subgraph(dg, [4, 5, 6]) + end + end + + @testset "Partition-preserving indexing" begin + sg1 = tpg[QuotientVertices([:a, :b])] + sg2 = subgraph(tpg, [QuotientVertex(:a), QuotientVertex(:b)]) + sg3 = subgraph(tpg, [QuotientVertex(:a)[Vertices([1])], QuotientVertex(:b)[Vertices([2, 3])]]) + + for sg in (sg1, sg2, sg3) + @test sg isa TestDataGraph + @test underlying_graph(sg) isa PartitionedGraph + + @test has_vertex(sg, 1) + @test has_vertex(sg, 2) + @test has_vertex(sg, 3) + @test !has_vertex(sg, 4) + @test !has_vertex(sg, 5) + @test !has_vertex(sg, 6) + + @test sg[1] == "1" + @test sg[2] == "2" + @test sg[3] == "3" + @test vertex_data(sg)[1] == "1" + @test vertex_data(sg)[2] == "2" + @test vertex_data(sg)[3] == "3" + @test_throws IndexError sg[4] + @test_throws IndexError vertex_data(sg)[5] + + @test has_edge(sg, 1 => 2) + @test has_edge(sg, 2 => 3) + @test !has_edge(sg, 3 => 4) + @test !has_vertex(sg, 4 => 5) + @test !has_vertex(sg, 5 => 6) + + @test sg[1 => 2] == ("1", "2") + @test sg[2 => 3] == ("2", "3") + @test edge_data(sg)[1 => 2] == ("1", "2") + @test edge_data(sg)[2 => 3] == ("2", "3") + @test_throws IndexError sg[3 => 4] + @test_throws IndexError edge_data(sg)[4 => 5] + + @test has_vertex(QuotientView(sg), :a) + @test has_vertex(QuotientView(sg), :b) + @test !has_vertex(QuotientView(sg), :c) + + @test has_edge(QuotientView(sg), :a => :b) + @test !has_edge(QuotientView(sg), :b => :c) + end + end +end + +end