diff --git a/Project.toml b/Project.toml index 5f177dc..19af653 100644 --- a/Project.toml +++ b/Project.toml @@ -1,19 +1,19 @@ name = "NamedGraphs" uuid = "678767b0-92e7-4007-89e4-4527a8725b19" authors = ["Matthew Fishman and contributors"] -version = "0.0.1" +version = "0.1.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" -MultiDimDictionaries = "87ff4268-a46e-478f-b30a-76b83dd64e3c" +SimpleTraits = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" [compat] AbstractTrees = "0.3, 0.4" Dictionaries = "0.3" Graphs = "1" -MultiDimDictionaries = "0.0.1" +SimpleTraits = "0.9" julia = "1.7" [extras] diff --git a/examples/README.jl b/examples/README.jl index d25fd11..aa8eeba 100644 --- a/examples/README.jl +++ b/examples/README.jl @@ -18,7 +18,7 @@ #' This packages introduces graph types with named edges, which are built on top of the `Graph`/`SimpleGraph` type in the [Graphs.jl](https://github.com/JuliaGraphs/Graphs.jl) package that only have contiguous integer edges (i.e. linear indexing). #' There is a supertype `AbstractNamedGraph` that defines an interface and fallback implementations of standard -#' Graphs.jl operations, and two implementations: `NamedGraph` and `NamedDimGraph`. +#' Graphs.jl operations, and two implementations: `NamedGraph` and `NamedDiGraph`. #' ## `NamedGraph` @@ -28,6 +28,7 @@ using Graphs using NamedGraphs g = NamedGraph(grid((4,)), ["A", "B", "C", "D"]) +g = NamedGraph(grid((4,)); vertices=["A", "B", "C", "D"]) # Same as above #'Common operations are defined as you would expect: #+ term=true @@ -42,84 +43,65 @@ g[["A", "B"]] #' Graph operations are implemented by mapping back and forth between the generalized named vertices and the linear index vertices of the `SimpleGraph`. -#' ## `NamedDimGraph` - -#' `NamedDimGraph` is very similar to a `NamedGraph` but a bit more sophisticated. It has generalized -#' multi-dimensional array indexing, mixed with named dimensions like [NamedDims.jl](https://github.com/invenia/NamedDims.jl). - -#' This allows for more sophisticated behavior, such as slicing dimensions and [disjoint unions](https://en.wikipedia.org/wiki/Disjoint_union) (generalizations of array concatenations). - -#' We start out by making a multi-dimensional graph where we specify the dimensions, which -#' assigns vertex labels based on cartesian coordinates: +#' It is natural to use tuples of integers as the names for the vertices of graphs with grid connectivities. +#' For this, we use the convention that if a tuple is input, it is interpreted as the grid size and +#' the vertex names label cartesian coordinates: #+ term=true -g = NamedDimGraph(grid((2, 2)); dims=(2, 2)) +g = NamedGraph(grid((2, 2)); vertices=(2, 2)) #' Internally the vertices are all stored as tuples with a label in each dimension. -#' Vertices can be referred to by their tuples or splatted indices in each dimension: +#' Vertices can be referred to by their tuples: #+ term=true has_vertex(g, (1, 1)) -has_vertex(g, 1, 1) has_edge(g, (1, 1) => (2, 1)) has_edge(g, (1, 1) => (2, 2)) neighbors(g, (2, 2)) -#' This allows the graph to be treated partially as a set of named vertices and -#' partially with multi-dimensional array indexing syntax. For example -#' you can slice a dimension to get the [induced subgraph](https://juliagraphs.org/Graphs.jl/dev/core_functions/operators/#Graphs.induced_subgraph-Union{Tuple{T},%20Tuple{U},%20Tuple{T,%20AbstractVector{U}}}%20where%20{U%3C:Integer,%20T%3C:AbstractGraph}): +#' You can use vertex names to get [induced subgraphs](https://juliagraphs.org/Graphs.jl/dev/core_functions/operators/#Graphs.induced_subgraph-Union{Tuple{T},%20Tuple{U},%20Tuple{T,%20AbstractVector{U}}}%20where%20{U%3C:Integer,%20T%3C:AbstractGraph}): #+ term=true -g[1, :] -g[:, 2] +subgraph(v -> v[1] == 1, g) +subgraph(v -> v[2] == 2, g) g[[(1, 1), (2, 2)]] -#' Note that slicing drops the dimensions of single indices, just like Julia Array slicing: - -#+ echo=false - -using Random -Random.seed!(1234); +#' Note that this is similar to multidimensional array slicing, and we may support syntax like `subgraph(v, 1, :)` in the future. +#' You can also take [disjoint unions](https://en.wikipedia.org/wiki/Disjoint_union) or concatenations of graphs: #+ term=true -x = randn(2, 2) -x[1, :] - -#' Graphs can also take [disjoint unions](https://en.wikipedia.org/wiki/Disjoint_union) or concatenations of graphs: -#+ term=true - -disjoint_union(g, g) -g ⊔ g +g₁ = g +g₂ = g +disjoint_union(g₁, g₂) +g₁ ⊔ g₂ # Same as above #' The symbol `⊔` is just an alias for `disjoint_union` and can be written in the terminal #' or in your favorite [ide with the appropriate Julia extension](https://julialang.org/) with `\sqcup` -#' Note that by default this introduces new dimension names (which by default are contiguous integers -#' starting from 1) in the first dimension of the graph, so we can get back -#' the original graphs by slicing and setting the first dimension: -#+ term=true +#' By default, this maps the vertices `v₁ ∈ vertices(g₁)` to `(v₁, 1)` and the vertices `v₂ ∈ vertices(g₂)` +#' to `(v₂, 1)`, so the resulting vertices of the unioned graph will always be unique. +#' The resulting graph will have no edges between vertices `(v₁, 1)` and `(v₂, 1)`, these would have to +#' be added manually. -(g ⊔ g)[1, :] -(g ⊔ g)[2, :] - -#' or slice across the graphs that we disjoint unioned: -#+ term=true - -(g ⊔ g)[:, 1, :] - -#' Additionally, we can use standard array concatenation syntax, such as: +#' The original graphs can be obtained from subgraphs: #+ term=true -[g; g] - -#' which is equivalent to `vcat(g, g)` or: -#+ term=true - -[g;; g] - -#' which is the same as `hcat(g, g)`. +rename_vertices(v -> v[1], subgraph(v -> v[2] == 1, g₁ ⊔ g₂)) +rename_vertices(v -> v[1], subgraph(v -> v[2] == 2, g₁ ⊔ g₂)) + +## #' Additionally, we can use standard array concatenation syntax, such as: +## #+ term=true +## +## [g; g] +## +## #' which is equivalent to `vcat(g, g)` or: +## #+ term=true +## +## [g;; g] +## +## #' which is the same as `hcat(g, g)`. #' ## Generating this README diff --git a/examples/disjoint_union.jl b/examples/disjoint_union.jl index d36c29a..fe6bb18 100644 --- a/examples/disjoint_union.jl +++ b/examples/disjoint_union.jl @@ -1,12 +1,12 @@ using Graphs using NamedGraphs -g1 = NamedDimGraph(grid((2, 2)); dims=(2, 2)) -g2 = NamedDimGraph(grid((2, 2)); dims=(2, 2)) -g = ⊔(g1, g2; new_dim_names=("X", "Y")) +g1 = NamedGraph(grid((2, 2)); vertices=(2, 2)) +g2 = NamedGraph(grid((2, 2)); vertices=(2, 2)) +g = ⊔("X" => g1, "Y" => g2) @show g1 @show g2 @show g -@show g["X", :] -@show g["Y", :] +@show subgraph(v -> v[1] == "X", g) +@show subgraph(v -> v[1] == "Y", g) diff --git a/examples/multidimgraph_1d.jl b/examples/multidimgraph_1d.jl index bc38ff1..5d2e412 100644 --- a/examples/multidimgraph_1d.jl +++ b/examples/multidimgraph_1d.jl @@ -2,8 +2,8 @@ using Graphs using NamedGraphs parent_graph = grid((4,)) -vertices = ["A", "B", "C", "D"] -g = NamedDimGraph(parent_graph, vertices) +vs = ["A", "B", "C", "D"] +g = NamedGraph(parent_graph, vs) @show has_vertex(g, "A") @show !has_vertex(g, "E") @@ -26,26 +26,28 @@ g_sub = g[["A", "B"]] @show has_edge(g_sub, "A" => "B") -g_sub = g[:] +g_sub = subgraph(Returns(true), g) @show has_vertex(g_sub, "A") @show has_vertex(g_sub, "B") @show has_vertex(g_sub, "C") @show has_vertex(g_sub, "D") -# Error: vertex names are the same -# g_vcat = [g; g] - -g_hcat = [g;; g] - -@show nv(g_hcat) == 8 -@show ne(g_hcat) == 6 - -@show has_vertex(g_hcat, "A", 1) - g_union = g ⊔ g @show nv(g_union) == 8 @show ne(g_union) == 6 -@show has_vertex(g_union, 1, "A") +@show has_vertex(g_union, ("A", 1)) +@show has_vertex(g_union, ("A", 2)) + +# Error: vertex names are the same +# g_vcat = [g; g] + +# TODO: Implement +## g_hcat = [g;; g] +## +## @show nv(g_hcat) == 8 +## @show ne(g_hcat) == 6 +## +## @show has_vertex(g_hcat, ("A", 1)) diff --git a/examples/multidimgraph_2d.jl b/examples/multidimgraph_2d.jl index 0902ae5..9f458a7 100644 --- a/examples/multidimgraph_2d.jl +++ b/examples/multidimgraph_2d.jl @@ -2,55 +2,57 @@ using Graphs using NamedGraphs parent_graph = grid((2, 2)) -vertices = [("X", 1), ("X", 2), ("Y", 1), ("Y", 2)] +vs = [("X", 1), ("X", 2), ("Y", 1), ("Y", 2)] -g = NamedDimGraph(parent_graph, vertices) +g = NamedGraph(parent_graph, vs) -@show has_vertex(g, "X", 1) +@show has_vertex(g, ("X", 1)) @show has_edge(g, ("X", 1) => ("X", 2)) @show !has_edge(g, ("X", 2) => ("Y", 1)) @show has_edge(g, ("X", 2) => ("Y", 2)) g_sub = g[[("X", 1)]] -@show has_vertex(g_sub, "X", 1) -@show !has_vertex(g_sub, "X", 2) -@show !has_vertex(g_sub, "Y", 1) -@show !has_vertex(g_sub, "Y", 2) +@show has_vertex(g_sub, ("X", 1)) +@show !has_vertex(g_sub, ("X", 2)) +@show !has_vertex(g_sub, ("Y", 1)) +@show !has_vertex(g_sub, ("Y", 2)) g_sub = g[[("X", 1), ("X", 2)]] -@show has_vertex(g_sub, "X", 1) -@show has_vertex(g_sub, "X", 2) -@show !has_vertex(g_sub, "Y", 1) -@show !has_vertex(g_sub, "Y", 2) +@show has_vertex(g_sub, ("X", 1)) +@show has_vertex(g_sub, ("X", 2)) +@show !has_vertex(g_sub, ("Y", 1)) +@show !has_vertex(g_sub, ("Y", 2)) -g_sub = g["X", :] +# g_sub = g["X", :] +g_sub = subgraph(v -> v[1] == "X", g) -@show has_vertex(g_sub, "X", 1) -@show has_vertex(g_sub, "X", 2) -@show !has_vertex(g_sub, "Y", 1) -@show !has_vertex(g_sub, "Y", 2) +@show has_vertex(g_sub, ("X", 1)) +@show has_vertex(g_sub, ("X", 2)) +@show !has_vertex(g_sub, ("Y", 1)) +@show !has_vertex(g_sub, ("Y", 2)) -g_sub = g[:, 2] +# g_sub = g[:, 2] +g_sub = subgraph(v -> v[2] == 2, g) -@show !has_vertex(g_sub, "X", 1) -@show has_vertex(g_sub, "X", 2) -@show !has_vertex(g_sub, "Y", 1) -@show has_vertex(g_sub, "Y", 2) +@show !has_vertex(g_sub, ("X", 1)) +@show has_vertex(g_sub, ("X", 2)) +@show !has_vertex(g_sub, ("Y", 1)) +@show has_vertex(g_sub, ("Y", 2)) parent_graph = grid((2, 2)) -g1 = NamedDimGraph(parent_graph; dims=(2, 2)) -g2 = NamedDimGraph(parent_graph; dims=(2, 2)) - -g_vcat = [g1; g2] - -@show nv(g_vcat) == 8 - -g_hcat = [g1;; g2] - -@show nv(g_hcat) == 8 +g1 = NamedGraph(parent_graph; vertices=(2, 2)) +g2 = NamedGraph(parent_graph; vertices=(2, 2)) g_disjoint_union = g1 ⊔ g2 @show nv(g_disjoint_union) == 8 + +## g_vcat = [g1; g2] +## +## @show nv(g_vcat) == 8 +## +## g_hcat = [g1;; g2] +## +## @show nv(g_hcat) == 8 diff --git a/examples/namedgraph.jl b/examples/namedgraph.jl index cd1b11c..bf73359 100644 --- a/examples/namedgraph.jl +++ b/examples/namedgraph.jl @@ -22,3 +22,4 @@ g_sub = g[["A", "B"]] @show has_vertex(g_sub, "B") @show !has_vertex(g_sub, "C") @show !has_vertex(g_sub, "D") +@show has_edge(g_sub, "A" => "B") diff --git a/src/Graphs/abstractgraph.jl b/src/Graphs/abstractgraph.jl index a80fc1a..885b134 100644 --- a/src/Graphs/abstractgraph.jl +++ b/src/Graphs/abstractgraph.jl @@ -1,3 +1,62 @@ +directed_graph(::Type{<:AbstractGraph}) = error("Not implemented") +undirected_graph(::Type{<:AbstractGraph}) = error("Not implemented") +# TODO: Implement generic version for `IsDirected` +# directed_graph(G::Type{IsDirected}) = G + +@traitfn directed_graph(graph::::IsDirected) = graph + +# TODO: Handle metadata in a generic way +@traitfn function directed_graph(graph::::(!IsDirected)) + digraph = directed_graph(typeof(graph))() + for v in vertices(graph) + add_vertex!(digraph, v) + end + for e in edges(graph) + add_edge!(digraph, e) + add_edge!(digraph, reverse(e)) + end + return digraph +end + +@traitfn undirected_graph(graph::::(!IsDirected)) = graph + +# TODO: Handle metadata in a generic way +# Must have the same argument name as: +# @traitfn undirected_graph(graph::::(!IsDirected)) +# to avoid method overwrite warnings, see: +# https://github.com/mauro3/SimpleTraits.jl#method-overwritten-warnings +@traitfn function undirected_graph(graph::::IsDirected) + undigraph = undirected_graph(typeof(graph))(vertices(graph)) + for e in edges(graph) + # TODO: Check for repeated edges? + add_edge!(undigraph, e) + end + return undigraph +end + +# Similar to `eltype`, but `eltype` doesn't work on types +vertextype(::Type{<:AbstractGraph{V}}) where {V} = V +vertextype(graph::AbstractGraph) = vertextype(typeof(graph)) + +# Function `f` maps original vertices `vᵢ` of `g` +# to new vertices `f(vᵢ)` of the output graph. +function rename_vertices(f::Function, g::AbstractGraph) + return set_vertices(g, f.(vertices(g))) +end + +function rename_vertices(g::AbstractGraph, name_map) + return rename_vertices(v -> name_map[v], g) +end + +# Alternative syntax to `getindex` for getting a subgraph +function subgraph(graph::AbstractGraph, subvertices::Vector) + return induced_subgraph(graph, subvertices)[1] +end + +function subgraph(f::Function, graph::AbstractGraph) + return induced_subgraph(graph, filter(f, vertices(graph)))[1] +end + # Used for tree iteration. # Assumes the graph is a [rooted directed tree](https://en.wikipedia.org/wiki/Tree_(graph_theory)#Rooted_tree). struct TreeGraph{G,V} @@ -13,33 +72,58 @@ AbstractTrees.printnode(io::IO, t::TreeGraph) = print(io, t.vertex) # Graph unions # -function vcat(graph1::AbstractGraph, graph2::AbstractGraph; kwargs...) - return hvncat(1, graph1, graph2; kwargs...) +# https://en.wikipedia.org/wiki/Disjoint_union +# Input maps the new index being appended to the vertices +# to the associated graph. +function disjoint_union(graphs::Dictionary{<:Any,<:AbstractGraph}) + return union((rename_vertices(v -> (v, i), graphs[i]) for i in keys(graphs))...) +end + +function disjoint_union(graphs::Vector{<:AbstractGraph}) + return disjoint_union(Dictionary(graphs)) +end + +disjoint_union(graph::AbstractGraph) = graph + +function disjoint_union(graph1::AbstractGraph, graphs_tail::AbstractGraph...) + return disjoint_union(Dictionary([graph1, graphs_tail...])) end -function hcat(graph1::AbstractGraph, graph2::AbstractGraph; kwargs...) - return hvncat(2, graph1, graph2; kwargs...) +function disjoint_union(pairs::Pair...) + return disjoint_union([pairs...]) end -# TODO: define `disjoint_union(graphs...; dim::Int, new_dim_names)` to do a disjoint union -# of a number of graphs. -function disjoint_union(graph1::AbstractGraph, graph2::AbstractGraph; dim::Int=0, kwargs...) - return hvncat(dim, graph1, graph2; kwargs...) +function disjoint_union(iter::Vector{<:Pair}) + return disjoint_union(dictionary(iter)) end -function ⊔(graph1::AbstractGraph, graph2::AbstractGraph; kwargs...) - return disjoint_union(graph1, graph2; kwargs...) +function ⊔(graphs...; kwargs...) + return disjoint_union(graphs...; kwargs...) end +# vcat, hcat, hvncat +# function vcat(graph1::AbstractGraph, graph2::AbstractGraph; kwargs...) +# return hvncat(1, graph1, graph2; kwargs...) +# end +# +# function hcat(graph1::AbstractGraph, graph2::AbstractGraph; kwargs...) +# return hvncat(2, graph1, graph2; kwargs...) +# end +# +# # TODO: define `disjoint_union(graphs...; dim::Int, new_dim_names)` to do a disjoint union +# # of a number of graphs. +# function disjoint_union(graph1::AbstractGraph, graph2::AbstractGraph; dim::Int=0, kwargs...) +# return hvncat(dim, graph1, graph2; kwargs...) +# end + # https://github.com/JuliaGraphs/Graphs.jl/issues/34 function is_tree(graph::AbstractGraph) return (ne(graph) == nv(graph) - 1) && is_connected(graph) end -function incident_edges(graph::AbstractGraph, vertex...) +function incident_edges(graph::AbstractGraph, vertex) return [ - edgetype(graph)(to_vertex(graph, vertex...), neighbor_vertex) for - neighbor_vertex in neighbors(graph, vertex...) + edgetype(graph)(vertex, neighbor_vertex) for neighbor_vertex in neighbors(graph, vertex) ] end @@ -53,36 +137,36 @@ end # function leaf_vertices(graph::AbstractGraph) # @assert is_tree(graph) - return filter(v -> is_leaf(graph, v...), vertices(graph)) + return filter(v -> is_leaf(graph, v), vertices(graph)) end # # Graph iteration # -@traitfn function post_order_dfs_vertices(graph::::(!IsDirected), root_vertex...) - dfs_tree_graph = dfs_tree(graph, root_vertex...) - return post_order_dfs_vertices(dfs_tree_graph, root_vertex...) +@traitfn function post_order_dfs_vertices(graph::::(!IsDirected), root_vertex) + dfs_tree_graph = dfs_tree(graph, root_vertex) + return post_order_dfs_vertices(dfs_tree_graph, root_vertex) end -@traitfn function post_order_dfs_edges(graph::::(!IsDirected), root_vertex...) - dfs_tree_graph = dfs_tree(graph, root_vertex...) - return post_order_dfs_edges(dfs_tree_graph, root_vertex...) +@traitfn function post_order_dfs_edges(graph::::(!IsDirected), root_vertex) + dfs_tree_graph = dfs_tree(graph, root_vertex) + return post_order_dfs_edges(dfs_tree_graph, root_vertex) end -@traitfn function is_leaf(graph::::(!IsDirected), vertex...) +@traitfn function is_leaf(graph::::(!IsDirected), vertex) # @assert is_tree(graph) - return isone(length(neighbors(graph, vertex...))) + return isone(length(neighbors(graph, vertex))) end # Paths for undirected tree-like graphs @traitfn function vertex_path(graph::::(!IsDirected), s, t) - dfs_tree_graph = dfs_tree(graph, t...) + dfs_tree_graph = dfs_tree(graph, t) return vertex_path(dfs_tree_graph, s, t) end @traitfn function edge_path(graph::::(!IsDirected), s, t) - dfs_tree_graph = dfs_tree(graph, t...) + dfs_tree_graph = dfs_tree(graph, t) return edge_path(dfs_tree_graph, s, t) end @@ -93,49 +177,47 @@ end # Get the parent vertex of a vertex. # Assumes the graph is a [rooted directed tree](https://en.wikipedia.org/wiki/Tree_(graph_theory)#Rooted_tree) -@traitfn function parent_vertex(graph::::IsDirected, vertex...) +@traitfn function parent_vertex(graph::::IsDirected, vertex) # @assert is_tree(graph) - in_neighbors = inneighbors(graph, vertex...) + in_neighbors = inneighbors(graph, vertex) isempty(in_neighbors) && return nothing return only(in_neighbors) end # Returns the edge directed **towards the parent/root vertex**! -@traitfn function parent_edge(graph::::IsDirected, vertex...) +@traitfn function parent_edge(graph::::IsDirected, vertex) # @assert is_tree(graph) - parent = parent_vertex(graph, vertex...) + parent = parent_vertex(graph, vertex) isnothing(parent) && return nothing - return edgetype(graph)(vertex..., parent) + return edgetype(graph)(vertex, parent) end # Get the children of a vertex. # Assumes the graph is a [rooted directed tree](https://en.wikipedia.org/wiki/Tree_(graph_theory)#Rooted_tree) -@traitfn function child_vertices(graph::::IsDirected, vertex...) +@traitfn function child_vertices(graph::::IsDirected, vertex) # @assert is_tree(graph) - return outneighbors(graph, vertex...) + return outneighbors(graph, vertex) end # Get the edges from the input vertex towards the child vertices. -@traitfn function child_edges(graph::::IsDirected, vertex...) +@traitfn function child_edges(graph::::IsDirected, vertex) # @assert is_tree(graph) return [ - edgetype(graph)(vertex..., child_vertex) for - child_vertex in child_vertices(graph, vertex...) + edgetype(graph)(vertex, child_vertex) for child_vertex in child_vertices(graph, vertex) ] end # Check if a vertex is a leaf. # Assumes the graph is a [rooted directed tree](https://en.wikipedia.org/wiki/Tree_(graph_theory)#Rooted_tree) -@traitfn function is_leaf(graph::::IsDirected, vertex...) +@traitfn function is_leaf(graph::::IsDirected, vertex) # @assert is_tree(graph) - return isempty(outneighbors(graph, vertex...)) + return isempty(outneighbors(graph, vertex)) end # Traverse the tree using a [post-order depth-first search](https://en.wikipedia.org/wiki/Tree_traversal#Depth-first_search), returning the vertices. # Assumes the graph is a [rooted directed tree](https://en.wikipedia.org/wiki/Tree_(graph_theory)#Rooted_tree) -@traitfn function post_order_dfs_vertices(graph::::IsDirected, root_index1, root_index...) +@traitfn function post_order_dfs_vertices(graph::::IsDirected, root_vertex) # @assert is_tree(graph) - root_vertex = to_vertex(graph, root_index1, root_index...) # Outputs a rooted directed tree (https://en.wikipedia.org/wiki/Arborescence_(graph_theory)) return [node.vertex for node in PostOrderDFS(TreeGraph(graph, root_vertex))] end @@ -143,9 +225,9 @@ end # Traverse the tree using a [post-order depth-first search](https://en.wikipedia.org/wiki/Tree_traversal#Depth-first_search), returning the edges where the source is the current vertex and the destination is the parent vertex. # Assumes the graph is a [rooted directed tree](https://en.wikipedia.org/wiki/Tree_(graph_theory)#Rooted_tree). # Returns a list of edges directed **towards the root vertex**! -@traitfn function post_order_dfs_edges(graph::::IsDirected, root_vertex...) +@traitfn function post_order_dfs_edges(graph::::IsDirected, root_vertex) # @assert is_tree(graph) - vertices = post_order_dfs_vertices(graph, root_vertex...) + vertices = post_order_dfs_vertices(graph, root_vertex) # Remove the root vertex pop!(vertices) return [parent_edge(graph, vertex) for vertex in vertices] @@ -155,7 +237,7 @@ end @traitfn function vertex_path(graph::::IsDirected, s, t) vertices = eltype(graph)[s] while vertices[end] != t - parent = parent_vertex(graph, vertices[end]...) + parent = parent_vertex(graph, vertices[end]) isnothing(parent) && return nothing push!(vertices, parent) end @@ -166,5 +248,19 @@ end vertices = vertex_path(graph, s, t) isnothing(vertices) && return nothing pop!(vertices) - return [edgetype(graph)(vertex, parent_vertex(graph, vertex...)) for vertex in vertices] + return [edgetype(graph)(vertex, parent_vertex(graph, vertex)) for vertex in vertices] +end + +######################################################################## +# Graphs.SimpleGraphs extensions + +# TODO: Move to `SimpleGraph` file +# TODO: Use trait dispatch to do no-ops when appropriate +directed_graph(G::Type{<:SimpleGraph}) = SimpleDiGraph{vertextype(G)} +undirected_graph(G::Type{<:SimpleGraph}) = G +directed_graph(G::Type{<:SimpleDiGraph}) = G +undirected_graph(G::Type{<:SimpleDiGraph}) = SimpleGraph{vertextype(G)} + +function set_vertices(graph::AbstractSimpleGraph, vertices::Vector) + return GenericNamedGraph(graph, vertices) end diff --git a/src/NamedGraphs.jl b/src/NamedGraphs.jl index 38e1676..481d364 100644 --- a/src/NamedGraphs.jl +++ b/src/NamedGraphs.jl @@ -1,12 +1,13 @@ module NamedGraphs using AbstractTrees using Dictionaries -using MultiDimDictionaries +using SimpleTraits using Graphs -using Graphs.SimpleTraits +using Graphs.SimpleGraphs -using MultiDimDictionaries: tuple_convert, IndexType, SliceIndex, ElementIndex +# General utility functions +not_implemented() = error("Not implemented") # abstractnamedgraph.jl import Graphs: @@ -42,34 +43,26 @@ import Graphs: tree, vertices -import Base: show, eltype, copy, getindex, convert, hcat, vcat, hvncat +import Base: show, eltype, copy, getindex, convert, hcat, vcat, hvncat, union # abstractnamededge.jl import Base: Pair, Tuple, show, ==, hash, eltype import Graphs: AbstractEdge, src, dst, reverse -import MultiDimDictionaries: disjoint_union, ⊔ - -# General utility functions -not_implemented() = error("Not implemented") -include("to_vertex.jl") include(joinpath("Graphs", "abstractgraph.jl")) include(joinpath("Graphs", "generators", "staticgraphs.jl")) include("abstractnamededge.jl") include("namededge.jl") -include("nameddimedge.jl") include("abstractnamedgraph.jl") include("namedgraph.jl") -#include("abstractnameddimgraph.jl") ## TODO -include("nameddimgraph.jl") -include("nameddimdigraph.jl") include(joinpath("generators", "named_staticgraphs.jl")) export NamedGraph, - NamedDimDiGraph, - NamedDimGraph, - NamedDimEdge, + NamedDiGraph, NamedEdge, + vertextype, + directed_graph, + undirected_graph, ⊔, disjoint_union, incident_edges, @@ -87,6 +80,7 @@ export NamedGraph, child_vertices, leaf_vertices, vertex_path, - edge_path + edge_path, + subgraph end # module AbstractNamedGraphs diff --git a/src/abstractnamedgraph.jl b/src/abstractnamedgraph.jl index c59f926..3357b0f 100644 --- a/src/abstractnamedgraph.jl +++ b/src/abstractnamedgraph.jl @@ -6,18 +6,54 @@ abstract type AbstractNamedGraph{V} <: AbstractGraph{V} end vertices(graph::AbstractNamedGraph) = not_implemented() parent_graph(graph::AbstractNamedGraph) = not_implemented() + +# TODO: Require this for the interface, or implement as: +# typeof(parent_graph(graph)) +# ? +parent_graph_type(graph::AbstractNamedGraph) = not_implemented() vertex_to_parent_vertex(graph::AbstractNamedGraph) = not_implemented() + +# TODO: rename `edge_type`? edgetype(graph::AbstractNamedGraph) = not_implemented() -is_directed(::Type{<:AbstractNamedGraph}) = not_implemented() +directed_graph(G::Type{<:AbstractNamedGraph}) = not_implemented() +undirected_graph(G::Type{<:AbstractNamedGraph}) = not_implemented() + +# In terms of `parent_graph_type` +# is_directed(::Type{<:AbstractNamedGraph}) = not_implemented() + +# TODO: implement as: +# +# graph = set_parent_graph(graph, copy(parent_graph(graph))) +# graph = set_vertices(graph, copy(vertices(graph))) +# +# or: +# +# graph_copy = similar(typeof(graph))(vertices(graph)) +# for e in edges(graph) +# add_edge!(graph_copy, e) +# end +copy(graph::AbstractNamedGraph) = not_implemented() + +# TODO: Implement using `copyto!`? +function directed_graph(graph::AbstractNamedGraph) + digraph = directed_graph(typeof(graph))(vertices(graph)) + for e in edges(graph) + add_edge!(digraph, e) + add_edge!(digraph, reverse(e)) + end + return digraph +end + +# Default, can overload +eltype(graph::AbstractNamedGraph) = eltype(vertices(graph)) -parent_graph_type(graph::AbstractNamedGraph) = typeof(parent_graph(graph)) parent_eltype(graph::AbstractNamedGraph) = eltype(parent_graph(graph)) # By default, assume `vertex_to_parent_vertex(graph)` # returns a data structure that you index into to map # from a vertex to a parent vertex. -function vertex_to_parent_vertex(graph::AbstractNamedGraph, vertex...) - return vertex_to_parent_vertex(graph)[vertex...] +function vertex_to_parent_vertex(graph::AbstractNamedGraph, vertex) + return vertex_to_parent_vertex(graph)[vertex] end # Convert parent vertex to vertex. @@ -29,7 +65,7 @@ end # This should be overloaded for multi-dimensional indexing. # Get the subset of vertices of the graph, for example # for an input slice `subvertices(graph, "X", :)`. -function subvertices(graph::AbstractNamedGraph, vertices...) +function subvertices(graph::AbstractNamedGraph, vertices) return not_implemented() end @@ -39,8 +75,8 @@ end # This is to handle `NamedDimGraph` where some of the dimensions # that are not slices get dropped. -function sliced_subvertices(graph::AbstractNamedGraph, vertices...) - return subvertices(graph, vertices...) +function sliced_subvertices(graph::AbstractNamedGraph, vertices) + return subvertices(graph, vertices) end function vertices_to_parent_vertices( @@ -49,8 +85,6 @@ function vertices_to_parent_vertices( return parent_eltype(graph)[vertex_to_parent_vertex(graph, vertex) for vertex in vertices] end -eltype(g::AbstractNamedGraph{V}) where {V} = V - parent_vertices(graph::AbstractNamedGraph) = vertices(parent_graph(graph)) parent_edges(graph::AbstractNamedGraph) = edges(parent_graph(graph)) parent_edgetype(graph::AbstractNamedGraph) = edgetype(parent_graph(graph)) @@ -77,8 +111,8 @@ end # TODO: write in terms of a generic function. for f in [:outneighbors, :inneighbors, :all_neighbors, :neighbors] @eval begin - function $f(graph::AbstractNamedGraph, vertex...) - parent_vertices = $f(parent_graph(graph), vertex_to_parent_vertex(graph, vertex...)) + function $f(graph::AbstractNamedGraph, vertex) + parent_vertices = $f(parent_graph(graph), vertex_to_parent_vertex(graph, vertex)) return [ parent_vertex_to_vertex(graph, parent_vertex) for parent_vertex in parent_vertices ] @@ -116,18 +150,22 @@ end has_edge(g::AbstractNamedGraph, edge) = has_edge(g, edgetype(g)(edge)) has_edge(g::AbstractNamedGraph, src, dst) = has_edge(g, edgetype(g)(src, dst)) -function add_vertex!(graph::AbstractNamedGraph, v...) - # Convert to a vertex of the graph type - # For example, for MultiDimNamedGraph, this does: - # - # to_vertex(graph, "X") # ("X",) - # to_vertex(graph, "X", 1) # ("X", 1) - # to_vertex(graph, ("X", 1)) # ("X", 1) - # - # For general graph types it is: - # - # to_vertex(graph, "X") # "X" - vertex = to_vertex(graph, v...) +function union(graph1::AbstractNamedGraph, graph2::AbstractNamedGraph) + union_graph = promote_type(typeof(graph1), typeof(graph2))() + union_vertices = union(vertices(graph1), vertices(graph2)) + for v in union_vertices + add_vertex!(union_graph, v) + end + for e in edges(graph1) + add_edge!(union_graph, e) + end + for e in edges(graph2) + add_edge!(union_graph, e) + end + return union_graph +end + +function add_vertex!(graph::AbstractNamedGraph, vertex) if vertex ∈ vertices(graph) throw(ArgumentError("Duplicate vertices are not allowed")) end @@ -139,10 +177,9 @@ function add_vertex!(graph::AbstractNamedGraph, v...) return graph end -function rem_vertex!(graph::AbstractNamedGraph, v...) - vertex = to_vertex(graph, v...) +function rem_vertex!(graph::AbstractNamedGraph, vertex) parent_vertex = vertex_to_parent_vertex(graph, vertex) - rem_vertex!(parent_graph(graph), vertex_to_parent_vertex(graph, vertex)) + rem_vertex!(parent_graph(graph), parent_vertex) # Insert the last vertex into the position of the vertex # that is being deleted, then remove the last vertex. @@ -165,13 +202,7 @@ function add_vertices!(graph::AbstractNamedGraph, vs::Vector) return graph end -function getindex(graph::AbstractNamedGraph, sub_vertices...) - graph_subvertices = subvertices(graph, sub_vertices...) - graph_sliced_subvertices = sliced_subvertices(graph, sub_vertices...) - parent_subgraph_vertices = vertices_to_parent_vertices(graph, graph_subvertices) - parent_subgraph, _ = induced_subgraph(parent_graph(graph), parent_subgraph_vertices) - return typeof(graph)(parent_subgraph, graph_sliced_subvertices) -end +is_directed(G::Type{<:AbstractNamedGraph}) = is_directed(parent_graph_type(G)) is_directed(graph::AbstractNamedGraph) = is_directed(parent_graph(graph)) @@ -179,15 +210,19 @@ is_connected(graph::AbstractNamedGraph) = is_connected(parent_graph(graph)) is_cyclic(graph::AbstractNamedGraph) = is_cyclic(parent_graph(graph)) -# Rename `disjoint_union`: https://networkx.org/documentation/stable/reference/algorithms/operators.html +# TODO: Move to namedgraph.jl, or make the output generic? function blockdiag(graph1::AbstractNamedGraph, graph2::AbstractNamedGraph) new_parent_graph = blockdiag(parent_graph(graph1), parent_graph(graph2)) new_vertices = vcat(vertices(graph1), vertices(graph2)) - return AbstractNamedGraph(new_parent_graph, new_vertices) + @assert allunique(new_vertices) + return GenericNamedGraph(new_parent_graph, new_vertices) end +# TODO: What `args` are needed? nv(graph::AbstractNamedGraph, args...) = nv(parent_graph(graph), args...) +# TODO: What `args` are needed? ne(graph::AbstractNamedGraph, args...) = ne(parent_graph(graph), args...) +# TODO: What `args` are needed? function adjacency_matrix(graph::AbstractNamedGraph, args...) return adjacency_matrix(parent_graph(graph), args...) end @@ -196,26 +231,41 @@ end # Graph traversals # -bfs_tree(g::AbstractNamedGraph, s...; kwargs...) = tree(g, bfs_parents(g, s...; kwargs...)) +# Overload Graphs.tree. Used for bfs_tree and dfs_tree +# traversal algorithms. +function tree(graph::AbstractNamedGraph, parents::AbstractVector) + n = length(parents) + # TODO: Use `directed_graph` here to make more generic? + t = GenericNamedGraph(DiGraph(n), vertices(graph)) + for (parent_v, u) in enumerate(parents) + v = vertices(graph)[parent_v] + if u != v + add_edge!(t, u, v) + end + end + return t +end + +bfs_tree(g::AbstractNamedGraph, s; kwargs...) = tree(g, bfs_parents(g, s; kwargs...)) # Disambiguation from Graphs.bfs_tree bfs_tree(g::AbstractNamedGraph, s::Integer; kwargs...) = bfs_tree(g, tuple(s); kwargs...) -function bfs_parents(graph::AbstractNamedGraph, s...; kwargs...) +function bfs_parents(graph::AbstractNamedGraph, s; kwargs...) parent_bfs_parents = bfs_parents( - parent_graph(graph), vertex_to_parent_vertex(graph)[s...]; kwargs... + parent_graph(graph), vertex_to_parent_vertex(graph)[s]; kwargs... ) return [vertices(graph)[parent_vertex] for parent_vertex in parent_bfs_parents] end -dfs_tree(g::AbstractNamedGraph, s...; kwargs...) = tree(g, dfs_parents(g, s...; kwargs...)) +dfs_tree(g::AbstractNamedGraph, s; kwargs...) = tree(g, dfs_parents(g, s; kwargs...)) # Disambiguation from Graphs.dfs_tree dfs_tree(g::AbstractNamedGraph, s::Integer; kwargs...) = dfs_tree(g, tuple(s); kwargs...) -function dfs_parents(graph::AbstractNamedGraph, s...; kwargs...) +function dfs_parents(graph::AbstractNamedGraph, s; kwargs...) parent_dfs_parents = dfs_parents( - parent_graph(graph), vertex_to_parent_vertex(graph)[s...]; kwargs... + parent_graph(graph), vertex_to_parent_vertex(graph)[s]; kwargs... ) return [vertices(graph)[parent_vertex] for parent_vertex in parent_dfs_parents] end @@ -242,7 +292,7 @@ show(io::IO, graph::AbstractNamedGraph) = show(io, MIME"text/plain"(), graph) # Convenience functions # -function Base.:(==)(g1::GT, g2::GT) where {GT<:AbstractNamedGraph} +function Base.:(==)(g1::AbstractNamedGraph, g2::AbstractNamedGraph) issetequal(vertices(g1), vertices(g2)) || return false for v in vertices(g1) issetequal(inneighbors(g1, v), inneighbors(g2, v)) || return false @@ -250,11 +300,3 @@ function Base.:(==)(g1::GT, g2::GT) where {GT<:AbstractNamedGraph} end return true end - -function rename_vertices(f::Function, g::AbstractNamedGraph) - return set_vertices(g, f.(vertices(g))) -end - -function rename_vertices(g::AbstractNamedGraph, name_map) - return rename_vertices(v -> name_map[v], g) -end diff --git a/src/generators/named_staticgraphs.jl b/src/generators/named_staticgraphs.jl index eb0514f..66d79d8 100644 --- a/src/generators/named_staticgraphs.jl +++ b/src/generators/named_staticgraphs.jl @@ -23,6 +23,7 @@ function set_named_vertices!( return named_vertices end +# TODO: Use vectors as vertex names? # k = 3: # 1 => (1,) # 2 => (1, 1) @@ -46,7 +47,7 @@ function named_bfs_tree( simple_graph::SimpleGraph, source::Integer=1; source_name=1, child_name=identity ) named_vertices = named_bfs_tree_vertices(simple_graph, source; source_name, child_name) - return NamedDimGraph(simple_graph; vertices=named_vertices) + return NamedGraph(simple_graph; vertices=named_vertices) end function named_binary_tree( @@ -56,14 +57,19 @@ function named_binary_tree( return named_bfs_tree(simple_graph, source; source_name, child_name) end -function named_grid(dims; periodic=false) - simple_graph = grid(dims; periodic) - return NamedDimGraph(simple_graph; dims=dims) +function named_grid(dim::Int; kwargs...) + simple_graph = grid((dim,); kwargs...) + return NamedGraph(simple_graph) +end + +function named_grid(dims; kwargs...) + simple_graph = grid(dims; kwargs...) + return NamedGraph(simple_graph; vertices=dims) end function named_comb_tree(dims::Tuple) simple_graph = comb_tree(dims) - return NamedDimGraph(simple_graph; dims=dims) + return NamedGraph(simple_graph; vertices=dims) end function named_comb_tree(tooth_lengths::Vector{<:Integer}) @@ -74,5 +80,5 @@ function named_comb_tree(tooth_lengths::Vector{<:Integer}) vertices = filter(Tuple.(CartesianIndices((nx, ny)))) do (jx, jy) jy <= tooth_lengths[jx] end - return NamedDimGraph(simple_graph; vertices) + return NamedGraph(simple_graph; vertices) end diff --git a/src/nameddimdigraph.jl b/src/nameddimdigraph.jl deleted file mode 100644 index d58de50..0000000 --- a/src/nameddimdigraph.jl +++ /dev/null @@ -1,164 +0,0 @@ -default_vertices(graph::DiGraph) = [tuple(v) for v in 1:nv(graph)] - -struct NamedDimDiGraph{V<:Tuple} <: AbstractNamedGraph{V} - parent_graph::SimpleDiGraph{Int} - vertices::Vector{V} - vertex_to_parent_vertex::MultiDimDictionary{V,Int} -end - -is_directed(::Type{<:NamedDimDiGraph}) = true - -function copy(graph::NamedDimDiGraph) - return NamedDimDiGraph( - copy(graph.parent_graph), copy(graph.vertices), copy(graph.vertex_to_parent_vertex) - ) -end - -function NamedDimDiGraph{V}( - parent_graph::DiGraph, vertices::Vector{V}=default_vertices(parent_graph) -) where {V<:Tuple} - return NamedDimDiGraph{V}( - parent_graph, vertices, MultiDimDictionary{V}(vertices, eachindex(vertices)) - ) -end - -function NamedDimDiGraph{V}(parent_graph::DiGraph, vertices::Vector) where {V<:Tuple} - graph_vertices = tuple_convert.(vertices) - return NamedDimDiGraph{V}( - parent_graph, - graph_vertices, - MultiDimDictionary{Tuple}(graph_vertices, eachindex(graph_vertices)), - ) -end - -function NamedDimDiGraph{V}(parent_graph::DiGraph, vertices) where {V<:Tuple} - return NamedDimDiGraph{V}(parent_graph, collect(vertices)) -end - -function NamedDimDiGraph{V}(parent_graph::DiGraph, vertices::Array) where {V<:Tuple} - return NamedDimDiGraph{V}(parent_graph, vec(vertices)) -end - -NamedDimDiGraph{V}() where {V} = NamedDimDiGraph{V}(DiGraph()) - -function NamedDimDiGraph(parent_graph::DiGraph, vertices::Array) - # Could default to `eltype(vertices)`, but in general - # we want the flexibility of `Tuple` for mixed key lengths - # and types. - return NamedDimDiGraph{Tuple}(parent_graph, vertices) -end - -function NamedDimDiGraph(parent_graph::DiGraph, vertices) - return NamedDimDiGraph(parent_graph, collect(vertices)) -end - -function NamedDimDiGraph(parent_graph::DiGraph; dims=nothing, vertices=nothing) - if !isnothing(dims) && !isnothing(vertices) - println("dims = ", dims) - println("vertices = ", vertices) - error("Must specify `dims` or `vertices` but not both.") - elseif isnothing(dims) && isnothing(vertices) - vertices = default_vertices(parent_graph) - elseif !isnothing(dims) # && isnothing(vertices) - vertices = Tuple.(CartesianIndices(dims)) - @assert prod(dims) == nv(parent_graph) - end - return NamedDimDiGraph(parent_graph, vertices) -end - -function NamedDimDiGraph(vertices::Array) - return NamedDimDiGraph(DiGraph(length(vertices)); vertices) -end - -NamedDimDiGraph() = NamedDimDiGraph(DiGraph()) - -# AbstractNamedGraph required interface. -parent_graph(graph::NamedDimDiGraph) = graph.parent_graph -vertices(graph::NamedDimDiGraph) = graph.vertices -vertex_to_parent_vertex(graph::NamedDimDiGraph) = graph.vertex_to_parent_vertex -edgetype(graph::NamedDimDiGraph{V}) where {V<:Tuple} = NamedDimEdge{V} - -# Convert to a vertex of the graph type -# For example, for MultiDimNamedGraph, this does: -# -# to_vertex(graph, "X") # ("X",) -# to_vertex(graph, "X", 1) # ("X", 1) -# to_vertex(graph, ("X", 1)) # ("X", 1) -# -# For general graph types it is: -# -# to_vertex(graph, "X") # "X" -to_vertex(::Type{<:NamedDimDiGraph}, v...) = tuple_convert(v...) - -function has_vertex(graph::NamedDimDiGraph{V}, v::Tuple) where {V<:Tuple} - return v in vertices(graph) -end - -function has_vertex(graph::NamedDimDiGraph, v...) - return has_vertex(graph, to_vertex(graph, v...)) -end - -# Customize obtaining subgraphs -# This version takes a list of vertices which are interpreted -# as the subvertices. -function subvertices(graph::NamedDimDiGraph{V}, vertices::Vector) where {V<:Tuple} - return convert(Vector{V}, tuple_convert.(vertices)) -end - -# A subset of the original vertices of `graph` based on a -# given slice of the vertices. -function subvertices(graph::NamedDimDiGraph, vertex_slice...) - return collect( - keys(getindex(SliceIndex(), graph.vertex_to_parent_vertex, tuple(vertex_slice...))) - ) -end - -# TODO: implement in terms of `subvertices` and a generic function -# for dopping the non-slice dimensions like `drop_nonslice_dims`. -# TODO: rename `subvertices_drop_nonslice_dims`. -function sliced_subvertices(graph::NamedDimDiGraph, vertex_slice...) - return collect(keys(graph.vertex_to_parent_vertex[vertex_slice...])) -end - -# TODO: rename `subvertices_drop_nonslice_dims`. -sliced_subvertices(graph::NamedDimDiGraph, vertices::Vector) = subvertices(graph, vertices) - -function hvncat( - dim::Int, graph1::NamedDimDiGraph, graph2::NamedDimDiGraph; new_dim_names=(1, 2) -) - graph_parent_graph = blockdiag(parent_graph(graph1), parent_graph(graph2)) - graph_vertex_to_parent_vertex = hvncat( - dim, - graph1.vertex_to_parent_vertex, - graph2.vertex_to_parent_vertex; - new_dim_keys=new_dim_names, - ) - graph_vertices = collect(keys(graph_vertex_to_parent_vertex)) - return NamedDimDiGraph(graph_parent_graph, graph_vertices) -end - -# Overload Graphs.tree. Used for bfs_tree and dfs_tree -# traversal algorithms. -function tree(graph::NamedDimGraph, parents::AbstractVector{T}) where {T<:Tuple} - n = length(parents) - - # TODO: change to: - # - # NamedDimDiGraph(DiGraph(n); vertices=vertices(graph)) - # - # or: - # - # NamedDimDiGraph(vertices(graph)) - t = NamedDimDiGraph{Tuple}(DiGraph(n), vertices(graph)) - for (parent_v, u) in enumerate(parents) - v = vertices(graph)[parent_v] - if u != v - add_edge!(t, u, v) - end - end - return t -end - -function set_vertices(graph::NamedDimDiGraph, vertices) - return NamedDimDiGraph(parent_graph(graph), vertices) -end diff --git a/src/nameddimedge.jl b/src/nameddimedge.jl deleted file mode 100644 index 702994e..0000000 --- a/src/nameddimedge.jl +++ /dev/null @@ -1,54 +0,0 @@ -# TODO: Generalize to `NamedDimEdge{V1,V2}`? -# The vertices could be different types... -struct NamedDimEdge{V<:Tuple} <: AbstractNamedEdge{V} - src::V - dst::V - function NamedDimEdge{V}(src::V, dst::V) where {V<:Tuple} - return new{V}(src, dst) - end -end - -# Convert to a vertex of the graph type -# For example, for MultiDimNamedGraph, this does: -# -# to_vertex(graph, "X") # ("X",) -# to_vertex(graph, "X", 1) # ("X", 1) -# to_vertex(graph, ("X", 1)) # ("X", 1) -# -# For general graph types it is: -# -# to_vertex(graph, "X") # "X" -# -# TODO: Rename `tuple_convert` to `to_tuple`. -to_vertex(::Type{<:NamedDimEdge}, v...) = tuple_convert(v...) -to_vertex(e::NamedDimEdge, v...) = to_vertex(typeof(e), v...) - -src(e::NamedDimEdge) = e.src -dst(e::NamedDimEdge) = e.dst - -function NamedDimEdge(src, dst) - return NamedDimEdge{Tuple}(to_vertex(NamedDimEdge, src), to_vertex(NamedDimEdge, dst)) -end - -function NamedDimEdge{V}(src, dst) where {V<:Tuple} - return NamedDimEdge{V}(to_vertex(NamedDimEdge, src), to_vertex(NamedDimEdge, dst)) -end - -NamedDimEdge{V}(e::NamedDimEdge{V}) where {V<:Tuple} = e - -NamedDimEdge(e::AbstractEdge) = NamedDimEdge(src(e), dst(e)) -NamedDimEdge{V}(e::AbstractEdge) where {V<:Tuple} = NamedDimEdge{V}(src(e), dst(e)) - -convert(E::Type{<:NamedDimEdge}, e::NamedDimEdge) = E(e) - -# Allows syntax like `dictionary[1 => 2]`. -convert(E::Type{<:NamedDimEdge}, e::Pair) = E(e) - -NamedDimEdge(p::Pair) = NamedDimEdge(p.first, p.second) -NamedDimEdge{V}(p::Pair) where {V<:Tuple} = NamedDimEdge{V}(p.first, p.second) - -# XXX: Is this a good idea? It clashes with Tuple vertices of NamedDimGraphs. -# NamedDimEdge(t::Tuple) = NamedDimEdge(t[1], t[2]) -# NamedDimEdge{V}(t::Tuple) where {V} = NamedDimEdge(V(t[1]), V(t[2])) - -set_vertices(e::NamedDimEdge, src, dst) = NamedDimEdge(src, dst) diff --git a/src/nameddimgraph.jl b/src/nameddimgraph.jl deleted file mode 100644 index fb22a53..0000000 --- a/src/nameddimgraph.jl +++ /dev/null @@ -1,150 +0,0 @@ -struct NamedDimGraph{V<:Tuple} <: AbstractNamedGraph{V} - parent_graph::SimpleGraph{Int} - vertices::Vector{V} - vertex_to_parent_vertex::MultiDimDictionary{V,Int} -end - -is_directed(::Type{<:NamedDimGraph}) = false - -function copy(graph::NamedDimGraph) - return NamedDimGraph( - copy(graph.parent_graph), copy(graph.vertices), copy(graph.vertex_to_parent_vertex) - ) -end - -function NamedDimGraph{V}(parent_graph::Graph, vertices::Vector{V}) where {V<:Tuple} - return NamedDimGraph{V}( - parent_graph, vertices, MultiDimDictionary{V}(vertices, eachindex(vertices)) - ) -end - -function NamedDimGraph{V}(parent_graph::Graph, vertices::Vector) where {V<:Tuple} - graph_vertices = tuple_convert.(vertices) - return NamedDimGraph{V}( - parent_graph, - graph_vertices, - MultiDimDictionary{Tuple}(graph_vertices, eachindex(graph_vertices)), - ) -end - -function NamedDimGraph{V}(parent_graph::Graph, vertices) where {V<:Tuple} - return NamedDimGraph{V}(parent_graph, collect(vertices)) -end - -function NamedDimGraph{V}(parent_graph::Graph, vertices::Array) where {V<:Tuple} - return NamedDimGraph{V}(parent_graph, vec(vertices)) -end - -NamedDimGraph{V}() where {V} = NamedDimGraph{V}(Graph()) - -function NamedDimGraph(parent_graph::Graph, vertices::Array) - # Could default to `eltype(vertices)`, but in general - # we want the flexibility of `Tuple` for mixed key lengths - # and types. - return NamedDimGraph{Tuple}(parent_graph, vertices) -end - -function NamedDimGraph(parent_graph::Graph, vertices) - return NamedDimGraph(parent_graph, collect(vertices)) -end - -# Convert to a vertex of the graph type -# For example, for MultiDimNamedGraph, this does: -# -# to_vertex(graph, "X") # ("X",) -# to_vertex(graph, "X", 1) # ("X", 1) -# to_vertex(graph, ("X", 1)) # ("X", 1) -# -# For general graph types it is: -# -# to_vertex(graph, "X") # "X" -to_vertex(::Type{<:NamedDimGraph}, v...) = tuple_convert(v...) - -default_vertices(graph::Graph) = collect(1:nv(graph)) - -function NamedDimGraph(parent_graph::Graph; dims=nothing, vertices=nothing) - if !isnothing(dims) && !isnothing(vertices) - println("dims = ", dims) - println("vertices = ", vertices) - error("Must specify `dims` or `vertices` but not both.") - elseif isnothing(dims) && isnothing(vertices) - vertices = default_vertices(parent_graph) - elseif !isnothing(dims) # && isnothing(vertices) - vertices = Tuple.(CartesianIndices(dims)) - @assert prod(dims) == nv(parent_graph) - end - return NamedDimGraph(parent_graph, vertices) -end - -function NamedDimGraph(vertices::Array) - return NamedDimGraph(Graph(length(vertices)); vertices) -end - -NamedDimGraph() = NamedDimGraph(Graph()) - -# AbstractNamedGraph required interface. -parent_graph(graph::NamedDimGraph) = graph.parent_graph -vertices(graph::NamedDimGraph) = graph.vertices -vertex_to_parent_vertex(graph::NamedDimGraph) = graph.vertex_to_parent_vertex -edgetype(graph::NamedDimGraph{V}) where {V<:Tuple} = NamedDimEdge{V} - -function has_vertex(graph::NamedDimGraph{V}, v::Tuple) where {V<:Tuple} - return v in vertices(graph) -end - -function has_vertex(graph::NamedDimGraph, v...) - return has_vertex(graph, tuple(v...)) -end - -# Customize obtaining subgraphs -# This version takes a list of vertices which are interpreted -# as the subvertices. -function subvertices(graph::NamedDimGraph{V}, vertices::Vector) where {V<:Tuple} - return convert(Vector{V}, tuple_convert.(vertices)) -end - -# A subset of the original vertices of `graph` based on a -# given slice of the vertices. -function subvertices(graph::NamedDimGraph, vertex_slice...) - return collect( - keys(getindex(SliceIndex(), graph.vertex_to_parent_vertex, tuple(vertex_slice...))) - ) -end - -# TODO: implement in terms of `subvertices` and a generic function -# for dopping the non-slice dimensions like `drop_nonslice_dims`. -# TODO: rename `subvertices_drop_nonslice_dims`. -function sliced_subvertices(graph::NamedDimGraph, vertex_slice...) - return collect(keys(graph.vertex_to_parent_vertex[vertex_slice...])) -end - -# TODO: rename `subvertices_drop_nonslice_dims`. -sliced_subvertices(graph::NamedDimGraph, vertices::Vector) = subvertices(graph, vertices) - -function hvncat( - dim::Int, graph1::NamedDimGraph, graph2::NamedDimGraph; new_dim_names=(1, 2) -) - graph_parent_graph = blockdiag(parent_graph(graph1), parent_graph(graph2)) - - vertex_to_parent_vertex_1 = vertex_to_parent_vertex(graph1) - vertex_to_parent_vertex_2 = vertex_to_parent_vertex(graph2) - # Shift the vertices - vertex_to_parent_vertex_2 = MultiDimDictionary(vertex_to_parent_vertex_2 .+ nv(graph1)) - - graph_vertex_to_parent_vertex = hvncat( - dim, vertex_to_parent_vertex_1, vertex_to_parent_vertex_2; new_dim_keys=new_dim_names - ) - - # Sort the vertices - graph_vertices = Vector{Tuple}(undef, nv(graph_parent_graph)) - for graph_vertex in keys(graph_vertex_to_parent_vertex) - parent_vertex = graph_vertex_to_parent_vertex[graph_vertex] - graph_vertices[parent_vertex] = graph_vertex - end - - return NamedDimGraph(graph_parent_graph, graph_vertices) -end - -function set_vertices(graph::NamedDimGraph, vertices) - return NamedDimGraph(parent_graph(graph), vertices) -end diff --git a/src/namededge.jl b/src/namededge.jl index 1a150d7..dea6a34 100644 --- a/src/namededge.jl +++ b/src/namededge.jl @@ -1,20 +1,24 @@ struct NamedEdge{V} <: AbstractNamedEdge{V} src::V dst::V + NamedEdge{V}(src::V, dst::V) where {V} = new{V}(src, dst) end +NamedEdge(src::V, dst::V) where {V} = NamedEdge{V}(src, dst) +NamedEdge(src::S, dst::D) where {S,D} = NamedEdge{promote_type(S, D)}(src, dst) src(e::NamedEdge) = e.src dst(e::NamedEdge) = e.dst NamedEdge{V}(e::NamedEdge{V}) where {V} = e +NamedEdge(e::NamedEdge) = e -NamedEdge{V}(e::AbstractNamedEdge) where {V} = NamedEdge{V}(V(e.src), V(e.dst)) +NamedEdge{V}(e::AbstractNamedEdge) where {V} = NamedEdge{V}(e.src, e.dst) convert(E::Type{<:NamedEdge}, e::NamedEdge) = E(e) NamedEdge(t::Tuple) = NamedEdge(t[1], t[2]) NamedEdge(p::Pair) = NamedEdge(p.first, p.second) -NamedEdge{V}(p::Pair) where {V} = NamedEdge(V(p.first), V(p.second)) -NamedEdge{V}(t::Tuple) where {V} = NamedEdge(V(t[1]), V(t[2])) +NamedEdge{V}(p::Pair) where {V} = NamedEdge{V}(p.first, p.second) +NamedEdge{V}(t::Tuple) where {V} = NamedEdge{V}(t[1], t[2]) set_vertices(e::NamedEdge, src, dst) = NamedEdge(src, dst) diff --git a/src/namedgraph.jl b/src/namedgraph.jl index eaeecb4..742f497 100644 --- a/src/namedgraph.jl +++ b/src/namedgraph.jl @@ -1,29 +1,173 @@ -struct NamedGraph{V} <: AbstractNamedGraph{V} - parent_graph::Graph{Int} +struct GenericNamedGraph{V,G<:AbstractSimpleGraph{Int}} <: AbstractNamedGraph{V} + parent_graph::G vertices::Vector{V} vertex_to_parent_vertex::Dictionary{V,Int} end -is_directed(::Type{<:NamedGraph}) = false +# +# Constructors from `AbstractSimpleGraph` +# -function NamedGraph{V}(parent_graph::Graph, vertices::Vector{V}) where {V} - return NamedGraph{V}(parent_graph, vertices, Dictionary(vertices, eachindex(vertices))) +# Inner constructor +function GenericNamedGraph{V,G}( + parent_graph::AbstractSimpleGraph, vertices::Vector +) where {V,G} + # Need to copy the vertices here, otherwise the Dictionary uses a view of the vertices + return GenericNamedGraph{V,G}( + parent_graph, vertices, Dictionary(copy(vertices), eachindex(vertices)) + ) end -function NamedGraph(parent_graph::Graph, vertices::Vector{V}) where {V} - return NamedGraph{V}(parent_graph, vertices) +function GenericNamedGraph{V}(parent_graph::AbstractSimpleGraph, vertices::Vector) where {V} + return GenericNamedGraph{V,typeof(parent_graph)}(parent_graph, vertices) end -function NamedGraph(vertices::Vector) - return NamedGraph(Graph(length(vertices)), vertices) +function GenericNamedGraph{<:Any,G}( + parent_graph::AbstractSimpleGraph, vertices::Vector +) where {G} + return GenericNamedGraph{eltype(vertices),G}(parent_graph, vertices) +end + +function GenericNamedGraph(parent_graph::AbstractSimpleGraph, vertices::Vector) + # Need to copy the vertices here, otherwise the Dictionary uses a view of the vertices + return GenericNamedGraph{eltype(vertices)}(parent_graph, vertices) +end + +# +# Constructors from vertex names +# + +function GenericNamedGraph{V,G}(vertices::Vector) where {V,G} + return GenericNamedGraph(G(length(vertices)), vertices) +end + +function GenericNamedGraph{V}(vertices::Vector) where {V} + return GenericNamedGraph{V,SimpleGraph{Int}}(vertices) +end + +function GenericNamedGraph{<:Any,G}(vertices::Vector) where {G} + return GenericNamedGraph{Any,G}(vertices) +end + +function GenericNamedGraph(vertices::Vector) + return GenericNamedGraph{eltype(vertices)}(vertices) +end + +# +# Empty constructors +# + +GenericNamedGraph{V,G}() where {V,G} = GenericNamedGraph{V,G}(V[]) + +GenericNamedGraph{V}() where {V} = GenericNamedGraph{V}(V[]) + +GenericNamedGraph{<:Any,G}() where {G} = GenericNamedGraph{<:Any,G}(Any[]) + +GenericNamedGraph() = GenericNamedGraph(Any[]) + +# +# Keyword argument constructor syntax +# + +function GenericNamedGraph{V,G}( + parent_graph::AbstractSimpleGraph; vertices=vertices(parent_graph) +) where {V,G} + return GenericNamedGraph{V,G}(parent_graph, vertices) +end + +function GenericNamedGraph{V}( + parent_graph::AbstractSimpleGraph; vertices=vertices(parent_graph) +) where {V} + return GenericNamedGraph{V}(parent_graph, vertices) +end + +function GenericNamedGraph{<:Any,G}( + parent_graph::AbstractSimpleGraph; vertices=vertices(parent_graph) +) where {G} + return GenericNamedGraph{<:Any,G}(parent_graph, vertices) +end + +function GenericNamedGraph( + parent_graph::AbstractSimpleGraph; vertices=vertices(parent_graph) +) + return GenericNamedGraph(parent_graph, vertices) +end + +# +# Convenient cartesian index constructor +# + +function GenericNamedGraph{V,G}( + parent_graph::AbstractSimpleGraph, grid_size::Tuple{Vararg{Int}} +) where {V,G} + vertices = Tuple.(CartesianIndices(grid_size)) + @assert prod(grid_size) == nv(parent_graph) + return GenericNamedGraph{V,G}(parent_graph, vec(vertices)) +end + +function GenericNamedGraph{V}( + parent_graph::AbstractSimpleGraph, grid_size::Tuple{Vararg{Int}} +) where {V} + return GenericNamedGraph{V,typeof(parent_graph)}(parent_graph, grid_size) +end + +function GenericNamedGraph{<:Any,G}( + parent_graph::AbstractSimpleGraph, grid_size::Tuple{Vararg{Int}} +) where {G} + return GenericNamedGraph{typeof(grid_size),G}(parent_graph, grid_size) +end + +function GenericNamedGraph(parent_graph::AbstractSimpleGraph, grid_size::Tuple{Vararg{Int}}) + return GenericNamedGraph{typeof(grid_size),typeof(parent_graph)}(parent_graph, grid_size) end # AbstractNamedGraph required interface. -parent_graph(graph::NamedGraph) = graph.parent_graph -vertices(graph::NamedGraph) = graph.vertices -vertex_to_parent_vertex(graph::NamedGraph) = graph.vertex_to_parent_vertex -edgetype(graph::NamedGraph{V}) where {V} = NamedEdge{V} +# TODO: rename `parent_graph` (type is implied by input) +parent_graph_type(::Type{<:GenericNamedGraph{V,G}}) where {V,G} = G +parent_graph(graph::GenericNamedGraph) = graph.parent_graph +vertices(graph::GenericNamedGraph) = graph.vertices +vertex_to_parent_vertex(graph::GenericNamedGraph) = graph.vertex_to_parent_vertex -function set_vertices(graph::NamedGraph, vertices) - return NamedGraph(parent_graph(graph), vertices) +# TODO: implement as: +# graph = set_parent_graph(graph, copy(parent_graph(graph))) +# graph = set_vertices(graph, copy(vertices(graph))) +function copy(graph::GenericNamedGraph) + return GenericNamedGraph(copy(parent_graph(graph)), copy(vertices(graph))) end + +edgetype(G::Type{<:GenericNamedGraph}) = NamedEdge{vertextype(G)} +edgetype(graph::GenericNamedGraph) = edgetype(typeof(graph)) + +function set_vertices(graph::GenericNamedGraph, vertices) + return GenericNamedGraph(parent_graph(graph), vertices) +end + +function directed_graph(G::Type{<:GenericNamedGraph}) + return GenericNamedGraph{vertextype(G),directed_graph(parent_graph_type(G))} +end +function undirected_graph(G::Type{<:GenericNamedGraph}) + return GenericNamedGraph{vertextype(G),undirected_graph(parent_graph_type(G))} +end + +is_directed(G::Type{<:GenericNamedGraph}) = is_directed(parent_graph_type(G)) + +# TODO: Implement an edgelist version +function induced_subgraph(graph::AbstractNamedGraph, subvertices::Vector) + subgraph = typeof(graph)(subvertices) + subvertices_set = Set(subvertices) + for src in subvertices + for dst in outneighbors(graph, src) + if dst in subvertices_set && has_edge(graph, src, dst) + add_edge!(subgraph, src => dst) + end + end + end + return subgraph, nothing +end + +# +# Type aliases +# + +const NamedGraph{V} = GenericNamedGraph{V,SimpleGraph{Int}} +const NamedDiGraph{V} = GenericNamedGraph{V,SimpleDiGraph{Int}} diff --git a/src/to_vertex.jl b/src/to_vertex.jl deleted file mode 100644 index 5a58838..0000000 --- a/src/to_vertex.jl +++ /dev/null @@ -1,7 +0,0 @@ -# Represents converting a vertex to the type expected for -# a graph or edge. -# Helpful for generic code with multi-dimensional indexing -# of graphs and edges. -to_vertex(::Type, v...) = v -to_vertex(e::AbstractEdge, v...) = to_vertex(typeof(e), v...) -to_vertex(g::AbstractGraph, v...) = to_vertex(typeof(g), v...) diff --git a/test/Project.toml b/test/Project.toml index 58e790b..5c3642d 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,6 +1,6 @@ [deps] +Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" -MultiDimDictionaries = "87ff4268-a46e-478f-b30a-76b83dd64e3c" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb" diff --git a/test/runtests.jl b/test/runtests.jl index b01cce0..28545b7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -7,7 +7,7 @@ test_files = filter( file -> startswith(file, "test_") && endswith(file, ".jl"), readdir(test_path) ) @testset "NamedGraphs.jl" begin - @testset "$(last(splitpath(test_path)))" for file in test_files + @testset "$(file)" for file in test_files file_path = joinpath(test_path, file) println("Running test $(file_path)") include(file_path) diff --git a/test/test_abstractgraph.jl b/test/test_abstractgraph.jl index b9a65ca..8f07478 100644 --- a/test/test_abstractgraph.jl +++ b/test/test_abstractgraph.jl @@ -65,15 +65,15 @@ end @test issetequal(leaf_vertices(g), [4, 5, 6]) ng = named_comb_tree((3, 2)) - @test is_leaf(ng, 1, 2) - @test is_leaf(ng, 2, 2) + @test is_leaf(ng, (1, 2)) + @test is_leaf(ng, (2, 2)) @test !is_leaf(ng, (1, 1)) @test issetequal(leaf_vertices(ng), [(1, 2), (2, 2), (3, 2)]) # directed trees dng = dfs_tree(ng, (2, 2)) - @test is_leaf(dng, 1, 2) - @test !is_leaf(dng, 2, 2) + @test is_leaf(dng, (1, 2)) + @test !is_leaf(dng, (2, 2)) @test !is_leaf(dng, (1, 1)) @test issetequal(leaf_vertices(dng), [(1, 2), (3, 2)]) end diff --git a/test/test_abstractnamedgraph.jl b/test/test_abstractnamedgraph.jl index aba80da..4839bd0 100644 --- a/test/test_abstractnamedgraph.jl +++ b/test/test_abstractnamedgraph.jl @@ -1,6 +1,6 @@ using Test +using Dictionaries using Graphs -using MultiDimDictionaries using NamedGraphs @testset "AbstractNamedGraph equality" begin @@ -19,11 +19,11 @@ using NamedGraphs rem_edge!(ng2, "B" => "A") @test ng1 != ng2 - # NamedDimGraph + # NamedGraph dvs = [("X", 1), ("X", 2), ("Y", 1), ("Y", 2)] - ndg1 = NamedDimGraph(g, dvs) - # construct same NamedDimGraph from different underlying structure - ndg2 = NamedDimGraph(Graph(4), dvs[[1, 4, 3, 2]]) + ndg1 = NamedGraph(g, dvs) + # construct same NamedGraph from different underlying structure + ndg2 = NamedGraph(Graph(4), dvs[[1, 4, 3, 2]]) add_edge!(ndg2, ("X", 1) => ("X", 2)) add_edge!(ndg2, ("X", 1) => ("Y", 1)) add_edge!(ndg2, ("X", 2) => ("Y", 2)) @@ -33,10 +33,10 @@ using NamedGraphs rem_edge!(ndg2, ("Y", 1) => ("X", 1)) @test ndg1 != ndg2 - # NamedDimDiGraph - nddg1 = NamedDimDiGraph(DiGraph(collect(edges(g))), dvs) - # construct same NamedDimDiGraph from different underlying structure - nddg2 = NamedDimDiGraph(DiGraph(4), dvs[[1, 4, 3, 2]]) + # NamedDiGraph + nddg1 = NamedDiGraph(DiGraph(collect(edges(g))), dvs) + # construct same NamedDiGraph from different underlying structure + nddg2 = NamedDiGraph(DiGraph(4), dvs[[1, 4, 3, 2]]) add_edge!(nddg2, ("X", 1) => ("X", 2)) add_edge!(nddg2, ("X", 1) => ("Y", 1)) add_edge!(nddg2, ("X", 2) => ("Y", 2)) @@ -58,14 +58,14 @@ end # NamedGraph ng = NamedGraph(g, string_names) # rename to integers - vmap_int = Dictionary(Graphs.vertices(ng), integer_names) + vmap_int = Dictionary(vertices(ng), integer_names) ng_int = rename_vertices(ng, vmap_int) @test isa(ng_int, NamedGraph{Int}) @test has_vertex(ng_int, 3) @test has_edge(ng_int, 1 => 2) @test has_edge(ng_int, 2 => 4) # rename to tuples - vmap_tuple = Dictionary(Graphs.vertices(ng), tuple_names) + vmap_tuple = Dictionary(vertices(ng), tuple_names) ng_tuple = rename_vertices(ng, vmap_tuple) @test isa(ng_tuple, NamedGraph{Tuple{String,Int}}) @test has_vertex(ng_tuple, ("X", 1)) @@ -78,64 +78,64 @@ end @test has_edge(ng_function, (1, "X") => (2, "X")) @test has_edge(ng_function, (2, "X") => (2, "Y")) - # NamedDimGraph + # NamedGraph ndg = named_grid((2, 2)) # rename to integers - vmap_int = Dictionary(Graphs.vertices(ndg), integer_names) + vmap_int = Dictionary(vertices(ndg), integer_names) ndg_int = rename_vertices(ndg, vmap_int) - @test isa(ndg_int, NamedDimGraph{Tuple}) - @test has_vertex(ndg_int, (1,)) - @test has_edge(ndg_int, (1,) => (2,)) - @test has_edge(ndg_int, (2,) => (4,)) + @test isa(ndg_int, NamedGraph{Int}) + @test has_vertex(ndg_int, 1) + @test has_edge(ndg_int, 1 => 2) + @test has_edge(ndg_int, 2 => 4) # rename to strings - vmap_string = Dictionary(Graphs.vertices(ndg), string_names) + vmap_string = Dictionary(vertices(ndg), string_names) ndg_string = rename_vertices(ndg, vmap_string) - @test isa(ndg_string, NamedDimGraph{Tuple}) - @test has_vertex(ndg_string, ("A",)) - @test has_edge(ndg_string, ("A",) => ("B",)) - @test has_edge(ndg_string, ("B",) => ("D",)) + @test isa(ndg_string, NamedGraph{String}) + @test has_vertex(ndg_string, "A") + @test has_edge(ndg_string, "A" => "B") + @test has_edge(ndg_string, "B" => "D") # rename to strings - vmap_tuple = Dictionary(Graphs.vertices(ndg), tuple_names) + vmap_tuple = Dictionary(vertices(ndg), tuple_names) ndg_tuple = rename_vertices(ndg, vmap_tuple) - @test isa(ndg_tuple, NamedDimGraph{Tuple}) - @test has_vertex(ndg_tuple, "X", 1) + @test isa(ndg_tuple, NamedGraph{Tuple{String,Int}}) + @test has_vertex(ndg_tuple, ("X", 1)) @test has_edge(ndg_tuple, ("X", 1) => ("X", 2)) @test has_edge(ndg_tuple, ("X", 2) => ("Y", 2)) # rename with name map function ndg_function = rename_vertices(function_name, ndg_tuple) - @test isa(ndg_function, NamedDimGraph{Tuple}) - @test has_vertex(ndg_function, 1, "X") + @test isa(ndg_function, NamedGraph{Tuple{Int,String}}) + @test has_vertex(ndg_function, (1, "X")) @test has_edge(ndg_function, (1, "X") => (2, "X")) @test has_edge(ndg_function, (2, "X") => (2, "Y")) - # NamedDimDiGraph - nddg = NamedDimDiGraph(DiGraph(collect(edges(g))), Graphs.vertices(ndg)) + # NamedDiGraph + nddg = NamedDiGraph(DiGraph(collect(edges(g))), vertices(ndg)) # rename to integers - vmap_int = Dictionary(Graphs.vertices(nddg), integer_names) + vmap_int = Dictionary(vertices(nddg), integer_names) nddg_int = rename_vertices(nddg, vmap_int) - @test isa(nddg_int, NamedDimDiGraph{Tuple}) - @test has_vertex(nddg_int, (1,)) - @test has_edge(nddg_int, (1,) => (2,)) - @test has_edge(nddg_int, (2,) => (4,)) + @test isa(nddg_int, NamedDiGraph{Int}) + @test has_vertex(nddg_int, 1) + @test has_edge(nddg_int, 1 => 2) + @test has_edge(nddg_int, 2 => 4) # rename to strings - vmap_string = Dictionary(Graphs.vertices(nddg), string_names) + vmap_string = Dictionary(vertices(nddg), string_names) nddg_string = rename_vertices(nddg, vmap_string) - @test isa(nddg_string, NamedDimDiGraph{Tuple}) - @test has_vertex(nddg_string, ("A",)) - @test has_edge(nddg_string, ("A",) => ("B",)) - @test has_edge(nddg_string, ("B",) => ("D",)) - @test !has_edge(nddg_string, ("D",) => ("B",)) + @test isa(nddg_string, NamedDiGraph{String}) + @test has_vertex(nddg_string, "A") + @test has_edge(nddg_string, "A" => "B") + @test has_edge(nddg_string, "B" => "D") + @test !has_edge(nddg_string, "D" => "B") # rename to strings - vmap_tuple = Dictionary(Graphs.vertices(nddg), tuple_names) + vmap_tuple = Dictionary(vertices(nddg), tuple_names) nddg_tuple = rename_vertices(nddg, vmap_tuple) - @test isa(nddg_tuple, NamedDimDiGraph{Tuple}) - @test has_vertex(nddg_tuple, "X", 1) + @test isa(nddg_tuple, NamedDiGraph{Tuple{String,Int}}) + @test has_vertex(nddg_tuple, ("X", 1)) @test has_edge(nddg_tuple, ("X", 1) => ("X", 2)) @test !has_edge(nddg_tuple, ("Y", 2) => ("X", 2)) # rename with name map function nddg_function = rename_vertices(function_name, nddg_tuple) - @test isa(nddg_function, NamedDimDiGraph{Tuple}) - @test has_vertex(nddg_function, 1, "X") + @test isa(nddg_function, NamedDiGraph{Tuple{Int,String}}) + @test has_vertex(nddg_function, (1, "X")) @test has_edge(nddg_function, (1, "X") => (2, "X")) @test has_edge(nddg_function, (2, "X") => (2, "Y")) @test !has_edge(nddg_function, (2, "Y") => (2, "X")) diff --git a/test/test_multidimgraph.jl b/test/test_multidimgraph.jl index a9c0bd7..808271d 100644 --- a/test/test_multidimgraph.jl +++ b/test/test_multidimgraph.jl @@ -1,16 +1,15 @@ using Graphs -using MultiDimDictionaries using NamedGraphs using Random using Test -@testset "NamedDimGraph" begin +@testset "NamedGraph" begin parent_graph = grid((2, 2)) vertices = [("X", 1), ("X", 2), ("Y", 1), ("Y", 2)] - g = NamedDimGraph(parent_graph, vertices) + g = NamedGraph(parent_graph, vertices) - @test has_vertex(g, "X", 1) + @test has_vertex(g, ("X", 1)) @test has_edge(g, ("X", 1) => ("X", 2)) @test !has_edge(g, ("X", 2) => ("Y", 1)) @test has_edge(g, ("X", 2) => ("Y", 2)) @@ -21,56 +20,58 @@ using Test g_sub = g[[("X", 1)]] - @test has_vertex(g_sub, "X", 1) - @test !has_vertex(g_sub, "X", 2) - @test !has_vertex(g_sub, "Y", 1) - @test !has_vertex(g_sub, "Y", 2) + @test has_vertex(g_sub, ("X", 1)) + @test !has_vertex(g_sub, ("X", 2)) + @test !has_vertex(g_sub, ("Y", 1)) + @test !has_vertex(g_sub, ("Y", 2)) g_sub = g[[("X", 1), ("X", 2)]] - @test has_vertex(g_sub, "X", 1) - @test has_vertex(g_sub, "X", 2) - @test !has_vertex(g_sub, "Y", 1) - @test !has_vertex(g_sub, "Y", 2) + @test has_vertex(g_sub, ("X", 1)) + @test has_vertex(g_sub, ("X", 2)) + @test !has_vertex(g_sub, ("Y", 1)) + @test !has_vertex(g_sub, ("Y", 2)) - g_sub = g["X", :] + # g_sub = g["X", :] + g_sub = subgraph(v -> v[1] == "X", g) - @test has_vertex(g_sub, "X", 1) - @test has_vertex(g_sub, "X", 2) - @test !has_vertex(g_sub, "Y", 1) - @test !has_vertex(g_sub, "Y", 2) + @test has_vertex(g_sub, ("X", 1)) + @test has_vertex(g_sub, ("X", 2)) + @test !has_vertex(g_sub, ("Y", 1)) + @test !has_vertex(g_sub, ("Y", 2)) @test has_edge(g_sub, ("X", 1) => ("X", 2)) - g_sub = g[:, 2] + # g_sub = g[:, 2] + g_sub = subgraph(v -> v[2] == 2, g) - @test has_vertex(g_sub, "X", 2) - @test has_vertex(g_sub, "Y", 2) - @test !has_vertex(g_sub, "X", 1) - @test !has_vertex(g_sub, "Y", 1) + @test has_vertex(g_sub, ("X", 2)) + @test has_vertex(g_sub, ("Y", 2)) + @test !has_vertex(g_sub, ("X", 1)) + @test !has_vertex(g_sub, ("Y", 1)) @test has_edge(g_sub, ("X", 2) => ("Y", 2)) - g1 = NamedDimGraph(grid((2, 2)); dims=(2, 2)) + g1 = NamedGraph(grid((2, 2)); vertices=(2, 2)) @test nv(g1) == 4 @test ne(g1) == 4 - @test has_vertex(g1, 1, 1) - @test has_vertex(g1, 2, 1) - @test has_vertex(g1, 1, 2) - @test has_vertex(g1, 2, 2) + @test has_vertex(g1, (1, 1)) + @test has_vertex(g1, (2, 1)) + @test has_vertex(g1, (1, 2)) + @test has_vertex(g1, (2, 2)) @test has_edge(g1, (1, 1) => (1, 2)) @test has_edge(g1, (1, 1) => (2, 1)) @test has_edge(g1, (1, 2) => (2, 2)) @test has_edge(g1, (2, 1) => (2, 2)) @test !has_edge(g1, (1, 1) => (2, 2)) - g2 = NamedDimGraph(grid((2, 2)); dims=(2, 2)) + g2 = NamedGraph(grid((2, 2)); vertices=(2, 2)) - g = ⊔(g1, g2; new_dim_names=("X", "Y")) + g = ("X" => g1) ⊔ ("Y" => g2) @test nv(g) == 8 @test ne(g) == 8 - @test has_vertex(g, "X", 1, 1) - @test has_vertex(g, "Y", 1, 1) + @test has_vertex(g, ((1, 1), "X")) + @test has_vertex(g, ((1, 1), "Y")) # TODO: Need to drop the dimensions to make these equal #@test issetequal(Graphs.vertices(g1), Graphs.vertices(g["X", :])) @@ -79,21 +80,21 @@ using Test #@test issetequal(edges(g1), edges(g["Y", :])) end -@testset "NamedDimGraph add vertices" begin +@testset "NamedGraph add vertices" begin parent_graph = grid((2, 2)) vertices = [("X", 1), ("X", 2), ("Y", 1), ("Y", 2)] - g = NamedDimGraph() - add_vertex!(g, "X", 1) - add_vertex!(g, "X", 2) + g = NamedGraph() + add_vertex!(g, ("X", 1)) + add_vertex!(g, ("X", 2)) add_vertex!(g, ("Y", 1)) - add_vertex!(g, "Y", 2) + add_vertex!(g, ("Y", 2)) @test nv(g) == 4 @test ne(g) == 0 - @test has_vertex(g, "X", 1) - @test has_vertex(g, "X", 2) - @test has_vertex(g, "Y", 1) - @test has_vertex(g, "Y", 2) + @test has_vertex(g, ("X", 1)) + @test has_vertex(g, ("X", 2)) + @test has_vertex(g, ("Y", 1)) + @test has_vertex(g, ("Y", 2)) add_edge!(g, ("X", 1) => ("Y", 2))