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/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..cd07b12 100644 --- a/src/DataGraphs.jl +++ b/src/DataGraphs.jl @@ -2,11 +2,10 @@ 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") export AbstractDataGraph, DataGraph diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl index 46430b4..2271330 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -1,118 +1,167 @@ -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_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 # 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() + +# 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) + +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::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_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}) = 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() + # Fix for ambiguity error with `AbstractGraph` version function Graphs.degree(graph::AbstractDataGraph, vertex::Integer) return Graphs.degree(underlying_graph(graph), vertex) @@ -131,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 @@ -182,14 +231,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 +273,7 @@ function Base.reverse(graph::AbstractDataGraph) return reversed_graph end + function Graphs.merge_vertices( graph::AbstractDataGraph, merge_vertices; @@ -205,7 +282,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 +293,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 +322,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,109 +356,61 @@ 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)) +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.isassigned(graph::AbstractDataGraph, edge::Pair) - return isassigned(graph, edgetype(graph)(edge)) +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, vertex) - set!(vertex_data(graph), vertex, data) - return graph +function Base.isassigned(graph::AbstractDataGraph, index) + return _isassigned(graph, to_graph_indices(graph, index)) 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 +_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.setindex!(graph::AbstractDataGraph, data, edge::Pair) - graph[edgetype(graph)(edge)] = data - return graph +function _isassigned(graph::AbstractDataGraph, edges::AbstractEdges) + return has_edges_data(graph, 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 +function _isassigned(graph::AbstractDataGraph, vertices::AbstractVertices) + return has_vertices_data(graph, vertices) end function induced_subgraph_datagraph(graph::AbstractDataGraph, subvertices) diff --git a/src/datagraph.jl b/src/datagraph.jl index 6317224..ea959a2 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,45 @@ 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 + +function GraphsExtensions.similar_graph(T::Type{<:DataGraph}) + similar_underlying_graph = similar_graph(underlying_graph_type(T)) + return T(similar_underlying_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 @@ -84,17 +117,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), + edge_data_eltype(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..5ab8a89 --- /dev/null +++ b/src/dataview.jl @@ -0,0 +1,63 @@ +using Dictionaries: Dictionaries, AbstractDictionary, gettokenvalue, filterview + +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 + +_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_eltype(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} = view.graph[key] +Base.getindex(view::EdgeDataView, key::Pair) = view.graph[key] + +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, 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 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 diff --git a/test/test_basics.jl b/test/test_basics.jl index 6270d6c..f826dd0 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_eltype, + underlying_graph, + vertex_data, + vertex_data_eltype 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,)) + 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) @@ -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,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))")`. @@ -121,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" @@ -173,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 @@ -244,11 +251,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 @@ -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) @@ -468,4 +473,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