diff --git a/Project.toml b/Project.toml index 2775d4a..29d2126 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "DataGraphs" uuid = "b5a273c3-7e6c-41f6-98bd-8d7f1525a36a" -version = "0.4.2" +version = "0.4.3" authors = ["Matthew Fishman and contributors"] [workspace] @@ -16,6 +16,10 @@ SimpleTraits = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" GraphsFlows = "06909019-6f44-4949-96fc-b9d9aaa02889" ITensorVisualizationBase = "cd2553d2-8bef-4d93-8a38-c62f17d5ad23" +[sources.NamedGraphs] +rev = "jd/fix-forest-cover-edge-sequence" +url = "https://www.github.com/ITensor/NamedGraphs.jl" + [extensions] DataGraphsGraphsFlowsExt = "GraphsFlows" DataGraphsITensorVisualizationBaseExt = "ITensorVisualizationBase" @@ -25,6 +29,6 @@ Dictionaries = "0.4" Graphs = "1" GraphsFlows = "0.1.1" ITensorVisualizationBase = "0.1" -NamedGraphs = "0.11.1" +NamedGraphs = "0.11.4" SimpleTraits = "0.9" julia = "1.10" diff --git a/src/DataGraphs.jl b/src/DataGraphs.jl index 6f93972..e201e5e 100644 --- a/src/DataGraphs.jl +++ b/src/DataGraphs.jl @@ -6,6 +6,9 @@ include("dataview.jl") include("abstractdatagraph.jl") include("indexing.jl") include("datagraph.jl") +include("abstractedgeorvertexdatagraph.jl") +include("vertexdatagraph.jl") +include("edgedatagraph.jl") # TODO: Turn into an extension once `PartitionedGraphs` is excised. include("lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl") diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl index 2fefa68..a913660 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -6,9 +6,15 @@ using NamedGraphs.GraphsExtensions: GraphsExtensions, add_edges!, add_vertices!, using NamedGraphs.OrdinalIndexing: OrdinalSuffixedInteger using NamedGraphs.SimilarType: similar_type using NamedGraphs: NamedGraphs, AbstractEdges, AbstractNamedEdge, AbstractNamedGraph, - AbstractVertices, NamedDiGraph, NamedGraph, position_graph_type, similar_graph + AbstractVertices, NamedDiGraph, NamedGraph, Vertices, position_graph_type, + similar_graph, subgraph_edges using SimpleTraits: SimpleTraits, @traitfn, Not +struct VertexEdgeDataTypes{VD, ED} + vertex_data_type::VD + edge_data_type::ED +end + abstract type AbstractDataGraph{V, VD, ED} <: AbstractNamedGraph{V} end vertex_data_type(::Type{<:AbstractGraph}) = Any @@ -21,12 +27,15 @@ edge_data_type(::Type{<:AbstractDataGraph{V, VD, ED}}) where {V, VD, ED} = ED # TODO: Define for `AbstractGraph` as a `DataGraphInterface`. underlying_graph(::AbstractDataGraph) = not_implemented() +# `isassigned` is_vertex_assigned(::AbstractDataGraph, vertex) = not_implemented() is_edge_assigned(::AbstractDataGraph, edge) = not_implemented() +# `getindex` get_vertex_data(::AbstractDataGraph, vertex) = not_implemented() get_edge_data(::AbstractDataGraph, edge) = not_implemented() +# `setindex!` set_vertex_data!(::AbstractDataGraph, data, vertex) = not_implemented() set_edge_data!(::AbstractDataGraph, data, edge) = not_implemented() @@ -41,12 +50,6 @@ function get_edges_data(g::AbstractGraph, edges) return map(e -> getindex(g, e), Indices(edges)) end -Graphs.has_vertex(g::AbstractDataGraph, vertex) = has_vertex(underlying_graph(g), vertex) -Graphs.has_edge(g::AbstractDataGraph, edge) = has_edge(underlying_graph(g), edge) -function Graphs.has_edge(g::AbstractDataGraph, edge::AbstractNamedEdge) - return has_edge(underlying_graph(g), edge) -end - vertex_data(dg::AbstractGraph) = VertexDataView(dg) edge_data(dg::AbstractGraph) = EdgeDataView(dg) @@ -57,9 +60,7 @@ function assigned_edges(graph::AbstractGraph) return Indices(filter(e -> isassigned(graph, e), edges(graph))) end -function Graphs.edgetype(graph::AbstractDataGraph) - return Graphs.edgetype(underlying_graph(graph)) -end +Graphs.edgetype(graph::AbstractDataGraph) = edgetype(underlying_graph(graph)) function Graphs.edgetype(graph_type::Type{<:AbstractDataGraph}) return edgetype(underlying_graph_type(graph_type)) end @@ -75,9 +76,38 @@ function NamedGraphs.position_graph_type(type::Type{<:AbstractDataGraph}) return position_graph_type(underlying_graph_type(type)) end -function Base.copyto!(dst_graph::AbstractDataGraph, src_graph::AbstractDataGraph) - vertex_data(dst_graph) .= vertex_data(src_graph) - edge_data(dst_graph) .= edge_data(src_graph) +function Base.copy(graph::AbstractDataGraph) + copy_graph = similar_graph(graph) + # Allow copies of graphs with undefined data. + copyto!(copy_graph, graph, assigned_vertices(graph)) + copyto!(copy_graph, graph, assigned_edges(graph)) + return copy_graph +end + +# Base method to specialize on. +function Base.copyto!(graph_dst::AbstractDataGraph, src, keys) + for key in keys + graph_dst[key] = src[key] + end + return graph_dst +end + +function Base.copyto!(graph_dst::AbstractDataGraph, src) + copyto!_datagraph(graph_dst, src) + return graph_dst +end + +# To prevent method ambiguities +function copyto!_datagraph(dst::AbstractDataGraph, src::AbstractDataGraph) + if !issetequal(vertices(dst), vertices(src)) + throw(ArgumentError("destination and source graphs must have the same vertices")) + end + copyto!(dst, src, vertices(src)) + copyto!(dst, src, edges(src)) + return dst +end +function copyto!_datagraph(dst_graph::AbstractDataGraph, src) + copyto!(dst_graph, src, keys(src)) return dst_graph end @@ -110,8 +140,8 @@ GraphsExtensions.convert_vertextype(::Type, ::AbstractDataGraph) = not_implement function Base.:(==)(dg1::AbstractDataGraph, dg2::AbstractDataGraph) underlying_graph(dg1) == underlying_graph(dg2) || return false - vertex_data(dg1) == vertex_data(dg2) || return false - edge_data(dg1) == edge_data(dg2) || return false + assigned_vertex_data(dg1) == assigned_vertex_data(dg2) || return false + assigned_edge_data(dg1) == assigned_edge_data(dg2) || return false return true end @@ -120,21 +150,19 @@ end """ similar_graph(datagraph::AbstractDataGraph, D::Type) similar_graph(datagraph::AbstractDataGraph, D::Type, vertices) - similar_graph(datagraph::AbstractDataGraph, VD::Type, ED::Type) - similar_graph(datagraph::AbstractDataGraph, VD::Type, ED::Type, vertices) + similar_graph(datagraph::AbstractDataGraph, D::VertexEdgeDataTypes) + similar_graph(datagraph::AbstractDataGraph, D::VertexEdgeDataTypes, vertices) Create an uninitialized data graph, similar to the provided `datagraph`, but with vertices defined by `vertices` and a vertex and edge data type `D`. One may also provide separate -vertex and edge data types `VD` and `ED`. +vertex and edge data types `VD` and `ED` by using the wrapper `VertexEdgeDataTypes(VD, ED)`. If vertices are not provided, then the graph is constructed with the same vertices and edges as the input graph. """ -function NamedGraphs.similar_graph( - graph::AbstractDataGraph - ) - VD = vertex_data_type(graph) - ED = edge_data_type(graph) - return similar_graph(graph, VD, ED) + +NamedGraphs.similar_graph(graph::AbstractDataGraph, D::Type) = similar_datagraph(graph, D) +function NamedGraphs.similar_graph(graph::AbstractDataGraph, D::VertexEdgeDataTypes) + return similar_datagraph(graph, D) end function NamedGraphs.similar_graph( @@ -143,37 +171,36 @@ function NamedGraphs.similar_graph( ) VD = vertex_data_type(graph) ED = edge_data_type(graph) - return similar_graph(graph, VD, ED, vertices) -end -function NamedGraphs.similar_graph( - graph::AbstractDataGraph, - D::Type - ) - return similar_graph(graph, D, D) + + return similar_graph(graph, VertexEdgeDataTypes(VD, ED), vertices) end +# Base case(s) (overload these if fallback not wanted). function NamedGraphs.similar_graph( graph::AbstractDataGraph, D::Type, vertices ) - return similar_graph(graph, D, D, vertices) + return similar_datagraph(graph, D, D, vertices) end - function NamedGraphs.similar_graph( graph::AbstractDataGraph, - VD::Type, - ED::Type + D::VertexEdgeDataTypes, + vertices ) - new_graph = similar_graph(graph, VD, ED, vertices(graph)) + return similar_datagraph(graph, D.vertex_data_type, D.edge_data_type, vertices) +end + +# Internal implementation functions, +function similar_datagraph(graph::AbstractGraph, D) + new_graph = similar_graph(graph, D, vertices(graph)) add_edges!(new_graph, edges(graph)) return new_graph end -# Base case(s) (overload these if fallback not wanted). -@traitfn function NamedGraphs.similar_graph( - graph::AbstractDataGraph::(!IsDirected), +@traitfn function similar_datagraph( + graph::AbstractGraph::(!IsDirected), VD::Type, ED::Type, vertices @@ -182,8 +209,8 @@ end return DataGraph(underlying_graph; vertex_data_type = VD, edge_data_type = ED) end -@traitfn function NamedGraphs.similar_graph( - graph::AbstractDataGraph::IsDirected, +@traitfn function similar_datagraph( + graph::AbstractGraph::IsDirected, VD::Type, ED::Type, vertices @@ -426,3 +453,8 @@ function Base.show(io::IO, mime::MIME"text/plain", graph::AbstractDataGraph) end Base.show(io::IO, graph::AbstractDataGraph) = show(io, MIME"text/plain"(), graph) + +function GraphsExtensions.forest_cover_edge_sequence(graph::AbstractDataGraph; kwargs...) + dummy_graph = add_edges!(NamedGraph(vertices(graph)), edges(graph)) + return GraphsExtensions.forest_cover_edge_sequence(dummy_graph; kwargs...) +end diff --git a/src/abstractedgeorvertexdatagraph.jl b/src/abstractedgeorvertexdatagraph.jl new file mode 100644 index 0000000..92216fd --- /dev/null +++ b/src/abstractedgeorvertexdatagraph.jl @@ -0,0 +1,275 @@ +using Dictionaries: Dictionaries, Indices, isinsertable, set! +using Graphs: edges, edgetype, has_edge, has_vertex, rem_edge!, rem_vertex!, vertices +using NamedGraphs: NamedGraphs, Vertices, similar_graph, subgraph_edges, to_graph_index + +abstract type AbstractVertexDataGraph{T, V} <: AbstractDataGraph{V, T, Nothing} end +abstract type AbstractEdgeDataGraph{T, V} <: AbstractDataGraph{V, Nothing, T} end + +const AbstractVertexOrEdgeDataGraph{T, V} = + Union{AbstractVertexDataGraph{T, V}, AbstractEdgeDataGraph{T, V}} + +Graphs.edgetype(graph::AbstractVertexOrEdgeDataGraph) = edgetype(typeof(graph)) + +function NamedGraphs.similar_graph( + graph::AbstractVertexOrEdgeDataGraph + ) + return similar_graph(graph, valtype(graph)) +end + +function NamedGraphs.similar_graph( + graph::AbstractVertexOrEdgeDataGraph, + vertices + ) + return similar_graph(graph, valtype(graph), vertices) +end + +function Base.copy(graph::AbstractVertexOrEdgeDataGraph) + graph_dst = similar_graph(graph) + # Allow copies of graphs with undefined data. + copyto!(graph_dst, graph, filter(key -> isassigned(graph, key), keys(graph))) + return graph_dst +end + +function Base.copyto!(graph_dst::AbstractVertexOrEdgeDataGraph, src) + copyto!(graph_dst, src, keys(src)) + return graph_dst +end + +Base.iterate(graph::AbstractVertexOrEdgeDataGraph) = iterate(index_data(graph)) +function Base.iterate(graph::AbstractVertexOrEdgeDataGraph, state) + return iterate(index_data(graph), state) +end + +Base.keytype(graph::AbstractVertexOrEdgeDataGraph) = keytype(typeof(graph)) + +Base.valtype(graph::AbstractVertexOrEdgeDataGraph) = valtype(typeof(graph)) +Base.valtype(::Type{<:AbstractVertexOrEdgeDataGraph{T}}) where {T} = T + +Base.eltype(graph::AbstractVertexOrEdgeDataGraph) = eltype(typeof(graph)) +Base.eltype(::Type{<:AbstractVertexOrEdgeDataGraph{T}}) where {T} = T + +Base.length(graph::AbstractVertexOrEdgeDataGraph) = length(index_data(graph)) +Base.keys(graph::AbstractVertexOrEdgeDataGraph) = keys(index_data(graph)) +Base.values(graph::AbstractVertexOrEdgeDataGraph) = values(index_data(graph)) + +Dictionaries.issettable(::AbstractVertexOrEdgeDataGraph) = true +Dictionaries.isinsertable(::AbstractVertexOrEdgeDataGraph) = false + +function Base.insert!(graph::AbstractVertexOrEdgeDataGraph, ind, data) + isinsertable(graph) || throw(ArgumentError("Graph does not support insertion.")) + insert!_datagraph(graph, to_graph_index(graph, ind), data) + return graph +end + +function Base.delete!(graph::AbstractVertexOrEdgeDataGraph, ind) + delete!_datagraph(graph, to_graph_index(graph, ind)) + return graph +end + +function Dictionaries.set!(graph::AbstractVertexOrEdgeDataGraph, ind, data) + set!_datagraph(graph, to_graph_index(graph, ind), data) + return graph +end + +# ================================== vertex data graph =================================== # + +Base.keytype(::Type{<:AbstractVertexDataGraph{T, V}}) where {T, V} = V + +is_edge_assigned(::AbstractVertexDataGraph, _edge) = false + +# For method ambiguity resolution, +# TODO: remove this method once generic `AbstractDataGraph` method is upgraded. +function set_index_data!(graph::AbstractVertexDataGraph, data, vertex::AbstractEdge) + return throw(MethodError(graph, (data, vertex))) +end +# `setindex!` +function set_index_data!(graph::AbstractVertexDataGraph, data, vertex) + if !has_vertex(graph, vertex) + throw(IndexError("Graph does not contain vertex $vertex")) + end + set_vertex_data!(graph, data, vertex) + return graph +end + +# `insert!` +function insert!_datagraph(graph::AbstractVertexDataGraph, vertex, data) + if has_vertex(graph, vertex) + throw(IndexError("Graph already contains vertex $vertex")) + end + insert_vertex_data!(graph, vertex, data) + return graph +end + +# `delete!` +function delete!_datagraph(graph::AbstractVertexDataGraph, vertex) + if !has_vertex(graph, vertex) + throw(IndexError("Graph does not contain vertex $vertex")) + end + rem_vertex!(graph, vertex) + return graph +end + +# `set!` +function set!_datagraph(graph::AbstractVertexDataGraph, vertex, data) + if has_vertex(graph, vertex) + graph[vertex] = data + else + insert!(graph, vertex, data) + end + return graph +end + +function NamedGraphs.similar_graph( + graph::AbstractVertexDataGraph, + T::Type + ) + new_graph = similar_graph(graph, T, vertices(graph)) + # we can add edges to a `AbstractVertexDataGraph`. + add_edges!(new_graph, edges(graph)) + return new_graph +end + +# Base method to overload. +function NamedGraphs.similar_graph( + ::AbstractVertexDataGraph, + T::Type, + vertices + ) + return similar_graph(VertexDataGraph{T}, vertices) +end + +function NamedGraphs.induced_subgraph_from_vertices( + graph::AbstractVertexDataGraph, + subvertices + ) + subnetwork = similar_graph(graph, subvertices) + add_edges!(subnetwork, subgraph_edges(graph, subvertices)) + tensors = view(vertex_data(graph), Indices(subvertices)) + copyto!(subnetwork, tensors) + return subnetwork, subvertices +end + +# Internal +index_data(graph::AbstractVertexDataGraph) = vertex_data(graph) + +function Base.show(io::IO, mime::MIME"text/plain", graph::AbstractVertexDataGraph) + println(io, "$(typeof(graph)) with $(nv(graph)) vertices:") + show(io, mime, vertices(graph)) + println(io, "\n") + println(io, "and $(ne(graph)) edge(s):") + for e in edges(graph) + show(io, mime, e) + println(io) + end + println(io) + println(io, "with vertex data:") + show(io, mime, vertex_data(graph)) + return nothing +end + +# =================================== edge data graph ==================================== # + +Base.keytype(::Type{<:AbstractEdgeDataGraph{T, V}}) where {T, V} = NamedEdge{V} + +is_vertex_assigned(::AbstractEdgeDataGraph, _vertex) = false + +# `setindex!` +function set_index_data!(graph::AbstractEdgeDataGraph, data, edge::AbstractEdge) + v1 = src(edge) + v2 = dst(edge) + if !has_vertex(graph, v1) + throw(IndexError("Graph does not contain vertex $v1")) + elseif !has_vertex(graph, v2) + throw(IndexError("Graph does not contain vertex $v2")) + end + set_edge_data!(graph, data, edge) + return graph +end + +# `insert!` +function insert!_datagraph(graph::AbstractEdgeDataGraph, edge::AbstractEdge, data) + v1 = src(edge) + v2 = dst(edge) + if has_vertex(graph, v1) && has_vertex(graph, v2) + throw(IndexError("Graph already contains vertices $v1 and $v2")) + end + insert_edge_data!(graph, edge, data) + return graph +end +function insert_edge_data!(graph::AbstractEdgeDataGraph, edge::AbstractEdge, data) + v1 = src(edge) + v2 = dst(edge) + has_vertex(graph, v1) || add_vertex!(graph, v1) + has_vertex(graph, v2) || add_vertex!(graph, v2) + set_edge_data!(graph, data, edge) + return graph +end + +# `delete!` +function delete!_datagraph(graph::AbstractEdgeDataGraph, edge) + if !has_edge(graph, edge) + throw(IndexError("Graph does not contain edge $edge")) + end + rem_edge!(graph, edge) + return graph +end + +# `set!` +function set!_datagraph(graph::AbstractEdgeDataGraph, edge::AbstractEdge, data) + if !has_vertex(graph, src(edge)) || !has_vertex(graph, dst(edge)) + insert!(graph, edge, data) + else + graph[edge] = data + end + return graph +end + +function NamedGraphs.similar_graph( + graph::AbstractEdgeDataGraph, + T::Type + ) + new_graph = similar_graph(graph, T, vertices(graph)) + # we can't generically add edges to an `AbstractEdgeDataGraph`. + return new_graph +end + +# Base method to overload. +function NamedGraphs.similar_graph( + ::AbstractEdgeDataGraph, + T::Type, + vertices + ) + return similar_graph(EdgeDataGraph{T}, vertices) +end + +function NamedGraphs.induced_subgraph_from_vertices( + graph::AbstractEdgeDataGraph, + subvertices + ) + subnetwork = similar_graph(graph, subvertices) + subedges = subgraph_edges(graph, subvertices) + + tensors = view(edge_data(graph), Indices(subedges)) + + copyto!(subnetwork, tensors) + + return subnetwork, subvertices +end + +# Internal +index_data(graph::AbstractEdgeDataGraph) = edge_data(graph) + +function Base.show(io::IO, mime::MIME"text/plain", graph::AbstractEdgeDataGraph) + println(io, "$(typeof(graph)) with $(nv(graph)) vertices:") + show(io, mime, vertices(graph)) + println(io, "\n") + println(io, "and $(ne(graph)) edge(s):") + for e in edges(graph) + show(io, mime, e) + println(io) + end + println(io) + println(io, "with edge data:") + show(io, mime, edge_data(graph)) + return nothing +end diff --git a/src/datagraph.jl b/src/datagraph.jl index 4a6b2fd..35c980f 100644 --- a/src/datagraph.jl +++ b/src/datagraph.jl @@ -54,7 +54,7 @@ edge_data_type(G::Type{<:DataGraph}) = eltype(fieldtype(G, :edge_data)) # Extras # Overwrite the `AbstractDataGraph` fallback (even though they coincide for `DataGraph`) -function NamedGraphs.similar_graph( +function similar_datagraph( graph::DataGraph, vertex_data_type::Type, edge_data_type::Type, diff --git a/src/dataview.jl b/src/dataview.jl index 8eb055f..cd7eb63 100644 --- a/src/dataview.jl +++ b/src/dataview.jl @@ -90,6 +90,9 @@ function Base.setindex!(view::EdgeDataView, vals, keys::Indices{<:Pair}) end function Base.setindex!(view::VertexOrEdgeDataView{K, V}, data::V, key::K) where {K, V} + if !haskey(view, key) + throw(IndexError("Dictionary does not contain index: $key")) + end setindex!(view.graph, data, key) return view end diff --git a/src/edgedatagraph.jl b/src/edgedatagraph.jl new file mode 100644 index 0000000..3252d8e --- /dev/null +++ b/src/edgedatagraph.jl @@ -0,0 +1,155 @@ +using Graphs: dst, has_edge, rem_edge!, rem_vertex!, src +using NamedGraphs: NamedEdge, NamedGraph, ordered_vertices, position_graph, vertex_positions + +struct EdgeDataGraph{T, V} <: AbstractEdgeDataGraph{T, V} + underlying_graph::NamedGraph{V} + edge_data::Dictionary{NamedEdge{V}, T} + function EdgeDataGraph{T, V}( + ::UndefInitializer, + vertices + ) where {T, V} + graph = NamedGraph{V}(vertices) + edge_data = Dictionary{NamedEdge{V}, T}() + return new{T, V}(graph, edge_data) + end +end + +Graphs.is_directed(::Type{<:EdgeDataGraph}) = false + +struct EdgeDataDiGraph{T, V} <: AbstractEdgeDataGraph{T, V} + underlying_graph::NamedDiGraph{V} + edge_data::Dictionary{NamedEdge{V}, T} + function EdgeDataDiGraph{T, V}( + ::UndefInitializer, + vertices + ) where {T, V} + graph = NamedDiGraph{V}(vertices) + edge_data = Dictionary{NamedEdge{V}, T}() + return new{T, V}(graph, edge_data) + end +end + +Graphs.is_directed(::Type{<:EdgeDataDiGraph}) = true + +for GType in (:EdgeDataGraph, :EdgeDataDiGraph) + @eval begin + $GType(::UndefInitializer, vertices) = $GType{Any}(undef, vertices) + function $GType{T}(::UndefInitializer, vertices) where {T} + return $GType{T, eltype(vertices)}(undef, vertices) + end + + $GType(data) = $GType{valtype(data)}(data) + $GType{T}(data) where {T} = $GType{T, vertextype(keytype(data))}(data) + + function $GType{T, V}(data) where {T, V} + edges = NamedEdge{V}.(keys(data)) + vertices = union(src.(edges), dst.(edges)) + graph = $GType{T, V}(undef, vertices) + copyto!(graph, data) + return graph + end + + function Base.:(==)(dg1::$GType, dg2::$GType) + return dg1.underlying_graph == dg2.underlying_graph && + dg1.edge_data == dg2.edge_data + end + end +end + +# ====================================== Graphs.jl ======================================= # + +for GType in (:EdgeDataGraph, :EdgeDataDiGraph) + @eval begin + Graphs.edgetype(::Type{<:$GType{T, V}}) where {T, V} = NamedEdge{V} + + function Graphs.add_vertex!(graph::$GType, vertex) + return add_vertex!(graph.underlying_graph, vertex) + end + + function Graphs.add_edge!(graph::$GType, edge) + G = esc($GType) + throw( + ArgumentError( + "cannot add data-free edges to $G; use `insert!`, `setindex!` or `set!` instead" + ) + ) + return nothing + end + + function Graphs.rem_vertex!(graph::$GType, vertex) + for edge in incident_edges(graph, vertex) + unset!(graph.edge_data, edge) + end + rem_vertex!(graph.underlying_graph, vertex) + return graph + end + + function Graphs.rem_edge!(graph::$GType, edge) + unset!(graph.edge_data, edge) + rem_edge!(graph.underlying_graph, edge) + return graph + end + + Graphs.vertices(graph::$GType) = vertices(graph.underlying_graph) + end +end + +# ==================================== NamedGraphs.jl ==================================== # + +for GType in (:EdgeDataGraph, :EdgeDataDiGraph) + @eval begin + function NamedGraphs.vertex_positions(graph::$GType) + return vertex_positions(graph.underlying_graph) + end + + function NamedGraphs.ordered_vertices(graph::$GType) + return ordered_vertices(graph.underlying_graph) + end + + function NamedGraphs.position_graph(graph::$GType) + return position_graph(graph.underlying_graph) + end + + function NamedGraphs.similar_graph(graph::$GType, T::Type, vertices) + new_graph = $GType{T}(undef, vertices) + return new_graph + end + + # We know how to add edges keys for these particurly concrete types + function NamedGraphs.similar_graph(graph::$GType, T::Type) + new_graph = similar_graph(graph, T, vertices(graph)) + add_edges!(new_graph.underlying_graph, edges(graph)) + return new_graph + end + + NamedGraphs.similar_graph(T::Type{<:$GType}, vertices) = T(undef, vertices) + end +end + +# ==================================== DataGraphs.jl ===================================== # + +for GType in (:EdgeDataGraph, :EdgeDataDiGraph) + @eval begin + edge_data_type(::Type{<:$GType{T}}) where {T} = T + + function set_edge_data!(graph::$GType, data, edge) + # Edges `upsert` if vertices are present. + has_edge(graph, edge) || add_edge!(graph.underlying_graph, edge) + set!(graph.edge_data, edge, data) + return graph + end + + get_edge_data(graph::$GType, edge) = graph.edge_data[edge] + + is_vertex_assigned(::$GType, _vertex) = false + is_edge_assigned(graph::$GType, edge) = isassigned(graph.edge_data, edge) + end +end + +# =================================== Dictionaries.jl ==================================== # + +for GType in (:EdgeDataGraph, :EdgeDataDiGraph) + @eval begin + Dictionaries.isinsertable(::$GType) = true + end +end diff --git a/src/vertexdatagraph.jl b/src/vertexdatagraph.jl new file mode 100644 index 0000000..e7fdd6d --- /dev/null +++ b/src/vertexdatagraph.jl @@ -0,0 +1,148 @@ +using Dictionaries: Dictionary, set! +using Graphs: Graphs, has_edge, rem_vertex! +using NamedGraphs: + NamedDiGraph, NamedEdge, NamedGraph, ordered_vertices, position_graph, vertex_positions + +struct VertexDataGraph{T, V} <: AbstractVertexDataGraph{T, V} + underlying_graph::NamedGraph{V} + vertex_data::Dictionary{V, T} + function VertexDataGraph{T, V}( + ::UndefInitializer, + vertices + ) where {T, V} + graph = NamedGraph{V}(vertices) + vertex_data = Dictionary{V, T}() + return new{T, V}(graph, vertex_data) + end +end + +struct VertexDataDiGraph{T, V} <: AbstractVertexDataGraph{T, V} + underlying_graph::NamedDiGraph{V} + vertex_data::Dictionary{V, T} + function VertexDataDiGraph{T, V}( + ::UndefInitializer, + vertices + ) where {T, V} + graph = NamedDiGraph{V}(vertices) + vertex_data = Dictionary{V, T}() + return new{T, V}(graph, vertex_data) + end +end + +Graphs.is_directed(::Type{<:VertexDataGraph}) = false +Graphs.is_directed(::Type{<:VertexDataDiGraph}) = true + +for GType in (:VertexDataGraph, :VertexDataDiGraph) + @eval begin + $GType(::UndefInitializer, vertices) = $GType{Any}(undef, vertices) + function $GType{T}(::UndefInitializer, vertices) where {T} + return $GType{T, eltype(vertices)}(undef, vertices) + end + + $GType(data) = $GType{valtype(data)}(data) + $GType{T}(data) where {T} = $GType{T, keytype(data)}(data) + + function $GType{T, V}(data) where {T, V} + vertices = keys(data) + cache = $GType{T, V}(undef, vertices) + return copyto!(cache, data) + end + + function Base.:(==)(dg1::$GType, dg2::$GType) + return dg1.underlying_graph == dg2.underlying_graph && + dg1.vertex_data == dg2.vertex_data + end + end +end + +# ====================================== Graphs.jl ======================================= # + +for GType in (:VertexDataGraph, :VertexDataDiGraph) + @eval begin + Graphs.edgetype(::Type{<:$GType{T, V}}) where {T, V} = NamedEdge{V} + + function Graphs.add_vertex!(graph::$GType, vertex) + return throw( + ArgumentError( + "cannot add data-free vertices to $GType; use `insert!`, `setindex!` or `set!` instead" + ) + ) + end + + function Graphs.add_edge!(graph::$GType, edge::NamedEdge) + return add_edge!(graph.underlying_graph, edge) + end + + function Graphs.rem_vertex!(graph::$GType, vertex) + unset!(graph.vertex_data, vertex) + rem_vertex!(graph.underlying_graph, vertex) + return graph + end + + function Graphs.rem_edge!(graph::$GType, vertex) + rem_edge!(graph.underlying_graph, vertex) + return graph + end + + Graphs.vertices(graph::$GType) = vertices(graph.underlying_graph) + end +end + +# ==================================== NamedGraphs.jl ==================================== # + +for GType in (:VertexDataGraph, :VertexDataDiGraph) + @eval begin + function NamedGraphs.vertex_positions(graph::$GType) + return vertex_positions(graph.underlying_graph) + end + + function NamedGraphs.ordered_vertices(graph::$GType) + return ordered_vertices(graph.underlying_graph) + end + + function NamedGraphs.position_graph(graph::$GType) + return position_graph(graph.underlying_graph) + end + + function NamedGraphs.similar_graph(graph::$GType, T::Type, vertices) + new_graph = $GType{T}(undef, vertices) + return new_graph + end + + NamedGraphs.similar_graph(T::Type{<:$GType}, vertices) = T(undef, vertices) + end +end + +# ==================================== DataGraphs.jl ===================================== # + +for GType in (:VertexDataGraph, :VertexDataDiGraph) + @eval begin + vertex_data_type(::Type{<:$GType{T}}) where {T} = T + + function set_vertex_data!(graph::$GType, data, vertex) + # We use an upsert here as we have already checked if the vertex (i.e. key) exists, + # but it might not exist in the internal `Dictionary`, so add it if not. + set!(graph.vertex_data, vertex, data) + return graph + end + + get_vertex_data(graph::$GType, vertex) = graph.vertex_data[vertex] + + is_vertex_assigned(graph::$GType, vertex) = isassigned(graph.vertex_data, vertex) + is_edge_assigned(::$GType, _edge) = false + end +end + +# =================================== Dictionaries.jl ==================================== # + +for GType in (:VertexDataGraph, :VertexDataDiGraph) + @eval begin + Dictionaries.isinsertable(::$GType) = true + + function insert_vertex_data!(graph::$GType, vertex, data) + add_vertex!(graph.underlying_graph, vertex) + insert!(graph.vertex_data, vertex, data) + return graph + end + end +end diff --git a/test/Project.toml b/test/Project.toml index 3463736..d87cefc 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -14,6 +14,10 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [sources.DataGraphs] path = ".." +[sources.NamedGraphs] +rev = "jd/fix-forest-cover-edge-sequence" +url = "https://www.github.com/ITensor/NamedGraphs.jl" + [compat] Aqua = "0.8.11" DataGraphs = "0.4" diff --git a/test/test_basics.jl b/test/test_basics.jl index 727da8d..0e54777 100644 --- a/test/test_basics.jl +++ b/test/test_basics.jl @@ -1,15 +1,16 @@ -using DataGraphs: DataGraphs, DataGraph, EdgeDataView, VertexDataView, edge_data, - edge_data_type, underlying_graph, vertex_data, vertex_data_type +using DataGraphs: DataGraphs, DataGraph, EdgeDataView, VertexDataView, VertexEdgeDataTypes, + edge_data, edge_data_type, underlying_graph, vertex_data, vertex_data_type using Dictionaries: Dictionaries, AbstractDictionary, AbstractIndices, Dictionary, IndexError, Indices, dictionary, unset! using Graphs: SimpleDiGraph, a_star, add_edge!, bfs_tree, connected_components, degree, dfs_tree, dijkstra_shortest_paths, dst, edges, edgetype, grid, has_edge, has_vertex, indegree, is_directed, ne, nv, outdegree, path_graph, src, steiner_tree, vertices using GraphsFlows: GraphsFlows -using NamedGraphs.GraphsExtensions: directed_graph, rename_vertices, subgraph, vertextype, ⊔ +using NamedGraphs.GraphsExtensions: + directed_graph, empty_graph, rename_vertices, subgraph, vertextype, ⊔ using NamedGraphs.NamedGraphGenerators: named_grid, named_path_graph using NamedGraphs.OrdinalIndexing: nd, rd, st, th -using NamedGraphs: NamedDiGraph, NamedEdge, NamedGraph, empty_graph, similar_graph +using NamedGraphs: NamedDiGraph, NamedEdge, NamedGraph, similar_graph using Test: @test, @test_broken, @testset @testset "DataGraphs.jl" begin @@ -620,21 +621,19 @@ using Test: @test, @test_broken, @testset @test g_copy["a" => "b"] == -1.0 @test g_copy["b" => "c"] == -2.0 - @test_throws IndexError copyto!(empty_graph(g_copy), g_copy) - g2 = similar_graph(g, ["u", "v"]) @test similar_graph(g2) isa typeof(g) @test has_vertex(g2, "u") @test has_vertex(g2, "v") @test ne(g2) == 0 - g2 = similar_graph(g, Float64, Int) + g2 = similar_graph(g, VertexEdgeDataTypes(Float64, Int)) @test has_vertex(g2, "a") @test has_edge(g2, "a" => "b") @test vertex_data_type(g2) === Float64 @test edge_data_type(g2) === Int - g2 = similar_graph(g, String, Tuple, [:a, :b]) + g2 = similar_graph(g, VertexEdgeDataTypes(String, Tuple), [:a, :b]) @test vertex_data_type(g2) === String @test edge_data_type(g2) === Tuple @test has_vertex(g2, :a) diff --git a/test/test_vertexoredgedatagraph.jl b/test/test_vertexoredgedatagraph.jl new file mode 100644 index 0000000..f59f839 --- /dev/null +++ b/test/test_vertexoredgedatagraph.jl @@ -0,0 +1,510 @@ +using DataGraphs: DataGraphs, EdgeDataDiGraph, EdgeDataGraph, EdgeDataView, + VertexDataDiGraph, VertexDataGraph, VertexDataView, edge_data, edge_data_type, + underlying_graph, vertex_data, vertex_data_type +using Dictionaries: + AbstractDictionary, Dictionary, IndexError, Indices, isinsertable, issettable, set! +using Graphs: AbstractGraph, AbstractSimpleGraph, add_edge!, add_vertex!, dst, edges, + edgetype, has_edge, has_vertex, is_directed, ne, nv, rem_edge!, rem_vertex!, src, + vertices +using NamedGraphs.GraphsExtensions: add_edge, subgraph, vertextype +using NamedGraphs: NamedDiGraph, NamedEdge, NamedGraph, ordered_vertices, position_graph, + similar_graph, vertex_positions +using Test: @test, @test_throws, @testset + +@testset "VertexDataGraph and EdgeDataGraph" begin + @testset "$GType basics" for GType in ( + VertexDataGraph{String, Int}, + VertexDataDiGraph{String, Int}, + ) + @testset "undef constructor" begin + g = GType(undef, [1, 2, 3]) + @test g isa GType + @test nv(g) == 3 + @test ne(g) == 0 + @test has_vertex(g, 1) + @test has_vertex(g, 2) + @test has_vertex(g, 3) + @test !has_vertex(g, 4) + @test Set(collect(vertices(g))) == Set([1, 2, 3]) + end + + @testset "data constructor" begin + data = Dictionary([1, 2, 3], ["V1", "V2", "V3"]) + g = GType(data) + @test g isa GType + @test nv(g) == 3 + @test isassigned(g, 1) + @test isassigned(g, 2) + @test isassigned(g, 3) + @test g[1] == "V1" + @test g[2] == "V2" + @test g[3] == "V3" + + data = Dictionary([1.0, 2.0, 3.0], SubString.(["V1", "V2", "V3"])) + g = GType(data) + @test g isa GType + end + + @testset "copy" begin + data = Dictionary([1, 2, 3], ["V1", "V2", "V3"]) + g = GType(data) + add_edge!(g, NamedEdge(1, 2)) + g_copy = copy(g) + + @test g_copy == g + @test g_copy !== g + + # Test we can copy a graph with undefined data. + g = GType(undef, [1, 2, 3]) + g[1] = "V1" + add_edge!(g, NamedEdge(2, 3)) + g_copy = copy(g) + + @test has_vertex(g_copy, 1) + @test has_vertex(g_copy, 2) + @test has_vertex(g_copy, 3) + @test has_edge(g_copy, 2 => 3) + @test isassigned(g_copy, 1) + @test g_copy[1] == "V1" + @test !isassigned(g_copy, 2) + @test !isassigned(g_copy, 3) + end + + @testset "Graphs.jl interface" begin + g = GType(undef, [1, 2, 3]) + @test vertextype(g) == Int + @test edgetype(g) == NamedEdge{Int} + + add_edge!(g, NamedEdge(1, 2)) + add_edge!(g, NamedEdge(2, 3)) + @test ne(g) == 2 + @test has_edge(g, NamedEdge(1, 2)) + @test has_edge(g, NamedEdge(2, 3)) + @test !has_edge(g, NamedEdge(1, 3)) + @test length(collect(edges(g))) == 2 + + rem_edge!(g, NamedEdge(1, 2)) + @test ne(g) == 1 + @test !has_edge(g, NamedEdge(1, 2)) + end + + @testset "rem_vertex!" begin + g = GType(undef, [1, 2, 3]) + add_edge!(g, NamedEdge(1, 2)) + rem_vertex!(g, 1) + @test !has_vertex(g, 1) + @test nv(g) == 2 + @test ne(g) == 0 + end + + @testset "DataGraphs interface" begin + g = GType(undef, [1, 2, 3]) + @test vertex_data_type(g) == String + @test vertex_data_type(GType) == String + @test !isassigned(g, 1) + @test !isassigned(g, 2) + @test !isassigned(g, 3) + add_edge!(g, NamedEdge(1, 2)) + @test !isassigned(g, 1 => 2) + end + + @testset "setindex! and getindex" begin + g = GType(undef, [1, 2, 3]) + g[1] = "V1" + g[2] = "V2" + g[3] = "V3" + @test isassigned(g, 1) + @test isassigned(g, 2) + @test isassigned(g, 3) + @test g[1] == "V1" + @test g[2] == "V2" + @test g[3] == "V3" + end + + @testset "NamedGraphs interface" begin + g = GType(undef, [1, 2, 3]) + @test position_graph(g) isa AbstractSimpleGraph{Int} + @test ordered_vertices(g) == [1, 2, 3] + @test keys(vertex_positions(g)) == vertices(g) + + g = add_edge(g, NamedEdge(1, 2)) + g[1] = "1" + + gs = similar_graph(g) + @test gs isa GType + @test has_vertex(gs, 1) + @test has_vertex(gs, 2) + @test has_vertex(gs, 3) + @test has_edge(gs, 1 => 2) + @test !isassigned(gs, 1) + + gs = similar_graph(g, vertices(g)) + @test vertices(gs) == vertices(g) + @test ne(gs) == 0 + @test !isassigned(gs, 1) + + gs = similar_graph(g, [1, 2, 4]) + @test has_vertex(gs, 1) + @test has_vertex(gs, 2) + @test has_vertex(gs, 4) + @test nv(gs) == 3 + @test ne(gs) == 0 + @test !isassigned(gs, 1) + + gs = similar_graph(g, Char) + @test vertex_data_type(gs) === Char + @test nv(gs) == 3 + @test ne(gs) == 1 + gs[1] = 'C' + @test gs[1] == 'C' + + gs = similar_graph(g, Float64, vertices(g)) + @test ne(gs) == 0 + + gs = similar_graph(GType, [1.0, 2.0]) + @test gs isa GType + @test has_vertex(gs, 1) + @test has_vertex(gs, 2) + @test ne(gs) == 0 + + g = GType(Dictionary([1, 2, 3], ["V1", "V2", "V3"])) + add_edge!(g, NamedEdge(1, 2)) + add_edge!(g, NamedEdge(2, 3)) + sg = subgraph(g, [1, 2]) + @test sg isa GType + @test has_vertex(sg, 1) + @test has_vertex(sg, 2) + @test !has_vertex(sg, 3) + @test has_edge(sg, 1 => 2) + @test !has_edge(sg, 2 => 3) + @test sg[1] == "V1" + @test sg[2] == "V2" + @test !isassigned(sg, 3) + end + + @testset "Dictionaries interface" begin + g = GType(undef, [1, 2, 3]) + @test keytype(g) == Int + @test valtype(g) == String + @test keys(g) isa Indices + @test length(g) == 3 + @test vertex_data(g) isa VertexDataView + + @test issettable(g) + @test isinsertable(g) + + insert!(g, 4, "V4") + @test_throws IndexError insert!(g, 4, "V4_again") + @test has_vertex(g, 4) + @test isassigned(g, 4) + @test g[4] == "V4" + @test nv(g) == 4 + + @test_throws IndexError g[5] = "V5" + @test !has_vertex(g, 5) + @test !isassigned(g, 5) + + set!(g, 5, "V5") + @test has_vertex(g, 5) + @test g[5] == "V5" + + g[5] = "V5_again" + @test g[5] == "V5_again" + @test nv(g) == 5 + end + + @testset "show" begin + g = GType(Dictionary([1, 2, 3], ["V1", "V2", "V3"])) + io = IOBuffer() + show(io, g) + str = String(take!(io)) + @test occursin("$GType", str) + @test occursin("V1", str) + @test occursin("V2", str) + @test occursin("V3", str) + @test !occursin("edge data", str) + @test occursin("vertex data", str) + end + end + + @testset "$GType basics" for GType in + (EdgeDataGraph{String, Int}, EdgeDataDiGraph{String, Int}) + @testset "undef constructor" begin + g = GType(undef, [1, 2, 3]) + @test g isa GType + @test nv(g) == 3 + @test ne(g) == 0 + end + + @testset "data constructor" begin + data = Dictionary([NamedEdge(1, 2), NamedEdge(2, 3)], ["E12", "E23"]) + g = GType(data) + @test g isa GType + @test nv(g) == 3 + @test ne(g) == 2 + @test isassigned(g, NamedEdge(1, 2)) + @test isassigned(g, NamedEdge(2, 3)) + @test g[NamedEdge(1, 2)] == "E12" + @test g[NamedEdge(2, 3)] == "E23" + + # With pairs + data = Dictionary([1.0 => 2.0, 2.0 => 3.0], ["E12", "E23"]) + g = GType(data) + @test g isa GType + @test nv(g) == 3 + @test ne(g) == 2 + @test isassigned(g, NamedEdge(1, 2)) + @test isassigned(g, NamedEdge(2, 3)) + @test g[NamedEdge(1, 2)] == "E12" + @test g[NamedEdge(2, 3)] == "E23" + end + + @testset "copy" begin + data = Dictionary([1 => 2, 2 => 3], ["E12", "E23"]) + g = GType(data) + g_copy = copy(g) + + @test g_copy == g + @test g_copy !== g + + # Test we can copy a graph with undefined data. + g = GType(undef, [1, 2, 3]) + g[1 => 2] = "E12" + g_copy = copy(g) + + @test has_vertex(g_copy, 1) + @test has_vertex(g_copy, 2) + @test has_vertex(g_copy, 3) + @test has_edge(g_copy, 1 => 2) + @test isassigned(g_copy, 1 => 2) + @test g_copy[1 => 2] == "E12" + @test !isassigned(g_copy, 2 => 3) + end + + @testset "Graphs.jl interface" begin + g = GType(undef, [1, 2, 3]) + @test has_vertex(g, 1) + @test has_vertex(g, 2) + @test !has_vertex(g, 4) + @test edgetype(g) == NamedEdge{Int} + + @test_throws ArgumentError add_edge!(g, NamedEdge(1, 2)) + @test_throws ArgumentError add_edge!(g, 2 => 3) + + add_vertex!(g, 4) + @test has_vertex(g, 4) + @test nv(g) == 4 + end + + @testset "DataGraphs interface" begin + g = GType(undef, [1, 2, 3]) + @test edge_data_type(GType) == String + @test edge_data_type(g) == String + @test !isassigned(g, 1) + @test !isassigned(g, NamedEdge(1, 2)) + end + + @testset "setindex! and getindex" begin + data = Dictionary([NamedEdge(1, 2), NamedEdge(2, 3)], ["E12", "E23"]) + g = GType(data) + g[NamedEdge(1, 2)] = "E12_updated" + @test g[NamedEdge(1, 2)] == "E12_updated" + @test g[NamedEdge(2, 3)] == "E23" + end + + @testset "rem_edge!" begin + data = Dictionary([NamedEdge(1, 2), NamedEdge(2, 3)], ["E12", "E23"]) + g = GType(data) + rem_edge!(g, NamedEdge(1, 2)) + @test !has_edge(g, NamedEdge(1, 2)) + @test ne(g) == 1 + end + + @testset "rem_vertex!" begin + data = Dictionary([NamedEdge(1, 2), NamedEdge(2, 3)], ["E12", "E23"]) + g = GType(data) + rem_vertex!(g, 1) + @test !has_vertex(g, 1) + @test !has_edge(g, NamedEdge(1, 2)) + @test !isassigned(g, NamedEdge(1, 2)) + @test ne(g) == 1 + @test g[NamedEdge(2, 3)] == "E23" + end + + @testset "NamedGraphs interface" begin + g = GType(Dictionary([1 => 2, 2 => 3], ["E12", "E23"])) + @test issetequal(vertices(g), [1, 2, 3]) + @test position_graph(g) isa AbstractSimpleGraph{Int} + @test ordered_vertices(g) isa AbstractVector + @test vertex_positions(g) isa AbstractDictionary + + gs = similar_graph(g) + @test gs isa GType + @test has_vertex(gs, 1) + @test has_vertex(gs, 2) + @test has_vertex(gs, 3) + @test has_edge(gs, 1 => 2) + @test !isassigned(gs, 1 => 2) + + gs = similar_graph(g, vertices(g)) + @test vertices(gs) == vertices(g) + @test ne(gs) == 0 + @test !isassigned(gs, 1 => 2) + + gs = similar_graph(g, [1, 2, 4]) + @test has_vertex(gs, 1) + @test has_vertex(gs, 2) + @test has_vertex(gs, 4) + @test nv(gs) == 3 + @test ne(gs) == 0 + @test !isassigned(gs, 1 => 2) + + gs = similar_graph(g, Char) + @test edge_data_type(gs) === Char + @test nv(gs) == 3 + @test ne(gs) == 2 + gs[3 => 1] = 'C' + @test gs[3 => 1] == 'C' + @test ne(gs) == 3 + + gs = similar_graph(g, Float64, vertices(g)) + @test ne(gs) == 0 + + gs = similar_graph(GType, [1.0, 2.0]) + @test gs isa GType + @test has_vertex(gs, 1) + @test has_vertex(gs, 2) + @test ne(gs) == 0 + + g = GType(Dictionary([1 => 2, 2 => 3], ["E12", "E23"])) + sg = subgraph(g, [1, 2]) + @test sg isa GType + @test has_vertex(sg, 1) + @test has_vertex(sg, 2) + @test !has_vertex(sg, 3) + @test has_edge(sg, 1 => 2) + @test !has_edge(sg, 2 => 3) + @test sg[1 => 2] == "E12" + end + + @testset "Dictionaries interface" begin + g = GType(undef, [1, 2, 3]) + @test keytype(g) == NamedEdge{Int} + @test valtype(g) == String + @test edge_data(g) isa EdgeDataView + + @test issettable(g) + @test isinsertable(g) + + g = GType(undef, [1.0, 2.0, 3.0]) + @test keytype(g) == NamedEdge{Int} + + g = GType(undef, [1, 2, 3]) + insert!(g, 3 => 4, "E34") + @test has_vertex(g, 4) + @test has_edge(g, 3 => 4) + @test isassigned(g, 3 => 4) + @test g[3 => 4] == "E34" + + g = GType(undef, [1, 2, 3]) + insert!(g, 5 => 6, "E56") + @test has_vertex(g, 5) + @test has_vertex(g, 6) + @test has_edge(g, 5 => 6) + @test isassigned(g, 5 => 6) + @test g[5 => 6] == "E56" + + g = GType(undef, [1, 2, 3]) + @test_throws IndexError insert!(g, 2 => 3, "E23") + @test !has_edge(g, 2 => 3) + + g = GType(undef, [1, 2, 3]) + set!(g, 1 => 2, "E12") + @test has_edge(g, 1 => 2) + @test g[1 => 2] == "E12" + + g = GType(undef, [1, 2, 3]) + set!(g, 3 => 4, "E34") + @test has_vertex(g, 4) + @test has_edge(g, 3 => 4) + @test g[3 => 4] == "E34" + + g = GType(undef, [1, 2, 3]) + set!(g, 4 => 5, "E45") + @test has_vertex(g, 4) + @test has_vertex(g, 5) + @test has_edge(g, 4 => 5) + @test g[4 => 5] == "E45" + set!(g, 4 => 5, "E45_again") + @test g[4 => 5] == "E45_again" + + g = GType(undef, [1, 2, 3]) + g[2 => 3] = "E23" + @test has_edge(g, 2 => 3) + @test g[2 => 3] == "E23" + @test_throws IndexError g[3 => 4] = "E34" + @test_throws IndexError g[4 => 5] = "E45" + + g = EdgeDataGraph{String, Int}(undef, [1, 2, 3]) + @test_throws IndexError edge_data(g)[2 => 3] = "E23" + end + + @testset "show" begin + g = GType(Dictionary([1 => 2, 2 => 3], ["E12", "E23"])) + io = IOBuffer() + show(io, g) + str = String(take!(io)) + @test occursin("$GType", str) + @test occursin("edge data", str) + @test occursin("E12", str) + @test occursin("E23", str) + @test !occursin("vertex data", str) + end + end + + @testset "(un)directed graph specific" begin + @testset "basics" begin + g = VertexDataGraph(undef, [1, 2, 3]) + @test !is_directed(VertexDataGraph) + @test !is_directed(g) + + g = VertexDataDiGraph(undef, [1, 2, 3]) + @test is_directed(VertexDataDiGraph) + @test is_directed(g) + + add_edge!(g, NamedEdge(1, 2)) + @test has_edge(g, NamedEdge(1, 2)) + @test !has_edge(g, NamedEdge(2, 1)) + @test ne(g) == 1 + + add_edge!(g, NamedEdge(2, 1)) + @test has_edge(g, NamedEdge(2, 1)) + @test ne(g) == 2 + + g = EdgeDataGraph(undef, [1, 2, 3]) + @test !is_directed(VertexDataGraph) + @test !is_directed(g) + + g = EdgeDataDiGraph(undef, [1, 2, 3]) + @test is_directed(VertexDataDiGraph) + @test is_directed(g) + end + + @testset "undirected edge access" begin + data = Dictionary([NamedEdge(1, 2)], ["E12"]) + g = EdgeDataGraph(data) + @test g[NamedEdge(1, 2)] == "E12" + @test g[NamedEdge(2, 1)] == "E12" + @test g[1 => 2] == "E12" + @test g[2 => 1] == "E12" + end + + @testset "directed edge access" begin + data = Dictionary([NamedEdge(1, 2)], ["E12"]) + g = EdgeDataDiGraph(data) + @test has_edge(g, NamedEdge(1, 2)) + @test !has_edge(g, NamedEdge(2, 1)) + @test g[NamedEdge(1, 2)] == "E12" + end + end +end