From b8fd11462a18fc3df3415e56b0aea56755b93ddd Mon Sep 17 00:00:00 2001 From: Matt Fishman Date: Fri, 26 Apr 2024 11:58:34 -0400 Subject: [PATCH] Redesign `GenericNamedGraph` type (#73) --- Project.toml | 2 +- README.md | 24 +- examples/mincut.jl | 3 +- examples/multidimgraph_1d.jl | 4 +- examples/multidimgraph_2d.jl | 10 +- .../NamedGraphsGraphsFlowsExt.jl | 25 +- src/NamedGraphs.jl | 2 + src/abstractnamedgraph.jl | 253 +++++++----------- src/dfs.jl | 12 +- src/distances_and_capacities.jl | 20 +- src/lib/GraphsExtensions/src/abstractgraph.jl | 9 + src/lib/GraphsExtensions/src/abstracttrees.jl | 24 +- src/lib/GraphsExtensions/src/simplegraph.jl | 4 + src/lib/GraphsExtensions/test/runtests.jl | 40 ++- .../src/OrderedDictionaries.jl | 5 + .../src/ordereddictionary.jl | 57 ++++ .../OrderedDictionaries/src/orderedindices.jl | 94 +++++++ .../src/ordinalindexing.jl | 32 +++ src/lib/OrderedDictionaries/test/Project.toml | 4 + src/lib/OrderedDictionaries/test/runtests.jl | 114 ++++++++ .../OrdinalIndexing/src/OrdinalIndexing.jl | 4 + src/lib/OrdinalIndexing/src/one.jl | 12 + .../src/ordinalsuffixedinteger.jl | 108 ++++++++ src/lib/OrdinalIndexing/test/Project.toml | 3 + src/lib/OrdinalIndexing/test/runtests.jl | 53 ++++ .../src/abstractpartitionedgraph.jl | 51 ++-- .../PartitionedGraphs/src/partitionedgraph.jl | 4 +- src/namedgraph.jl | 176 ++++++------ src/shortestpaths.jl | 32 +-- src/steiner_tree.jl | 10 +- test/Project.toml | 1 + test/test_abstractnamedgraph.jl | 6 +- test/test_libs.jl | 19 ++ test/test_multidimgraph.jl | 6 +- test/test_namedgraph.jl | 17 +- 35 files changed, 885 insertions(+), 355 deletions(-) create mode 100644 src/lib/OrderedDictionaries/src/OrderedDictionaries.jl create mode 100644 src/lib/OrderedDictionaries/src/ordereddictionary.jl create mode 100644 src/lib/OrderedDictionaries/src/orderedindices.jl create mode 100644 src/lib/OrderedDictionaries/src/ordinalindexing.jl create mode 100644 src/lib/OrderedDictionaries/test/Project.toml create mode 100644 src/lib/OrderedDictionaries/test/runtests.jl create mode 100644 src/lib/OrdinalIndexing/src/OrdinalIndexing.jl create mode 100644 src/lib/OrdinalIndexing/src/one.jl create mode 100644 src/lib/OrdinalIndexing/src/ordinalsuffixedinteger.jl create mode 100644 src/lib/OrdinalIndexing/test/Project.toml create mode 100644 src/lib/OrdinalIndexing/test/runtests.jl create mode 100644 test/test_libs.jl diff --git a/Project.toml b/Project.toml index 2b8a015..c94b574 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "NamedGraphs" uuid = "678767b0-92e7-4007-89e4-4527a8725b19" authors = ["Matthew Fishman and contributors"] -version = "0.5.1" +version = "0.6.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" diff --git a/README.md b/README.md index 3a8c865..f08b0e1 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ julia> using NamedGraphs.GraphsExtensions: ⊔, disjoint_union, subgraph, rename julia> g = NamedGraph(grid((4,)), ["A", "B", "C", "D"]) NamedGraphs.NamedGraph{String} with 4 vertices: -4-element Dictionaries.Indices{String} +4-element NamedGraphs.OrderedDictionaries.OrderedIndices{String} "A" "B" "C" @@ -82,7 +82,7 @@ julia> neighbors(g, "B") julia> subgraph(g, ["A", "B"]) NamedGraphs.NamedGraph{String} with 2 vertices: -2-element Dictionaries.Indices{String} +2-element NamedGraphs.OrderedDictionaries.OrderedIndices{String} "A" "B" @@ -109,7 +109,7 @@ julia> dims = (2, 2) julia> g = NamedGraph(grid(dims), Tuple.(CartesianIndices(dims))) NamedGraphs.NamedGraph{Tuple{Int64, Int64}} with 4 vertices: -4-element Dictionaries.Indices{Tuple{Int64, Int64}} +4-element NamedGraphs.OrderedDictionaries.OrderedIndices{Tuple{Int64, Int64}} (1, 1) (2, 1) (1, 2) @@ -153,7 +153,7 @@ You can use vertex names to get [induced subgraphs](https://juliagraphs.org/Grap ```julia julia> subgraph(v -> v[1] == 1, g) NamedGraphs.NamedGraph{Tuple{Int64, Int64}} with 2 vertices: -2-element Dictionaries.Indices{Tuple{Int64, Int64}} +2-element NamedGraphs.OrderedDictionaries.OrderedIndices{Tuple{Int64, Int64}} (1, 1) (1, 2) @@ -163,7 +163,7 @@ and 1 edge(s): julia> subgraph(v -> v[2] == 2, g) NamedGraphs.NamedGraph{Tuple{Int64, Int64}} with 2 vertices: -2-element Dictionaries.Indices{Tuple{Int64, Int64}} +2-element NamedGraphs.OrderedDictionaries.OrderedIndices{Tuple{Int64, Int64}} (1, 2) (2, 2) @@ -173,7 +173,7 @@ and 1 edge(s): julia> subgraph(g, [(1, 1), (2, 2)]) NamedGraphs.NamedGraph{Tuple{Int64, Int64}} with 2 vertices: -2-element Dictionaries.Indices{Tuple{Int64, Int64}} +2-element NamedGraphs.OrderedDictionaries.OrderedIndices{Tuple{Int64, Int64}} (1, 1) (2, 2) @@ -187,7 +187,7 @@ You can also take [disjoint unions](https://en.wikipedia.org/wiki/Disjoint_union ```julia julia> g₁ = g NamedGraphs.NamedGraph{Tuple{Int64, Int64}} with 4 vertices: -4-element Dictionaries.Indices{Tuple{Int64, Int64}} +4-element NamedGraphs.OrderedDictionaries.OrderedIndices{Tuple{Int64, Int64}} (1, 1) (2, 1) (1, 2) @@ -202,7 +202,7 @@ and 4 edge(s): julia> g₂ = g NamedGraphs.NamedGraph{Tuple{Int64, Int64}} with 4 vertices: -4-element Dictionaries.Indices{Tuple{Int64, Int64}} +4-element NamedGraphs.OrderedDictionaries.OrderedIndices{Tuple{Int64, Int64}} (1, 1) (2, 1) (1, 2) @@ -217,7 +217,7 @@ and 4 edge(s): julia> disjoint_union(g₁, g₂) NamedGraphs.NamedGraph{Tuple{Tuple{Int64, Int64}, Int64}} with 8 vertices: -8-element Dictionaries.Indices{Tuple{Tuple{Int64, Int64}, Int64}} +8-element NamedGraphs.OrderedDictionaries.OrderedIndices{Tuple{Tuple{Int64, Int64}, Int64}} ((1, 1), 1) ((2, 1), 1) ((1, 2), 1) @@ -240,7 +240,7 @@ and 8 edge(s): julia> g₁ ⊔ g₂ # Same as above NamedGraphs.NamedGraph{Tuple{Tuple{Int64, Int64}, Int64}} with 8 vertices: -8-element Dictionaries.Indices{Tuple{Tuple{Int64, Int64}, Int64}} +8-element NamedGraphs.OrderedDictionaries.OrderedIndices{Tuple{Tuple{Int64, Int64}, Int64}} ((1, 1), 1) ((2, 1), 1) ((1, 2), 1) @@ -280,7 +280,7 @@ The original graphs can be obtained from subgraphs: ```julia julia> rename_vertices(first, subgraph(v -> v[2] == 1, g₁ ⊔ g₂)) NamedGraphs.NamedGraph{Tuple{Int64, Int64}} with 4 vertices: -4-element Dictionaries.Indices{Tuple{Int64, Int64}} +4-element NamedGraphs.OrderedDictionaries.OrderedIndices{Tuple{Int64, Int64}} (1, 1) (2, 1) (1, 2) @@ -295,7 +295,7 @@ and 4 edge(s): julia> rename_vertices(first, subgraph(v -> v[2] == 2, g₁ ⊔ g₂)) NamedGraphs.NamedGraph{Tuple{Int64, Int64}} with 4 vertices: -4-element Dictionaries.Indices{Tuple{Int64, Int64}} +4-element NamedGraphs.OrderedDictionaries.OrderedIndices{Tuple{Int64, Int64}} (1, 1) (2, 1) (1, 2) diff --git a/examples/mincut.jl b/examples/mincut.jl index 92dfa3e..86e504b 100644 --- a/examples/mincut.jl +++ b/examples/mincut.jl @@ -1,5 +1,4 @@ using Graphs: path_graph -using GraphsFlows: GraphsFlows using NamedGraphs: NamedGraph using NamedGraphs.GraphsExtensions: mincut_partitions @@ -8,6 +7,8 @@ g = NamedGraph(path_graph(4), ["A", "B", "C", "D"]) part1, part2 = mincut_partitions(g) @show part1, part2 +# Requires `GraphsFlows` to be loaded. +using GraphsFlows: GraphsFlows part1, part2 = mincut_partitions(g, "A", "D") @show part1, part2 diff --git a/examples/multidimgraph_1d.jl b/examples/multidimgraph_1d.jl index b015bdb..6aa6bd3 100644 --- a/examples/multidimgraph_1d.jl +++ b/examples/multidimgraph_1d.jl @@ -2,9 +2,9 @@ using Graphs: grid, has_edge, has_vertex, ne, nv using NamedGraphs: NamedGraph using NamedGraphs.GraphsExtensions: ⊔, subgraph -parent_graph = grid((4,)) +position_graph = grid((4,)) vs = ["A", "B", "C", "D"] -g = NamedGraph(parent_graph, vs) +g = NamedGraph(position_graph, vs) @show has_vertex(g, "A") @show !has_vertex(g, "E") diff --git a/examples/multidimgraph_2d.jl b/examples/multidimgraph_2d.jl index 3cd95d5..c330c02 100644 --- a/examples/multidimgraph_2d.jl +++ b/examples/multidimgraph_2d.jl @@ -2,10 +2,10 @@ using Graphs: grid, has_edge, has_vertex, nv using NamedGraphs: NamedGraph using NamedGraphs.GraphsExtensions: ⊔, subgraph -parent_graph = grid((2, 2)) +position_graph = grid((2, 2)) vs = [("X", 1), ("X", 2), ("Y", 1), ("Y", 2)] -g = NamedGraph(parent_graph, vs) +g = NamedGraph(position_graph, vs) @show has_vertex(g, ("X", 1)) @show has_edge(g, ("X", 1) => ("X", 2)) @@ -42,9 +42,9 @@ g_sub = subgraph(v -> v[2] == 2, g) @show !has_vertex(g_sub, ("Y", 1)) @show has_vertex(g_sub, ("Y", 2)) -parent_graph = grid((2, 2)) -g1 = NamedGraph(parent_graph, Tuple.(CartesianIndices((2, 2)))) -g2 = NamedGraph(parent_graph, Tuple.(CartesianIndices((2, 2)))) +position_graph = grid((2, 2)) +g1 = NamedGraph(position_graph, Tuple.(CartesianIndices((2, 2)))) +g2 = NamedGraph(position_graph, Tuple.(CartesianIndices((2, 2)))) g_disjoint_union = g1 ⊔ g2 diff --git a/ext/NamedGraphsGraphsFlowsExt/NamedGraphsGraphsFlowsExt.jl b/ext/NamedGraphsGraphsFlowsExt/NamedGraphsGraphsFlowsExt.jl index 241de46..3f2f7a8 100644 --- a/ext/NamedGraphsGraphsFlowsExt/NamedGraphsGraphsFlowsExt.jl +++ b/ext/NamedGraphsGraphsFlowsExt/NamedGraphsGraphsFlowsExt.jl @@ -6,14 +6,14 @@ using NamedGraphs: AbstractNamedGraph, DefaultNamedCapacity, _symmetrize, - dist_matrix_to_parent_dist_matrix, - parent_graph, - parent_vertices_to_vertices, - vertex_to_parent_vertex + dist_matrix_to_position_dist_matrix, + ordered_vertices, + position_graph, + vertex_positions using NamedGraphs.GraphsExtensions: GraphsExtensions, directed_graph using SimpleTraits: SimpleTraits, @traitfn -@traitfn function NamedGraphs.dist_matrix_to_parent_dist_matrix( +@traitfn function NamedGraphs.dist_matrix_to_position_dist_matrix( graph::AbstractNamedGraph::IsDirected, dist_matrix::DefaultNamedCapacity ) return GraphsFlows.DefaultCapacity(graph) @@ -26,15 +26,16 @@ end capacity_matrix=DefaultNamedCapacity(graph), algorithm::GraphsFlows.AbstractFlowAlgorithm=GraphsFlows.PushRelabelAlgorithm(), ) - parent_part1, parent_part2, flow = GraphsFlows.mincut( - directed_graph(parent_graph(graph)), - vertex_to_parent_vertex(graph, source), - vertex_to_parent_vertex(graph, target), - dist_matrix_to_parent_dist_matrix(graph, capacity_matrix), + position_part1, position_part2, flow = GraphsFlows.mincut( + directed_graph(position_graph(graph)), + vertex_positions(graph)[source], + vertex_positions(graph)[target], + dist_matrix_to_position_dist_matrix(graph, capacity_matrix), algorithm, ) - part1 = parent_vertices_to_vertices(graph, parent_part1) - part2 = parent_vertices_to_vertices(graph, parent_part2) + (part1, part2) = map((position_part1, position_part2)) do position_part + return map(v -> ordered_vertices(graph)[v], position_part) + end return (part1, part2, flow) end diff --git a/src/NamedGraphs.jl b/src/NamedGraphs.jl index 23f7f15..2c99f05 100644 --- a/src/NamedGraphs.jl +++ b/src/NamedGraphs.jl @@ -1,6 +1,8 @@ module NamedGraphs include("lib/SimilarType/src/SimilarType.jl") include("lib/Keys/src/Keys.jl") +include("lib/OrdinalIndexing/src/OrdinalIndexing.jl") +include("lib/OrderedDictionaries/src/OrderedDictionaries.jl") include("lib/GraphGenerators/src/GraphGenerators.jl") include("lib/GraphsExtensions/src/GraphsExtensions.jl") include("utils.jl") diff --git a/src/abstractnamedgraph.jl b/src/abstractnamedgraph.jl index 822f169..654f724 100644 --- a/src/abstractnamedgraph.jl +++ b/src/abstractnamedgraph.jl @@ -44,29 +44,27 @@ abstract type AbstractNamedGraph{V} <: AbstractGraph{V} end # Graphs.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() +position_graph(graph::AbstractNamedGraph) = not_implemented() Graphs.rem_vertex!(graph::AbstractNamedGraph, vertex) = not_implemented() Graphs.add_vertex!(graph::AbstractNamedGraph, vertex) = not_implemented() GraphsExtensions.rename_vertices(f::Function, g::AbstractNamedGraph) = not_implemented() +# TODO: Is this a good definition? Maybe make it generic to any graph? function GraphsExtensions.permute_vertices(graph::AbstractNamedGraph, permutation) - return subgraph(graph, parent_vertices_to_vertices(graph, permutation)) + return subgraph(graph, map(v -> ordered_vertices(graph)[v], permutation)) end -# Convert vertex to parent vertex -# Inverse map of `parent_vertex_to_vertex`. -vertex_to_parent_vertex(graph::AbstractNamedGraph, vertex) = not_implemented() +# Outputs an object that when indexed by a vertex +# returns the position of that vertex in the parent +# graph `position_graph(graph::AbstractNamedGraph)`. +# Inverse map of `ordered_vertices`. +vertex_positions(graph::AbstractNamedGraph) = not_implemented() -# Convert parent vertex to vertex. -# Use `vertices`, assumes `vertices` is indexed by a parent vertex (a Vector for linear indexed parent vertices, a dictionary in general). -parent_vertex_to_vertex(graph::AbstractNamedGraph, parent_vertex) = not_implemented() +# Outputs an object that when indexed by a vertex position +# returns the corresponding vertex. +ordered_vertices(graph::AbstractNamedGraph) = not_implemented() Graphs.edgetype(graph::AbstractNamedGraph) = not_implemented() @@ -74,14 +72,14 @@ Graphs.edgetype(graph::AbstractNamedGraph) = not_implemented() GraphsExtensions.directed_graph_type(G::Type{<:AbstractNamedGraph}) = not_implemented() GraphsExtensions.undirected_graph_type(G::Type{<:AbstractNamedGraph}) = not_implemented() -# In terms of `parent_graph_type` +# In terms of `position_graph_type` # is_directed(::Type{<:AbstractNamedGraph}) = not_implemented() GraphsExtensions.convert_vertextype(::Type, ::AbstractNamedGraph) = not_implemented() # TODO: implement as: # -# graph = set_parent_graph(graph, copy(parent_graph(graph))) +# graph = set_position_graph(graph, copy(position_graph(graph))) # graph = set_vertices(graph, copy(vertices(graph))) # # or: @@ -102,21 +100,14 @@ end # Derived interface # +position_graph_type(graph::AbstractNamedGraph) = typeof(position_graph(graph)) + function Graphs.has_vertex(graph::AbstractNamedGraph, vertex) + # TODO: `vertices` should have fast lookup! return vertex ∈ vertices(graph) end -parent_vertextype(graph::AbstractNamedGraph) = vertextype(parent_graph(graph)) - -Graphs.SimpleDiGraph(graph::AbstractNamedGraph) = SimpleDiGraph(parent_graph(graph)) - -# Convenient shorthands for using in higher order functions like `map`. -function vertex_to_parent_vertex(graph::AbstractNamedGraph) - return Base.Fix1(vertex_to_parent_vertex, graph) -end -function parent_vertex_to_vertex(graph::AbstractNamedGraph) - return Base.Fix1(parent_vertex_to_vertex, graph) -end +Graphs.SimpleDiGraph(graph::AbstractNamedGraph) = SimpleDiGraph(position_graph(graph)) Base.zero(G::Type{<:AbstractNamedGraph}) = G() @@ -133,62 +124,22 @@ end # Default, can overload Base.eltype(graph::AbstractNamedGraph) = eltype(vertices(graph)) -parent_eltype(graph::AbstractNamedGraph) = eltype(parent_graph(graph)) - -function vertices_to_parent_vertices(graph::AbstractNamedGraph, vertices) - return map(vertex_to_parent_vertex(graph), vertices) -end - -function vertices_to_parent_vertices(graph::AbstractNamedGraph) - return Base.Fix1(vertices_to_parent_vertices, graph) -end - -function parent_vertices_to_vertices(graph::AbstractNamedGraph, parent_vertices) - return map(parent_vertex_to_vertex(graph), parent_vertices) -end - -function parent_vertices_to_vertices(graph::AbstractNamedGraph) - return Base.Fix1(parent_vertices_to_vertices, graph) -end - -parent_vertices(graph::AbstractNamedGraph) = vertices(parent_graph(graph)) -parent_edges(graph::AbstractNamedGraph) = edges(parent_graph(graph)) -parent_edgetype(graph::AbstractNamedGraph) = edgetype(parent_graph(graph)) - -function edge_to_parent_edge(graph::AbstractNamedGraph, edge::AbstractEdge) - parent_src = vertex_to_parent_vertex(graph, src(edge)) - parent_dst = vertex_to_parent_vertex(graph, dst(edge)) - return parent_edgetype(graph)(parent_src, parent_dst) -end - -function edge_to_parent_edge(graph::AbstractNamedGraph, edge) - return edge_to_parent_edge(graph, edgetype(graph)(edge)) -end - -edge_to_parent_edge(graph::AbstractNamedGraph) = Base.Fix1(edge_to_parent_edge, graph) - -function edges_to_parent_edges(graph::AbstractNamedGraph, edges) - return map(edge_to_parent_edge(graph), edges) -end - -function parent_edge_to_edge(graph::AbstractNamedGraph, parent_edge::AbstractEdge) - source = parent_vertex_to_vertex(graph, src(parent_edge)) - destination = parent_vertex_to_vertex(graph, dst(parent_edge)) - return edgetype(graph)(source, destination) -end - -function parent_edge_to_edge(graph::AbstractNamedGraph, parent_edge) - return parent_edge_to_edge(graph, parent_edgetype(parent_edge)) +# TODO: Rename `position_edges(graph::AbstractNamedGraph)`. +function edge_to_position_edge(graph::AbstractNamedGraph, edge::AbstractEdge) + return edgetype(position_graph(graph))( + vertex_positions(graph)[src(edge)], vertex_positions(graph)[dst(edge)] + ) end -parent_edge_to_edge(graph::AbstractNamedGraph) = Base.Fix1(parent_edge_to_edge, graph) - -function parent_edges_to_edges(graph::AbstractNamedGraph, parent_edges) - return map(parent_edge_to_edge(graph), parent_edges) +# TODO: Rename `named_edges(graph::AbstractNamedGraph)`. +function position_edge_to_edge(graph::AbstractNamedGraph, position_edge::AbstractEdge) + return edgetype(graph)( + ordered_vertices(graph)[src(position_edge)], ordered_vertices(graph)[dst(position_edge)] + ) end function Graphs.edges(graph::AbstractNamedGraph) - return parent_edges_to_edges(graph, parent_edges(graph)) + return map(e -> position_edge_to_edge(graph, e), edges(position_graph(graph))) end # TODO: write in terms of a generic function. @@ -200,14 +151,14 @@ for f in [ ] @eval begin function $f(graph::AbstractNamedGraph, vertex) - parent_vertices = $f(parent_graph(graph), vertex_to_parent_vertex(graph, vertex)) - return parent_vertices_to_vertices(graph, parent_vertices) + position_vertices = $f(position_graph(graph), vertex_positions(graph)[vertex]) + return map(v -> ordered_vertices(graph)[v], position_vertices) end # Ambiguity errors with Graphs.jl function $f(graph::AbstractNamedGraph, vertex::Integer) - parent_vertices = $f(parent_graph(graph), vertex_to_parent_vertex(graph, vertex)) - return parent_vertices_to_vertices(graph, parent_vertices) + position_vertices = $f(position_graph(graph), vertex_positions(graph)[vertex]) + return map(v -> ordered_vertices(graph)[v], position_vertices) end end end @@ -260,13 +211,11 @@ end function namedgraph_neighborhood( graph::AbstractNamedGraph, vertex, d, distmx=weights(graph); dir=:out ) - parent_distmx = dist_matrix_to_parent_dist_matrix(graph, distmx) - parent_vertices = neighborhood( - parent_graph(graph), vertex_to_parent_vertex(graph, vertex), d, parent_distmx; dir + position_distmx = dist_matrix_to_position_dist_matrix(graph, distmx) + position_vertices = neighborhood( + position_graph(graph), vertex_positions(graph)[vertex], d, position_distmx; dir ) - return [ - parent_vertex_to_vertex(graph, parent_vertex) for parent_vertex in parent_vertices - ] + return [ordered_vertices(graph)[position_vertex] for position_vertex in position_vertices] end function Graphs.neighborhood( @@ -290,13 +239,13 @@ function Graphs.neighborhood( end function namedgraph_neighborhood_dists(graph::AbstractNamedGraph, vertex, d, distmx; dir) - parent_distmx = dist_matrix_to_parent_dist_matrix(graph, distmx) - parent_vertices_and_dists = neighborhood_dists( - parent_graph(graph), vertex_to_parent_vertex(graph, vertex), d, parent_distmx; dir + position_distmx = dist_matrix_to_position_dist_matrix(graph, distmx) + position_vertices_and_dists = neighborhood_dists( + position_graph(graph), vertex_positions(graph)[vertex], d, position_distmx; dir ) return [ - (parent_vertex_to_vertex(graph, parent_vertex), dist) for - (parent_vertex, dist) in parent_vertices_and_dists + (ordered_vertices(graph)[position_vertex], dist) for + (position_vertex, dist) in position_vertices_and_dists ] end @@ -321,9 +270,9 @@ function Graphs.neighborhood_dists( end function namedgraph_mincut(graph::AbstractNamedGraph, distmx) - parent_distmx = dist_matrix_to_parent_dist_matrix(graph, distmx) - parent_parity, bestcut = Graphs.mincut(parent_graph(graph), parent_distmx) - return Dictionary(vertices(graph), parent_parity), bestcut + position_distmx = dist_matrix_to_position_dist_matrix(graph, distmx) + position_parity, bestcut = Graphs.mincut(position_graph(graph), position_distmx) + return Dictionary(vertices(graph), position_parity), bestcut end function Graphs.mincut(graph::AbstractNamedGraph, distmx=weights(graph)) @@ -336,19 +285,17 @@ end # TODO: Make this more generic? function GraphsExtensions.partitioned_vertices( - g::AbstractNamedGraph; npartitions=nothing, nvertices_per_partition=nothing, kwargs... + graph::AbstractNamedGraph; npartitions=nothing, nvertices_per_partition=nothing, kwargs... ) vertex_partitions = partitioned_vertices( - parent_graph(g); npartitions, nvertices_per_partition, kwargs... + position_graph(graph); npartitions, nvertices_per_partition, kwargs... ) - #[inv(vertex_to_parent_vertex(g))[v] for v in partitions] # TODO: output the reverse of this dictionary (a Vector of Vector # of the vertices in each partition). # return Dictionary(vertices(g), partitions) - return [ - parent_vertices_to_vertices(g, vertex_partition) for - vertex_partition in vertex_partitions - ] + return map(vertex_partitions) do vertex_partition + return map(v -> ordered_vertices(graph)[v], vertex_partition) + end end function namedgraph_a_star( @@ -359,16 +306,16 @@ function namedgraph_a_star( heuristic::Function=(v -> zero(eltype(distmx))), edgetype_to_return=edgetype(graph), ) - parent_distmx = dist_matrix_to_parent_dist_matrix(graph, distmx) - parent_shortest_path = a_star( - parent_graph(graph), - vertex_to_parent_vertex(graph, source), - vertex_to_parent_vertex(graph, destination), - dist_matrix_to_parent_dist_matrix(graph, distmx), + position_distmx = dist_matrix_to_position_dist_matrix(graph, distmx) + position_shortest_path = a_star( + position_graph(graph), + vertex_positions(graph)[source], + vertex_positions(graph)[destination], + dist_matrix_to_position_dist_matrix(graph, distmx), heuristic, SimpleEdge, ) - return parent_edges_to_edges(graph, parent_shortest_path) + return map(e -> position_edge_to_edge(graph, e), position_shortest_path) end function Graphs.a_star(graph::AbstractNamedGraph, source, destination, args...) @@ -382,51 +329,55 @@ function Graphs.a_star( return namedgraph_a_star(graph, source, destination, args...) end +# Fix ambiguity error with `AbstractGraph` version +function Graphs.a_star( + graph::AbstractNamedGraph, source::Integer, destination::Integer, args... +) + return namedgraph_a_star(graph, source, destination, args...) +end + function Graphs.spfa_shortest_paths( graph::AbstractNamedGraph, vertex, distmx=weights(graph) ) - parent_distmx = dist_matrix_to_parent_dist_matrix(graph, distmx) - parent_shortest_paths = spfa_shortest_paths( - parent_graph(graph), vertex_to_parent_vertex(graph, vertex), parent_distmx + position_distmx = dist_matrix_to_position_dist_matrix(graph, distmx) + position_shortest_paths = spfa_shortest_paths( + position_graph(graph), vertex_positions(graph)[vertex], position_distmx ) - return Dictionary(vertices(graph), parent_shortest_paths) + return Dictionary(vertices(graph), position_shortest_paths) end function Graphs.boruvka_mst( g::AbstractNamedGraph, distmx::AbstractMatrix{<:Real}=weights(g); minimize=true ) - parent_mst, weights = boruvka_mst(parent_graph(g), distmx; minimize) - return parent_edges_to_edges(g, parent_mst), weights + position_mst, weights = boruvka_mst(position_graph(g), distmx; minimize) + return map(e -> position_edge_to_edge(g, e), position_mst), weights end function Graphs.kruskal_mst( g::AbstractNamedGraph, distmx::AbstractMatrix{<:Real}=weights(g); minimize=true ) - parent_mst = kruskal_mst(parent_graph(g), distmx; minimize) - return parent_edges_to_edges(g, parent_mst) + position_mst = kruskal_mst(position_graph(g), distmx; minimize) + return map(e -> position_edge_to_edge(g, e), position_mst) end function Graphs.prim_mst(g::AbstractNamedGraph, distmx::AbstractMatrix{<:Real}=weights(g)) - parent_mst = prim_mst(parent_graph(g), distmx) - return parent_edges_to_edges(g, parent_mst) + position_mst = prim_mst(position_graph(g), distmx) + return map(e -> position_edge_to_edge(g, e), position_mst) end -function Graphs.add_edge!(graph::AbstractNamedGraph, edge::AbstractEdge) - add_edge!(parent_graph(graph), edge_to_parent_edge(graph, edge)) +function Graphs.add_edge!(graph::AbstractNamedGraph, edge) + add_edge!(position_graph(graph), edge_to_position_edge(graph, edgetype(graph)(edge))) return graph end - -# handles single-argument edge constructors such as pairs and tuples -Graphs.add_edge!(g::AbstractNamedGraph, edge) = add_edge!(g, edgetype(g)(edge)) Graphs.add_edge!(g::AbstractNamedGraph, src, dst) = add_edge!(g, edgetype(g)(src, dst)) function Graphs.rem_edge!(graph::AbstractNamedGraph, edge) - rem_edge!(parent_graph(graph), edge_to_parent_edge(graph, edge)) + rem_edge!(position_graph(graph), edge_to_position_edge(graph, edgetype(graph)(edge))) return graph end function Graphs.has_edge(graph::AbstractNamedGraph, edge::AbstractNamedEdge) - return has_edge(parent_graph(graph), edge_to_parent_edge(graph, edge)) + return has_edge(position_graph(graph), edge_to_position_edge(graph, edge)) end # handles two-argument edge constructors like src,dst @@ -437,10 +388,10 @@ function Graphs.has_path( graph::AbstractNamedGraph, source, destination; exclude_vertices=vertextype(graph)[] ) return has_path( - parent_graph(graph), - vertex_to_parent_vertex(graph, source), - vertex_to_parent_vertex(graph, destination); - exclude_vertices=vertices_to_parent_vertices(graph, exclude_vertices), + position_graph(graph), + vertex_positions(graph)[source], + vertex_positions(graph)[destination]; + exclude_vertices=map(v -> vertex_positions(graph)[v], exclude_vertices), ) end @@ -468,44 +419,46 @@ function Base.union( return union(union(graph1, graph2), graph3, graph_rest...) end -Graphs.is_directed(G::Type{<:AbstractNamedGraph}) = is_directed(parent_graph_type(G)) +function Graphs.is_directed(graph_type::Type{<:AbstractNamedGraph}) + return is_directed(position_graph_type(graph_type)) +end -Graphs.is_directed(graph::AbstractNamedGraph) = is_directed(parent_graph(graph)) +Graphs.is_directed(graph::AbstractNamedGraph) = is_directed(position_graph(graph)) -Graphs.is_connected(graph::AbstractNamedGraph) = is_connected(parent_graph(graph)) +Graphs.is_connected(graph::AbstractNamedGraph) = is_connected(position_graph(graph)) -Graphs.is_cyclic(graph::AbstractNamedGraph) = is_cyclic(parent_graph(graph)) +Graphs.is_cyclic(graph::AbstractNamedGraph) = is_cyclic(position_graph(graph)) @traitfn function Base.reverse(graph::AbstractNamedGraph::IsDirected) - reversed_parent_graph = reverse(parent_graph(graph)) - return h + return not_implemented() end @traitfn function Base.reverse!(g::AbstractNamedGraph::IsDirected) - g.fadjlist, g.badjlist = g.badjlist, g.fadjlist - return g + return not_implemented() end # TODO: Move to `namedgraph.jl`, or make the output generic? function Graphs.blockdiag(graph1::AbstractNamedGraph, graph2::AbstractNamedGraph) - new_parent_graph = blockdiag(parent_graph(graph1), parent_graph(graph2)) + new_position_graph = blockdiag(position_graph(graph1), position_graph(graph2)) new_vertices = vcat(vertices(graph1), vertices(graph2)) @assert allunique(new_vertices) - return GenericNamedGraph(new_parent_graph, new_vertices) + return GenericNamedGraph(new_position_graph, new_vertices) end # TODO: What `args` are needed? -Graphs.nv(graph::AbstractNamedGraph, args...) = nv(parent_graph(graph), args...) +Graphs.nv(graph::AbstractNamedGraph, args...) = nv(position_graph(graph), args...) # TODO: What `args` are needed? -Graphs.ne(graph::AbstractNamedGraph, args...) = ne(parent_graph(graph), args...) +Graphs.ne(graph::AbstractNamedGraph, args...) = ne(position_graph(graph), args...) # TODO: What `args` are needed? function Graphs.adjacency_matrix(graph::AbstractNamedGraph, args...) - return adjacency_matrix(parent_graph(graph), args...) + return adjacency_matrix(position_graph(graph), args...) end function Graphs.connected_components(graph::AbstractNamedGraph) - parent_connected_components = connected_components(parent_graph(graph)) - return map(parent_vertices_to_vertices(graph), parent_connected_components) + position_connected_components = connected_components(position_graph(graph)) + return map(position_connected_components) do position_connected_component + return map(v -> ordered_vertices(graph)[v], position_connected_component) + end end function Graphs.merge_vertices( @@ -563,8 +516,8 @@ end # Returns a Dictionary mapping a vertex to it's parent # vertex in the traversal/spanning tree. function namedgraph_bfs_parents(graph::AbstractNamedGraph, vertex; kwargs...) - parent_bfs_parents = bfs_parents( - parent_graph(graph), vertex_to_parent_vertex(graph, vertex); kwargs... + position_bfs_parents = bfs_parents( + position_graph(graph), vertex_positions(graph)[vertex]; kwargs... ) # Works around issue in this `Dictionary` constructor: # https://github.com/andyferris/Dictionaries.jl/blob/v0.4.1/src/Dictionary.jl#L139-L145 @@ -572,8 +525,10 @@ function namedgraph_bfs_parents(graph::AbstractNamedGraph, vertex; kwargs...) # TODO: Raise an issue with `Dictionaries.jl`. ## vertices_graph = Indices(collect(vertices(graph))) # This makes the vertices ordered according to the parent vertices. - vertices_graph = parent_vertices_to_vertices(graph, parent_vertices(graph)) - return Dictionary(vertices_graph, parent_vertices_to_vertices(graph, parent_bfs_parents)) + vertices_graph = map(v -> ordered_vertices(graph)[v], vertices(position_graph(graph))) + return Dictionary( + vertices_graph, map(v -> ordered_vertices(graph)[v], position_bfs_parents) + ) end # Disambiguation from Graphs.jl function Graphs.bfs_parents(graph::AbstractNamedGraph, vertex::Integer; kwargs...) diff --git a/src/dfs.jl b/src/dfs.jl index dedd093..cd9d920 100644 --- a/src/dfs.jl +++ b/src/dfs.jl @@ -2,7 +2,7 @@ using Graphs: Graphs, dfs_parents, dfs_tree, topological_sort_by_dfs using SimpleTraits: SimpleTraits, Not, @traitfn @traitfn function Graphs.topological_sort_by_dfs(g::AbstractNamedGraph::IsDirected) - return parent_vertices_to_vertices(g, topological_sort_by_dfs(parent_graph(g))) + return map(v -> ordered_vertices(g)[v], topological_sort_by_dfs(position_graph(g))) end function namedgraph_dfs_tree(graph::AbstractNamedGraph, vertex; kwargs...) @@ -18,8 +18,8 @@ end # Returns a Dictionary mapping a vertex to it's parent # vertex in the traversal/spanning tree. function namedgraph_dfs_parents(graph::AbstractNamedGraph, vertex; kwargs...) - parent_dfs_parents = dfs_parents( - parent_graph(graph), vertex_to_parent_vertex(graph, vertex); kwargs... + position_dfs_parents = dfs_parents( + position_graph(graph), vertex_positions(graph)[vertex]; kwargs... ) # Works around issue in this `Dictionary` constructor: # https://github.com/andyferris/Dictionaries.jl/blob/v0.4.1/src/Dictionary.jl#L139-L145 @@ -27,8 +27,10 @@ function namedgraph_dfs_parents(graph::AbstractNamedGraph, vertex; kwargs...) # TODO: Raise an issue with `Dictionaries.jl`. ## vertices_graph = Indices(collect(vertices(graph))) # This makes the vertices ordered according to the parent vertices. - vertices_graph = parent_vertices_to_vertices(graph, parent_vertices(graph)) - return Dictionary(vertices_graph, parent_vertices_to_vertices(graph, parent_dfs_parents)) + vertices_graph = map(v -> ordered_vertices(graph)[v], vertices(position_graph(graph))) + return Dictionary( + vertices_graph, map(v -> ordered_vertices(graph)[v], position_dfs_parents) + ) end # Disambiguation from Graphs.dfs_parents function Graphs.dfs_parents(graph::AbstractNamedGraph, vertex::Integer; kwargs...) diff --git a/src/distances_and_capacities.jl b/src/distances_and_capacities.jl index a101f7d..4c5f514 100644 --- a/src/distances_and_capacities.jl +++ b/src/distances_and_capacities.jl @@ -31,32 +31,32 @@ end getindex_dist_matrix(dist_matrix, I...) = dist_matrix[I...] getindex_dist_matrix(dist_matrix::AbstractDictionary, I...) = dist_matrix[I] -function namedgraph_dist_matrix_to_parent_dist_matrix( +function namedgraph_dist_matrix_to_position_dist_matrix( graph::AbstractNamedGraph, dist_matrix ) - parent_dist_matrix = spzeros(valtype(dist_matrix), nv(graph), nv(graph)) + position_dist_matrix = spzeros(valtype(dist_matrix), nv(graph), nv(graph)) for e in edges(graph) - parent_e = edge_to_parent_edge(graph, e) - parent_dist_matrix[src(parent_e), dst(parent_e)] = getindex_dist_matrix( + position_e = edge_to_position_edge(graph, e) + position_dist_matrix[src(position_e), dst(position_e)] = getindex_dist_matrix( dist_matrix, src(e), dst(e) ) end - return parent_dist_matrix + return position_dist_matrix end -@traitfn function dist_matrix_to_parent_dist_matrix( +@traitfn function dist_matrix_to_position_dist_matrix( graph::AbstractNamedGraph::IsDirected, dist_matrix ) - return namedgraph_dist_matrix_to_parent_dist_matrix(graph, dist_matrix) + return namedgraph_dist_matrix_to_position_dist_matrix(graph, dist_matrix) end -@traitfn function dist_matrix_to_parent_dist_matrix( +@traitfn function dist_matrix_to_position_dist_matrix( graph::AbstractNamedGraph::(!IsDirected), dist_matrix ) - return _symmetrize(namedgraph_dist_matrix_to_parent_dist_matrix(graph, dist_matrix)) + return _symmetrize(namedgraph_dist_matrix_to_position_dist_matrix(graph, dist_matrix)) end -function dist_matrix_to_parent_dist_matrix( +function dist_matrix_to_position_dist_matrix( graph::AbstractNamedGraph, distmx::Graphs.DefaultDistance ) return distmx diff --git a/src/lib/GraphsExtensions/src/abstractgraph.jl b/src/lib/GraphsExtensions/src/abstractgraph.jl index 2c1c891..9090b4e 100644 --- a/src/lib/GraphsExtensions/src/abstractgraph.jl +++ b/src/lib/GraphsExtensions/src/abstractgraph.jl @@ -13,6 +13,7 @@ using Graphs: eccentricity, edgetype, has_edge, + has_vertex, indegree, induced_subgraph, inneighbors, @@ -85,6 +86,14 @@ end vertextype(::Type{<:AbstractGraph{V}}) where {V} = V vertextype(graph::AbstractGraph) = vertextype(typeof(graph)) +function has_vertices(graph::AbstractGraph, vertices) + return all(v -> has_vertex(graph, v), vertices) +end + +function has_edges(graph::AbstractGraph, edges) + return all(e -> has_edge(graph, e), edges) +end + # Uniform interface for `outneighbors`, `inneighbors`, and `all_neighbors` function _neighbors(graph::AbstractGraph, vertex; dir=:out) if dir == :out diff --git a/src/lib/GraphsExtensions/src/abstracttrees.jl b/src/lib/GraphsExtensions/src/abstracttrees.jl index 47342e6..159a371 100644 --- a/src/lib/GraphsExtensions/src/abstracttrees.jl +++ b/src/lib/GraphsExtensions/src/abstracttrees.jl @@ -1,20 +1,22 @@ # AbstractTreeGraph # Tree view of a graph. abstract type AbstractTreeGraph{V} <: AbstractGraph{V} end -parent_graph_type(type::Type{<:AbstractTreeGraph}) = not_implemented() -parent_graph(graph::AbstractTreeGraph) = not_implemented() +position_graph_type(type::Type{<:AbstractTreeGraph}) = not_implemented() +position_graph(graph::AbstractTreeGraph) = not_implemented() -Graphs.is_directed(type::Type{<:AbstractTreeGraph}) = is_directed(parent_graph_type(type)) -Graphs.edgetype(graph::AbstractTreeGraph) = edgetype(parent_graph(graph)) +function Graphs.is_directed(type::Type{<:AbstractTreeGraph}) + return is_directed(position_graph_type(type)) +end +Graphs.edgetype(graph::AbstractTreeGraph) = edgetype(position_graph(graph)) function Graphs.outneighbors(graph::AbstractTreeGraph, vertex) - return outneighbors(parent_graph(graph), vertex) + return outneighbors(position_graph(graph), vertex) end function Graphs.inneighbors(graph::AbstractTreeGraph, vertex) - return inneighbors(parent_graph(graph), vertex) + return inneighbors(position_graph(graph), vertex) end -Graphs.nv(graph::AbstractTreeGraph) = nv(parent_graph(graph)) -Graphs.ne(graph::AbstractTreeGraph) = ne(parent_graph(graph)) -Graphs.vertices(graph::AbstractTreeGraph) = vertices(parent_graph(graph)) +Graphs.nv(graph::AbstractTreeGraph) = nv(position_graph(graph)) +Graphs.ne(graph::AbstractTreeGraph) = ne(position_graph(graph)) +Graphs.vertices(graph::AbstractTreeGraph) = vertices(position_graph(graph)) # AbstractTrees using AbstractTrees: @@ -60,5 +62,5 @@ end @assert is_tree(g) return _TreeGraph(g) end -parent_graph(graph::TreeGraph) = getfield(graph, :graph) -parent_graph_type(type::Type{<:TreeGraph}) = fieldtype(type, :graph) +position_graph(graph::TreeGraph) = getfield(graph, :graph) +position_graph_type(type::Type{<:TreeGraph}) = fieldtype(type, :graph) diff --git a/src/lib/GraphsExtensions/src/simplegraph.jl b/src/lib/GraphsExtensions/src/simplegraph.jl index af3fbf2..df53d92 100644 --- a/src/lib/GraphsExtensions/src/simplegraph.jl +++ b/src/lib/GraphsExtensions/src/simplegraph.jl @@ -1,5 +1,9 @@ using Graphs.SimpleGraphs: AbstractSimpleGraph +function permute_vertices(graph::AbstractSimpleGraph, permutation) + return graph[permutation] +end + # https://github.com/JuliaGraphs/Graphs.jl/issues/365 function graph_from_vertices(graph_type::Type{<:AbstractSimpleGraph}, vertices) @assert vertices == Base.OneTo(length(vertices)) diff --git a/src/lib/GraphsExtensions/test/runtests.jl b/src/lib/GraphsExtensions/test/runtests.jl index 36db807..b645b64 100644 --- a/src/lib/GraphsExtensions/test/runtests.jl +++ b/src/lib/GraphsExtensions/test/runtests.jl @@ -16,6 +16,7 @@ using Graphs: add_vertex!, edges, edgetype, + has_edge, inneighbors, is_cyclic, is_directed, @@ -51,7 +52,9 @@ using NamedGraphs.GraphsExtensions: directed_graph_type, disjoint_union, distance_to_leaves, + has_edges, has_leaf_neighbor, + has_vertices, incident_edges, indegrees, is_arborescence, @@ -98,6 +101,19 @@ using Test: @test, @test_broken, @test_throws, @testset # - random_bfs_tree @testset "NamedGraphs.GraphsExtensions" begin + # has_vertices + g = path_graph(4) + @test has_vertices(g, 1:3) + @test has_vertices(g, [2, 4]) + @test !has_vertices(g, [2, 5]) + + # has_edges + g = path_graph(4) + @test has_edges(g, [1 => 2, 2 => 3, 3 => 4]) + @test has_edges(g, [2 => 3]) + @test !has_edges(g, [1 => 3]) + @test !has_edges(g, [4 => 5]) + # convert_vertextype for g in (path_graph(4), path_digraph(4)) g_uint16 = convert_vertextype(UInt16, g) @@ -162,7 +178,29 @@ using Test: @test, @test_broken, @test_throws, @testset # permute_vertices g = path_graph(4) - @test_broken permute_vertices(g, [2, 1, 4, 3]) + g_perm = permute_vertices(g, [2, 1, 4, 3]) + @test nv(g_perm) == 4 + @test ne(g_perm) == 3 + @test vertices(g_perm) == 1:4 + @test has_edge(g_perm, 1 => 2) + @test has_edge(g_perm, 2 => 1) + @test has_edge(g_perm, 1 => 4) + @test has_edge(g_perm, 4 => 1) + @test has_edge(g_perm, 3 => 4) + @test has_edge(g_perm, 4 => 3) + @test !has_edge(g_perm, 2 => 3) + @test !has_edge(g_perm, 3 => 2) + g = path_digraph(4) + g_perm = permute_vertices(g, [2, 1, 4, 3]) + @test nv(g_perm) == 4 + @test ne(g_perm) == 3 + @test vertices(g_perm) == 1:4 + @test has_edge(g_perm, 2 => 1) + @test !has_edge(g_perm, 1 => 2) + @test has_edge(g_perm, 1 => 4) + @test !has_edge(g_perm, 4 => 1) + @test has_edge(g_perm, 4 => 3) + @test !has_edge(g_perm, 3 => 4) # all_edges g = path_graph(4) diff --git a/src/lib/OrderedDictionaries/src/OrderedDictionaries.jl b/src/lib/OrderedDictionaries/src/OrderedDictionaries.jl new file mode 100644 index 0000000..a748d78 --- /dev/null +++ b/src/lib/OrderedDictionaries/src/OrderedDictionaries.jl @@ -0,0 +1,5 @@ +module OrderedDictionaries +include("orderedindices.jl") +include("ordereddictionary.jl") +include("ordinalindexing.jl") +end diff --git a/src/lib/OrderedDictionaries/src/ordereddictionary.jl b/src/lib/OrderedDictionaries/src/ordereddictionary.jl new file mode 100644 index 0000000..96ed50a --- /dev/null +++ b/src/lib/OrderedDictionaries/src/ordereddictionary.jl @@ -0,0 +1,57 @@ +using Dictionaries: AbstractDictionary + +struct OrderedDictionary{I,T} <: AbstractDictionary{I,T} + indices::OrderedIndices{I} + values::Vector{T} + global function _OrderedDictionary(inds::OrderedIndices, values::Vector) + @assert length(values) == length(inds) + return new{eltype(inds),eltype(values)}(inds, values) + end +end + +function OrderedDictionary(indices::OrderedIndices, values::Vector) + return _OrderedDictionary(indices, values) +end + +function OrderedDictionary(indices, values) + return OrderedDictionary(OrderedIndices(indices), Vector(values)) +end + +Base.values(dict::OrderedDictionary) = getfield(dict, :values) + +# https://github.com/andyferris/Dictionaries.jl/tree/master?tab=readme-ov-file#abstractdictionary +Base.keys(dict::OrderedDictionary) = getfield(dict, :indices) + +ordered_indices(dict::OrderedDictionary) = ordered_indices(keys(dict)) + +# https://github.com/andyferris/Dictionaries.jl/tree/master?tab=readme-ov-file#implementing-the-token-interface-for-abstractdictionary +Dictionaries.istokenizable(dict::OrderedDictionary) = Dictionaries.istokenizable(keys(dict)) +Base.@propagate_inbounds function Dictionaries.gettokenvalue( + dict::OrderedDictionary, token::Int +) + return values(dict)[token] +end +function Dictionaries.istokenassigned(dict::OrderedDictionary, token::Int) + return isassigned(values(dict), token) +end + +Dictionaries.issettable(dict::OrderedDictionary) = true +Base.@propagate_inbounds function Dictionaries.settokenvalue!( + dict::OrderedDictionary{<:Any,T}, token::Int, value::T +) where {T} + values(dict)[token] = value + return dict +end + +Dictionaries.isinsertable(dict::OrderedDictionary) = true +Dictionaries.gettoken!(dict::OrderedDictionary, index) = error() +Dictionaries.deletetoken!(dict::OrderedDictionary, token) = error() + +function Base.similar(indices::OrderedIndices, type::Type) + return OrderedDictionary(indices, Vector{type}(undef, length(indices))) +end + +# Circumvents https://github.com/andyferris/Dictionaries.jl/pull/140 +function Base.map(f, dict::OrderedDictionary) + return OrderedDictionary(keys(dict), map(f, values(dict))) +end diff --git a/src/lib/OrderedDictionaries/src/orderedindices.jl b/src/lib/OrderedDictionaries/src/orderedindices.jl new file mode 100644 index 0000000..066d054 --- /dev/null +++ b/src/lib/OrderedDictionaries/src/orderedindices.jl @@ -0,0 +1,94 @@ +using Dictionaries: Dictionaries, AbstractIndices, Dictionary + +# Represents a [set](https://en.wikipedia.org/wiki/Set_(mathematics)) of indices +# `I` whose elements/members are ordered in a sequence such that each element can be +# associated with a position which is a positive integer +# (1-based [natural numbers](https://en.wikipedia.org/wiki/Natural_number)) +# which can be accessed through ordinal indexing (`I[4th]`). +# Related to an (indexed family)[https://en.wikipedia.org/wiki/Indexed_family], +# [index set](https://en.wikipedia.org/wiki/Index_set), or +# [sequence](https://en.wikipedia.org/wiki/Sequence). +# In other words, it is a [bijection](https://en.wikipedia.org/wiki/Bijection) +# from a finite subset of 1-based natural numbers to a set of corresponding +# elements/members. +struct OrderedIndices{I} <: AbstractIndices{I} + ordered_indices::Vector{I} + index_positions::Dictionary{I,Int} + function OrderedIndices{I}(indices) where {I} + ordered_indices = collect(indices) + index_positions = Dictionary{I,Int}(copy(ordered_indices), undef) + for i in eachindex(ordered_indices) + index_positions[ordered_indices[i]] = i + end + return new{I}(ordered_indices, index_positions) + end +end +OrderedIndices(indices) = OrderedIndices{eltype(indices)}(indices) + +OrderedIndices{I}(indices::OrderedIndices{I}) where {I} = copy(indices) + +ordered_indices(indices::OrderedIndices) = getfield(indices, :ordered_indices) +# TODO: Better name for this? +index_positions(indices::OrderedIndices) = getfield(indices, :index_positions) +# TODO: Better name for this? +parent_indices(indices::OrderedIndices) = keys(index_positions(indices)) + +# https://github.com/andyferris/Dictionaries.jl/tree/master?tab=readme-ov-file#abstractindices +function Dictionaries.iterate(indices::OrderedIndices, state...) + return Dictionaries.iterate(ordered_indices(indices), state...) +end +function Base.in(index::I, indices::OrderedIndices{I}) where {I} + return in(index, parent_indices(indices)) +end +Base.length(indices::OrderedIndices) = length(ordered_indices(indices)) + +# https://github.com/andyferris/Dictionaries.jl/tree/master?tab=readme-ov-file#implementing-the-token-interface-for-abstractindices +Dictionaries.istokenizable(indices::OrderedIndices) = true +Dictionaries.tokentype(indices::OrderedIndices) = Int +function Dictionaries.iteratetoken(indices::OrderedIndices, state...) + return iterate(Base.OneTo(length(indices)), state...) +end +function Dictionaries.iteratetoken_reverse(indices::OrderedIndices, state...) + return iterate(reverse(Base.OneTo(length(indices))), state...) +end +function Dictionaries.gettoken(indices::OrderedIndices, key) + if !haskey(index_positions(indices), key) + return (false, 0) + end + return (true, index_positions(indices)[key]) +end +function Dictionaries.gettokenvalue(indices::OrderedIndices, token) + return ordered_indices(indices)[token] +end + +Dictionaries.isinsertable(indices::OrderedIndices) = true +function Dictionaries.gettoken!(indices::OrderedIndices{I}, key::I) where {I} + (hadtoken, token) = Dictionaries.gettoken(indices, key) + if hadtoken + return (true, token) + end + push!(ordered_indices(indices), key) + token = length(ordered_indices(indices)) + insert!(index_positions(indices), key, token) + return (false, token) +end +function Dictionaries.deletetoken!(indices::OrderedIndices, token) + len = length(indices) + position = token + index = ordered_indices(indices)[position] + # Move the last vertex to the position of the deleted one. + if position < len + ordered_indices(indices)[position] = last(ordered_indices(indices)) + end + last_index = pop!(ordered_indices(indices)) + delete!(index_positions(indices), index) + if position < len + index_positions(indices)[last_index] = position + end + return indices +end + +# Circumvents https://github.com/andyferris/Dictionaries.jl/pull/140 +function Base.map(f, indices::OrderedIndices) + return OrderedDictionary(indices, map(f, ordered_indices(indices))) +end diff --git a/src/lib/OrderedDictionaries/src/ordinalindexing.jl b/src/lib/OrderedDictionaries/src/ordinalindexing.jl new file mode 100644 index 0000000..c77c6f2 --- /dev/null +++ b/src/lib/OrderedDictionaries/src/ordinalindexing.jl @@ -0,0 +1,32 @@ +using ..OrdinalIndexing: OrdinalSuffixedInteger, cardinal, th + +Base.@propagate_inbounds function Base.getindex( + indices::OrderedIndices, ordinal_index::OrdinalSuffixedInteger +) + return ordered_indices(indices)[cardinal(ordinal_index)] +end +Base.@propagate_inbounds function Base.setindex!( + indices::OrderedIndices, value, ordinal_index::OrdinalSuffixedInteger +) + old_value = indices[ordinal_index] + ordered_indices(indices)[cardinal(index)] = value + delete!(index_ordinals(indices), old_value) + set!(index_ordinals(indices), value, cardinal(index)) + return indices +end +each_ordinal_index(indices::OrderedIndices) = (Base.OneTo(length(indices))) * th + +Base.@propagate_inbounds function Base.getindex( + dict::OrderedDictionary, ordinal_index::OrdinalSuffixedInteger +) + return dict[ordered_indices(dict)[cardinal(ordinal_index)]] +end +Base.@propagate_inbounds function Base.setindex!( + dict::OrderedDictionary, value, ordinal_index::OrdinalSuffixedInteger +) + index = keys(dict)[ordinal_index] + old_value = dict[index] + dict[index] = value + return dict +end +each_ordinal_index(dict::OrderedDictionary) = (Base.OneTo(length(dict))) * th diff --git a/src/lib/OrderedDictionaries/test/Project.toml b/src/lib/OrderedDictionaries/test/Project.toml new file mode 100644 index 0000000..9b4efaf --- /dev/null +++ b/src/lib/OrderedDictionaries/test/Project.toml @@ -0,0 +1,4 @@ +[deps] +Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4" +NamedGraphs = "678767b0-92e7-4007-89e4-4527a8725b19" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/src/lib/OrderedDictionaries/test/runtests.jl b/src/lib/OrderedDictionaries/test/runtests.jl new file mode 100644 index 0000000..de5a078 --- /dev/null +++ b/src/lib/OrderedDictionaries/test/runtests.jl @@ -0,0 +1,114 @@ +@eval module $(gensym()) +using Dictionaries: Dictionary +using NamedGraphs.OrderedDictionaries: + OrderedDictionaries, OrderedDictionary, OrderedIndices, each_ordinal_index +using NamedGraphs.OrdinalIndexing: th +using Test: @test, @testset +@testset "OrderedDictionaries" begin + @testset "OrderedIndices" begin + i = OrderedIndices(["x1", "x2", "x3", "x4"]) + @test i isa OrderedIndices{String} + @test length(i) == 4 + @test collect(i) == ["x1", "x2", "x3", "x4"] + @test eachindex(i) isa OrderedIndices{String} + @test keys(i) isa OrderedIndices{String} + @test issetequal(i, ["x1", "x2", "x3", "x4"]) + @test keys(i) == OrderedIndices(["x1", "x2", "x3", "x4"]) + @test issetequal(keys(i), ["x1", "x2", "x3", "x4"]) + @test i["x1"] == "x1" + @test i["x2"] == "x2" + @test i["x3"] == "x3" + @test i["x4"] == "x4" + @test i[1th] == "x1" + @test i[2th] == "x2" + @test i[3th] == "x3" + @test i[4th] == "x4" + + i = OrderedIndices(["x1", "x2", "x3", "x4"]) + delete!(i, "x2") + @test length(i) == 3 + @test collect(i) == ["x1", "x4", "x3"] + @test i["x1"] == "x1" + @test i["x3"] == "x3" + @test i["x4"] == "x4" + @test "x1" ∈ i + @test !("x2" ∈ i) + @test "x3" ∈ i + @test "x4" ∈ i + @test i[1th] == "x1" + @test i[2th] == "x4" + @test i[3th] == "x3" + @test OrderedDictionaries.ordered_indices(i) == ["x1", "x4", "x3"] + @test OrderedDictionaries.index_positions(i) == + Dictionary(["x1", "x3", "x4"], [1, 3, 2]) + + # Test for deleting the last index, this is a special + # case in the code. + i = OrderedIndices(["x1", "x2", "x3", "x4"]) + delete!(i, "x4") + @test length(i) == 3 + @test collect(i) == ["x1", "x2", "x3"] + @test i["x1"] == "x1" + @test i["x2"] == "x2" + @test i["x3"] == "x3" + @test "x1" ∈ i + @test "x2" ∈ i + @test "x3" ∈ i + @test !("x4" ∈ i) + @test i[1th] == "x1" + @test i[2th] == "x2" + @test i[3th] == "x3" + @test OrderedDictionaries.ordered_indices(i) == ["x1", "x2", "x3"] + @test OrderedDictionaries.index_positions(i) == + Dictionary(["x1", "x2", "x3"], [1, 2, 3]) + + i = OrderedIndices(["x1", "x2", "x3", "x4"]) + d = Dictionary(["x1", "x2", "x3", "x4"], [:x1, :x2, :x3, :x4]) + mapped_i = map(i -> d[i], i) + @test mapped_i == Dictionary(["x1", "x2", "x3", "x4"], [:x1, :x2, :x3, :x4]) + @test mapped_i == OrderedDictionary(["x1", "x2", "x3", "x4"], [:x1, :x2, :x3, :x4]) + @test mapped_i isa OrderedDictionary{String,Symbol} + @test mapped_i["x1"] === :x1 + @test mapped_i["x2"] === :x2 + @test mapped_i["x3"] === :x3 + @test mapped_i["x4"] === :x4 + + i = OrderedIndices(["x1", "x2", "x3"]) + insert!(i, "x4") + @test length(i) == 4 + @test collect(i) == ["x1", "x2", "x3", "x4"] + @test i["x1"] == "x1" + @test i["x2"] == "x2" + @test i["x3"] == "x3" + @test i["x4"] == "x4" + @test i[1th] == "x1" + @test i[2th] == "x2" + @test i[3th] == "x3" + @test i[4th] == "x4" + + i = OrderedIndices(["x1", "x2", "x3"]) + ords = each_ordinal_index(i) + @test ords == (1:3)th + @test i[ords[1]] == "x1" + @test i[ords[2]] == "x2" + @test i[ords[3]] == "x3" + end + @testset "OrderedDictionaries" begin + d = OrderedDictionary(["x1", "x2", "x3"], [1, 2, 3]) + @test d["x1"] == 1 + @test d["x2"] == 2 + @test d["x3"] == 3 + + d = OrderedDictionary(["x1", "x2", "x3"], [1, 2, 3]) + d["x2"] = 4 + @test d["x1"] == 1 + @test d["x2"] == 4 + @test d["x3"] == 3 + + d = OrderedDictionary(["x1", "x2", "x3"], [1, 2, 3]) + @test d[1th] == 1 + @test d[2th] == 2 + @test d[3th] == 3 + end +end +end diff --git a/src/lib/OrdinalIndexing/src/OrdinalIndexing.jl b/src/lib/OrdinalIndexing/src/OrdinalIndexing.jl new file mode 100644 index 0000000..460c408 --- /dev/null +++ b/src/lib/OrdinalIndexing/src/OrdinalIndexing.jl @@ -0,0 +1,4 @@ +module OrdinalIndexing +include("one.jl") +include("ordinalsuffixedinteger.jl") +end diff --git a/src/lib/OrdinalIndexing/src/one.jl b/src/lib/OrdinalIndexing/src/one.jl new file mode 100644 index 0000000..5c531e2 --- /dev/null +++ b/src/lib/OrdinalIndexing/src/one.jl @@ -0,0 +1,12 @@ +struct One <: Integer end +const 𝟏 = One() +Base.convert(type::Type{<:Number}, ::One) = one(type) +Base.promote_rule(type1::Type{One}, type2::Type{<:Number}) = type2 +Base.:(*)(x::One, y::One) = 𝟏 + +# Needed for Julia 1.7. +Base.convert(::Type{One}, ::One) = One() + +function Base.show(io::IO, ordinal::One) + return print(io, "𝟏") +end diff --git a/src/lib/OrdinalIndexing/src/ordinalsuffixedinteger.jl b/src/lib/OrdinalIndexing/src/ordinalsuffixedinteger.jl new file mode 100644 index 0000000..e8befe7 --- /dev/null +++ b/src/lib/OrdinalIndexing/src/ordinalsuffixedinteger.jl @@ -0,0 +1,108 @@ +struct OrdinalSuffixedInteger{T<:Integer} <: Integer + cardinal::T + function OrdinalSuffixedInteger{T}(cardinal::Integer) where {T<:Integer} + cardinal ≥ 0 || throw(ArgumentError("ordinal must be > 0")) + return new{T}(cardinal) + end +end +function OrdinalSuffixedInteger(cardinal::Integer) + return OrdinalSuffixedInteger{typeof(cardinal)}(cardinal) +end +function OrdinalSuffixedInteger{T}(ordinal::OrdinalSuffixedInteger) where {T<:Integer} + return OrdinalSuffixedInteger{T}(cardinal(ordinal)) +end + +cardinal(ordinal::OrdinalSuffixedInteger) = getfield(ordinal, :cardinal) +function cardinal_type(ordinal_type::Type{<:OrdinalSuffixedInteger}) + return fieldtype(ordinal_type, :cardinal) +end + +const th = OrdinalSuffixedInteger(𝟏) +const st = th +const nd = th +const rd = th + +function Base.widen(ordinal_type::Type{<:OrdinalSuffixedInteger}) + return OrdinalSuffixedInteger{widen(cardinal_type(ordinal_type))} +end + +Base.Int(ordinal::OrdinalSuffixedInteger) = Int(cardinal(ordinal)) + +function Base.:(*)(a::OrdinalSuffixedInteger, b::Integer) + return OrdinalSuffixedInteger(cardinal(a) * b) +end +function Base.:(*)(a::Integer, b::OrdinalSuffixedInteger) + return OrdinalSuffixedInteger(a * cardinal(b)) +end +function Base.:(:)( + start::OrdinalSuffixedInteger{T}, stop::OrdinalSuffixedInteger{T} +) where {T<:Integer} + return UnitRange{OrdinalSuffixedInteger{T}}(start, stop) +end + +function Base.:(*)(a::OrdinalSuffixedInteger, b::OrdinalSuffixedInteger) + return (cardinal(a) * cardinal(b)) * th +end +function Base.:(+)(a::OrdinalSuffixedInteger, b::OrdinalSuffixedInteger) + return (cardinal(a) + cardinal(b)) * th +end +function Base.:(+)(a::OrdinalSuffixedInteger, b::Integer) + return a + b * th +end +function Base.:(+)(a::Integer, b::OrdinalSuffixedInteger) + return a * th + b +end +function Base.:(-)(a::OrdinalSuffixedInteger, b::OrdinalSuffixedInteger) + return (cardinal(a) - cardinal(b)) * th +end +function Base.:(-)(a::OrdinalSuffixedInteger, b::Integer) + return a - b * th +end +function Base.:(-)(a::Integer, b::OrdinalSuffixedInteger) + return a * th - b +end + +function Base.:(:)(a::Integer, b::OrdinalSuffixedInteger) + return (a * th):b +end + +function Base.:(<)(a::OrdinalSuffixedInteger, b::OrdinalSuffixedInteger) + return (cardinal(a) < cardinal(b)) +end +Base.:(<)(a::OrdinalSuffixedInteger, b::Integer) = (a < b * th) +Base.:(<)(a::Integer, b::OrdinalSuffixedInteger) = (a * th < b) +function Base.:(<=)(a::OrdinalSuffixedInteger, b::OrdinalSuffixedInteger) + return (cardinal(a) <= cardinal(b)) +end +Base.:(<=)(a::OrdinalSuffixedInteger, b::Integer) = (a <= b * th) +Base.:(<=)(a::Integer, b::OrdinalSuffixedInteger) = (a * th <= b) + +function Broadcast.broadcasted( + ::Broadcast.DefaultArrayStyle{1}, + ::typeof(*), + r::UnitRange, + t::OrdinalSuffixedInteger{One}, +) + return (first(r) * t):(last(r) * t) +end +function Broadcast.broadcasted( + ::Broadcast.DefaultArrayStyle{1}, + ::typeof(*), + r::Base.OneTo, + t::OrdinalSuffixedInteger{One}, +) + return Base.OneTo(last(r) * t) +end + +function Base.show(io::IO, ordinal::OrdinalSuffixedInteger) + n = cardinal(ordinal) + m = n % 10 + if m == 1 + return print(io, n, n == 11 ? "th" : "st") + elseif m == 2 + return print(io, n, n == 12 ? "th" : "nd") + elseif m == 3 + return print(io, n, n == 13 ? "th" : "rd") + end + return print(io, n, "th") +end diff --git a/src/lib/OrdinalIndexing/test/Project.toml b/src/lib/OrdinalIndexing/test/Project.toml new file mode 100644 index 0000000..295df54 --- /dev/null +++ b/src/lib/OrdinalIndexing/test/Project.toml @@ -0,0 +1,3 @@ +[deps] +Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" +NamedGraphs = "678767b0-92e7-4007-89e4-4527a8725b19" diff --git a/src/lib/OrdinalIndexing/test/runtests.jl b/src/lib/OrdinalIndexing/test/runtests.jl new file mode 100644 index 0000000..0dbe71b --- /dev/null +++ b/src/lib/OrdinalIndexing/test/runtests.jl @@ -0,0 +1,53 @@ +@eval module $(gensym()) +using NamedGraphs.OrdinalIndexing: One, 𝟏 +using NamedGraphs.OrdinalIndexing: OrdinalSuffixedInteger, th +using Test: @test, @test_broken, @test_throws, @testset +@testset "OrdinalIndexing" begin + @testset "One" begin + @test One() === 𝟏 + @test One() == 1 + @test 𝟏 * 2 === 2 + @test 2 * 𝟏 === 2 + @test 2 + 𝟏 === 3 + @test 𝟏 + 2 === 3 + @test 2 - 𝟏 === 1 + @test 𝟏 - 2 === -1 + end + @testset "OrdinalSuffixedInteger" begin + @test th === OrdinalSuffixedInteger(𝟏) + @test 1th === OrdinalSuffixedInteger(1) + @test 2th === OrdinalSuffixedInteger(2) + @test_throws ArgumentError -1th + r = (2th):(4th) + @test r isa UnitRange{OrdinalSuffixedInteger{Int}} + @test r === (2:4)th + r = Base.OneTo(4th) + @test r isa Base.OneTo{OrdinalSuffixedInteger{Int}} + @test r === Base.OneTo(4)th + for r in ((1:4)th, Base.OneTo(4)th) + @test first(r) === 1th + @test step(r) === 1th + @test last(r) === 4th + @test length(r) === 4th + @test collect(r) == [1th, 2th, 3th, 4th] + end + @testset "$suffix1, $suffix2" for (suffix1, suffix2) in ((th, th), (th, 𝟏), (𝟏, th)) + @test 2suffix1 + 3suffix2 === 5th + @test 4suffix1 - 2suffix2 === 2th + @test 2suffix1 * 3suffix2 === 6th + @test 2suffix1 < 3suffix2 + @test !(2suffix1 < 2suffix2) + @test !(3suffix1 < 2suffix2) + @test !(2suffix1 > 3suffix2) + @test !(2suffix1 > 2suffix2) + @test 3suffix1 > 2suffix2 + @test 2suffix1 <= 3suffix2 + @test 2suffix1 <= 2suffix2 + @test !(3suffix1 <= 2suffix2) + @test !(2suffix1 >= 3suffix2) + @test 2suffix1 >= 2suffix2 + @test 3suffix1 >= 2suffix2 + end + end +end +end diff --git a/src/lib/PartitionedGraphs/src/abstractpartitionedgraph.jl b/src/lib/PartitionedGraphs/src/abstractpartitionedgraph.jl index 83d3b15..246dfe3 100644 --- a/src/lib/PartitionedGraphs/src/abstractpartitionedgraph.jl +++ b/src/lib/PartitionedGraphs/src/abstractpartitionedgraph.jl @@ -1,19 +1,26 @@ using Graphs: - Graphs, AbstractEdge, add_vertex!, dst, edgetype, has_vertex, rem_vertex!, src, vertices -using ..NamedGraphs: - NamedGraphs, - AbstractNamedGraph, - parent_graph, - parent_graph_type, - parent_vertex_to_vertex, - vertex_to_parent_vertex -using ..NamedGraphs.GraphsExtensions: GraphsExtensions, add_vertices!, rem_vertices! + Graphs, + AbstractEdge, + add_vertex!, + dst, + edgetype, + has_vertex, + is_directed, + rem_vertex!, + src, + vertices +using ..NamedGraphs: NamedGraphs, AbstractNamedGraph +using ..NamedGraphs.GraphsExtensions: + GraphsExtensions, add_vertices!, not_implemented, rem_vertices! abstract type AbstractPartitionedGraph{V,PV} <: AbstractNamedGraph{V} end #Needed for interface partitioned_graph(pg::AbstractPartitionedGraph) = not_implemented() unpartitioned_graph(pg::AbstractPartitionedGraph) = not_implemented() +function unpartitioned_graph_type(pg::Type{<:AbstractPartitionedGraph}) + return not_implemented() +end partitionvertex(pg::AbstractPartitionedGraph, vertex) = not_implemented() partitionvertices(pg::AbstractPartitionedGraph, verts) = not_implemented() partitionvertices(pg::AbstractPartitionedGraph) = not_implemented() @@ -23,6 +30,10 @@ insert_to_vertex_map!(pg::AbstractPartitionedGraph, vertex) = not_implemented() partitionedge(pg::AbstractPartitionedGraph, edge) = not_implemented() partitionedges(pg::AbstractPartitionedGraph, edges) = not_implemented() partitionedges(pg::AbstractPartitionedGraph) = not_implemented() +function unpartitioned_graph_type(pg::AbstractPartitionedGraph) + return typeof(unpartitioned_graph(pg)) +end + function Graphs.edges(pg::AbstractPartitionedGraph, partitionedge::AbstractPartitionEdge) return not_implemented() end @@ -34,7 +45,6 @@ function Graphs.vertices( ) where {V<:AbstractPartitionVertex} return not_implemented() end -NamedGraphs.parent_graph_type(PG::Type{<:AbstractPartitionedGraph}) = not_implemented() function GraphsExtensions.directed_graph_type(PG::Type{<:AbstractPartitionedGraph}) return not_implemented() end @@ -42,21 +52,23 @@ function GraphsExtensions.undirected_graph_type(PG::Type{<:AbstractPartitionedGr return not_implemented() end +# AbstractGraph interface. +function Graphs.is_directed(graph_type::Type{<:AbstractPartitionedGraph}) + return is_directed(unpartitioned_graph_type(graph_type)) +end + #Functions for the abstract type Graphs.vertices(pg::AbstractPartitionedGraph) = vertices(unpartitioned_graph(pg)) -function NamedGraphs.parent_graph(pg::AbstractPartitionedGraph) - return parent_graph(unpartitioned_graph(pg)) +function NamedGraphs.position_graph(pg::AbstractPartitionedGraph) + return NamedGraphs.position_graph(unpartitioned_graph(pg)) end -function NamedGraphs.vertex_to_parent_vertex(pg::AbstractPartitionedGraph, vertex) - return vertex_to_parent_vertex(unpartitioned_graph(pg), vertex) +function NamedGraphs.vertex_positions(pg::AbstractPartitionedGraph) + return NamedGraphs.vertex_positions(unpartitioned_graph(pg)) end -function NamedGraphs.parent_vertex_to_vertex(pg::AbstractPartitionedGraph, parent_vertex) - return parent_vertex_to_vertex(unpartitioned_graph(pg), parent_vertex) +function NamedGraphs.ordered_vertices(pg::AbstractPartitionedGraph) + return NamedGraphs.ordered_vertices(unpartitioned_graph(pg)) end Graphs.edgetype(pg::AbstractPartitionedGraph) = edgetype(unpartitioned_graph(pg)) -function NamedGraphs.parent_graph_type(pg::AbstractPartitionedGraph) - return parent_graph_type(unpartitioned_graph(pg)) -end function Graphs.nv(pg::AbstractPartitionedGraph, pv::AbstractPartitionVertex) return length(vertices(pg, pv)) end @@ -81,7 +93,6 @@ function Graphs.add_edge!(pg::AbstractPartitionedGraph, edge::AbstractEdge) if src(pg_edge) != dst(pg_edge) add_edge!(partitioned_graph(pg), pg_edge) end - return pg end diff --git a/src/lib/PartitionedGraphs/src/partitionedgraph.jl b/src/lib/PartitionedGraphs/src/partitionedgraph.jl index 0997b92..b0ab386 100644 --- a/src/lib/PartitionedGraphs/src/partitionedgraph.jl +++ b/src/lib/PartitionedGraphs/src/partitionedgraph.jl @@ -48,11 +48,13 @@ end #Needed for interface partitioned_graph(pg::PartitionedGraph) = getfield(pg, :partitioned_graph) unpartitioned_graph(pg::PartitionedGraph) = getfield(pg, :graph) +function unpartitioned_graph_type(graph_type::Type{<:PartitionedGraph}) + return fieldtype(graph_type, :graph) +end function GraphsExtensions.partitioned_vertices(pg::PartitionedGraph) return getfield(pg, :partitioned_vertices) end which_partition(pg::PartitionedGraph) = getfield(pg, :which_partition) -NamedGraphs.parent_graph_type(PG::Type{<:PartitionedGraph}) = fieldtype(PG, :graph) function Graphs.vertices(pg::PartitionedGraph, partitionvert::PartitionVertex) return partitioned_vertices(pg)[parent(partitionvert)] end diff --git a/src/namedgraph.jl b/src/namedgraph.jl index 6c6e075..c4a0a1d 100644 --- a/src/namedgraph.jl +++ b/src/namedgraph.jl @@ -13,35 +13,39 @@ using Graphs: using Graphs.SimpleGraphs: AbstractSimpleGraph, SimpleDiGraph, SimpleGraph using .GraphsExtensions: GraphsExtensions, vertextype, directed_graph_type, undirected_graph_type +using .OrderedDictionaries: OrderedDictionaries, OrderedIndices +using .OrdinalIndexing: th struct GenericNamedGraph{V,G<:AbstractSimpleGraph{Int}} <: AbstractNamedGraph{V} - parent_graph::G - parent_vertex_to_vertex::Vector{V} - vertex_to_parent_vertex::Dictionary{V,Int} + position_graph::G + vertices::OrderedIndices{V} + global function _GenericNamedGraph(position_graph, vertices) + @assert length(vertices) == nv(position_graph) + return new{eltype(vertices),typeof(position_graph)}(position_graph, vertices) + end end # AbstractNamedGraph required interface. -parent_graph_type(G::Type{<:GenericNamedGraph}) = fieldtype(G, :parent_graph) -parent_graph(graph::GenericNamedGraph) = getfield(graph, :parent_graph) -function vertex_to_parent_vertex(graph::GenericNamedGraph, vertex) - return graph.vertex_to_parent_vertex[vertex] +function position_graph_type(graph_type::Type{<:GenericNamedGraph}) + return fieldtype(graph_type, :position_graph) +end +position_graph(graph::GenericNamedGraph) = getfield(graph, :position_graph) +function vertex_positions(graph::GenericNamedGraph) + return OrderedDictionaries.index_positions(vertices(graph)) end -function parent_vertex_to_vertex(graph::GenericNamedGraph, parent_vertex) - return graph.parent_vertex_to_vertex[parent_vertex] +function ordered_vertices(graph::GenericNamedGraph) + return OrderedDictionaries.ordered_indices(vertices(graph)) end -# TODO: Order them according to the internal ordering? -Graphs.vertices(graph::GenericNamedGraph) = keys(graph.vertex_to_parent_vertex) +# TODO: Decide what this should output. +Graphs.vertices(graph::GenericNamedGraph) = getfield(graph, :vertices) function Graphs.add_vertex!(graph::GenericNamedGraph, vertex) if vertex ∈ vertices(graph) return false end - add_vertex!(graph.parent_graph) - # Update the forward map - push!(graph.parent_vertex_to_vertex, vertex) - # Update the reverse map - insert!(graph.vertex_to_parent_vertex, vertex, nv(graph.parent_graph)) + add_vertex!(position_graph(graph)) + insert!(vertices(graph), vertex) return true end @@ -49,21 +53,16 @@ function Graphs.rem_vertex!(graph::GenericNamedGraph, vertex) if vertex ∉ vertices(graph) return false end - parent_vertex = graph.vertex_to_parent_vertex[vertex] - rem_vertex!(graph.parent_graph, parent_vertex) - # Insert the last vertex into the position of the vertex - # that is being deleted, then remove the last vertex. - last_vertex = last(graph.parent_vertex_to_vertex) - graph.parent_vertex_to_vertex[parent_vertex] = last_vertex - last_vertex = pop!(graph.parent_vertex_to_vertex) - graph.vertex_to_parent_vertex[last_vertex] = parent_vertex - delete!(graph.vertex_to_parent_vertex, vertex) - return true + position_vertex = vertex_positions(graph)[vertex] + rem_vertex!(position_graph(graph), position_vertex) + delete!(vertices(graph), vertex) + return graph end -function GraphsExtensions.rename_vertices(f::Function, g::GenericNamedGraph) - # TODO: Could be implemented as `set_vertices(g, f.(g.parent_vertex_to_vertex))`. - return GenericNamedGraph(g.parent_graph, f.(g.parent_vertex_to_vertex)) +function GraphsExtensions.rename_vertices(f::Function, graph::GenericNamedGraph) + # TODO: Fix broadcasting of `OrderedIndices`. + # return GenericNamedGraph(position_graph(graph), f.(vertices(graph))) + return GenericNamedGraph(position_graph(graph), map(f, vertices(graph))) end function GraphsExtensions.rename_vertices(f::Function, g::AbstractSimpleGraph) @@ -74,100 +73,81 @@ end function GraphsExtensions.convert_vertextype(vertextype::Type, graph::GenericNamedGraph) return GenericNamedGraph( - parent_graph(graph), convert(Vector{vertextype}, graph.parent_vertex_to_vertex) + position_graph(graph), convert(Vector{vertextype}, graph.ordered_vertices) ) end -# -# Convert inputs to vertex list -# - -function to_vertices(vertices) - return vec(collect(vertices)) -end -to_vertices(vertices::Vector) = vertices -to_vertices(vertices::Array) = vec(vertices) -# Treat tuple inputs as cartesian grid sizes -function to_vertices(vertices::Tuple{Vararg{Integer}}) - return vec(Tuple.(CartesianIndices(vertices))) -end -to_vertices(vertices::Integer) = to_vertices(Base.OneTo(vertices)) -function to_vertices(vertextype::Type, vertices) - return convert(Vector{vertextype}, to_vertices(vertices)) -end - # # Constructors from `AbstractSimpleGraph` # +to_vertices(vertices) = vertices +to_vertices(vertices::AbstractArray) = vec(vertices) +to_vertices(vertices::Integer) = Base.OneTo(vertices) + # Inner constructor +# TODO: Is this needed? function GenericNamedGraph{V,G}( - parent_graph::AbstractSimpleGraph, vertices::Vector{V} -) where {V,G} - @assert length(vertices) == nv(parent_graph) - # 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)) - ) + position_graph::G, vertices::OrderedIndices{V} +) where {V,G<:AbstractSimpleGraph{Int}} + return _GenericNamedGraph(position_graph, vertices) end -function GenericNamedGraph{V,G}(parent_graph::AbstractSimpleGraph, vertices) where {V,G} - return GenericNamedGraph{V,G}(parent_graph, to_vertices(V, vertices)) +function GenericNamedGraph{V,G}( + position_graph::AbstractSimpleGraph, vertices +) where {V,G<:AbstractSimpleGraph{Int}} + return GenericNamedGraph{V,G}( + convert(G, position_graph), OrderedIndices{V}(to_vertices(vertices)) + ) end -function GenericNamedGraph{V}(parent_graph::AbstractSimpleGraph, vertices) where {V} - return GenericNamedGraph{V,typeof(parent_graph)}(parent_graph, vertices) +function GenericNamedGraph{V}(position_graph::AbstractSimpleGraph, vertices) where {V} + return GenericNamedGraph{V,typeof(position_graph)}(position_graph, vertices) end function GenericNamedGraph{<:Any,G}( - parent_graph::AbstractSimpleGraph, vertices::Vector -) where {G} - return GenericNamedGraph{eltype(vertices),G}(parent_graph, vertices) -end - -function GenericNamedGraph{<:Any,G}(parent_graph::AbstractSimpleGraph, vertices) where {G} - return GenericNamedGraph{<:Any,G}(parent_graph, to_vertices(vertices)) + position_graph::AbstractSimpleGraph, vertices +) where {G<:AbstractSimpleGraph{Int}} + return GenericNamedGraph{eltype(vertices),G}(position_graph, vertices) end -function GenericNamedGraph{<:Any,G}(parent_graph::AbstractSimpleGraph) where {G} - return GenericNamedGraph{<:Any,G}(parent_graph, vertices(parent_graph)) -end - -function GenericNamedGraph(parent_graph::AbstractSimpleGraph, vertices::Vector) - return GenericNamedGraph{eltype(vertices)}(parent_graph, vertices) +function GenericNamedGraph{<:Any,G}( + position_graph::AbstractSimpleGraph +) where {G<:AbstractSimpleGraph{Int}} + return GenericNamedGraph{<:Any,G}(position_graph, vertices(position_graph)) end -function GenericNamedGraph(parent_graph::AbstractSimpleGraph, vertices) - return GenericNamedGraph(parent_graph, to_vertices(vertices)) +function GenericNamedGraph(position_graph::AbstractSimpleGraph, vertices) + return GenericNamedGraph{eltype(vertices)}(position_graph, vertices) end -function GenericNamedGraph(parent_graph::AbstractSimpleGraph) - return GenericNamedGraph(parent_graph, vertices(parent_graph)) +function GenericNamedGraph(position_graph::AbstractSimpleGraph) + return GenericNamedGraph(position_graph, vertices(position_graph)) end # # Tautological constructors # -GenericNamedGraph{V,G}(graph::GenericNamedGraph{V,G}) where {V,G} = copy(graph) +function GenericNamedGraph{V,G}( + graph::GenericNamedGraph{V,G} +) where {V,G<:AbstractSimpleGraph{Int}} + return copy(graph) +end # # Constructors from vertex names # -function GenericNamedGraph{V,G}(vertices::Vector{V}) where {V,G} - return GenericNamedGraph(G(length(vertices)), vertices) -end - -function GenericNamedGraph{V,G}(vertices) where {V,G} - return GenericNamedGraph{V,G}(to_vertices(V, vertices)) +function GenericNamedGraph{V,G}(vertices) where {V,G<:AbstractSimpleGraph{Int}} + return GenericNamedGraph(G(length(to_vertices(vertices))), vertices) end function GenericNamedGraph{V}(vertices) where {V} return GenericNamedGraph{V,SimpleGraph{Int}}(vertices) end -function GenericNamedGraph{<:Any,G}(vertices) where {G} +function GenericNamedGraph{<:Any,G}(vertices) where {G<:AbstractSimpleGraph{Int}} return GenericNamedGraph{eltype(vertices),G}(vertices) end @@ -179,32 +159,40 @@ end # Empty constructors # -GenericNamedGraph{V,G}() where {V,G} = GenericNamedGraph{V,G}(V[]) +GenericNamedGraph{V,G}() where {V,G<:AbstractSimpleGraph{Int}} = GenericNamedGraph{V,G}(V[]) GenericNamedGraph{V}() where {V} = GenericNamedGraph{V}(V[]) -GenericNamedGraph{<:Any,G}() where {G} = GenericNamedGraph{<:Any,G}(Any[]) +function GenericNamedGraph{<:Any,G}() where {G<:AbstractSimpleGraph{Int}} + return GenericNamedGraph{<:Any,G}(Any[]) +end GenericNamedGraph() = GenericNamedGraph(Any[]) # TODO: implement as: -# graph = set_parent_graph(graph, copy(parent_graph(graph))) +# graph = set_position_graph(graph, copy(position_graph(graph))) # graph = set_vertices(graph, copy(vertices(graph))) function Base.copy(graph::GenericNamedGraph) - return GenericNamedGraph(copy(graph.parent_graph), copy(graph.parent_vertex_to_vertex)) + return GenericNamedGraph(copy(position_graph(graph)), copy(vertices(graph))) end -Graphs.edgetype(G::Type{<:GenericNamedGraph}) = NamedEdge{vertextype(G)} +Graphs.edgetype(graph_type::Type{<:GenericNamedGraph}) = NamedEdge{vertextype(graph_type)} Graphs.edgetype(graph::GenericNamedGraph) = edgetype(typeof(graph)) -function GraphsExtensions.directed_graph_type(G::Type{<:GenericNamedGraph}) - return GenericNamedGraph{vertextype(G),directed_graph_type(parent_graph_type(G))} +function GraphsExtensions.directed_graph_type(graph_type::Type{<:GenericNamedGraph}) + return GenericNamedGraph{ + vertextype(graph_type),directed_graph_type(position_graph_type(graph_type)) + } end -function GraphsExtensions.undirected_graph_type(G::Type{<:GenericNamedGraph}) - return GenericNamedGraph{vertextype(G),undirected_graph_type(parent_graph_type(G))} +function GraphsExtensions.undirected_graph_type(graph_type::Type{<:GenericNamedGraph}) + return GenericNamedGraph{ + vertextype(graph_type),undirected_graph_type(position_graph_type(graph_type)) + } end -Graphs.is_directed(G::Type{<:GenericNamedGraph}) = is_directed(parent_graph_type(G)) +function Graphs.is_directed(graph_type::Type{<:GenericNamedGraph}) + return is_directed(position_graph_type(graph_type)) +end # TODO: Implement an edgelist version function namedgraph_induced_subgraph(graph::AbstractGraph, subvertices) diff --git a/src/shortestpaths.jl b/src/shortestpaths.jl index 8b9398a..37223f9 100644 --- a/src/shortestpaths.jl +++ b/src/shortestpaths.jl @@ -24,28 +24,28 @@ function NamedDijkstraState(parents, dists, predecessors, pathcounts, closest_ve ) end -function parent_path_state_to_path_state( - graph::AbstractNamedGraph, parent_path_state::Graphs.DijkstraState +function position_path_state_to_path_state( + graph::AbstractNamedGraph, position_path_state::Graphs.DijkstraState ) - parent_path_state_parents = map(eachindex(parent_path_state.parents)) do i - pᵢ = parent_path_state.parents[i] + position_path_state_parents = map(eachindex(position_path_state.parents)) do i + pᵢ = position_path_state.parents[i] return iszero(pᵢ) ? i : pᵢ end # Works around issue in this `Dictionary` constructor: # https://github.com/andyferris/Dictionaries.jl/blob/v0.4.1/src/Dictionary.jl#L139-L145 # when `inds` has holes. This removes the holes. # TODO: Raise an issue with `Dictionaries.jl`. - ## vertices_graph = Indices(collect(vertices(graph))) + ## graph_vertices = Indices(collect(vertices(graph))) # This makes the vertices ordered according to the parent vertices. - vertices_graph = parent_vertices_to_vertices(graph, parent_vertices(graph)) + graph_vertices = map(v -> ordered_vertices(graph)[v], vertices(position_graph(graph))) return NamedDijkstraState( Dictionary( - vertices_graph, parent_vertices_to_vertices(graph, parent_path_state_parents) + graph_vertices, map(v -> ordered_vertices(graph)[v], position_path_state_parents) ), - Dictionary(vertices_graph, parent_path_state.dists), - map(x -> parent_vertices_to_vertices(graph, x), parent_path_state.predecessors), - Dictionary(vertices_graph, parent_path_state.pathcounts), - parent_vertices_to_vertices(graph, parent_path_state.closest_vertices), + Dictionary(graph_vertices, position_path_state.dists), + map(x -> map(v -> ordered_vertices(graph)[v], x), position_path_state.predecessors), + Dictionary(graph_vertices, position_path_state.pathcounts), + map(v -> ordered_vertices(graph)[v], position_path_state.closest_vertices), ) end @@ -56,14 +56,14 @@ function namedgraph_dijkstra_shortest_paths( allpaths=false, trackvertices=false, ) - parent_path_state = dijkstra_shortest_paths( - parent_graph(graph), - vertices_to_parent_vertices(graph, srcs), - dist_matrix_to_parent_dist_matrix(graph, distmx); + position_path_state = dijkstra_shortest_paths( + position_graph(graph), + map(v -> vertex_positions(graph)[v], srcs), + dist_matrix_to_position_dist_matrix(graph, distmx); allpaths, trackvertices, ) - return parent_path_state_to_path_state(graph, parent_path_state) + return position_path_state_to_path_state(graph, position_path_state) end function Graphs.dijkstra_shortest_paths( diff --git a/src/steiner_tree.jl b/src/steiner_tree.jl index bd730ca..c01207f 100644 --- a/src/steiner_tree.jl +++ b/src/steiner_tree.jl @@ -4,10 +4,10 @@ using SimpleTraits: SimpleTraits, Not, @traitfn @traitfn function Graphs.steiner_tree( g::AbstractNamedGraph::(!IsDirected), term_vert, distmx=weights(g) ) - parent_tree = steiner_tree( - parent_graph(g), - vertices_to_parent_vertices(g, term_vert), - dist_matrix_to_parent_dist_matrix(g, distmx), + position_tree = steiner_tree( + position_graph(g), + map(v -> vertex_positions(g)[v], term_vert), + dist_matrix_to_position_dist_matrix(g, distmx), ) - return typeof(g)(parent_tree, parent_vertices_to_vertices(g, Base.OneTo(nv(parent_tree)))) + return typeof(g)(position_tree, map(v -> ordered_vertices(g)[v], vertices(position_tree))) end diff --git a/test/Project.toml b/test/Project.toml index e96a9f3..e1ba9e3 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,4 +1,5 @@ [deps] +AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" GraphsFlows = "06909019-6f44-4949-96fc-b9d9aaa02889" diff --git a/test/test_abstractnamedgraph.jl b/test/test_abstractnamedgraph.jl index 8473f15..379656a 100644 --- a/test/test_abstractnamedgraph.jl +++ b/test/test_abstractnamedgraph.jl @@ -18,7 +18,7 @@ using Test: @test, @testset add_edge!(ng2, "A" => "C") add_edge!(ng2, "B" => "D") add_edge!(ng2, "C" => "D") - @test NamedGraphs.parent_graph(ng1) != NamedGraphs.parent_graph(ng2) + @test NamedGraphs.position_graph(ng1) != NamedGraphs.position_graph(ng2) @test ng1 == ng2 rem_edge!(ng2, "B" => "A") @test ng1 != ng2 @@ -32,7 +32,7 @@ using Test: @test, @testset add_edge!(ndg2, ("X", 1) => ("Y", 1)) add_edge!(ndg2, ("X", 2) => ("Y", 2)) add_edge!(ndg2, ("Y", 1) => ("Y", 2)) - @test NamedGraphs.parent_graph(ndg1) != NamedGraphs.parent_graph(ndg2) + @test NamedGraphs.position_graph(ndg1) != NamedGraphs.position_graph(ndg2) @test ndg1 == ndg2 rem_edge!(ndg2, ("Y", 1) => ("X", 1)) @test ndg1 != ndg2 @@ -45,7 +45,7 @@ using Test: @test, @testset add_edge!(nddg2, ("X", 1) => ("Y", 1)) add_edge!(nddg2, ("X", 2) => ("Y", 2)) add_edge!(nddg2, ("Y", 1) => ("Y", 2)) - @test NamedGraphs.parent_graph(nddg1) != NamedGraphs.parent_graph(nddg2) + @test NamedGraphs.position_graph(nddg1) != NamedGraphs.position_graph(nddg2) @test nddg1 == nddg2 rem_edge!(nddg2, ("X", 1) => ("Y", 1)) add_edge!(nddg2, ("Y", 1) => ("X", 1)) diff --git a/test/test_libs.jl b/test/test_libs.jl new file mode 100644 index 0000000..e47038c --- /dev/null +++ b/test/test_libs.jl @@ -0,0 +1,19 @@ +@eval module $(gensym()) +using NamedGraphs: NamedGraphs +using Test: @testset +libs = [ + #:GraphGenerators, + :GraphsExtensions, + #:Keys, + #:NamedGraphGenerators, + :OrderedDictionaries, + :OrdinalIndexing, + #:PartitionedGraphs, + #:SimilarType, +] +@testset "Test lib $lib" for lib in libs + path = joinpath(pkgdir(NamedGraphs), "src", "lib", String(lib), "test", "runtests.jl") + println("Runnint lib test $path") + include(path) +end +end diff --git a/test/test_multidimgraph.jl b/test/test_multidimgraph.jl index 824cfb2..5d4e922 100644 --- a/test/test_multidimgraph.jl +++ b/test/test_multidimgraph.jl @@ -5,10 +5,10 @@ using NamedGraphs.GraphsExtensions: ⊔, disjoint_union, subgraph using Test: @test, @testset @testset "NamedGraph" begin - parent_graph = grid((2, 2)) + position_graph = grid((2, 2)) vertices = [("X", 1), ("X", 2), ("Y", 1), ("Y", 2)] - g = NamedGraph(parent_graph, vertices) + g = NamedGraph(position_graph, vertices) @test has_vertex(g, ("X", 1)) @test has_edge(g, ("X", 1) => ("X", 2)) @@ -91,7 +91,7 @@ using Test: @test, @testset end @testset "NamedGraph add vertices" begin - parent_graph = grid((2, 2)) + position_graph = grid((2, 2)) vertices = [("X", 1), ("X", 2), ("Y", 1), ("Y", 2)] g = NamedGraph() add_vertex!(g, ("X", 1)) diff --git a/test/test_namedgraph.jl b/test/test_namedgraph.jl index 99e3304..6f4610e 100644 --- a/test/test_namedgraph.jl +++ b/test/test_namedgraph.jl @@ -71,6 +71,7 @@ using NamedGraphs.GraphsExtensions: dijkstra_mst, dijkstra_parents, dijkstra_tree, + has_vertices, incident_edges, indegrees, inner_boundary_vertices, @@ -103,7 +104,6 @@ end @testset "NamedGraph" begin @testset "Basics" begin g = NamedGraph(grid((4,)), ["A", "B", "C", "D"]) - @test nv(g) == 4 @test ne(g) == 3 @test sum(g) == 3 @@ -114,18 +114,27 @@ end @test has_edge(g, "A" => "B") @test issetequal(common_neighbors(g, "A", "C"), ["B"]) @test isempty(common_neighbors(g, "A", "D")) + @test degree(g, "A") == 1 + @test degree(g, "B") == 2 + g = NamedGraph(grid((4,)), ["A", "B", "C", "D"]) zg = zero(g) @test zg isa NamedGraph{String} @test nv(zg) == 0 @test ne(zg) == 0 - @test degree(g, "A") == 1 - @test degree(g, "B") == 2 - + g = NamedGraph(grid((4,)), ["A", "B", "C", "D"]) add_vertex!(g, "E") @test has_vertex(g, "E") + @test nv(g) == 5 + @test has_vertices(g, ["A", "B", "C", "D", "E"]) + + g = NamedGraph(grid((5,)), ["A", "B", "C", "D", "E"]) + rem_vertex!(g, "E") + @test !has_vertex(g, "E") + g = NamedGraph(grid((4,)), ["A", "B", "C", "D"]) + add_vertex!(g, "E") rem_vertex!(g, "E") @test !has_vertex(g, "E")