diff --git a/Project.toml b/Project.toml index 10d1188..465b690 100644 --- a/Project.toml +++ b/Project.toml @@ -9,9 +9,11 @@ Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" GraphsFlows = "06909019-6f44-4949-96fc-b9d9aaa02889" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +Requires = "ae029012-a4dd-5104-9daa-d747884805df" SimpleTraits = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" SplitApplyCombine = "03a91e81-4c3e-53e1-a0a4-9c0c8f19dd66" +Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb" SymRCM = "286e6d88-80af-4590-acc9-0001b223b9bd" [compat] @@ -24,6 +26,8 @@ SimpleTraits = "0.9" SparseArrays = "1.7" SplitApplyCombine = "1.2.2" SymRCM = "0.2.1" +Suppressor = "0.2" +Requires = "1.3" julia = "1.7" [extras] diff --git a/examples/partitioning.jl b/examples/partitioning.jl new file mode 100644 index 0000000..47a0e8b --- /dev/null +++ b/examples/partitioning.jl @@ -0,0 +1,16 @@ +using NamedGraphs +using Metis + +g = named_grid((4, 4)) +npartitions = 4 + +pg_metis = PartitionedGraph(g; npartitions, backend="Metis") + +@show pg_metis isa PartitionedGraph + +if !Sys.iswindows() + using KaHyPar + pg_kahypar = PartitionedGraph(g; npartitions, backend="KaHyPar") + @show nv(partitioned_graph(pg_kahypar)) == nv(partitioned_graph(pg_metis)) == npartitions + @show pg_kahypar isa PartitionedGraph +end diff --git a/src/Graphs/abstractgraph.jl b/src/Graphs/abstractgraph.jl index 06b4409..f24e003 100644 --- a/src/Graphs/abstractgraph.jl +++ b/src/Graphs/abstractgraph.jl @@ -43,7 +43,7 @@ end # to avoid method overwrite warnings, see: # https://github.com/mauro3/SimpleTraits.jl#method-overwritten-warnings @traitfn function undirected_graph(graph::::IsDirected) - undigraph = undirected_graph(typeof(graph))(vertices(graph)) + undigraph = undirected_graph_type(typeof(graph))(vertices(graph)) for e in edges(graph) # TODO: Check for repeated edges? add_edge!(undigraph, e) @@ -384,6 +384,29 @@ function mincut_partitions(graph::AbstractGraph, distmx=weights(graph)) return parts[1], parts[2] end +function insert_vertex!(graph::AbstractGraph, vertex) + in_graph = !add_vertex!(graph, vertex) + if in_graph + error("Duplicate vertices are not allowed") + end + return graph +end + +function delete_vertex!(graph::AbstractGraph, vertex) + in_graph = rem_vertex!(graph, vertex) + if !in_graph + error("Vertex not in graph") + end + return graph +end + +function add_vertices!(graph::AbstractGraph, vs::Vector) + for vertex in vs + add_vertex!(graph, vertex) + end + return graph +end + """Remove a list of edges from a graph g""" function rem_edges!(g::AbstractGraph, edges) for e in edges @@ -450,7 +473,6 @@ function random_bfs_tree(g::AbstractGraph, s; maxiter=1000 * (nv(g) + ne(g))) end end end - isempty_Q = isempty(Q) if isempty_Q break @@ -459,6 +481,5 @@ function random_bfs_tree(g::AbstractGraph, s; maxiter=1000 * (nv(g) + ne(g))) if !isempty_Q error("Search failed to cover the graph in time. Consider increasing maxiter.") end - return g_out end diff --git a/src/Graphs/partitionedgraphs/abstractpartitionedge.jl b/src/Graphs/partitionedgraphs/abstractpartitionedge.jl new file mode 100644 index 0000000..e907238 --- /dev/null +++ b/src/Graphs/partitionedgraphs/abstractpartitionedge.jl @@ -0,0 +1,7 @@ +abstract type AbstractPartitionEdge{V} <: AbstractNamedEdge{V} end + +parent(pe::AbstractPartitionEdge) = not_implemented() +src(pe::AbstractPartitionEdge) = not_implemented() +dst(pe::AbstractPartitionEdge) = not_implemented() + +#Don't have the vertices wrapped. But wrap them with source and edge. diff --git a/src/Graphs/partitionedgraphs/abstractpartitionedgraph.jl b/src/Graphs/partitionedgraphs/abstractpartitionedgraph.jl new file mode 100644 index 0000000..5ef0692 --- /dev/null +++ b/src/Graphs/partitionedgraphs/abstractpartitionedgraph.jl @@ -0,0 +1,173 @@ +abstract type AbstractPartitionedGraph{V,PV} <: AbstractNamedGraph{V} end + +#Needed for interface +partitioned_graph(pg::AbstractPartitionedGraph) = not_implemented() +unpartitioned_graph(pg::AbstractPartitionedGraph) = not_implemented() +which_partition(pg::AbstractPartitionedGraph, vertex) = not_implemented() +partitioned_vertices(pg::AbstractPartitionedGraph) = not_implemented() +copy(pg::AbstractPartitionedGraph) = not_implemented() +delete_from_vertex_map!(pg::AbstractPartitionedGraph, vertex) = not_implemented() +insert_to_vertex_map!(pg::AbstractPartitionedGraph, vertex) = not_implemented() +partition_edge(pg::AbstractPartitionedGraph, edge) = not_implemented() +function edges(pg::AbstractPartitionedGraph, partition_edge::AbstractPartitionEdge) + return not_implemented() +end +vertices(pg::AbstractPartitionedGraph, pv::AbstractPartitionVertex) = not_implemented() +function vertices( + pg::AbstractPartitionedGraph, partition_verts::Vector{V} +) where {V<:AbstractPartitionVertex} + return not_implemented() +end +parent_graph_type(PG::Type{<:AbstractPartitionedGraph}) = not_implemented() +directed_graph_type(PG::Type{<:AbstractPartitionedGraph}) = not_implemented() +undirected_graph_type(PG::Type{<:AbstractPartitionedGraph}) = not_implemented() + +#Functions for the abstract type +vertices(pg::AbstractPartitionedGraph) = vertices(unpartitioned_graph(pg)) +parent_graph(pg::AbstractPartitionedGraph) = parent_graph(unpartitioned_graph(pg)) +function vertex_to_parent_vertex(pg::AbstractPartitionedGraph, vertex) + return vertex_to_parent_vertex(unpartitioned_graph(pg), vertex) +end +edgetype(pg::AbstractPartitionedGraph) = edgetype(unpartitioned_graph(pg)) +parent_graph_type(pg::AbstractPartitionedGraph) = parent_graph_type(unpartitioned_graph(pg)) +nv(pg::AbstractPartitionedGraph, pv::AbstractPartitionVertex) = length(vertices(pg, pv)) +function has_vertex(pg::AbstractPartitionedGraph, partition_vertex::AbstractPartitionVertex) + return has_vertex(partitioned_graph(pg), parent(partition_vertex)) +end + +function has_edge(pg::AbstractPartitionedGraph, edge::AbstractPartitionEdge) + return has_edge(partitioned_graph(pg), parent(partition_edge)) +end + +function is_boundary_edge(pg::AbstractPartitionedGraph, edge::AbstractEdge) + p_edge = partition_edge(pg, edge) + return src(p_edge) == dst(p_edge) +end + +function add_edge!(pg::AbstractPartitionedGraph, edge::AbstractEdge) + add_edge!(unpartitioned_graph(pg), edge) + pg_edge = parent(partition_edge(pg, edge)) + if src(pg_edge) != dst(pg_edge) + add_edge!(partitioned_graph(pg), pg_edge) + end + + return pg +end + +function rem_edge!(pg::AbstractPartitionedGraph, edge::AbstractEdge) + pg_edge = partition_edge(pg, edge) + if has_edge(partitioned_graph(pg), pg_edge) + g_edges = edges(pg, pg_edge) + if length(g_edges) == 1 + rem_edge!(partitioned_graph(pg), pg_edge) + end + end + return rem_edge!(unpartitioned_graph(pg), edge) +end + +function rem_edge!(pg::AbstractPartitionedGraph, partition_edge::AbstractPartitionEdge) + return rem_edges!(pg, edges(pg, parent(partition_edge))) +end + +function rem_edge(pg::AbstractPartitionedGraph, partition_edge::AbstractPartitionEdge) + pg_new = copy(pg) + rem_edge!(pg_new, partition_edge) + return pg_new +end + +function rem_edges!( + pg::AbstractPartitionedGraph, partition_edges::Vector{<:AbstractPartitionEdge} +) + for pe in partition_edges + rem_edge!(pg, pe) + end + return pg +end + +function rem_edges( + pg::AbstractPartitionedGraph, partition_edges::Vector{<:AbstractPartitionEdge} +) + pg_new = copy(pg) + rem_edges!(pg_new, partition_edges) + return pg_new +end + +#Vertex addition and removal. I think it's important not to allow addition of a vertex without specification of PV +function add_vertex!( + pg::AbstractPartitionedGraph, vertex, partition_vertex::AbstractPartitionVertex +) + add_vertex!(unpartitioned_graph(pg), vertex) + add_vertex!(partitioned_graph(pg), parent(partition_vertex)) + insert_to_vertex_map!(pg, vertex, partition_vertex) + return pg +end + +function add_vertices!( + pg::AbstractPartitionedGraph, + vertices::Vector, + partition_vertices::Vector{<:AbstractPartitionVertex}, +) + @assert length(vertices) == length(partition_vertices) + for (v, pv) in zip(vertices, partition_vertices) + add_vertex!(pg, v, pv) + end + + return pg +end + +function add_vertices!( + pg::AbstractPartitionedGraph, vertices::Vector, partition_vertex::AbstractPartitionVertex +) + add_vertices!(pg, vertices, fill(partition_vertex, length(vertices))) + return pg +end + +function rem_vertex!(pg::AbstractPartitionedGraph, vertex) + pv = which_partition(pg, vertex) + delete_from_vertex_map!(pg, pv, vertex) + rem_vertex!(unpartitioned_graph(pg), vertex) + if !haskey(partitioned_vertices(pg), parent(pv)) + rem_vertex!(partitioned_graph(pg), parent(pv)) + end + return pg +end + +function rem_vertex!( + pg::AbstractPartitionedGraph, partition_vertex::AbstractPartitionVertex +) + rem_vertices!(pg, vertices(pg, partition_vertex)) + return pg +end + +function rem_vertex(pg::AbstractPartitionedGraph, partition_vertex::AbstractPartitionVertex) + pg_new = copy(pg) + rem_vertex!(pg_new, partition_vertex) + return pg_new +end + +function add_vertex!(pg::AbstractPartitionedGraph, vertex) + return error("Need to specify a partition where the new vertex will go.") +end + +function (pg1::AbstractPartitionedGraph == pg2::AbstractPartitionedGraph) + if unpartitioned_graph(pg1) != unpartitioned_graph(pg2) || + partitioned_graph(pg1) != partitioned_graph(pg2) + return false + end + for v in vertices(pg1) + if which_partition(pg1, v) != which_partition(pg2, v) + return false + end + end + return true +end + +function subgraph(pg::AbstractPartitionedGraph, partition_vertex::AbstractPartitionVertex) + return first(induced_subgraph(unpartitioned_graph(pg), vertices(pg, [partition_vertex]))) +end + +function induced_subgraph( + pg::AbstractPartitionedGraph, partition_vertex::AbstractPartitionVertex +) + return subgraph(pg, partition_vertex), nothing +end diff --git a/src/Graphs/partitionedgraphs/abstractpartitionvertex.jl b/src/Graphs/partitionedgraphs/abstractpartitionvertex.jl new file mode 100644 index 0000000..856da00 --- /dev/null +++ b/src/Graphs/partitionedgraphs/abstractpartitionvertex.jl @@ -0,0 +1,4 @@ +abstract type AbstractPartitionVertex{V} <: Any where {V} end + +#Parent, wrap, unwrap, vertex? +parent(pv::AbstractPartitionVertex) = not_implemented() diff --git a/src/Graphs/partitionedgraphs/partitionedge.jl b/src/Graphs/partitionedgraphs/partitionedge.jl new file mode 100644 index 0000000..4607b9d --- /dev/null +++ b/src/Graphs/partitionedgraphs/partitionedge.jl @@ -0,0 +1,8 @@ +struct PartitionEdge{V,E<:AbstractEdge{V}} <: AbstractPartitionEdge{V} + edge::E +end + +parent(pe::PartitionEdge) = getfield(pe, :edge) +src(pe::PartitionEdge) = PartitionVertex(src(parent(pe))) +dst(pe::PartitionEdge) = PartitionVertex(dst(parent(pe))) +PartitionEdge(p::Pair) = PartitionEdge(NamedEdge(first(p) => last(p))) diff --git a/src/Graphs/partitionedgraphs/partitionedgraph.jl b/src/Graphs/partitionedgraphs/partitionedgraph.jl new file mode 100644 index 0000000..33c71a2 --- /dev/null +++ b/src/Graphs/partitionedgraphs/partitionedgraph.jl @@ -0,0 +1,133 @@ +struct PartitionedGraph{V,PV,G<:AbstractGraph{V},PG<:AbstractGraph{PV}} <: + AbstractPartitionedGraph{V,PV} + graph::G + partitioned_graph::PG + partitioned_vertices::Dictionary + which_partition::Dictionary +end + +##Constructors. +function PartitionedGraph(g::AbstractGraph, partitioned_vertices) + pvs = keys(partitioned_vertices) + pg = NamedGraph(pvs) + which_partition = Dictionary() + for v in vertices(g) + v_pvs = Set(findall(pv -> v ∈ partitioned_vertices[pv], pvs)) + @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) +end + +function PartitionedGraph(partitioned_vertices) + return PartitionedGraph(NamedGraph(keys(partitioned_vertices)), partitioned_vertices) +end + +function PartitionedGraph(g::AbstractGraph; kwargs...) + partitioned_vertices = partition_vertices(g; kwargs...) + return PartitionedGraph(g, partitioned_vertices) +end + +#Needed for interface +partitioned_graph(pg::PartitionedGraph) = getfield(pg, :partitioned_graph) +unpartitioned_graph(pg::PartitionedGraph) = getfield(pg, :graph) +partitioned_vertices(pg::PartitionedGraph) = getfield(pg, :partitioned_vertices) +which_partition(pg::PartitionedGraph) = getfield(pg, :which_partition) +parent_graph_type(PG::Type{<:PartitionedGraph}) = fieldtype(PG, :graph) +function vertices(pg::PartitionedGraph, partition_vert::PartitionVertex) + return partitioned_vertices(pg)[parent(partition_vert)] +end +function vertices(pg::PartitionedGraph, partition_verts::Vector{<:PartitionVertex}) + return unique(reduce(vcat, [vertices(pg, pv) for pv in partition_verts])) +end +function which_partition(pg::PartitionedGraph, vertex) + return PartitionVertex(which_partition(pg)[vertex]) +end + +function partition_edge(pg::PartitionedGraph, edge::AbstractEdge) + return PartitionEdge( + parent(which_partition(pg, src(edge))) => parent(which_partition(pg, dst(edge))) + ) +end + +function partition_edges(pg::PartitionedGraph, partition_edge::PartitionEdge) + psrc_vs = vertices(pg, PartitionVertex(src(partition_edge))) + pdst_vs = vertices(pg, PartitionVertex(dst(partition_edge))) + psrc_subgraph = subgraph(unpartitioned_graph(pg), psrc_vs) + pdst_subgraph = subgraph(pg, pdst_vs) + full_subgraph = subgraph(pg, vcat(psrc_vs, pdst_vs)) + + return setdiff(edges(full_subgraph), vcat(edges(psrc_subgraph), edges(pdst_subgraph))) +end + +function copy(pg::PartitionedGraph) + return PartitionedGraph( + copy(unpartitioned_graph(pg)), + copy(partitioned_graph(pg)), + copy_keys_values(partitioned_vertices(pg)), + copy_keys_values(which_partition(pg)), + ) +end + +function insert_to_vertex_map!( + pg::PartitionedGraph, vertex, partition_vertex::PartitionVertex +) + pv = parent(partition_vertex) + if pv ∉ keys(partitioned_vertices(pg)) + insert!(partitioned_vertices(pg), pv, [vertex]) + else + partitioned_vertices(pg)[pv] = unique(vcat(vertices(pg, partition_vertex), [vertex])) + end + + insert!(which_partition(pg), vertex, pv) + return pg +end + +function delete_from_vertex_map!(pg::PartitionedGraph, vertex) + pv = which_partition(pg, vertex) + return delete_from_vertex_map!(pg, pv, vertex) +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])) + end + + delete!(which_partition(pg), vertex) + return partitioned_vertex +end + +### PartitionedGraph Specific Functions +function induced_subgraph(pg::PartitionedGraph, vertices::Vector) + sub_pg_graph, _ = induced_subgraph(unpartitioned_graph(pg), vertices) + sub_partitioned_vertices = copy_keys_values(partitioned_vertices(pg)) + for pv in NamedGraphs.vertices(partitioned_graph(pg)) + vs = intersect(vertices, sub_partitioned_vertices[pv]) + if !isempty(vs) + sub_partitioned_vertices[pv] = vs + else + delete!(sub_partitioned_vertices, pv) + end + end + + return PartitionedGraph(sub_pg_graph, sub_partitioned_vertices), nothing +end + +function induced_subgraph( + pg::PartitionedGraph, partition_verts::Vector{V} +) where {V<:PartitionVertex} + return induced_subgraph(pg, vertices(pg, partition_verts)) +end diff --git a/src/Graphs/partitionedgraphs/partitioning.jl b/src/Graphs/partitionedgraphs/partitioning.jl new file mode 100644 index 0000000..05551b5 --- /dev/null +++ b/src/Graphs/partitionedgraphs/partitioning.jl @@ -0,0 +1,106 @@ +""" +Graph partitioning backend +""" +struct Backend{T} end + +Backend(s::Symbol) = Backend{s}() +Backend(s::String) = Backend(Symbol(s)) +Backend(backend::Backend) = backend + +macro Backend_str(s) + return :(Backend{$(Expr(:quote, Symbol(s)))}) +end + +""" +Current default graph partitioning backend +""" +const CURRENT_PARTITIONING_BACKEND = Ref{Union{Missing,Backend}}(missing) + +""" +Get the graph partitioning backend +""" +current_partitioning_backend() = CURRENT_PARTITIONING_BACKEND[] + +""" +Set the graph partitioning backend +""" +function set_partitioning_backend!(backend::Union{Missing,Backend,String}) + CURRENT_PARTITIONING_BACKEND[] = Backend(backend) + return nothing +end + +# KaHyPar configuration options +# +# configurations = readdir(joinpath(pkgdir(KaHyPar), "src", "config")) +# "cut_kKaHyPar_sea20.ini" +# "cut_rKaHyPar_sea20.ini" +# "km1_kKaHyPar-E_sea20.ini" +# "km1_kKaHyPar_eco_sea20.ini" +# "km1_kKaHyPar_sea20.ini" +# "km1_rKaHyPar_sea20.ini" +# +const kahypar_configurations = Dict([ + (objective="edge_cut", alg="kway") => "cut_kKaHyPar_sea20.ini", + (objective="edge_cut", alg="recursive") => "cut_rKaHyPar_sea20.ini", + (objective="connectivity", alg="kway") => "km1_kKaHyPar_sea20.ini", + (objective="connectivity", alg="recursive") => "km1_rKaHyPar_sea20.ini", +]) + +# Metis configuration options +const metis_algs = Dict(["kway" => :KWAY, "recursive" => :RECURSIVE]) + +function _npartitions( + g::AbstractGraph, npartitions::Integer, nvertices_per_partition::Nothing +) + return npartitions +end + +function _npartitions( + g::AbstractGraph, npartitions::Nothing, nvertices_per_partition::Integer +) + return nv(g) ÷ nvertices_per_partition +end + +function _npartitions(g::AbstractGraph, npartitions::Int, nvertices_per_partition::Int) + return error("Can't specify both `npartitions` and `nvertices_per_partition`") +end + +function _npartitions( + g::AbstractGraph, npartitions::Nothing, nvertices_per_partition::Nothing +) + return error("Must specify either `npartitions` or `nvertices_per_partition`") +end + +function partition_vertices( + g::Graph; + npartitions=nothing, + nvertices_per_partition=nothing, + backend=current_partitioning_backend(), + kwargs..., +) + #Metis cannot handle the edge case npartitions = 1, so we will fix it here for now + #Is this now + if (_npartitions(g, npartitions, nvertices_per_partition) == 1) + return group(v -> 1, collect(vertices(g))) + end + + return partition_vertices( + Backend(backend), g, _npartitions(g, npartitions, nvertices_per_partition); kwargs... + ) +end + +function partition_vertices( + g::AbstractNamedGraph; npartitions=nothing, nvertices_per_partition=nothing, kwargs... +) + vertex_partitions = partition_vertices( + parent_graph(g); 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 + ] +end diff --git a/src/Graphs/partitionedgraphs/partitionvertex.jl b/src/Graphs/partitionedgraphs/partitionvertex.jl new file mode 100644 index 0000000..8a66ff7 --- /dev/null +++ b/src/Graphs/partitionedgraphs/partitionvertex.jl @@ -0,0 +1,5 @@ +struct PartitionVertex{V} <: AbstractPartitionVertex{V} + vertex::V +end + +parent(pv::PartitionVertex) = getfield(pv, :vertex) diff --git a/src/NamedGraphs.jl b/src/NamedGraphs.jl index 8de2717..e3f1e0a 100644 --- a/src/NamedGraphs.jl +++ b/src/NamedGraphs.jl @@ -8,6 +8,8 @@ using SimpleTraits using SparseArrays using SplitApplyCombine using SymRCM +using Suppressor +using Requires using Graphs.SimpleGraphs @@ -119,11 +121,21 @@ include(joinpath("generators", "named_staticgraphs.jl")) include(joinpath("Graphs", "generators", "staticgraphs.jl")) include(joinpath("Graphs", "generators", "namedgraphs.jl")) include(joinpath("Graphs", "generators", "decoratedgraphs.jl")) +include(joinpath("Graphs", "partitionedgraphs", "abstractpartitionedge.jl")) +include(joinpath("Graphs", "partitionedgraphs", "abstractpartitionvertex.jl")) +include(joinpath("Graphs", "partitionedgraphs", "abstractpartitionedgraph.jl")) +include(joinpath("Graphs", "partitionedgraphs", "partitioning.jl")) +include(joinpath("Graphs", "partitionedgraphs", "partitionedge.jl")) +include(joinpath("Graphs", "partitionedgraphs", "partitionvertex.jl")) +include(joinpath("Graphs", "partitionedgraphs", "partitionedgraph.jl")) # TODO: reexport Graphs.jl (except for `Graphs.contract`) export NamedGraph, NamedDiGraph, NamedEdge, + PartitionedGraph, + PartitionEdge, + PartitionVertex, Key, ⊔, named_binary_tree, @@ -167,6 +179,8 @@ export NamedGraph, neighborhood, neighborhood_dists, neighbors, + nv, + partitioned_graph, path_digraph, path_graph, periphery, @@ -184,6 +198,16 @@ export NamedGraph, outdegrees, mincut_partitions, steiner_tree, + unpartitioned_graph, weights +function __init__() + @require KaHyPar = "2a6221f6-aa48-11e9-3542-2d9e0ef01880" include( + joinpath("requires", "kahypar.jl") + ) + @require Metis = "2679e427-3c69-5b7f-982b-ece356f1e94b" include( + joinpath("requires", "metis.jl") + ) +end + end # module AbstractNamedGraphs diff --git a/src/abstractnamedgraph.jl b/src/abstractnamedgraph.jl index 78cf47f..319fbda 100644 --- a/src/abstractnamedgraph.jl +++ b/src/abstractnamedgraph.jl @@ -36,8 +36,8 @@ end # TODO: rename `edge_type`? edgetype(graph::AbstractNamedGraph) = not_implemented() -directed_graph(G::Type{<:AbstractNamedGraph}) = not_implemented() -undirected_graph(G::Type{<:AbstractNamedGraph}) = not_implemented() +directed_graph_type(G::Type{<:AbstractNamedGraph}) = not_implemented() +undirected_graph_type(G::Type{<:AbstractNamedGraph}) = not_implemented() # In terms of `parent_graph_type` # is_directed(::Type{<:AbstractNamedGraph}) = not_implemented() @@ -61,7 +61,7 @@ zero(G::Type{<:AbstractNamedGraph}) = G() # TODO: Implement using `copyto!`? function directed_graph(graph::AbstractNamedGraph) - digraph = directed_graph(typeof(graph))(vertices(graph)) + digraph = directed_graph_type(typeof(graph))(vertices(graph)) for e in edges(graph) add_edge!(digraph, e) add_edge!(digraph, reverse(e)) @@ -381,20 +381,11 @@ function union(graph1::AbstractNamedGraph, graph2::AbstractNamedGraph) return union_graph end -function add_vertex!(graph::AbstractNamedGraph, vertex) - if vertex ∈ vertices(graph) - throw(ArgumentError("Duplicate vertices are not allowed")) +function rem_vertex!(graph::AbstractNamedGraph, vertex) + if vertex ∉ vertices(graph) + return false end - add_vertex!(parent_graph(graph)) - # Update the vertex list - push!(vertices(graph), vertex) - # Update the reverse map - # TODO: Make this more generic - insert!(graph.vertex_to_parent_vertex, vertex, last(parent_vertices(graph))) - return graph -end -function rem_vertex!(graph::AbstractNamedGraph, vertex) parent_vertex = vertex_to_parent_vertex(graph, vertex) rem_vertex!(parent_graph(graph), parent_vertex) @@ -411,14 +402,22 @@ function rem_vertex!(graph::AbstractNamedGraph, vertex) # TODO: Make this more generic delete!(graph.vertex_to_parent_vertex, vertex) - return graph + return true end -function add_vertices!(graph::AbstractNamedGraph, vs::Vector) - for vertex in vs - add_vertex!(graph, vertex) +function add_vertex!(graph::AbstractNamedGraph, vertex) + if vertex ∈ vertices(graph) + return false end - return graph + + add_vertex!(parent_graph(graph)) + # Update the vertex list + push!(vertices(graph), vertex) + # Update the reverse map + # TODO: Make this more generic + insert!(graph.vertex_to_parent_vertex, vertex, last(parent_vertices(graph))) + + return true end is_directed(G::Type{<:AbstractNamedGraph}) = is_directed(parent_graph_type(G)) @@ -471,9 +470,8 @@ function merge_vertices( graph::AbstractNamedGraph, merge_vertices; merged_vertex=first(merge_vertices) ) merged_graph = copy(graph) - if !has_vertex(graph, merged_vertex) - add_vertex!(merged_graph, merged_vertex) - end + add_vertex!(merged_graph, merged_vertex) + for vertex in merge_vertices for e in incident_edges(graph, vertex; dir=:both) merged_edge = rename_vertices(v -> v == vertex ? merged_vertex : v, e) @@ -500,7 +498,7 @@ function tree(graph::AbstractNamedGraph, parents) n = length(parents) # TODO: Use `directed_graph` here to make more generic? ## t = GenericNamedGraph(DiGraph(n), vertices(graph)) - t = directed_graph(typeof(graph))(vertices(graph)) + t = directed_graph_type(typeof(graph))(vertices(graph)) for destination in eachindex(parents) source = parents[destination] if source != destination diff --git a/src/namedgraph.jl b/src/namedgraph.jl index 14fed66..aaaf1fd 100644 --- a/src/namedgraph.jl +++ b/src/namedgraph.jl @@ -159,10 +159,10 @@ function set_vertices(graph::GenericNamedGraph, vertices) return GenericNamedGraph(parent_graph(graph), vertices) end -function directed_graph(G::Type{<:GenericNamedGraph}) +function directed_graph_type(G::Type{<:GenericNamedGraph}) return GenericNamedGraph{vertextype(G),directed_graph(parent_graph_type(G))} end -function undirected_graph(G::Type{<:GenericNamedGraph}) +function undirected_graph_type(G::Type{<:GenericNamedGraph}) return GenericNamedGraph{vertextype(G),undirected_graph(parent_graph_type(G))} end diff --git a/src/requires/kahypar.jl b/src/requires/kahypar.jl new file mode 100644 index 0000000..eecb0ef --- /dev/null +++ b/src/requires/kahypar.jl @@ -0,0 +1,33 @@ +set_partitioning_backend!(Backend"KaHyPar"()) + +# https://github.com/kahypar/KaHyPar.jl/issues/20 +KaHyPar.HyperGraph(g::SimpleGraph) = incidence_matrix(g) + +""" + 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 partition_vertices( + ::Backend"KaHyPar", + g::SimpleGraph, + npartitions::Integer; + objective="edge_cut", + alg="kway", + configuration=nothing, + kwargs..., +) + if isnothing(configuration) + configuration = joinpath( + pkgdir(KaHyPar), + "src", + "config", + kahypar_configurations[(objective=objective, alg=alg)], + ) + end + partitions = @suppress KaHyPar.partition(g, npartitions; configuration, kwargs...) + return groupfind(partitions .+ 1) +end diff --git a/src/requires/metis.jl b/src/requires/metis.jl new file mode 100644 index 0000000..d88562d --- /dev/null +++ b/src/requires/metis.jl @@ -0,0 +1,33 @@ +set_partitioning_backend!(Backend"Metis"()) + +""" + 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 partition_vertices( + ::Backend"Metis", g::SimpleGraph, npartitions::Integer; alg="recursive", kwargs... +) + metis_alg = metis_algs[alg] + partitions = Metis.partition(g, npartitions; alg=metis_alg, kwargs...) + return groupfind(Int.(partitions)) +end + +## #= +## Metis.partition(G, n; alg = :KWAY) +## +## 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 partition(g::Metis.Graph, npartitions::Integer) +## return Metis.partition(g, npartitions; alg=:KWAY) +## end +## +## function partition(g::Graph, npartitions::Integer) +## return partition(Metis.graph(adjacency_matrix(g)), npartitions) +## end diff --git a/src/traversals/trees_and_forests.jl b/src/traversals/trees_and_forests.jl index de433ee..7f9c996 100644 --- a/src/traversals/trees_and_forests.jl +++ b/src/traversals/trees_and_forests.jl @@ -35,7 +35,7 @@ end #Given a graph, split it into its connected components, construct a spanning tree, using the function spanning_tree, over each of them # and take the union. function spanning_forest(g::AbstractNamedGraph; spanning_tree=spanning_tree) - return reduce(union, (spanning_tree(g[vs]) for vs in connected_components(g))) + return reduce(union, (spanning_tree(subgraph(g, vs)) for vs in connected_components(g))) end #Given an undirected graph g with vertex set V, build a set of forests (each with vertex set V) which covers all edges in g @@ -54,3 +54,5 @@ function forest_cover(g::AbstractNamedGraph; spanning_tree=spanning_tree) return forests end + +#forest_cover(g::PartitionedGraph; kwargs...) = not_implemented() diff --git a/test/Project.toml b/test/Project.toml index 4b36d4e..2a66a4c 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -7,3 +7,5 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" Weave = "44d3d7a6-8a23-5bf8-98c5-b353f8df5ec9" +KaHyPar = "2a6221f6-aa48-11e9-3542-2d9e0ef01880" +Metis = "2679e427-3c69-5b7f-982b-ece356f1e94b" diff --git a/test/test_examples.jl b/test/test_examples.jl index 96a383e..01b7da5 100644 --- a/test/test_examples.jl +++ b/test/test_examples.jl @@ -4,7 +4,9 @@ using Suppressor using Test examples_path = joinpath(pkgdir(NamedGraphs), "examples") -@testset "Run examples: $filename" for filename in readdir(examples_path) +examples_to_exclude = [] +@testset "Run examples: $filename" for filename in + setdiff(readdir(examples_path), examples_to_exclude) if endswith(filename, ".jl") @suppress include(joinpath(examples_path, filename)) end diff --git a/test/test_partitionedgraph.jl b/test/test_partitionedgraph.jl new file mode 100644 index 0000000..35faf72 --- /dev/null +++ b/test/test_partitionedgraph.jl @@ -0,0 +1,121 @@ +using Test +using NamedGraphs +using NamedGraphs: + spanning_forest, + spanning_tree, + forest_cover, + PartitionEdge, + PartitionVertex, + parent, + default_root_vertex, + triangular_lattice_graph, + add_edges! +using Dictionaries +using Graphs + +@testset "Test Partitioned Graph Constructors" begin + nx, ny = 10, 10 + g = named_grid((nx, ny)) + + partitions = [[(i, j) for j in 1:ny] for i in 1:nx] + pg = PartitionedGraph(g, partitions) + @test vertextype(partitioned_graph(pg)) == Int64 + @test vertextype(unpartitioned_graph(pg)) == vertextype(g) + @test is_tree(partitioned_graph(pg)) + @test nv(pg) == nx * ny + @test nv(partitioned_graph(pg)) == nx + + partition_dict = Dictionary([first(partition) for partition in partitions], partitions) + pg = PartitionedGraph(g, partition_dict) + @test vertextype(partitioned_graph(pg)) == vertextype(g) + @test vertextype(unpartitioned_graph(pg)) == vertextype(g) + @test is_tree(partitioned_graph(pg)) + @test nv(pg) == nx * ny + @test nv(partitioned_graph(pg)) == nx + + pg = PartitionedGraph([i for i in 1:nx]) + @test unpartitioned_graph(pg) == partitioned_graph(pg) + @test nv(pg) == nx + @test nv(partitioned_graph(pg)) == nx + @test ne(pg) == 0 + @test ne(partitioned_graph(pg)) == 0 +end + +@testset "Test Partitioned Graph Vertex/Edge Addition and Removal" begin + nx, ny = 10, 10 + g = named_grid((nx, ny)) + + partitions = [[(i, j) for j in 1:ny] for i in 1:nx] + pg = PartitionedGraph(g, partitions) + + pv = PartitionVertex(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(partitioned_graph(pg)) + @test parent(pv) ∉ vertices(partitioned_graph(pg)) + @test !has_vertex(pg, pv) + @test nv(pg) == (nx - 1) * ny + @test nv(partitioned_graph(pg)) == nx - 1 + @test !is_tree(partitioned_graph(pg)) + + #Add the column back to the in place graph + add_vertices!(pg, v_set, pv) + add_edges!(pg, edges_involving_v_set) + @test is_connected(pg.graph) && is_path_graph(partitioned_graph(pg)) + @test parent(pv) ∈ vertices(partitioned_graph(pg)) + @test has_vertex(pg, pv) + @test is_tree(partitioned_graph(pg)) + @test nv(pg) == nx * ny + @test nv(partitioned_graph(pg)) == nx +end + +@testset "Test Partitioned Graph Subgraph Functionality" begin + n, z = 12, 4 + g = NamedGraph(random_regular_graph(n, z)) + partitions = dictionary([ + 1 => [1, 2, 3], 2 => [4, 5, 6], 3 => [7, 8, 9], 4 => [10, 11, 12] + ]) + pg = PartitionedGraph(g, partitions) + + subgraph_partitioned_vertices = [1, 2] + subgraph_vertices = reduce( + vcat, [partitions[spv] for spv in subgraph_partitioned_vertices] + ) + + pg_1 = subgraph(pg, PartitionVertex.(subgraph_partitioned_vertices)) + pg_2 = subgraph(pg, subgraph_vertices) + @test pg_1 == pg_2 + @test nv(pg_1) == length(subgraph_vertices) + @test nv(partitioned_graph(pg_1)) == length(subgraph_partitioned_vertices) + + subgraph_partitioned_vertex = 3 + subgraph_vertices = partitions[subgraph_partitioned_vertex] + g_1 = subgraph(pg, PartitionVertex(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) +end + +@testset "Test NamedGraphs Functions on Partitioned Graph" begin + functions = [is_tree, default_root_vertex, center, diameter, radius] + gs = [ + named_comb_tree((4, 4)), + named_grid((2, 2, 2)), + NamedGraph(random_regular_graph(12, 3)), + triangular_lattice_graph(7, 7), + ] + + for f in functions + for g in gs + pg = PartitionedGraph(g, [vertices(g)]) + @test f(pg) == f(unpartitioned_graph(pg)) + @test nv(pg) == nv(g) + @test nv(partitioned_graph(pg)) == 1 + @test ne(pg) == ne(g) + @test ne(partitioned_graph(pg)) == 0 + end + end +end