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/examples/datagraph.jl b/examples/datagraph.jl index 433ba80..c1becea 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,)) -dg = DataGraph(g; vertex_data_eltype = String, edge_data_eltype = Symbol) -@show !isassigned(dg, Edge(1, 2)) +g = named_grid((4)) +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, 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/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/ext/DataGraphsNamedGraphsExt/DataGraphsNamedGraphsExt.jl b/ext/DataGraphsNamedGraphsExt/DataGraphsNamedGraphsExt.jl deleted file mode 100644 index 8595bc3..0000000 --- a/ext/DataGraphsNamedGraphsExt/DataGraphsNamedGraphsExt.jl +++ /dev/null @@ -1,40 +0,0 @@ -module DataGraphsNamedGraphsExt -using DataGraphs: DataGraphs, AbstractDataGraph, underlying_graph -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 -# (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..6f93972 100644 --- a/src/DataGraphs.jl +++ b/src/DataGraphs.jl @@ -2,11 +2,12 @@ module DataGraphs include("utils.jl") include("traits/isunderlyinggraph.jl") +include("dataview.jl") include("abstractdatagraph.jl") +include("indexing.jl") include("datagraph.jl") -# TODO: Turn into a weak dependency once `GraphsExtensions` -# is split off from `NamedGraphs`. -include("../ext/DataGraphsNamedGraphsExt/DataGraphsNamedGraphsExt.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 46430b4..92b3c05 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -1,118 +1,154 @@ -using Dictionaries: set!, unset! +using Dictionaries: set!, unset!, Indices using Graphs: Graphs, AbstractEdge, - AbstractGraph, IsDirected, add_edge!, + add_vertex!, a_star, edges, ne, nv, steiner_tree, vertices +using NamedGraphs: + NamedGraphs, + AbstractNamedGraph, + AbstractNamedEdge, + AbstractVertices, + AbstractEdges, + position_graph_type using NamedGraphs.GraphsExtensions: - GraphsExtensions, arrange_edge, incident_edges, is_edge_arranged, vertextype + GraphsExtensions, + arrange_edge, + incident_edges, + is_edge_arranged, + vertextype, + similar_graph, + add_vertices! using NamedGraphs.SimilarType: similar_type +using NamedGraphs.OrdinalIndexing: OrdinalSuffixedInteger using SimpleTraits: SimpleTraits, Not, @traitfn -abstract type AbstractDataGraph{V, VD, ED} <: AbstractGraph{V} end +is_underlying_graph(::Type{<:AbstractNamedGraph}) = true + +abstract type AbstractDataGraph{V, VD, ED} <: AbstractNamedGraph{V} end + +vertex_data_type(::Type{<:AbstractGraph}) = Any +edge_data_type(::Type{<:AbstractGraph}) = Any + +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`. 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() -# Derived interface +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, 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) + + +# 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) + +# 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) + +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 + +function edges_data_eltype(G::Type{<:AbstractGraph}, E::Type{<:AbstractEdges{V, ET} where {V, ET}}) + return Dictionary{eltype(E), edge_data_type(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::AbstractGraph) = VertexDataView(dg) +edge_data(dg::AbstractGraph) = EdgeDataView(dg) + +function assigned_vertices(graph::AbstractGraph) + return Indices(filter(v -> isassigned(graph, v), vertices(graph))) +end +function assigned_edges(graph::AbstractGraph) + return Indices(filter(e -> isassigned(graph, e), edges(graph))) +end + +function Graphs.edgetype(graph::AbstractDataGraph) + return Graphs.edgetype(underlying_graph(graph)) +end function Graphs.edgetype(graph_type::Type{<:AbstractDataGraph}) - return Graphs.edgetype(underlying_graph_type(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) = eltype(vertex_data(graph)) -edge_data_eltype(graph::AbstractDataGraph) = eltype(edge_data(graph)) -Base.zero(graph_type::Type{<:AbstractDataGraph}) = graph_type() +underlying_graph_type(graph::AbstractGraph) = typeof(underlying_graph(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)) +end + +Base.zero(graph_type::Type{<:AbstractDataGraph}) = similar_graph(graph_type) # Graphs overloads 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() + +# 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) @@ -131,12 +167,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 @@ -182,14 +218,41 @@ end return digraph 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_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) + 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))) + 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 @@ -197,6 +260,7 @@ function Base.reverse(graph::AbstractDataGraph) return reversed_graph end + function Graphs.merge_vertices( graph::AbstractDataGraph, merge_vertices; @@ -205,7 +269,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 @@ -217,8 +280,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 @@ -248,37 +309,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 @@ -301,114 +343,58 @@ 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) - graph′ = copy(graph) +function map_vertex_data(f, graph::AbstractGraph; vertices = nothing) + 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) +function map_edge_data(f, graph::AbstractGraph; edges = nothing) + 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) +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 -function Base.getindex(graph::AbstractDataGraph, vertex) - return vertex_data(graph)[vertex] -end - -function Base.get(graph::AbstractDataGraph, vertex, default) - return get(vertex_data(graph), vertex, default) -end -function Base.get!(graph::AbstractDataGraph, vertex, default) - return get!(vertex_data(graph), vertex, default) -end - -function Base.getindex(graph::AbstractDataGraph, edge::AbstractEdge) - data = edge_data(graph)[arrange_edge(graph, edge)] - return reverse_data_direction(graph, edge, data) -end - -# Support syntax `g[v1 => v2]` -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 - -function Base.isassigned(graph::AbstractDataGraph, edge::AbstractEdge) - return isassigned(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) - return graph +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 - -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) - return graph +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 default() + end 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 +function NamedGraphs.induced_subgraph_from_vertices(graph::AbstractDataGraph, subvertices) + return induced_subgraph_datagraph(graph, subvertices) end - function induced_subgraph_datagraph(graph::AbstractDataGraph, subvertices) underlying_subgraph, vlist = Graphs.induced_subgraph(underlying_graph(graph), subvertices) - subgraph = similar_type(graph)(underlying_subgraph) + + subgraph = similar_graph(graph, underlying_subgraph) + for v in vertices(subgraph) if isassigned(graph, v) subgraph[v] = graph[v] @@ -421,15 +407,6 @@ function induced_subgraph_datagraph(graph::AbstractDataGraph, subvertices) end return subgraph, vlist end -function Graphs.induced_subgraph(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) -end # # Printing diff --git a/src/datagraph.jl b/src/datagraph.jl index 6317224..df662db 100644 --- a/src/datagraph.jl +++ b/src/datagraph.jl @@ -1,14 +1,20 @@ using Dictionaries: Dictionary -using Graphs: Graphs, edgetype -using Graphs.SimpleGraphs: SimpleGraph -using NamedGraphs.GraphsExtensions: convert_vertextype, directed_graph, vertextype +using Graphs: Graphs, edgetype, has_edge, has_vertex +using NamedGraphs: GenericNamedGraph +using NamedGraphs.GraphsExtensions: + convert_vertextype, + directed_graph, + vertextype, + directed_graph_type, + similar_graph, + rename_vertices # TODO: define VertexDataGraph, a graph with only data on the # vertices, and EdgeDataGraph, a graph with only data on the edges. # 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} @@ -26,18 +32,52 @@ struct DataGraph{V, VD, ED, G <: AbstractGraph, E <: AbstractEdge} <: AbstractDa ) end 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)) + +# Interface 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)) +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_type(G::Type{<:DataGraph}) = eltype(fieldtype(G, :vertex_data)) +edge_data_type(G::Type{<:DataGraph}) = eltype(fieldtype(G, :edge_data)) + +# Extras + +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 -# 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 @@ -47,13 +87,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 @@ -84,17 +124,15 @@ 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 -# 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), - vertex_data_eltype(graph_type), - edgetype(graph_type), + vertex_data_type(graph_type), + edge_data_type(graph_type), directed_graph_type(underlying_graph_type(graph_type)), edgetype(graph_type), } diff --git a/src/dataview.jl b/src/dataview.jl new file mode 100644 index 0000000..e6cb1da --- /dev/null +++ b/src/dataview.jl @@ -0,0 +1,106 @@ +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 + function VertexDataView(graph) + V = vertextype(graph) + VD = vertex_data_type(graph) + G = typeof(graph) + return new{V, VD, G}(graph) + end +end + +_keys(view::VertexDataView) = Indices(vertices(view.graph)) + +struct EdgeDataView{E, ED, G <: AbstractGraph} <: AbstractDictionary{E, ED} + graph::G + function EdgeDataView(graph) + E = edgetype(graph) + ED = edge_data_type(graph) + G = typeof(graph) + return new{E, ED, G}(graph) + end +end + +_keys(view::EdgeDataView) = Indices(edges(view.graph)) + +const VertexOrEdgeDataView{K, V, G} = Union{VertexDataView{K, V, G}, EdgeDataView{K, V, G}} + +function Base.keys(view::VertexOrEdgeDataView) + return filterview(k -> isassigned(view.graph, k), _keys(view)) +end + +Base.isassigned(view::VertexOrEdgeDataView{K}, key::K) where {K} = isassigned(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) + ind = gettokenvalue(keys(view), token) + return isassigned(view, ind) +end +function Dictionaries.gettokenvalue(view::VertexOrEdgeDataView, 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, gettokenvalue(keys(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 + +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.fill!(view::VertexOrEdgeDataView{<:Any, T}, value::T) where {T} + for key in keys(view) + view[key] = value + end + return view +end diff --git a/src/indexing.jl b/src/indexing.jl new file mode 100644 index 0000000..7fb20e2 --- /dev/null +++ b/src/indexing.jl @@ -0,0 +1,212 @@ +using NamedGraphs: + to_graph_index, + to_graph_indices, + AbstractEdges, + AbstractVertices, + to_vertices, + to_edges, + AbstractGraphIndices +using NamedGraphs.GraphsExtensions: subgraph + +# ====================================== 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 + +# 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 get_indices_data(graph::AbstractGraph, vertices::AbstractVertices) + return get_vertices_data(graph, vertices) +end + +function get_indices_data(graph::AbstractGraph, edges::AbstractEdges) + return get_edges_data(graph, edges) +end + +# ====================================== isassigned ====================================== # + +function Base.isassigned(graph::AbstractDataGraph, index) + return _isassigned(graph, to_graph_index(graph, index)) +end + +_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_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_index_data!(graph, arranged_data, arranged_edge) + return graph +end + +function _setindex!(graph::AbstractGraph, val, vertices::AbstractVertices) + return set_indices_data!(graph, val, vertices) +end +function _setindex!(graph::AbstractGraph, val, edges::AbstractEdges) + 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)]` +function Base.setindex!(graph::AbstractDataGraph, x, i1, i2, i...) + graph[(i1, i2, i...)] = x + return graph +end + +# Ordinal Indexing +function NamedGraphs.to_graph_index( + graph::AbstractGraph, + pair::Pair{<:OrdinalSuffixedInteger, <:OrdinalSuffixedInteger} + ) + vs = vertices(graph) + v1, v2 = pair + return to_graph_index(graph, vs[v1] => vs[v2]) +end +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 new file mode 100644 index 0000000..9a914c5 --- /dev/null +++ b/src/lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl @@ -0,0 +1,261 @@ +module DataGraphsPartitionedGraphsExt +using NamedGraphs.PartitionedGraphs: quotient_graph_vertextype +using Graphs: Graphs, AbstractGraph, AbstractEdge, vertices, edges +using ..DataGraphs: + DataGraph, + _DataGraph, + _getindex, + AbstractDataGraph, + DataGraphs, + edge_data, + edgetype, + underlying_graph, + vertex_data, + get_vertex_data, + 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, + 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, + QuotientEdgeEdge, + QuotientEdges, + QuotientEdgeEdges, + QuotientVertex, + QuotientVertexOrEdge, + QuotientVertexVertex, + QuotientVertexVertices, + QuotientVerticesVertices, + QuotientVertices, + QuotientView, + partitioned_vertices, + quotientvertices, + quotientedges, + parent_graph_type, + 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. + +# ======================== 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 DataGraphs.get_vertex_data(qv::QuotientView, v) + return get_graph_index(parent(qv), QuotientVertex(v)) +end + +function DataGraphs.get_edge_data(qv::QuotientView, v) + return get_graph_index(parent(qv), QuotientEdge(v)) +end + +function DataGraphs.has_vertex_data(qv::QuotientView, v) + return isassigned(parent(qv), QuotientVertex(v)) +end + +function DataGraphs.has_edge_data(qv::QuotientView, v) + return isassigned(parent(qv), QuotientEdge(v)) +end + +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 + +function DataGraphs.unset_vertex_data!(qv::QuotientView, v) + return unset!(parent(qv), QuotientVertex(v)) +end +function DataGraphs.unset_edge_data!(qe::QuotientView, e) + return unset!(parent(qe), QuotientEdge(e)) +end + +# =========================== 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 _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 + + +function DataGraphs.vertex_data_type(T::Type{<:QuotientView}) + PGT = parent_graph_type(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(get_index_data, PGT, QuotientEdge{vertextype(T), edgetype(T)}) +end + +DataGraphs.underlying_graph(qv::QuotientView) = underlying_graph(copy(qv)) + +Base.isassigned(qv::QuotientView, ind) = DataGraphs._isassigned(qv, to_graph_index(qv, ind)) + +# PartitionedGraphs interface +function PartitionedGraphs.partitioned_vertices(dg::AbstractDataGraph) + return partitioned_vertices(underlying_graph(dg)) +end + +PartitionedGraphs.partitionedgraph(::AbstractDataGraph, parts) = not_implemented() +PartitionedGraphs.departition(::AbstractDataGraph) = not_implemented() + + +function DataGraphs.vertices_data_eltype( + T::Type{<:AbstractGraph}, + QV::Type{<:QuotientVertices} + ) + return eltype(Base.promote_op(get_vertices_data, T, QV)) +end + +function DataGraphs.edges_data_eltype( + T::Type{<:AbstractGraph}, + QE::Type{<:QuotientEdges} + ) + 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/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" diff --git a/test/test_basics.jl b/test/test_basics.jl index 6270d6c..7e64fe9 100644 --- a/test/test_basics.jl +++ b/test/test_basics.jl @@ -1,5 +1,13 @@ using DataGraphs: - DataGraphs, DataGraph, edge_data, edge_data_eltype, vertex_data, vertex_data_eltype + DataGraph, + DataGraphs, + EdgeDataView, + VertexDataView, + edge_data, + edge_data_type, + underlying_graph, + vertex_data, + vertex_data_type using Dictionaries: AbstractIndices, Dictionary, Indices, dictionary using Graphs: add_edge!, @@ -23,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 @@ -47,11 +54,11 @@ using Test: @test, @test_broken, @testset end @testset "Basics" begin - g = grid((4,)) - dg = DataGraph(g; vertex_data_eltype = String, edge_data_eltype = Symbol) - @test !isassigned(dg, SimpleEdge(1, 2)) + g = named_grid(4) + 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, SimpleEdge(1 => 2)) + @test !isassigned(dg, NamedEdge(1 => 2)) @test !isassigned(dg, 1 => 3) @test !isassigned(dg, 1) @test !isassigned(dg, 2) @@ -86,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 @@ -101,11 +108,11 @@ 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))")`. - 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 @@ -121,15 +128,15 @@ 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 vertex_data_eltype(dg_uint16) === String - @test edge_data_eltype(dg_uint16) === String + @test edgetype(dg_uint16) === NamedEdge{UInt16} + @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" @@ -173,8 +180,8 @@ using Test: @test, @test_broken, @testset end @testset "get and get! functions" begin - g = grid((4,)) - dg = DataGraph(g; vertex_data_eltype = String, edge_data_eltype = Symbol) + g = named_grid(4) + dg = DataGraph(g; vertex_data_type = String, edge_data_type = Symbol) # Test for vertices @test get(dg, 1, "default") == "default" @@ -229,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 @@ -244,17 +251,15 @@ 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 @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" @@ -297,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"] @@ -349,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 @@ -392,7 +397,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 +412,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) @@ -442,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" @@ -468,4 +473,50 @@ 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_type = Int, + edge_data_type = 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 + + 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