diff --git a/Project.toml b/Project.toml index 7a69e9d..883294d 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "NamedGraphs" uuid = "678767b0-92e7-4007-89e4-4527a8725b19" authors = ["Matthew Fishman , Joseph Tindall and contributors"] -version = "0.7.4" +version = "0.8.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" diff --git a/docs/Project.toml b/docs/Project.toml index 16ff40a..2309122 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -6,4 +6,4 @@ NamedGraphs = "678767b0-92e7-4007-89e4-4527a8725b19" [compat] Documenter = "1.10" Literate = "2.20.1" -NamedGraphs = "0.7" +NamedGraphs = "0.8" diff --git a/examples/Project.toml b/examples/Project.toml index 123e6c3..07edffc 100644 --- a/examples/Project.toml +++ b/examples/Project.toml @@ -4,4 +4,4 @@ NamedGraphs = "678767b0-92e7-4007-89e4-4527a8725b19" [compat] Graphs = "1.12.0" -NamedGraphs = "0.7.0" +NamedGraphs = "0.8.0" diff --git a/ext/NamedGraphsKaHyParExt/NamedGraphsKaHyParExt.jl b/ext/NamedGraphsKaHyParExt/NamedGraphsKaHyParExt.jl index c19fe1d..7e31afe 100644 --- a/ext/NamedGraphsKaHyParExt/NamedGraphsKaHyParExt.jl +++ b/ext/NamedGraphsKaHyParExt/NamedGraphsKaHyParExt.jl @@ -27,14 +27,14 @@ const KAHYPAR_ALGS = Dict( ) """ -partitioned_vertices(::Backend"kahypar", g::Graph, npartiations::Integer; objective="edge_cut", alg="kway", kwargs...) + partition_vertices(::Backend"kahypar", g::Graph, npartiations::Integer; objective="edge_cut", alg="kway", kwargs...) - default_configuration => "cut_kKaHyPar_sea20.ini" - :edge_cut => "cut_kKaHyPar_sea20.ini" - :connectivity => "km1_kKaHyPar_sea20.ini" - imbalance::Number=0.03 """ -function GraphsExtensions.partitioned_vertices( +function GraphsExtensions.partition_vertices( ::Backend"kahypar", g::AbstractSimpleGraph, npartitions::Integer; diff --git a/ext/NamedGraphsMetisExt/NamedGraphsMetisExt.jl b/ext/NamedGraphsMetisExt/NamedGraphsMetisExt.jl index 9738c37..89682d9 100644 --- a/ext/NamedGraphsMetisExt/NamedGraphsMetisExt.jl +++ b/ext/NamedGraphsMetisExt/NamedGraphsMetisExt.jl @@ -10,14 +10,14 @@ GraphsExtensions.set_partitioning_backend!(Backend"metis"()) const METIS_ALGS = Dict(["kway" => :KWAY, "recursive" => :RECURSIVE]) """ - partitioned_vertices(::Backend"metis", g::AbstractGraph, npartitions::Integer; alg="recursive") + partition_vertices(::Backend"metis", g::AbstractGraph, npartitions::Integer; alg="recursive") Partition the graph `G` in `n` parts. The partition algorithm is defined by the `alg` keyword: - :KWAY: multilevel k-way partitioning - :RECURSIVE: multilevel recursive bisection """ -function GraphsExtensions.partitioned_vertices( +function GraphsExtensions.partition_vertices( ::Backend"metis", g::AbstractSimpleGraph, npartitions::Integer; alg = "recursive", kwargs... ) metis_alg = METIS_ALGS[alg] diff --git a/src/abstractnamedgraph.jl b/src/abstractnamedgraph.jl index 61cdc26..0efe375 100644 --- a/src/abstractnamedgraph.jl +++ b/src/abstractnamedgraph.jl @@ -32,7 +32,7 @@ using .GraphsExtensions: GraphsExtensions, directed_graph, incident_edges, - partitioned_vertices, + partition_vertices, rename_vertices, subgraph using SimpleTraits: SimpleTraits, Not, @traitfn @@ -66,7 +66,8 @@ vertex_positions(graph::AbstractNamedGraph) = not_implemented() # returns the corresponding vertex. ordered_vertices(graph::AbstractNamedGraph) = not_implemented() -Graphs.edgetype(graph::AbstractNamedGraph) = not_implemented() +Graphs.edgetype(graph::AbstractNamedGraph) = edgetype(typeof(graph)) +Graphs.edgetype(::Type{<:AbstractNamedGraph}) = not_implemented() # TODO: Define generic version in `GraphsExtensions`. GraphsExtensions.directed_graph_type(G::Type{<:AbstractNamedGraph}) = not_implemented() @@ -285,10 +286,10 @@ function Graphs.mincut(graph::AbstractNamedGraph, distmx::AbstractMatrix{<:Real} end # TODO: Make this more generic? -function GraphsExtensions.partitioned_vertices( +function GraphsExtensions.partition_vertices( graph::AbstractNamedGraph; npartitions = nothing, nvertices_per_partition = nothing, kwargs... ) - vertex_partitions = partitioned_vertices( + vertex_partitions = partition_vertices( position_graph(graph); npartitions, nvertices_per_partition, kwargs... ) # TODO: output the reverse of this dictionary (a Vector of Vector @@ -446,13 +447,10 @@ function Graphs.blockdiag(graph1::AbstractNamedGraph, graph2::AbstractNamedGraph return GenericNamedGraph(new_position_graph, new_vertices) end -# TODO: What `args` are needed? -Graphs.nv(graph::AbstractNamedGraph, args...) = nv(position_graph(graph), args...) -# TODO: What `args` are needed? -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(position_graph(graph), args...) +Graphs.nv(graph::AbstractNamedGraph) = nv(position_graph(graph)) +Graphs.ne(graph::AbstractNamedGraph) = ne(position_graph(graph)) +function Graphs.adjacency_matrix(graph::AbstractNamedGraph) + return adjacency_matrix(position_graph(graph)) end function Graphs.connected_components(graph::AbstractNamedGraph) diff --git a/src/lib/GraphsExtensions/src/partitioning.jl b/src/lib/GraphsExtensions/src/partitioning.jl index c9d5c5e..0691e30 100644 --- a/src/lib/GraphsExtensions/src/partitioning.jl +++ b/src/lib/GraphsExtensions/src/partitioning.jl @@ -54,7 +54,7 @@ function _npartitions( return error("Must specify either `npartitions` or `nvertices_per_partition`") end -function partitioned_vertices( +function partition_vertices( g::AbstractSimpleGraph; npartitions = nothing, nvertices_per_partition = nothing, @@ -66,11 +66,7 @@ function partitioned_vertices( if (_npartitions(g, npartitions, nvertices_per_partition) == 1) return group(v -> 1, collect(vertices(g))) end - return partitioned_vertices( + return partition_vertices( Backend(backend), g, _npartitions(g, npartitions, nvertices_per_partition); kwargs... ) end - -function partitioned_vertices(g::AbstractGraph; kwargs...) - return not_implemented() -end diff --git a/src/lib/PartitionedGraphs/src/PartitionedGraphs.jl b/src/lib/PartitionedGraphs/src/PartitionedGraphs.jl index a14d5ef..03803bd 100644 --- a/src/lib/PartitionedGraphs/src/PartitionedGraphs.jl +++ b/src/lib/PartitionedGraphs/src/PartitionedGraphs.jl @@ -1,9 +1,9 @@ module PartitionedGraphs -include("abstractpartitionvertex.jl") -include("abstractpartitionedge.jl") +include("supervertex.jl") +include("subsupervertex.jl") +include("superedge.jl") include("abstractpartitionedgraph.jl") -include("partitionvertex.jl") -include("partitionedge.jl") +include("partitionedview.jl") include("partitionedgraph.jl") -include("partitionsgraphview.jl") +include("quotientgraph.jl") end diff --git a/src/lib/PartitionedGraphs/src/abstractpartitionedge.jl b/src/lib/PartitionedGraphs/src/abstractpartitionedge.jl deleted file mode 100644 index 57188e6..0000000 --- a/src/lib/PartitionedGraphs/src/abstractpartitionedge.jl +++ /dev/null @@ -1,11 +0,0 @@ -using Graphs: Graphs -using ..NamedGraphs: AbstractNamedEdge - -abstract type AbstractPartitionEdge{V} <: AbstractNamedEdge{V} end - -Base.parent(pe::AbstractPartitionEdge) = not_implemented() -Graphs.src(pe::AbstractPartitionEdge) = not_implemented() -Graphs.dst(pe::AbstractPartitionEdge) = not_implemented() -Base.reverse(pe::AbstractPartitionEdge) = not_implemented() - -#Don't have the vertices wrapped. But wrap them with source and edge. diff --git a/src/lib/PartitionedGraphs/src/abstractpartitionedgraph.jl b/src/lib/PartitionedGraphs/src/abstractpartitionedgraph.jl index 45ccce7..1f5d651 100644 --- a/src/lib/PartitionedGraphs/src/abstractpartitionedgraph.jl +++ b/src/lib/PartitionedGraphs/src/abstractpartitionedgraph.jl @@ -1,6 +1,8 @@ +using Dictionaries: Dictionary using Graphs: - Graphs, AbstractEdge, + AbstractGraph, + Graphs, add_vertex!, dst, edgetype, @@ -11,47 +13,132 @@ using Graphs: vertices using ..NamedGraphs: NamedGraphs, AbstractNamedGraph using ..NamedGraphs.GraphsExtensions: - GraphsExtensions, add_vertices!, not_implemented, rem_vertices! + GraphsExtensions, add_vertices!, not_implemented, rem_vertices!, subgraph, vertextype -abstract type AbstractPartitionedGraph{V, PV} <: AbstractNamedGraph{V} end +# For you own graph type `g`, you should define a method for this function if you +# desire custom partitioning. +partitioned_vertices(g::AbstractGraph) = [vertices(g)] -#Needed for interface -partitions_graph(pg::AbstractPartitionedGraph) = not_implemented() -unpartitioned_graph(pg::AbstractPartitionedGraph) = not_implemented() -function unpartitioned_graph_type(pg::Type{<:AbstractPartitionedGraph}) - return not_implemented() +# For fast quotient edge checking and graph construction, one should overload this function. +function quotient_graph(g::AbstractGraph) + + qg = NamedGraph(quotient_vertices(g)) + + for e in edges(g) + qv_src = quotient_vertex(g, src(e)) + qv_dst = quotient_vertex(g, dst(e)) + qe = qv_src => qv_dst + if qv_src != qv_dst && !has_edge(qg, qe) + add_edge!(qg, qe) + end + end + + return qg end -partitionvertex(pg::AbstractPartitionedGraph, vertex) = not_implemented() -partitionvertices(pg::AbstractPartitionedGraph, verts) = not_implemented() -partitionvertices(pg::AbstractPartitionedGraph) = not_implemented() -Base.copy(pg::AbstractPartitionedGraph) = not_implemented() -delete_from_vertex_map!(pg::AbstractPartitionedGraph, vertex) = not_implemented() -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)) + +# Overload this for fast inverse mapping for vertices and edges +function quotient_vertex(g, vertex) + pvs = partitioned_vertices(g) + rv = findfirst(pv -> vertex ∈ pv, pvs) + if isnothing(rv) + error("Vertex $vertex not found in any partition.") + end + return rv end -function Graphs.edges(pg::AbstractPartitionedGraph, partitionedge::AbstractPartitionEdge) - return not_implemented() +function quotient_edge(g::AbstractGraph, edge) + if !has_edge(g, edge) + throw(ArgumentError("Graph does not have an edge $edge")) + end + qv_src = quotient_vertex(g, src(edge)) + qv_dst = quotient_vertex(g, dst(edge)) + return quotient_edgetype(g)(qv_src => qv_dst) end -function Graphs.vertices(pg::AbstractPartitionedGraph, pv::AbstractPartitionVertex) - return not_implemented() + +function partitioned_edges(g::AbstractGraph) + + dict = Dictionary{quotient_edgetype(g), Vector{edgetype(g)}}() + + for e in edges(g) + + qe = quotient_edge(g, e) + + if is_self_loop(qe) + continue + end + push!(get!(dict, qe, edgetype(g)[]), e) + end + + return dict +end + +function quotient_vertices(g) + QGT = quotient_graph_type(g) + qg = QGT(keys(partitioned_vertices(g))) + return vertices(qg) +end +quotient_edges(g::AbstractGraph) = edges(quotient_graph(g)) + +function is_partition_boundary_edge(pg::AbstractGraph, edge) + p_edge = superedge(pg, edge) + return src(p_edge) != dst(p_edge) +end + +function boundary_superedges(pg::AbstractGraph, supervertices; kwargs...) + return SuperEdge.( + boundary_edges(quotient_graph(pg), parent.(supervertices); kwargs...) + ) +end + +function boundary_superedges( + pg::AbstractGraph, supervertex::SuperVertex; kwargs... + ) + return boundary_superedges(pg, [supervertex]; kwargs...) end -function Graphs.vertices( - pg::AbstractPartitionedGraph, partitionverts::Vector{V} - ) where {V <: AbstractPartitionVertex} + +quotient_graph_type(g) = quotient_graph_type(typeof(g)) +quotient_graph_type(::Type{<:AbstractGraph}) = NamedGraph{Int} +quotient_vertextype(G) = vertextype(quotient_graph_type(G)) +quotient_edgetype(G) = edgetype(quotient_graph_type(G)) + +""" +abstract type AbstractPartitionedGraph{V, PV} <: AbstractNamedGraph{V} + +To use `AbstractPartitionedGraph` one should defined `unpartitioned_graph` that returns +an underlying graph *without* any partitioning. One should also define: +""" +abstract type AbstractPartitionedGraph{V, PV} <: AbstractNamedGraph{V} end + +# Remove one layer of partitioning. +departition(pg::AbstractPartitionedGraph) = unpartitioned_graph(pg) +departition(g::AbstractGraph) = g + +# Recursively remove all layers of partitioning. +function unpartition(pg::AbstractGraph) + g = departition(pg) + g === pg && return pg + return unpartition(g) +end + +# Required for interface +unpartitioned_graph(::AbstractPartitionedGraph) = not_implemented() +Base.copy(::AbstractPartitionedGraph) = not_implemented() + +function unpartitioned_graph_type(::Type{<:AbstractPartitionedGraph}) return not_implemented() end -function GraphsExtensions.directed_graph_type(PG::Type{<:AbstractPartitionedGraph}) +function GraphsExtensions.directed_graph_type(::Type{<:AbstractPartitionedGraph}) return not_implemented() end -function GraphsExtensions.undirected_graph_type(PG::Type{<:AbstractPartitionedGraph}) +function GraphsExtensions.undirected_graph_type(::Type{<:AbstractPartitionedGraph}) return not_implemented() end +function unpartitioned_graph_type(pg::AbstractPartitionedGraph) + return typeof(unpartitioned_graph(pg)) +end + + # AbstractGraph interface. function Graphs.is_directed(graph_type::Type{<:AbstractPartitionedGraph}) return is_directed(unpartitioned_graph_type(graph_type)) @@ -59,6 +146,8 @@ end #Functions for the abstract type Graphs.vertices(pg::AbstractPartitionedGraph) = vertices(unpartitioned_graph(pg)) +Graphs.edges(pg::AbstractPartitionedGraph) = edges(unpartitioned_graph(pg)) + function NamedGraphs.position_graph(pg::AbstractPartitionedGraph) return NamedGraphs.position_graph(unpartitioned_graph(pg)) end @@ -69,130 +158,30 @@ function NamedGraphs.ordered_vertices(pg::AbstractPartitionedGraph) return NamedGraphs.ordered_vertices(unpartitioned_graph(pg)) end Graphs.edgetype(pg::AbstractPartitionedGraph) = edgetype(unpartitioned_graph(pg)) -function Graphs.nv(pg::AbstractPartitionedGraph, pv::AbstractPartitionVertex) - return length(vertices(pg, pv)) -end -function Graphs.has_vertex( - pg::AbstractPartitionedGraph, partitionvertex::AbstractPartitionVertex - ) - return has_vertex(partitions_graph(pg), parent(partitionvertex)) -end - -function Graphs.has_edge(pg::AbstractPartitionedGraph, partitionedge::AbstractPartitionEdge) - return has_edge(partitions_graph(pg), parent(partitionedge)) -end - -function is_boundary_edge(pg::AbstractPartitionedGraph, edge::AbstractEdge) - p_edge = partitionedge(pg, edge) - return src(p_edge) == dst(p_edge) -end - -function Graphs.add_edge!(pg::AbstractPartitionedGraph, edge::AbstractEdge) - add_edge!(unpartitioned_graph(pg), edge) - pg_edge = parent(partitionedge(pg, edge)) - if src(pg_edge) != dst(pg_edge) - add_edge!(partitions_graph(pg), pg_edge) - end - return pg -end - -function Graphs.rem_edge!(pg::AbstractPartitionedGraph, edge::AbstractEdge) - pg_edge = partitionedge(pg, edge) - if has_edge(partitions_graph(pg), pg_edge) - g_edges = edges(pg, pg_edge) - if length(g_edges) == 1 - rem_edge!(partitions_graph(pg), pg_edge) - end - end - return rem_edge!(unpartitioned_graph(pg), edge) -end - -function Graphs.rem_edge!( - pg::AbstractPartitionedGraph, partitionedge::AbstractPartitionEdge - ) - return rem_edges!(pg, edges(pg, parent(partitionedge))) -end - -#Vertex addition and removal. I think it's important not to allow addition of a vertex without specification of PV -function Graphs.add_vertex!( - pg::AbstractPartitionedGraph, vertex, partitionvertex::AbstractPartitionVertex - ) - add_vertex!(unpartitioned_graph(pg), vertex) - add_vertex!(partitions_graph(pg), parent(partitionvertex)) - insert_to_vertex_map!(pg, vertex, partitionvertex) - return pg -end - -function GraphsExtensions.add_vertices!( - pg::AbstractPartitionedGraph, - vertices::Vector, - partitionvertices::Vector{<:AbstractPartitionVertex}, - ) - @assert length(vertices) == length(partitionvertices) - for (v, pv) in zip(vertices, partitionvertices) - add_vertex!(pg, v, pv) - end - - return pg -end - -function GraphsExtensions.add_vertices!( - pg::AbstractPartitionedGraph, vertices::Vector, partitionvertex::AbstractPartitionVertex - ) - add_vertices!(pg, vertices, fill(partitionvertex, length(vertices))) - return pg -end - -function Graphs.rem_vertex!(pg::AbstractPartitionedGraph, vertex) - pv = partitionvertex(pg, vertex) - delete_from_vertex_map!(pg, pv, vertex) - rem_vertex!(unpartitioned_graph(pg), vertex) - if !haskey(partitioned_vertices(pg), parent(pv)) - rem_vertex!(partitions_graph(pg), parent(pv)) - end - return pg -end -function Graphs.rem_vertex!( - pg::AbstractPartitionedGraph, partitionvertex::AbstractPartitionVertex - ) - rem_vertices!(pg, vertices(pg, partitionvertex)) - return pg -end +Graphs.rem_vertex!(::AbstractPartitionedGraph{V}, vertex::V) where {V} = not_implemented() -function GraphsExtensions.rem_vertex( - pg::AbstractPartitionedGraph, partitionvertex::AbstractPartitionVertex - ) - pg_new = copy(pg) - rem_vertex!(pg_new, partitionvertex) - return pg_new +function Graphs.add_vertex!(::AbstractPartitionedGraph, vertex) + return error("Need to specify a partition where the new vertex will go.") end -function Graphs.add_vertex!(pg::AbstractPartitionedGraph, vertex) - return error("Need to specify a partition where the new vertex will go.") +function Graphs.add_vertex!(pg::AbstractPartitionedGraph, ssv::SubSuperVertex) + return add_subsupervertex!(pg, ssv.vertex, ssv.subvertex) end function Base.:(==)(pg1::AbstractPartitionedGraph, pg2::AbstractPartitionedGraph) if unpartitioned_graph(pg1) != unpartitioned_graph(pg2) || - partitions_graph(pg1) != partitions_graph(pg2) + QuotientView(pg1) != QuotientView(pg2) return false end for v in vertices(pg1) - if partitionvertex(pg1, v) != partitionvertex(pg2, v) + if supervertex(pg1, v) != supervertex(pg2, v) return false end end return true end -function GraphsExtensions.subgraph( - pg::AbstractPartitionedGraph, partitionvertex::AbstractPartitionVertex - ) - return first(induced_subgraph(unpartitioned_graph(pg), vertices(pg, [partitionvertex]))) -end - -function Graphs.induced_subgraph( - pg::AbstractPartitionedGraph, partitionvertex::AbstractPartitionVertex - ) - return subgraph(pg, partitionvertex), nothing +function NamedGraphs._induced_subgraph(pg::AbstractPartitionedGraph, vlist) + return NamedGraphs._induced_subgraph(unpartitioned_graph(pg), vlist) end diff --git a/src/lib/PartitionedGraphs/src/abstractpartitionvertex.jl b/src/lib/PartitionedGraphs/src/abstractpartitionvertex.jl deleted file mode 100644 index 31bc58b..0000000 --- a/src/lib/PartitionedGraphs/src/abstractpartitionvertex.jl +++ /dev/null @@ -1,4 +0,0 @@ -abstract type AbstractPartitionVertex{V} <: Any where {V} end - -#Parent, wrap, unwrap, vertex? -Base.parent(pv::AbstractPartitionVertex) = not_implemented() diff --git a/src/lib/PartitionedGraphs/src/partitionedge.jl b/src/lib/PartitionedGraphs/src/partitionedge.jl deleted file mode 100644 index d473522..0000000 --- a/src/lib/PartitionedGraphs/src/partitionedge.jl +++ /dev/null @@ -1,12 +0,0 @@ -using Graphs: Graphs, AbstractEdge, dst, src - -struct PartitionEdge{V, E <: AbstractEdge{V}} <: AbstractPartitionEdge{V} - edge::E -end - -Base.parent(pe::PartitionEdge) = getfield(pe, :edge) -Graphs.src(pe::PartitionEdge) = PartitionVertex(src(parent(pe))) -Graphs.dst(pe::PartitionEdge) = PartitionVertex(dst(parent(pe))) -PartitionEdge(p::Pair) = PartitionEdge(NamedEdge(first(p) => last(p))) -PartitionEdge(vsrc, vdst) = PartitionEdge(vsrc => vdst) -Base.reverse(pe::PartitionEdge) = PartitionEdge(reverse(parent(pe))) diff --git a/src/lib/PartitionedGraphs/src/partitionedgraph.jl b/src/lib/PartitionedGraphs/src/partitionedgraph.jl index 9192d61..3feecd5 100644 --- a/src/lib/PartitionedGraphs/src/partitionedgraph.jl +++ b/src/lib/PartitionedGraphs/src/partitionedgraph.jl @@ -1,39 +1,46 @@ using Dictionaries: Dictionary using Graphs: - AbstractEdge, AbstractGraph, add_edge!, edges, has_edge, induced_subgraph, vertices -using .GraphsExtensions: - GraphsExtensions, boundary_edges, is_self_loop, partitioned_vertices -using ..NamedGraphs: NamedEdge, NamedGraph + AbstractEdge, AbstractGraph, add_edge!, edges, has_edge, induced_subgraph, vertices, dst, src, edgetype +using ..NamedGraphs: NamedGraphs, NamedEdge, NamedGraph +using ..NamedGraphs.GraphsExtensions: GraphsExtensions, boundary_edges, is_self_loop, partition_vertices +using ..NamedGraphs.OrderedDictionaries: OrderedDictionary # TODO: Parametrize `partitioned_vertices` and `which_partition`, # see https://github.com/mtfishman/NamedGraphs.jl/issues/63. -struct PartitionedGraph{V, PV, G <: AbstractGraph{V}, PG <: AbstractGraph{PV}} <: - AbstractPartitionedGraph{V, PV} +struct PartitionedGraph{V, PV, G <: AbstractGraph{V}, P} <: AbstractPartitionedGraph{V, PV} graph::G - partitions_graph::PG - partitioned_vertices::Dictionary - which_partition::Dictionary + quotient_graph::NamedGraph{PV} + partitioned_vertices::P + which_partition::Dictionary{V, PV} end +partitionedgraph(g::AbstractGraph, partition) = PartitionedGraph(g, partition) + +# Interface overloads +partitioned_vertices(pg::PartitionedGraph) = pg.partitioned_vertices +quotient_graph(pg::PartitionedGraph) = pg.quotient_graph +quotient_vertex(pg::PartitionedGraph, vertex) = pg.which_partition[vertex] + +quotient_graph_type(::Type{<:AbstractPartitionedGraph{V, PV}}) where {V, PV} = NamedGraph{PV} + +Graphs.edgetype(::Type{<:PartitionedGraph{V, PV, G}}) where {V, PV, G} = edgetype(G) + ##Constructors. function PartitionedGraph(g::AbstractGraph, partitioned_vertices) pvs = keys(partitioned_vertices) - pg = NamedGraph(pvs) - # TODO: Make this type more specific. - which_partition = Dictionary() + which_partition = Dictionary{vertextype(g), eltype(pvs)}() for v in vertices(g) - v_pvs = Set(findall(pv -> v ∈ partitioned_vertices[pv], pvs)) + v_pvs = Set(findall(pv -> v ∈ pv, partitioned_vertices)) @assert length(v_pvs) == 1 insert!(which_partition, v, first(v_pvs)) end - for e in edges(g) - pv_src, pv_dst = which_partition[src(e)], which_partition[dst(e)] - pe = NamedEdge(pv_src => pv_dst) - if pv_src != pv_dst && !has_edge(pg, pe) - add_edge!(pg, pe) - end - end - return PartitionedGraph(g, pg, Dictionary(partitioned_vertices), which_partition) + qg = quotient_graph(PartitionedView(g, partitioned_vertices)) + return PartitionedGraph( + g, + qg, + Dictionary(partitioned_vertices), + which_partition + ) end function PartitionedGraph(partitioned_vertices) @@ -41,127 +48,118 @@ function PartitionedGraph(partitioned_vertices) end function PartitionedGraph(g::AbstractGraph; kwargs...) - partitioned_verts = partitioned_vertices(g; kwargs...) + partitioned_verts = partition_vertices(g; kwargs...) return PartitionedGraph(g, partitioned_verts) end + #Needed for interface -partitions_graph(pg::PartitionedGraph) = PartitionsGraphView(pg) 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) -function Graphs.vertices(pg::PartitionedGraph, partitionvert::PartitionVertex) - return partitioned_vertices(pg)[parent(partitionvert)] -end -function Graphs.vertices(pg::PartitionedGraph, partitionverts::Vector{<:PartitionVertex}) - return unique(reduce(vcat, Iterators.map(pv -> vertices(pg, pv), partitionverts))) -end -function partitionvertex(pg::PartitionedGraph, vertex) - return PartitionVertex(which_partition(pg)[vertex]) -end - -function partitionvertices(pg::PartitionedGraph, verts) - return unique(partitionvertex(pg, v) for v in verts) -end -function partitionvertices(pg::PartitionedGraph) - return PartitionVertex.(vertices(pg.partitions_graph)) +function Base.copy(pg::PartitionedGraph) + return PartitionedGraph( + copy(pg.graph), + copy(pg.quotient_graph), + copy(pg.partitioned_vertices), + copy(pg.which_partition), + ) end -function partitionedge(pg::PartitionedGraph, edge::AbstractEdge) - return PartitionEdge( - parent(partitionvertex(pg, src(edge))) => parent(partitionvertex(pg, dst(edge))) +function insert_to_vertex_map!( + pg::PartitionedGraph, vertex, supervertex::SuperVertex ) -end + pv = parent(supervertex) -partitionedge(pg::PartitionedGraph, p::Pair) = partitionedge(pg, edgetype(pg)(p)) + push!(get!(pg.partitioned_vertices, pv, []), vertex) + unique!(pg.partitioned_vertices[pv]) -function partitionedges(pg::PartitionedGraph, edges::Vector) - return filter(!is_self_loop, unique([partitionedge(pg, e) for e in edges])) -end + insert!(pg.which_partition, vertex, pv) -function partitionedges(pg::PartitionedGraph) - return PartitionEdge.(edges(pg.partitions_graph)) + return pg end -function Graphs.edges(pg::PartitionedGraph, partitionedge::PartitionEdge) - psrc_vs = vertices(pg, src(partitionedge)) - pdst_vs = vertices(pg, dst(partitionedge)) - psrc_subgraph, _ = induced_subgraph(unpartitioned_graph(pg), psrc_vs) - pdst_subgraph, _ = induced_subgraph(pg, pdst_vs) - full_subgraph, _ = induced_subgraph(pg, vcat(psrc_vs, pdst_vs)) - - return setdiff(edges(full_subgraph), vcat(edges(psrc_subgraph), edges(pdst_subgraph))) +function delete_from_vertex_map!(pg::PartitionedGraph{V}, vertex::V) where {V} + sv = quotient_vertex(pg, vertex) + return delete_from_vertex_map!(pg, sv, vertex) end -function Graphs.edges(pg::PartitionedGraph, partitionedges::Vector{<:PartitionEdge}) - return unique(reduce(vcat, [edges(pg, pe) for pe in partitionedges])) +function delete_from_vertex_map!( + pg::PartitionedGraph{V}, sv::SuperVertex, vertex::V + ) where {V} + return delete_from_vertex_map!(pg, parent(sv), vertex) end -function boundary_partitionedges(pg::PartitionedGraph, partitionvertices; kwargs...) - return PartitionEdge.( - boundary_edges(pg.partitions_graph, parent.(partitionvertices); kwargs...) - ) -end +function delete_from_vertex_map!( + pg::PartitionedGraph{V, PV}, qv::PV, vertex::V + ) where {V, PV} -function boundary_partitionedges( - pg::PartitionedGraph, partitionvertex::PartitionVertex; kwargs... - ) - return boundary_partitionedges(pg, [partitionvertex]; kwargs...) -end + vs = partitioned_vertices(pg)[qv] -function Base.copy(pg::PartitionedGraph) - return PartitionedGraph( - copy(unpartitioned_graph(pg)), - copy(pg.partitions_graph), - copy(partitioned_vertices(pg)), - copy(which_partition(pg)), - ) + delete!(pg.partitioned_vertices, qv) + + if length(vs) != 1 + insert!(pg.partitioned_vertices, qv, setdiff(vs, [vertex])) + end + + delete!(pg.which_partition, vertex) + return pg end -function insert_to_vertex_map!( - pg::PartitionedGraph, vertex, partitionvertex::PartitionVertex - ) - pv = parent(partitionvertex) - if pv ∉ keys(partitioned_vertices(pg)) - insert!(partitioned_vertices(pg), pv, [vertex]) - else - partitioned_vertices(pg)[pv] = unique(vcat(vertices(pg, partitionvertex), [vertex])) +function Graphs.rem_vertex!(pg::PartitionedGraph{V}, vertex::V) where {V} + qv = quotient_vertex(pg, vertex) + + delete_from_vertex_map!(pg, qv, vertex) + + rem_vertex!(pg.graph, vertex) + + # If the super-vertex is now empty, remove it from the quotient graph + if !haskey(pg.partitioned_vertices, qv) + rem_vertex!(pg.quotient_graph, qv) end - insert!(which_partition(pg), vertex, pv) return pg end -function delete_from_vertex_map!(pg::PartitionedGraph, vertex) - pv = partitionvertex(pg, vertex) - return delete_from_vertex_map!(pg, pv, vertex) +function add_subsupervertex!(pg::PartitionedGraph{V}, sv::SuperVertex, vertex::V) where {V} + add_vertex!(pg.graph, vertex) + add_vertex!(pg.quotient_graph, parent(sv)) + insert_to_vertex_map!(pg, vertex, sv) + return pg end -function delete_from_vertex_map!( - pg::PartitionedGraph, partitioned_vertex::PartitionVertex, vertex - ) - vs = vertices(pg, partitioned_vertex) - delete!(partitioned_vertices(pg), parent(partitioned_vertex)) - if length(vs) != 1 - insert!(partitioned_vertices(pg), parent(partitioned_vertex), setdiff(vs, [vertex])) +function Graphs.add_edge!(pg::PartitionedGraph, edge::AbstractEdge) + @assert edge isa edgetype(pg) + add_edge!(pg.graph, edge) + pg_edge = quotient_edge(pg, edge) + if src(pg_edge) != dst(pg_edge) + add_edge!(pg.quotient_graph, pg_edge) end + return pg +end - delete!(which_partition(pg), vertex) - return partitioned_vertex +function Graphs.rem_edge!(pg::PartitionedGraph, edge::AbstractEdge) + @assert edge isa edgetype(pg) + # This already checks if the edge is in pg + se = superedge(pg, edge) + if se in superedges(pg) || reverse(se) in superedges(pg) + g_edges = edges(pg, se) + if length(g_edges) == 1 + # Remove the entire super-edge + return rem_edge!(pg.quotient_graph, parent(se)) + end + end + return rem_edge!(pg.graph, edge) end ### PartitionedGraph Specific Functions -function partitionedgraph_induced_subgraph(pg::PartitionedGraph, vertices::Vector) - sub_pg_graph, _ = induced_subgraph(unpartitioned_graph(pg), vertices) +function partitionedgraph_induced_subgraph(pg::PartitionedGraph, vlist) + sub_pg_graph, _ = induced_subgraph(unpartitioned_graph(pg), vlist) sub_partitioned_vertices = copy(partitioned_vertices(pg)) - for pv in NamedGraphs.vertices(pg.partitions_graph) - vs = intersect(vertices, sub_partitioned_vertices[pv]) + for pv in quotient_vertices(pg) + vs = intersect(vlist, sub_partitioned_vertices[pv]) if !isempty(vs) sub_partitioned_vertices[pv] = vs else @@ -172,17 +170,6 @@ function partitionedgraph_induced_subgraph(pg::PartitionedGraph, vertices::Vecto return PartitionedGraph(sub_pg_graph, sub_partitioned_vertices), nothing end -function partitionedgraph_induced_subgraph( - pg::PartitionedGraph, partitionverts::Vector{<:PartitionVertex} - ) - return induced_subgraph(pg, vertices(pg, partitionverts)) -end - -function Graphs.induced_subgraph(pg::PartitionedGraph, vertices) - return partitionedgraph_induced_subgraph(pg, vertices) -end - -# Fixes ambiguity error with `Graphs.jl`. -function Graphs.induced_subgraph(pg::PartitionedGraph, vertices::Vector{<:Integer}) - return partitionedgraph_induced_subgraph(pg, vertices) +function NamedGraphs._induced_subgraph(pg::PartitionedGraph, vlist) + return partitionedgraph_induced_subgraph(pg, vlist) end diff --git a/src/lib/PartitionedGraphs/src/partitionedview.jl b/src/lib/PartitionedGraphs/src/partitionedview.jl new file mode 100644 index 0000000..7fc7514 --- /dev/null +++ b/src/lib/PartitionedGraphs/src/partitionedview.jl @@ -0,0 +1,13 @@ +using ..OrderedDictionaries: OrderedIndices + +struct PartitionedView{V, PV, G <: AbstractGraph{V}, P} <: AbstractPartitionedGraph{V, PV} + graph::G + partitioned_vertices::P + function PartitionedView(graph::G, partitioned_vertices::P) where {V, G <: AbstractGraph{V}, P} + PV = keytype(partitioned_vertices) + return new{V, PV, G, P}(graph, partitioned_vertices) + end +end + +unpartitioned_graph(pv::PartitionedView) = getfield(pv, :graph) +partitioned_vertices(pv::PartitionedView) = getfield(pv, :partitioned_vertices) diff --git a/src/lib/PartitionedGraphs/src/partitionsgraphview.jl b/src/lib/PartitionedGraphs/src/partitionsgraphview.jl deleted file mode 100644 index 461d8ab..0000000 --- a/src/lib/PartitionedGraphs/src/partitionsgraphview.jl +++ /dev/null @@ -1,36 +0,0 @@ -struct PartitionsGraphView{V, G <: AbstractGraph{V}} <: AbstractNamedGraph{V} - graph::G -end - -Base.copy(g::PartitionsGraphView) = PartitionsGraphView(copy(g.graph)) - -using Graphs: AbstractGraph -partitions_graph(g::AbstractGraph) = PartitionsGraphView(PartitionedGraph(g, [vertices(g)])) - -# Graphs.jl and NamedGraphs.jl interface overloads for `PartitionsGraphView` wrapping -# a `PartitionedGraph`. -function NamedGraphs.position_graph_type( - type::Type{<:PartitionsGraphView{V, G}} - ) where {V, G <: PartitionedGraph{V}} - return fieldtype(fieldtype(fieldtype(type, :graph), :partitions_graph), :position_graph) -end -for f in [ - :(Graphs.add_vertex!), - :(Graphs.edges), - :(Graphs.vertices), - :(Graphs.rem_vertex!), - :(NamedGraphs.edgetype), - :(NamedGraphs.namedgraph_induced_subgraph), - :(NamedGraphs.ordered_vertices), - :(NamedGraphs.position_graph), - :(NamedGraphs.vertex_positions), - :(NamedGraphs.vertextype), - ] - @eval begin - function $f( - g::PartitionsGraphView{V, G}, args...; kwargs... - ) where {V, G <: PartitionedGraph{V}} - return $f(g.graph.partitions_graph, args...; kwargs...) - end - end -end diff --git a/src/lib/PartitionedGraphs/src/partitionvertex.jl b/src/lib/PartitionedGraphs/src/partitionvertex.jl deleted file mode 100644 index a2d46c6..0000000 --- a/src/lib/PartitionedGraphs/src/partitionvertex.jl +++ /dev/null @@ -1,5 +0,0 @@ -struct PartitionVertex{V} <: AbstractPartitionVertex{V} - vertex::V -end - -Base.parent(pv::PartitionVertex) = getfield(pv, :vertex) diff --git a/src/lib/PartitionedGraphs/src/quotientgraph.jl b/src/lib/PartitionedGraphs/src/quotientgraph.jl new file mode 100644 index 0000000..114e3ed --- /dev/null +++ b/src/lib/PartitionedGraphs/src/quotientgraph.jl @@ -0,0 +1,54 @@ +using Graphs: AbstractGraph, rem_vertex!, rem_edge!, vertices, edges +using .GraphsExtensions: add_edges! +using ..NamedGraphs: NamedGraph, position_graph_type + +struct QuotientView{V, G <: AbstractGraph} <: AbstractNamedGraph{V} + graph::G + QuotientView(graph::G) where {G} = new{quotient_vertextype(G), G}(graph) +end + +Base.parent(qg::QuotientView) = qg.graph +parent_graph_type(::Type{<:QuotientView{V, G}}) where {V, G} = G + +function Base.convert(GT::Type{<:AbstractGraph}, g::QuotientView) + qg = GT(vertices(g)) + add_edges!(qg, edges(g)) + return qg +end + +NamedGraphs.edgetype(Q::Type{<:QuotientView}) = quotient_edgetype(parent_graph_type(Q)) + +Graphs.vertices(qg::QuotientView) = quotient_vertices(parent(qg)) +Graphs.edges(qg::QuotientView) = quotient_edges(parent(qg)) + +Base.copy(g::QuotientView) = QuotientView(copy(parent(g))) + +# Graphs.jl and NamedGraphs.jl interface overloads for `PartitionsGraphView` wrapping +# a `PartitionedGraph`. +function NamedGraphs.position_graph_type(type::Type{<:QuotientView}) + return position_graph_type(quotient_graph_type(parent_graph_type(type))) +end + +function Graphs.rem_vertex!(qg::QuotientView, v) + rem_supervertex!(parent(qg), SuperVertex(v)) + return qg +end +function Graphs.rem_edge!(qg::QuotientView, v) + rem_superedge!(parent(qg), SuperEdge(v)) + return qg +end + +for f in [ + :(NamedGraphs.namedgraph_induced_subgraph), + :(NamedGraphs.ordered_vertices), + :(NamedGraphs.position_graph), + :(NamedGraphs.vertex_positions), + ] + @eval begin + function $f( + g::QuotientView{V, G}, args...; kwargs... + ) where {V, G} + return $f(convert(NamedGraph, (g)), args...; kwargs...) + end + end +end diff --git a/src/lib/PartitionedGraphs/src/subsupervertex.jl b/src/lib/PartitionedGraphs/src/subsupervertex.jl new file mode 100644 index 0000000..ac3597a --- /dev/null +++ b/src/lib/PartitionedGraphs/src/subsupervertex.jl @@ -0,0 +1,6 @@ +struct SubSuperVertex{V, SV} + vertex::SuperVertex{V} + subvertex::SV +end + +Base.getindex(sv::SuperVertex, v) = SubSuperVertex(sv, v) diff --git a/src/lib/PartitionedGraphs/src/superedge.jl b/src/lib/PartitionedGraphs/src/superedge.jl new file mode 100644 index 0000000..be34605 --- /dev/null +++ b/src/lib/PartitionedGraphs/src/superedge.jl @@ -0,0 +1,62 @@ +using Graphs: AbstractGraph, Graphs, AbstractEdge, dst, src, ne, has_edge +using ..NamedGraphs: AbstractNamedEdge +using ..NamedGraphs.GraphsExtensions: GraphsExtensions, not_implemented, rem_edges!, rem_edge + +struct SuperEdge{V, E <: AbstractEdge{V}} <: AbstractNamedEdge{V} + edge::E +end + +superedge(pg::AbstractGraph, edge::AbstractEdge) = SuperEdge(quotient_edge(pg, edge)) +superedge(pg::AbstractGraph, p::Pair) = superedge(pg, edgetype(pg)(p)) + +""" + superedges(pg::AbstractPartitionedGraph, es = edges(pg)) + +Return all unique super edges corresponding to the set edges `es` of the graph `pg`. +""" +superedges(pg::AbstractGraph) = SuperEdge.(quotient_edges(pg)) +function superedges(pg::AbstractGraph, es) + return filter!(!is_self_loop, unique(map(e -> superedge(pg, e), es))) +end + +Base.parent(se::SuperEdge) = getfield(se, :edge) +Graphs.src(se::SuperEdge) = SuperVertex(src(parent(se))) +Graphs.dst(se::SuperEdge) = SuperVertex(dst(parent(se))) +SuperEdge(p::Pair) = SuperEdge(NamedEdge(first(p) => last(p))) +SuperEdge(vsrc, vdst) = SuperEdge(vsrc => vdst) +Base.reverse(se::SuperEdge) = SuperEdge(reverse(parent(se))) + +""" + edges(pg::AbstractGraph, superedge::SuperEdge) + edges(pg::AbstractGraph, superedges::Vector{SuperEdge}) + +Return the set of edges in the partitioned graph `pg` that correspond to the super edge ` +superedge` or set of super edges `superedges`. +""" +function Graphs.edges(pg::AbstractGraph, superedge::SuperEdge) + + pes = partitioned_edges(pg) + defval = edgetype(pg)[] + + rv = get(pes, parent(superedge), defval) + if !is_directed(quotient_graph_type(pg)) && isempty(rv) + append!(rv, get(pes, reverse(parent(superedge)), defval)) + end + + isempty(rv) && throw(ArgumentError("Super edge $superedge not in graph")) + + return rv +end +function Graphs.edges(pg::AbstractGraph, superedges::Vector{<:SuperEdge}) + return unique(reduce(vcat, [edges(pg, se) for se in superedges])) +end + +has_superedge(g::AbstractGraph, se::SuperEdge) = has_edge(quotient_graph(g), parent(se)) + +Graphs.ne(g::AbstractGraph, se::SuperEdge) = length(edges(g, se)) + +function GraphsExtensions.rem_edges!(g::AbstractGraph, sv::SuperEdge) + rv = rem_edges!(g, edges(g, sv)) + return rv +end +rem_superedge!(pg::AbstractGraph, sv::SuperEdge) = rem_edges!(pg, sv) diff --git a/src/lib/PartitionedGraphs/src/supervertex.jl b/src/lib/PartitionedGraphs/src/supervertex.jl new file mode 100644 index 0000000..96c697d --- /dev/null +++ b/src/lib/PartitionedGraphs/src/supervertex.jl @@ -0,0 +1,54 @@ +using Graphs: AbstractGraph, Graphs, nv, induced_subgraph +using ..NamedGraphs: NamedGraphs, AbstractNamedGraph +using ..NamedGraphs.GraphsExtensions: GraphsExtensions, rem_vertices!, subgraph + +struct SuperVertex{V} + vertex::V +end + +Base.parent(sv::SuperVertex) = getfield(sv, :vertex) + +supervertex(g::AbstractGraph, vertex) = SuperVertex(quotient_vertex(g, vertex)) + +""" + supervertices(g::AbstractGraph, vs = vertices(pg)) + +Return all unique super vertices corresponding to the set vertices `vs` of the graph `pg`. +""" +supervertices(g::AbstractGraph) = SuperVertex.(quotient_vertices(g)) +supervertices(g::AbstractGraph, vs) = unique(map(v -> supervertex(g, v), vs)) + +""" + vertices(g::AbstractGraph, supervertex::SuperVertex) + vertices(g::AbstractGraph, supervertices::Vector{SuperVertex}) + +Return the set of vertices in the graph `g` associated with the super vertex +`supervertex` or set of super vertices `supervertices`. +""" +function Graphs.vertices(g::AbstractGraph, supervertex::SuperVertex) + qv = parent(supervertex) + + pvs = partitioned_vertices(g) + haskey(pvs, qv) || throw(ArgumentError("Super vertex $supervertex not in graph")) + + return pvs[qv] +end +function Graphs.vertices(g::AbstractGraph, supervertices::Vector{<:SuperVertex}) + return unique(mapreduce(sv -> vertices(g, sv), vcat, supervertices)) +end + +function has_supervertex(g::AbstractGraph, supervertex::SuperVertex) + qg = quotient_graph_type(g)(quotient_vertices(g)) + return has_vertex(qg, parent(supervertex)) +end + +Graphs.nv(g::AbstractGraph, sv::SuperVertex) = length(vertices(g, sv)) + +function GraphsExtensions.rem_vertices!(g::AbstractGraph, sv::SuperVertex) + return rem_vertices!(g, vertices(g, sv)) +end +rem_supervertex!(pg::AbstractGraph, sv::SuperVertex) = rem_vertices!(pg, sv) + +function NamedGraphs.to_vertices(g::AbstractGraph, sv::Union{SV, Vector{SV}}) where {SV <: SuperVertex} + return vertices(g, sv) +end diff --git a/src/namedgraph.jl b/src/namedgraph.jl index daa7d56..5a6eaab 100644 --- a/src/namedgraph.jl +++ b/src/namedgraph.jl @@ -81,6 +81,7 @@ end # Constructors from `AbstractSimpleGraph` # +to_vertices(graph, vertices) = to_vertices(vertices) to_vertices(vertices) = vertices to_vertices(vertices::AbstractArray) = vec(vertices) to_vertices(vertices::Integer) = Base.OneTo(vertices) @@ -97,7 +98,7 @@ 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)) + convert(G, position_graph), OrderedIndices{V}(to_vertices(position_graph, vertices)) ) end @@ -198,7 +199,6 @@ function Base.copy(graph::GenericNamedGraph) end Graphs.edgetype(graph_type::Type{<:GenericNamedGraph}) = NamedEdge{vertextype(graph_type)} -Graphs.edgetype(graph::GenericNamedGraph) = edgetype(typeof(graph)) function GraphsExtensions.directed_graph_type(graph_type::Type{<:GenericNamedGraph}) return GenericNamedGraph{ @@ -229,11 +229,15 @@ function namedgraph_induced_subgraph(graph::AbstractGraph, subvertices) return subgraph, nothing end -function Graphs.induced_subgraph(graph::AbstractNamedGraph, subvertices) - return namedgraph_induced_subgraph(graph, subvertices) +function Graphs.induced_subgraph(graph::AbstractNamedGraph, vlist) + return _induced_subgraph(graph, to_vertices(graph, vlist)) +end +# For method ambiguity resolution with Graphs.jl +function Graphs.induced_subgraph(graph::AbstractNamedGraph, vlist::AbstractVector{<:Integer}) + return _induced_subgraph(graph, to_vertices(graph, vlist)) end -function Graphs.induced_subgraph(graph::AbstractNamedGraph, subvertices::Vector{<:Integer}) +function _induced_subgraph(graph::AbstractNamedGraph, subvertices) return namedgraph_induced_subgraph(graph, subvertices) end diff --git a/test/Project.toml b/test/Project.toml index e75deaf..b78e64e 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -24,7 +24,7 @@ Graphs = "1.12" GraphsFlows = "0.1.1" KaHyPar = "0.3.1" Metis = "1.5.0" -NamedGraphs = "0.7.0" +NamedGraphs = "0.8.0" SafeTestsets = "0.1.0" SimpleGraphAlgorithms = "0.6.0" SimpleGraphConverter = "0.1.0" diff --git a/test/test_partitionedgraph.jl b/test/test_partitionedgraph.jl index e3c35d3..6a6463e 100644 --- a/test/test_partitionedgraph.jl +++ b/test/test_partitionedgraph.jl @@ -1,10 +1,13 @@ @eval module $(gensym()) using Graphs: + AbstractGraph, + Graphs, a_star, center, connected_components, diameter, edges, + has_edge, has_vertex, is_connected, is_directed, @@ -35,20 +38,30 @@ using NamedGraphs.NamedGraphGenerators: named_comb_tree, named_grid, named_triangular_lattice_graph using NamedGraphs.OrderedDictionaries: OrderedDictionary using NamedGraphs.PartitionedGraphs: - PartitionEdge, - PartitionVertex, + AbstractPartitionedGraph, PartitionedGraph, - PartitionsGraphView, - boundary_partitionedges, - partitions_graph, - partitionedge, - partitionedges, - partitionvertex, - partitionvertices, + PartitionedGraphs, + PartitionedView, + QuotientView, + SuperEdge, + SuperVertex, + boundary_superedges, + departition, + has_supervertex, + partitioned_edges, + partitioned_vertices, + partitionedgraph, + quotient_graph, + rem_supervertex!, + superedge, + superedges, + supervertex, + supervertices, + unpartition, unpartitioned_graph using Dictionaries: Dictionary, dictionary using Pkg: Pkg -using Test: @test, @testset +using Test: @test, @testset, @test_throws @testset "Test Partitioned Graph Constructors" begin nx, ny = 10, 10 @@ -57,20 +70,20 @@ using Test: @test, @testset #Partition it column-wise (into a 1D chain) partitions = [[(i, j) for j in 1:ny] for i in 1:nx] pg = PartitionedGraph(g, partitions) - @test vertextype(partitions_graph(pg)) == Int64 + @test vertextype(QuotientView(pg)) == Int64 @test vertextype(unpartitioned_graph(pg)) == vertextype(g) - @test isa(partitionvertices(pg), OrderedDictionary{Int64, PartitionVertex{Int64}}) - @test isa(partitionedges(pg), Vector{PartitionEdge{Int64, NamedEdge{Int64}}}) - @test is_tree(partitions_graph(pg)) + @test isa(supervertices(pg), OrderedDictionary{Int64, SuperVertex{Int64}}) + @test isa(superedges(pg), Vector{SuperEdge{Int64, NamedEdge{Int64}}}) + @test is_tree(QuotientView(pg)) @test nv(pg) == nx * ny - @test nv(partitions_graph(pg)) == nx + @test nv(QuotientView(pg)) == nx pg_c = copy(pg) @test pg_c == pg #PartionsGraphView test - pgv = PartitionsGraphView(pg) - @test vertices(pgv) == parent.(partitionvertices(pg)) - @test edges(pgv) == parent.(partitionedges(pg)) + pgv = QuotientView(pg) + @test vertices(pgv) == parent.(supervertices(pg)) + @test edges(pgv) == parent.(superedges(pg)) @test is_tree(pgv) == true @test neighbors(pgv, 1) == [2] @test issetequal(vertices(subgraph(pgv, [2, 3, 4])), [2, 3, 4]) @@ -79,29 +92,35 @@ using Test: @test, @testset #Same partitioning but with a dictionary constructor partition_dict = Dictionary([first(partition) for partition in partitions], partitions) pg = PartitionedGraph(g, partition_dict) - @test vertextype(partitions_graph(pg)) == vertextype(g) + @test vertextype(QuotientView(pg)) == vertextype(g) @test vertextype(unpartitioned_graph(pg)) == vertextype(g) @test isa( - partitionvertices(pg), - OrderedDictionary{Tuple{Int64, Int64}, PartitionVertex{Tuple{Int64, Int64}}}, + supervertices(pg), + OrderedDictionary{Tuple{Int64, Int64}, SuperVertex{Tuple{Int64, Int64}}}, ) @test isa( - partitionedges(pg), - Vector{PartitionEdge{Tuple{Int64, Int64}, NamedEdge{Tuple{Int64, Int64}}}}, + superedges(pg), + Vector{SuperEdge{Tuple{Int64, Int64}, NamedEdge{Tuple{Int64, Int64}}}}, ) - @test is_tree(partitions_graph(pg)) + @test is_tree(QuotientView(pg)) @test nv(pg) == nx * ny - @test nv(partitions_graph(pg)) == nx + @test nv(pg, SuperVertex((1, 1))) == ny + @test_throws ArgumentError nv(pg, SuperVertex((11, 11))) + @test nv(QuotientView(pg)) == nx + @test ne(pg) == (nx - 1) * ny + nx * (ny - 1) + @test ne(pg, SuperEdge((1, 1) => (2, 1))) == ny + @test_throws ArgumentError ne(pg, SuperEdge((1, 1) => (1, 2))) + @test ne(QuotientView(pg)) == 9 pg_c = copy(pg) @test pg_c == pg #Partition the whole thing into just 1 vertex pg = PartitionedGraph([i for i in 1:nx]) - @test unpartitioned_graph(pg) == partitions_graph(pg) + @test unpartitioned_graph(pg) == QuotientView(pg) @test nv(pg) == nx - @test nv(partitions_graph(pg)) == nx + @test nv(QuotientView(pg)) == nx @test ne(pg) == 0 - @test ne(partitions_graph(pg)) == 0 + @test ne(QuotientView(pg)) == 0 pg_c = copy(pg) @test pg_c == pg end @@ -113,25 +132,25 @@ end #Partition it column-wise (into a square grid) partitions = [[(i, j, k) for k in 1:nz] for i in 1:nx for j in 1:ny] pg = PartitionedGraph(g, partitions) - @test Set(partitionvertices(pg)) == Set(partitionvertices(pg, vertices(g))) - @test Set(partitionedges(pg)) == Set(partitionedges(pg, edges(g))) - @test is_self_loop(partitionedge(pg, (1, 1, 1) => (1, 1, 2))) - @test !is_self_loop(partitionedge(pg, (1, 2, 1) => (1, 1, 1))) - @test partitionvertex(pg, (1, 1, 1)) == partitionvertex(pg, (1, 1, nz)) - @test partitionvertex(pg, (2, 1, 1)) != partitionvertex(pg, (1, 1, nz)) - - @test partitionedge(pg, (1, 1, 1) => (2, 1, 1)) == - partitionedge(pg, (1, 1, 2) => (2, 1, 2)) + @test Set(supervertices(pg)) == Set(supervertices(pg, vertices(g))) + @test Set(superedges(pg)) == Set(superedges(pg, edges(g))) + @test is_self_loop(superedge(pg, (1, 1, 1) => (1, 1, 2))) + @test !is_self_loop(superedge(pg, (1, 2, 1) => (1, 1, 1))) + @test supervertex(pg, (1, 1, 1)) == supervertex(pg, (1, 1, nz)) + @test supervertex(pg, (2, 1, 1)) != supervertex(pg, (1, 1, nz)) + + @test superedge(pg, (1, 1, 1) => (2, 1, 1)) == + superedge(pg, (1, 1, 2) => (2, 1, 2)) inter_column_edges = [(1, 1, i) => (2, 1, i) for i in 1:nz] - @test length(partitionedges(pg, inter_column_edges)) == 1 - @test length(partitionvertices(pg, [(1, 2, i) for i in 1:nz])) == 1 - @test all([length(edges(pg, pe)) == nz for pe in partitionedges(pg)]) + @test length(superedges(pg, inter_column_edges)) == 1 + @test length(supervertices(pg, [(1, 2, i) for i in 1:nz])) == 1 + @test all([length(edges(pg, pe)) == nz for pe in superedges(pg)]) - boundary_sizes = [length(boundary_partitionedges(pg, pv)) for pv in partitionvertices(pg)] + boundary_sizes = [length(boundary_superedges(pg, pv)) for pv in supervertices(pg)] #Partitions into a square grid so each partition should have maximum 4 incoming edges and minimum 2 @test maximum(boundary_sizes) == 4 @test minimum(boundary_sizes) == 2 - @test isempty(boundary_partitionedges(pg, partitionvertices(pg))) + @test isempty(boundary_superedges(pg, supervertices(pg))) end @testset "Test Partitioned Graph Vertex/Edge Addition and Removal" begin @@ -141,28 +160,29 @@ end partitions = [[(i, j) for j in 1:ny] for i in 1:nx] pg = PartitionedGraph(g, partitions) - pv = PartitionVertex(5) + pv = SuperVertex(5) v_set = vertices(pg, pv) edges_involving_v_set = boundary_edges(g, v_set) #Strip the middle column from pg via the partitioned graph vertex, and make a new pg - rem_vertex!(pg, pv) - @test !is_connected(unpartitioned_graph(pg)) && !is_connected(partitions_graph(pg)) - @test parent(pv) ∉ vertices(partitions_graph(pg)) + rem_supervertex!(pg, pv) + @test !is_connected(unpartitioned_graph(pg)) && !is_connected(QuotientView(pg)) + @test parent(pv) ∉ vertices(QuotientView(pg)) @test !has_vertex(pg, pv) @test nv(pg) == (nx - 1) * ny - @test nv(partitions_graph(pg)) == nx - 1 - @test !is_tree(partitions_graph(pg)) + @test nv(QuotientView(pg)) == nx - 1 + @test !is_tree(QuotientView(pg)) #Add the column back to the in place graph - add_vertices!(pg, v_set, pv) + add_vertices!(pg, map(v -> pv[v], v_set)) add_edges!(pg, edges_involving_v_set) - @test is_connected(pg.graph) && is_path_graph(partitions_graph(pg)) - @test parent(pv) ∈ vertices(partitions_graph(pg)) - @test has_vertex(pg, pv) - @test is_tree(partitions_graph(pg)) + @test is_connected(pg.graph) + @test is_path_graph(QuotientView(pg)) + @test parent(pv) ∈ vertices(QuotientView(pg)) + @test has_supervertex(pg, pv) + @test is_tree(QuotientView(pg)) @test nv(pg) == nx * ny - @test nv(partitions_graph(pg)) == nx + @test nv(QuotientView(pg)) == nx end @testset "Test Partitioned Graph Subgraph Functionality" begin @@ -180,15 +200,15 @@ end vcat, [partitions[spv] for spv in subgraph_partitioned_vertices] ) - pg_1 = subgraph(pg, PartitionVertex.(subgraph_partitioned_vertices)) + pg_1 = subgraph(pg, SuperVertex.(subgraph_partitioned_vertices)) pg_2 = subgraph(pg, subgraph_vertices) @test pg_1 == pg_2 @test nv(pg_1) == length(subgraph_vertices) - @test nv(partitions_graph(pg_1)) == length(subgraph_partitioned_vertices) + @test nv(QuotientView(pg_1)) == length(subgraph_partitioned_vertices) subgraph_partitioned_vertex = 3 subgraph_vertices = partitions[subgraph_partitioned_vertex] - g_1 = subgraph(pg, PartitionVertex(subgraph_partitioned_vertex)) + g_1 = subgraph(pg, SuperVertex(subgraph_partitioned_vertex)) pg_1 = subgraph(pg, subgraph_vertices) @test unpartitioned_graph(pg_1) == subgraph(g, subgraph_vertices) @test g_1 == subgraph(g, subgraph_vertices) @@ -207,9 +227,9 @@ end pg = PartitionedGraph(g, [vertices(g)]) @test f(pg) == f(unpartitioned_graph(pg)) @test nv(pg) == nv(g) - @test nv(partitions_graph(pg)) == 1 + @test nv(QuotientView(pg)) == 1 @test ne(pg) == ne(g) - @test ne(partitions_graph(pg)) == 0 + @test ne(QuotientView(pg)) == 0 end end end @@ -226,7 +246,102 @@ end for backend in backends pg = PartitionedGraph(g; npartitions, backend = "metis") @test pg isa PartitionedGraph - @test nv(partitions_graph(pg)) == npartitions + @test nv(QuotientView(pg)) == npartitions end end + +# Not an AbstractPartitionedGraph +struct MyGraph{V, P} <: AbstractGraph{V} + g::NamedGraph{V} + partitioned_vertices::P +end +struct MyFastGraph{V, PV, QG, PE} <: AbstractGraph{V} + g::NamedGraph{V} + partitioned_vertices::PV + quotient_graph::QG + partitioned_edges::PE +end + +Graphs.edges(mg::MyGraph) = edges(mg.g) +Graphs.vertices(mg::MyGraph) = vertices(mg.g) + +Graphs.edgetype(mg::MyGraph) = edgetype(mg.g) +Graphs.has_edge(mg::MyGraph, e) = has_edge(mg.g, e) + +PartitionedGraphs.partitioned_vertices(mg::MyGraph) = mg.partitioned_vertices + +PartitionedGraphs.partitioned_vertices(mg::MyFastGraph) = mg.partitioned_vertices +PartitionedGraphs.quotient_graph(mg::MyFastGraph) = mg.quotient_graph +PartitionedGraphs.partitioned_edges(mg::MyFastGraph) = mg.partitioned_edges + +struct WrapperGraph{V, G <: AbstractGraph{V}} <: AbstractGraph{V} + g::G +end + +Graphs.edges(wg::WrapperGraph) = edges(wg.g) +Graphs.vertices(wg::WrapperGraph) = vertices(wg.g) + +PartitionedGraphs.partitioned_vertices(wg::WrapperGraph) = partitioned_vertices(wg.g) + +@testset "Partitioning of non-partitioned graphs" begin + nx, ny = 4, 4 + + g = named_grid((nx, ny)) + + partitions = [[(i, j) for j in 1:ny] for i in 1:nx] + + @test nv(QuotientView(g)) == 1 + @test ne(QuotientView(g)) == 0 + + @test nv(PartitionedView(g, partitions)) == nx * ny + @test ne(PartitionedView(g, partitions)) == (nx - 1) * ny + nx * (ny - 1) + + @test nv(QuotientView(PartitionedView(g, partitions))) == nx + @test ne(QuotientView(PartitionedView(g, partitions))) == ny - 1 + + qg = quotient_graph(PartitionedView(g, partitions)) + pes = partitioned_edges(PartitionedView(g, partitions)) + + mg = MyGraph(g, partitions) + @test nv(QuotientView(mg)) == nx + @test ne(QuotientView(mg)) == ny - 1 + + @test quotient_graph(mg) == qg + @test partitioned_edges(mg) == pes + + mfg = MyFastGraph(g, partitions, qg, pes) + + # Test overloads are working correctly. + @test partitioned_vertices(mfg) === partitions + @test quotient_graph(mfg) === qg + @test partitioned_edges(mfg) === pes +end + +@testset "Nesting partitions" begin + nx, ny, nz = 3, 4, 5 + g = named_grid((nx, ny, nz)) + + # First partition: columns along z-axis + p1 = Dict((i, j) => [(i, j, k) for k in 1:nz] for i in 1:nx for j in 1:ny) + # Second partition: columns along y-axis + p2 = [[(i, j, k) for j in 1:ny] for i in 1:nx for k in 1:nz] + + wg = WrapperGraph(g) + pwg1 = partitionedgraph(wg, p1) + @test nv(QuotientView(pwg1)) == nx * ny + pwg2 = partitionedgraph(wg, p2) + @test nv(QuotientView(pwg2)) == nx * nz + + pwg1_2 = partitionedgraph(pwg2, p1) + @test nv(QuotientView(pwg1)) == nx * ny + pwg2_1 = partitionedgraph(pwg1, p2) + @test nv(QuotientView(pwg2)) == nx * nz + + @test departition(pwg1_2) == pwg2 + @test departition(pwg2_1) == pwg1 + @test unpartition(pwg1_2) == wg + + p1q = [[(i, j) for j in 1:ny] for i in 1:nx] + partitionedgraph(QuotientView(pwg1), p1q) +end end