diff --git a/Project.toml b/Project.toml index 2775d4a..e8b9800 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "DataGraphs" uuid = "b5a273c3-7e6c-41f6-98bd-8d7f1525a36a" -version = "0.4.2" +version = "0.4.3" authors = ["Matthew Fishman and contributors"] [workspace] 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..c96d0dd 100644 --- a/src/abstractdatagraph.jl +++ b/src/abstractdatagraph.jl @@ -6,7 +6,8 @@ using NamedGraphs.GraphsExtensions: GraphsExtensions, add_edges!, add_vertices!, using NamedGraphs.OrdinalIndexing: OrdinalSuffixedInteger using NamedGraphs.SimilarType: similar_type using NamedGraphs: NamedGraphs, AbstractEdges, AbstractNamedEdge, AbstractNamedGraph, - AbstractVertices, NamedDiGraph, NamedGraph, position_graph_type, similar_graph + AbstractVertices, NamedDiGraph, NamedGraph, Vertices, position_graph_type, + similar_graph, subgraph_edges using SimpleTraits: SimpleTraits, @traitfn, Not abstract type AbstractDataGraph{V, VD, ED} <: AbstractNamedGraph{V} end @@ -21,12 +22,15 @@ edge_data_type(::Type{<:AbstractDataGraph{V, VD, ED}}) where {V, VD, ED} = ED # TODO: Define for `AbstractGraph` as a `DataGraphInterface`. underlying_graph(::AbstractDataGraph) = not_implemented() +# `isassigned` is_vertex_assigned(::AbstractDataGraph, vertex) = not_implemented() is_edge_assigned(::AbstractDataGraph, edge) = not_implemented() +# `getindex` get_vertex_data(::AbstractDataGraph, vertex) = not_implemented() get_edge_data(::AbstractDataGraph, edge) = not_implemented() +# `setindex!` set_vertex_data!(::AbstractDataGraph, data, vertex) = not_implemented() set_edge_data!(::AbstractDataGraph, data, edge) = not_implemented() @@ -57,9 +61,7 @@ function assigned_edges(graph::AbstractGraph) return Indices(filter(e -> isassigned(graph, e), edges(graph))) end -function Graphs.edgetype(graph::AbstractDataGraph) - return Graphs.edgetype(underlying_graph(graph)) -end +Graphs.edgetype(graph::AbstractDataGraph) = edgetype(underlying_graph(graph)) function Graphs.edgetype(graph_type::Type{<:AbstractDataGraph}) return edgetype(underlying_graph_type(graph_type)) end @@ -75,11 +77,27 @@ function NamedGraphs.position_graph_type(type::Type{<:AbstractDataGraph}) return position_graph_type(underlying_graph_type(type)) end +function Base.copy(graph::AbstractDataGraph) + copy_graph = similar_graph(graph) + copyto!(copy_graph, graph) + return graph +end + function Base.copyto!(dst_graph::AbstractDataGraph, src_graph::AbstractDataGraph) vertex_data(dst_graph) .= vertex_data(src_graph) edge_data(dst_graph) .= edge_data(src_graph) return dst_graph end +function Base.copyto!( + graph_dst::AbstractDataGraph, + dict_src::Union{Dict, AbstractDictionary}, + dimnames = keys(dict_src) + ) + for key in dimnames + graph_dst[key] = dict_src[key] + end + return graph_dst +end # Graphs overloads function Graphs.vertices(graph::AbstractDataGraph) @@ -110,8 +128,8 @@ GraphsExtensions.convert_vertextype(::Type, ::AbstractDataGraph) = not_implement function Base.:(==)(dg1::AbstractDataGraph, dg2::AbstractDataGraph) underlying_graph(dg1) == underlying_graph(dg2) || return false - vertex_data(dg1) == vertex_data(dg2) || return false - edge_data(dg1) == edge_data(dg2) || return false + assigned_vertex_data(dg1) == assigned_vertex_data(dg2) || return false + assigned_edge_data(dg1) == assigned_edge_data(dg2) || return false return true end diff --git a/src/abstractedgeorvertexdatagraph.jl b/src/abstractedgeorvertexdatagraph.jl new file mode 100644 index 0000000..47e1502 --- /dev/null +++ b/src/abstractedgeorvertexdatagraph.jl @@ -0,0 +1,310 @@ +using Dictionaries: Dictionaries, Indices, set! +using Graphs: edges, edgetype, has_edge, has_vertex, rem_edge!, rem_vertex!, vertices +using NamedGraphs: NamedGraphs, Vertices, similar_graph, subgraph_edges, to_graph_index + +abstract type AbstractVertexOrEdgeDataGraph{I, T, V} <: AbstractDataGraph{V, T, T} end + +Graphs.edgetype(graph::AbstractVertexOrEdgeDataGraph) = edgetype(typeof(graph)) + +function NamedGraphs.similar_graph( + graph::AbstractVertexOrEdgeDataGraph, + T::Type + ) + return similar_graph(graph, T, vertices(graph)) +end + +function NamedGraphs.similar_graph( + graph::AbstractVertexOrEdgeDataGraph, + T::Type, + vertices + ) + return similar_graph(graph, T, Vertices(vertices)) +end + +function Base.:(==)(dg1::AbstractVertexOrEdgeDataGraph, dg2::AbstractVertexOrEdgeDataGraph) + return underlying_graph(dg1) == underlying_graph(dg2) && + index_data(dg1) == index_data(dg2) +end + +function Base.copyto!( + graph_dst::G, + graph_src::G, + dimnames = nothing + ) where {G <: AbstractVertexOrEdgeDataGraph} + copyto!_indexdatagraph(graph_dst, index_data(graph_src), dimnames) + return graph_dst +end +function Base.copyto!( + graph_dst::AbstractVertexOrEdgeDataGraph, + dictionary_src::AbstractDictionary, + dimnames = nothing + ) + copyto!_indexdatagraph(graph_dst, dictionary_src, dimnames) + return graph_dst +end + +Base.iterate(graph::AbstractVertexOrEdgeDataGraph) = iterate(index_data(graph)) +function Base.iterate(graph::AbstractVertexOrEdgeDataGraph, state) + return iterate(index_data(graph), state) +end + +Base.keytype(graph::AbstractVertexOrEdgeDataGraph) = keytype(typeof(graph)) +Base.keytype(::Type{<:AbstractVertexOrEdgeDataGraph{I, T, V}}) where {I, T, V} = I + +Base.valtype(graph::AbstractVertexOrEdgeDataGraph) = valtype(typeof(graph)) +Base.valtype(::Type{<:AbstractVertexOrEdgeDataGraph{I, T, V}}) where {I, T, V} = T + +Base.eltype(graph::AbstractVertexOrEdgeDataGraph) = eltype(typeof(graph)) +Base.eltype(::Type{<:AbstractVertexOrEdgeDataGraph{I, T, V}}) where {I, T, V} = T + +Base.length(graph::AbstractVertexOrEdgeDataGraph) = length(index_data(graph)) +Base.keys(graph::AbstractVertexOrEdgeDataGraph) = keys(index_data(graph)) +Base.values(graph::AbstractVertexOrEdgeDataGraph) = values(index_data(graph)) + +Dictionaries.issettable(::AbstractVertexOrEdgeDataGraph) = true +Dictionaries.isinsertable(::AbstractVertexOrEdgeDataGraph) = false + +function Base.insert!( + graph::AbstractVertexOrEdgeDataGraph{I, T}, + ind::I, + data::T + ) where {I, T} + insert!_datagraph(graph, to_graph_index(graph, ind), data) + return graph +end +function Base.delete!(graph::AbstractVertexOrEdgeDataGraph{I, T}, ind::T) where {I, T} + delete!_datagraph(graph, to_graph_index(graph, ind)) + return graph +end + +function Dictionaries.set!(graph::AbstractVertexOrEdgeDataGraph, ind, data) + set!_datagraph(graph, to_graph_index(graph, ind), data) + return graph +end + +# ================================== vertex data graph =================================== # + +abstract type AbstractVertexDataGraph{V, T} <: AbstractVertexOrEdgeDataGraph{V, T, V} end + +is_edge_assigned(::AbstractVertexDataGraph, _edge) = false + +# `setindex!` +function set_index_data!(graph::AbstractVertexDataGraph, data, vertex) + if !has_vertex(graph, vertex) + throw(IndexError("Graph does not contain vertex $vertex")) + else + set_vertex_data!(graph, data, vertex) + end + return graph +end + +# `insert!` +function insert!_datagraph(graph::AbstractVertexDataGraph, vertex, data) + 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")) + else + rem_vertex!(graph, vertex) + end + return graph +end + +# `set!` +function set!_datagraph(graph::AbstractVertexDataGraph, vertex, data) + if has_vertex(graph, vertex) + set_vertex_data!(graph, data, vertex) + else + insert_vertex_data!(graph, vertex, data) + end + return graph +end + +function copyto!_indexdatagraph( + dst::AbstractVertexDataGraph, + src, # not a graph. + dimnames = nothing + ) + dimnames = isnothing(dimnames) ? Indices(keys(src)) : Indices(dimnames) + view(index_data(dst), dimnames) .= view(src, dimnames) + return dst +end + +# For ambiguity resolution. +function NamedGraphs.similar_graph( + graph::AbstractVertexDataGraph, + VD::Type, + ED::Type + ) + return similar_graph(graph, VD, ED, vertices(graph)) # goes to fallback +end + +function NamedGraphs.similar_graph( + graph::AbstractVertexDataGraph, + VD::Type, + ::Type{<:Nothing}, + vertices + ) + return similar_graph(graph, VD, vertices) +end + +function NamedGraphs.similar_graph( + graph::AbstractVertexDataGraph, + T::Type, + vertices::Vertices + ) + return DataGraph( + similar_graph(underlying_graph(graph), collect(vertices)); + vertex_data_type = Nothing, + edge_data_type = T + ) +end + +function NamedGraphs.induced_subgraph_from_vertices( + graph::AbstractVertexDataGraph, + subvertices + ) + subnetwork = similar_graph(graph, subvertices) + tensors = view(vertex_data(graph), Indices(subvertices)) + copyto!(subnetwork, tensors) + return subnetwork, subvertices +end + +# Internal +index_data(graph::AbstractVertexDataGraph) = vertex_data(graph) + +function Base.show(io::IO, mime::MIME"text/plain", graph::AbstractVertexDataGraph) + println(io, "$(typeof(graph)) with $(nv(graph)) vertices:") + show(io, mime, vertices(graph)) + println(io, "\n") + println(io, "and $(ne(graph)) edge(s):") + for e in edges(graph) + show(io, mime, e) + println(io) + end + println(io) + println(io, "with vertex data:") + show(io, mime, vertex_data(graph)) + return nothing +end + +# =================================== edge data graph ==================================== # + +abstract type AbstractEdgeDataGraph{E, T, V} <: AbstractVertexOrEdgeDataGraph{E, T, V} end + +is_vertex_assigned(::AbstractEdgeDataGraph, _vertex) = false + +# `setindex!` +function set_index_data!(graph::AbstractEdgeDataGraph, data, edge::AbstractEdge) + if !has_edge(graph, edge) + throw(IndexError("Graph does not contain edge $edge")) + else + set_edge_data!(graph, data, edge) + end + return graph +end + +# `insert!` +function insert!_datagraph(graph::AbstractEdgeDataGraph, edge::AbstractEdge, data) + insert_edge_data!(graph, edge, data) + return graph +end + +# `delete!` +function delete!_datagraph(graph::AbstractEdgeDataGraph, edge) + if !has_edge(graph, edge) + throw(IndexError("Graph does not contain edge $edge")) + else + rem_edge!(graph, edge) + end + return graph +end + +# `set!` +function set!_datagraph(graph::AbstractEdgeDataGraph, edge::AbstractEdge, data) + if has_edge(graph, edge) + set_edge_data!(graph, data, edge) + else + insert_edge_data!(graph, edge, data) + end + return graph +end + +function copyto!_indexdatagraph( + dst::AbstractEdgeDataGraph, + src, # not a graph. + dimnames = nothing + ) + dimnames = isnothing(dimnames) ? Indices(keys(src)) : Indices(dimnames) + # In analogy to SparseArrays, we allow `copyto!` to add in missing edges. + for edge in dimnames + set!(dst, edge, src[edge]) + end + return dst +end + +# For ambiguity resolution. +function NamedGraphs.similar_graph( + graph::AbstractEdgeDataGraph, + VD::Type, + ED::Type + ) + return similar_graph(graph, VD, ED, vertices(graph)) # goes to fallback +end + +function NamedGraphs.similar_graph( + graph::AbstractEdgeDataGraph, + ::Type{<:Nothing}, + ED::Type, + vertices + ) + return similar_graph(graph, ED, vertices) +end + +function NamedGraphs.similar_graph( + graph::AbstractEdgeDataGraph, + T::Type, + vertices::Vertices + ) + return DataGraph( + similar_graph(underlying_graph(graph), collect(vertices)); + vertex_data_type = Nothing, + edge_data_type = T + ) +end + +function NamedGraphs.induced_subgraph_from_vertices( + graph::AbstractEdgeDataGraph, + subvertices + ) + subnetwork = similar_graph(graph, subvertices) + subedges = subgraph_edges(graph, subvertices) + + 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/edgedatagraph.jl b/src/edgedatagraph.jl new file mode 100644 index 0000000..8d9b15b --- /dev/null +++ b/src/edgedatagraph.jl @@ -0,0 +1,119 @@ +using Graphs: dst, has_edge, rem_edge!, rem_vertex!, src +using NamedGraphs: NamedEdge, NamedGraph, ordered_vertices, position_graph, vertex_positions + +struct EdgeDataGraph{E <: NamedEdge{V} where {V}, T, V} <: AbstractEdgeDataGraph{E, T, V} + underlying_graph::NamedGraph{V} + edge_data::Dictionary{E, T} + function EdgeDataGraph{E, T, V}( + ::UndefInitializer, + vertices + ) where {V, E <: NamedEdge{V}, T} + graph = NamedGraph{V}(vertices) + edge_data = Dictionary{E, T}() + return new{E, T, V}(graph, edge_data) + end +end + +EdgeDataGraph(data) = EdgeDataGraph{keytype(data)}(data) +EdgeDataGraph{I}(data) where {I} = EdgeDataGraph{I, valtype(data)}(data) +EdgeDataGraph{I, T}(data) where {I, T} = EdgeDataGraph{I, T, vertextype(I)}(data) + +Graphs.is_directed(::Type{<:EdgeDataGraph}) = false + +struct EdgeDataDiGraph{E <: NamedEdge{V} where {V}, T, V} <: AbstractEdgeDataGraph{E, T, V} + underlying_graph::NamedDiGraph{V} + edge_data::Dictionary{E, T} + function EdgeDataDiGraph{E, T, V}( + ::UndefInitializer, + vertices + ) where {V, E <: NamedEdge{V}, T} + graph = NamedDiGraph{V}(vertices) + edge_data = Dictionary{E, T}() + return new{E, T, V}(graph, edge_data) + end +end + +EdgeDataDiGraph(data) = EdgeDataDiGraph{keytype(data)}(data) +EdgeDataDiGraph{I}(data) where {I} = EdgeDataDiGraph{I, valtype(data)}(data) +EdgeDataDiGraph{I, T}(data) where {I, T} = EdgeDataDiGraph{I, T, vertextype(I)}(data) + +Graphs.is_directed(::Type{<:EdgeDataDiGraph}) = true + +const GenericEdgeDataGraph{I, T, V} = + Union{EdgeDataGraph{I, T, V}, EdgeDataDiGraph{I, T, V}} + +function (GType::Type{<:GenericEdgeDataGraph{I, T, V}})(data) where {I, T, V} + edges = keys(data) + vertices = union(src.(edges), dst.(edges)) + graph = GType(undef, vertices) + copyto!(graph, data) + return graph +end + +# ====================================== Graphs.jl ======================================= # +Graphs.edgetype(::Type{<:GenericEdgeDataGraph{I, T}}) where {I, T} = I + +function Graphs.has_vertex(graph::GenericEdgeDataGraph, vertex) + return has_vertex(graph.underlying_graph, vertex) +end +function Graphs.has_edge(graph::GenericEdgeDataGraph, edge::NamedEdge) + return has_edge(graph.underlying_graph, edge) +end + +function Graphs.rem_edge!(graph::GenericEdgeDataGraph, edge) + unset!(graph.edge_data, edge) + rem_edge!(graph.underlying_graph, edge) + return graph +end + +function Graphs.rem_vertex!(graph::GenericEdgeDataGraph, vertex) + for edge in incident_edges(graph, vertex) + unset!(graph.edge_data, edge) + end + rem_vertex!(graph.underlying_graph, vertex) + return graph +end + +Graphs.vertices(graph::GenericEdgeDataGraph) = vertices(graph.underlying_graph) + +# ==================================== NamedGraphs.jl ==================================== # + +function NamedGraphs.vertex_positions(graph::GenericEdgeDataGraph) + return vertex_positions(graph.underlying_graph) +end + +function NamedGraphs.ordered_vertices(graph::GenericEdgeDataGraph) + return ordered_vertices(graph.underlying_graph) +end + +function NamedGraphs.position_graph(graph::GenericEdgeDataGraph) + return position_graph(graph.underlying_graph) +end + +# ==================================== DataGraphs.jl ===================================== # + +edge_data_type(::Type{<:GenericEdgeDataGraph{I, T}}) where {I, T} = T + +function set_edge_data!(graph::GenericEdgeDataGraph, data, edge) + graph.edge_data[edge] = data + return graph +end + +get_edge_data(graph::GenericEdgeDataGraph, edge) = graph.edge_data[edge] + +is_vertex_assigned(::GenericEdgeDataGraph, _vertex) = false +is_edge_assigned(graph::GenericEdgeDataGraph, edge) = isassigned(graph.edge_data, edge) + +# =================================== Dictionaries.jl ==================================== # + +Dictionaries.isinsertable(::Type{<:GenericEdgeDataGraph}, _edge) = true + +function insert_edge_data!(graph::AbstractEdgeDataGraph, edge::AbstractEdge, data) + if has_edge(graph, edge) + throw(IndexError("Graph already contains edge $edge")) + else + add_edge!(graph.underlying_graph, edge) + insert!(graph.edge_data, edge, data) + end + return graph +end diff --git a/src/vertexdatagraph.jl b/src/vertexdatagraph.jl new file mode 100644 index 0000000..cf5b65e --- /dev/null +++ b/src/vertexdatagraph.jl @@ -0,0 +1,115 @@ +using Dictionaries: Dictionary, set! +using Graphs: Graphs, has_edge, rem_vertex! +using NamedGraphs: + NamedDiGraph, NamedEdge, NamedGraph, ordered_vertices, position_graph, vertex_positions + +struct VertexDataGraph{V, T} <: AbstractVertexDataGraph{V, T} + underlying_graph::NamedGraph{V} + vertex_data::Dictionary{V, T} + function VertexDataGraph{V, T}( + ::UndefInitializer, + vertices + ) where {V, T} + graph = NamedGraph{V}(vertices) + vertex_data = Dictionary{V, T}() + return new{V, T}(graph, vertex_data) + end +end + +VertexDataGraph(data) = VertexDataGraph{keytype(data)}(data) +VertexDataGraph{V}(data) where {V} = VertexDataGraph{V, valtype(data)}(data) + +struct VertexDataDiGraph{V, T} <: AbstractVertexDataGraph{V, T} + underlying_graph::NamedDiGraph{V} + vertex_data::Dictionary{V, T} + function VertexDataDiGraph{V, T}( + ::UndefInitializer, + vertices + ) where {V, T} + graph = NamedDiGraph{V}(vertices) + vertex_data = Dictionary{V, T}() + return new{V, T}(graph, vertex_data) + end +end + +VertexDataDiGraph(data) = VertexDataDiGraph{keytype(data)}(data) +VertexDataDiGraph{V}(data) where {V} = VertexDataDiGraph{V, valtype(data)}(data) + +const GenericVertexDataGraph{V, T} = Union{VertexDataGraph{V, T}, VertexDataDiGraph{V, T}} + +function (GType::Type{<:GenericVertexDataGraph{V, T}})(data) where {V, T} + vertices = keys(data) + cache = GType(undef, vertices) + return copyto!(cache, data) +end + +Graphs.is_directed(::Type{<:VertexDataGraph}) = false +Graphs.is_directed(::Type{<:VertexDataDiGraph}) = true + +# ====================================== Graphs.jl ======================================= # + +Graphs.edgetype(::Type{<:GenericVertexDataGraph{V, T}}) where {V, T} = NamedEdge{V} + +function Graphs.has_vertex(graph::GenericVertexDataGraph, vertex) + return has_vertex(graph.underlying_graph, vertex) +end +function Graphs.has_edge(graph::GenericVertexDataGraph, edge::NamedEdge) + return has_edge(graph.underlying_graph, edge) +end + +function Graphs.rem_vertex!(graph::GenericVertexDataGraph, vertex) + unset!(graph.vertex_data, vertex) + rem_vertex!(graph.underlying_graph, vertex) + return graph +end + +Graphs.vertices(graph::GenericVertexDataGraph) = vertices(graph.underlying_graph) + +# ==================================== NamedGraphs.jl ==================================== # + +function NamedGraphs.vertex_positions(graph::GenericVertexDataGraph) + return vertex_positions(graph.underlying_graph) +end + +function NamedGraphs.ordered_vertices(graph::GenericVertexDataGraph) + return ordered_vertices(graph.underlying_graph) +end + +function NamedGraphs.position_graph(graph::GenericVertexDataGraph) + return position_graph(graph.underlying_graph) +end + +# ==================================== DataGraphs.jl ===================================== # + +underlying_graph(graph::VertexDataGraph) = getfield(graph, :underlying_graph) +underlying_graph(graph::VertexDataDiGraph) = getfield(graph, :underlying_graph) + +vertex_data_type(::Type{<:GenericVertexDataGraph{V, T}}) where {V, T} = T + +function set_vertex_data!(graph::GenericVertexDataGraph, data, vertex) + # We use an upsert here as we have already checked if the vertex (i.e. key) exists, + # but it might not exist in the internal `Dictionary`, so add it if not. + set!(graph.vertex_data, vertex, data) + return graph +end + +get_vertex_data(graph::GenericVertexDataGraph, vertex) = graph.vertex_data[vertex] + +function is_vertex_assigned(graph::GenericVertexDataGraph, vertex) + return isassigned(graph.vertex_data, vertex) +end +is_edge_assigned(::GenericVertexDataGraph, _edge) = false + +# =================================== Dictionaries.jl ==================================== # + +Dictionaries.isinsertable(::Type{<:GenericVertexDataGraph}, _edge) = true + +function insert_vertex_data!(graph::AbstractVertexDataGraph, vertex, data) + if has_vertex(graph, vertex) + throw(IndexError("Graph already contains vertex $vertex")) + else + add_vertex!(graph.underlying_graph, vertex) + insert!(graph.vertex_data, vertex, data) + end + return graph +end diff --git a/test/test_vertexoredgedatagraph.jl b/test/test_vertexoredgedatagraph.jl new file mode 100644 index 0000000..0e6fed8 --- /dev/null +++ b/test/test_vertexoredgedatagraph.jl @@ -0,0 +1,293 @@ +using DataGraphs: DataGraphs, EdgeDataDiGraph, EdgeDataGraph, EdgeDataView, + VertexDataDiGraph, VertexDataGraph, VertexDataView, edge_data, edge_data_type, + underlying_graph, vertex_data, vertex_data_type +using Dictionaries: AbstractDictionary, Dictionary, Indices +using Graphs: AbstractGraph, add_edge!, dst, edges, edgetype, has_edge, has_vertex, + is_directed, ne, nv, rem_edge!, rem_vertex!, src, vertices +using NamedGraphs.GraphsExtensions: vertextype +using NamedGraphs: + NamedDiGraph, NamedEdge, NamedGraph, ordered_vertices, position_graph, vertex_positions +using Test: @test, @testset + +@testset "VertexDataGraph and EdgeDataGraph" begin + @testset "VertexDataGraph" begin + @testset "undef constructor" begin + g = VertexDataGraph{Int, String}(undef, [1, 2, 3]) + @test g isa VertexDataGraph{Int, String} + @test nv(g) == 3 + @test ne(g) == 0 + @test has_vertex(g, 1) + @test has_vertex(g, 2) + @test has_vertex(g, 3) + @test !has_vertex(g, 4) + @test Set(collect(vertices(g))) == Set([1, 2, 3]) + end + + @testset "data constructor" begin + data = Dictionary([1, 2, 3], ["V1", "V2", "V3"]) + g = VertexDataGraph(data) + @test g isa VertexDataGraph{Int, String} + @test nv(g) == 3 + @test isassigned(g, 1) + @test isassigned(g, 2) + @test isassigned(g, 3) + @test g[1] == "V1" + @test g[2] == "V2" + @test g[3] == "V3" + end + + @testset "Graphs.jl interface" begin + g = VertexDataGraph{Int, String}(undef, [1, 2, 3]) + @test !is_directed(VertexDataGraph) + @test !is_directed(g) + @test vertextype(g) == Int + @test edgetype(g) == NamedEdge{Int} + + add_edge!(g, NamedEdge(1, 2)) + add_edge!(g, NamedEdge(2, 3)) + @test ne(g) == 2 + @test has_edge(g, NamedEdge(1, 2)) + @test has_edge(g, NamedEdge(2, 3)) + @test !has_edge(g, NamedEdge(1, 3)) + @test length(collect(edges(g))) == 2 + + rem_edge!(g, NamedEdge(1, 2)) + @test ne(g) == 1 + @test !has_edge(g, NamedEdge(1, 2)) + end + + @testset "rem_vertex!" begin + g = VertexDataGraph{Int, String}(undef, [1, 2, 3]) + add_edge!(g, NamedEdge(1, 2)) + rem_vertex!(g, 1) + @test !has_vertex(g, 1) + @test nv(g) == 2 + @test ne(g) == 0 + end + + @testset "DataGraphs interface" begin + g = VertexDataGraph{Int, String}(undef, [1, 2, 3]) + @test underlying_graph(g) isa NamedGraph{Int} + @test vertex_data_type(g) == String + @test vertex_data_type(VertexDataGraph{Int, String}) == String + @test !isassigned(g, 1) + @test !isassigned(g, 2) + @test !isassigned(g, 3) + end + + @testset "setindex! and getindex" begin + g = VertexDataGraph{Int, String}(undef, [1, 2, 3]) + g[1] = "V1" + g[2] = "V2" + g[3] = "V3" + @test isassigned(g, 1) + @test isassigned(g, 2) + @test isassigned(g, 3) + @test g[1] == "V1" + @test g[2] == "V2" + @test g[3] == "V3" + end + + @testset "NamedGraphs interface" begin + g = VertexDataGraph{Int, String}(undef, [1, 2, 3]) + @test underlying_graph(g) isa NamedGraph{Int} + @test position_graph(g) isa AbstractGraph + @test ordered_vertices(g) isa AbstractVector + @test vertex_positions(g) isa AbstractDictionary + end + + @testset "Dictionaries interface" begin + g = VertexDataGraph{Int, String}(undef, [1, 2, 3]) + @test keytype(g) == Int + @test valtype(g) == String + @test keys(g) isa Indices + @test length(g) == 3 + @test vertex_data(g) isa VertexDataView + end + end + + @testset "VertexDataDiGraph" begin + @testset "undef constructor" begin + g = VertexDataDiGraph{Int, String}(undef, [1, 2, 3]) + @test g isa VertexDataDiGraph{Int, String} + @test nv(g) == 3 + @test ne(g) == 0 + @test has_vertex(g, 1) + @test !has_vertex(g, 4) + end + + @testset "data constructor" begin + data = Dictionary([1, 2, 3], ["V1", "V2", "V3"]) + g = VertexDataDiGraph(data) + @test g isa VertexDataDiGraph{Int, String} + @test nv(g) == 3 + @test g[1] == "V1" + @test g[2] == "V2" + @test g[3] == "V3" + end + + @testset "directed graph" begin + g = VertexDataDiGraph{Int, String}(undef, [1, 2, 3]) + @test is_directed(VertexDataDiGraph) + @test is_directed(g) + @test underlying_graph(g) isa NamedDiGraph{Int} + end + + @testset "directed edges" begin + g = VertexDataDiGraph{Int, String}(undef, [1, 2, 3]) + add_edge!(g, NamedEdge(1, 2)) + @test has_edge(g, NamedEdge(1, 2)) + @test !has_edge(g, NamedEdge(2, 1)) + @test ne(g) == 1 + end + + @testset "DataGraphs interface" begin + g = VertexDataDiGraph{Int, String}(undef, [1, 2, 3]) + @test vertex_data_type(g) == String + @test vertex_data_type(VertexDataDiGraph{Int, String}) == String + @test vertextype(g) == Int + @test edgetype(g) == NamedEdge{Int} + end + + @testset "Dictionaries interface" begin + g = VertexDataDiGraph{Int, String}(undef, [1, 2, 3]) + @test keytype(g) == Int + @test valtype(g) == String + @test keys(g) isa Indices + @test length(g) == 3 + @test vertex_data(g) isa VertexDataView + end + end + + @testset "EdgeDataGraph" begin + E = NamedEdge{Int} + + @testset "undef constructor" begin + g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + @test g isa EdgeDataGraph{E, String, Int} + @test nv(g) == 3 + @test ne(g) == 0 + end + + @testset "data constructor" begin + data = Dictionary([NamedEdge(1, 2), NamedEdge(2, 3)], ["E12", "E23"]) + g = EdgeDataGraph(data) + @test g isa EdgeDataGraph{E, String, Int} + @test nv(g) == 3 + @test ne(g) == 2 + @test isassigned(g, NamedEdge(1, 2)) + @test isassigned(g, NamedEdge(2, 3)) + @test g[NamedEdge(1, 2)] == "E12" + @test g[NamedEdge(2, 3)] == "E23" + end + + @testset "Graphs.jl interface" begin + g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + @test !is_directed(EdgeDataGraph) + @test !is_directed(g) + @test has_vertex(g, 1) + @test has_vertex(g, 2) + @test !has_vertex(g, 4) + @test edgetype(g) == E + + add_edge!(g, NamedEdge(1, 2)) + add_edge!(g, NamedEdge(2, 3)) + @test ne(g) == 2 + @test has_edge(g, NamedEdge(1, 2)) + @test has_edge(g, NamedEdge(2, 3)) + @test !has_edge(g, NamedEdge(1, 3)) + end + + @testset "DataGraphs interface" begin + g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + @test edge_data_type(EdgeDataGraph{E, String, Int}) == String + @test edge_data_type(g) == String + @test !isassigned(g, 1) + @test !isassigned(g, NamedEdge(1, 2)) + end + + @testset "setindex! and getindex" begin + data = Dictionary([NamedEdge(1, 2), NamedEdge(2, 3)], ["E12", "E23"]) + g = EdgeDataGraph(data) + g[NamedEdge(1, 2)] = "E12_updated" + @test g[NamedEdge(1, 2)] == "E12_updated" + @test g[NamedEdge(2, 3)] == "E23" + end + + @testset "undirected edge access" begin + data = Dictionary([NamedEdge(1, 2)], ["E12"]) + g = EdgeDataGraph(data) + @test g[NamedEdge(1, 2)] == "E12" + @test g[NamedEdge(2, 1)] == "E12" + @test g[1 => 2] == "E12" + @test g[2 => 1] == "E12" + end + + @testset "rem_edge!" begin + data = Dictionary([NamedEdge(1, 2), NamedEdge(2, 3)], ["E12", "E23"]) + g = EdgeDataGraph(data) + rem_edge!(g, NamedEdge(1, 2)) + @test !has_edge(g, NamedEdge(1, 2)) + @test ne(g) == 1 + end + + @testset "NamedGraphs interface" begin + g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + @test Set(collect(vertices(g))) == Set([1, 2, 3]) + @test position_graph(g) isa AbstractGraph + @test ordered_vertices(g) isa AbstractVector + @test vertex_positions(g) isa AbstractDictionary + end + + @testset "Dictionaries interface" begin + g = EdgeDataGraph{E, String, Int}(undef, [1, 2, 3]) + @test keytype(g) == E + @test valtype(g) == String + @test edge_data(g) isa EdgeDataView + end + end + + @testset "EdgeDataDiGraph" begin + E = NamedEdge{Int} + + @testset "undef constructor" begin + g = EdgeDataDiGraph{E, String, Int}(undef, [1, 2, 3]) + @test g isa EdgeDataDiGraph{E, String, Int} + @test nv(g) == 3 + @test ne(g) == 0 + end + + @testset "data constructor" begin + data = Dictionary([NamedEdge(1, 2), NamedEdge(2, 3)], ["E12", "E23"]) + g = EdgeDataDiGraph(data) + @test g isa EdgeDataDiGraph{E, String, Int} + @test nv(g) == 3 + @test ne(g) == 2 + @test g[NamedEdge(1, 2)] == "E12" + @test g[NamedEdge(2, 3)] == "E23" + end + + @testset "directed graph" begin + g = EdgeDataDiGraph{E, String, Int}(undef, [1, 2, 3]) + @test is_directed(EdgeDataDiGraph) + @test is_directed(g) + end + + @testset "directed edges" begin + data = Dictionary([NamedEdge(1, 2)], ["E12"]) + g = EdgeDataDiGraph(data) + @test has_edge(g, NamedEdge(1, 2)) + @test !has_edge(g, NamedEdge(2, 1)) + @test g[NamedEdge(1, 2)] == "E12" + end + + @testset "DataGraphs interface" begin + g = EdgeDataDiGraph{E, String, Int}(undef, [1, 2, 3]) + @test edge_data_type(EdgeDataDiGraph{E, String, Int}) == String + @test edge_data_type(g) == String + @test keytype(g) == E + @test valtype(g) == String + @test edge_data(g) isa EdgeDataView + end + end +end