org.jgrapht.alg.clique.CliqueMinimalSeparatorDecomposition Maven / Gradle / Ivy
/*
* (C) Copyright 2015-2023, by Florian Buenzli and Contributors.
*
* JGraphT : a free Java graph-theory library
*
* See the CONTRIBUTORS.md file distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the
* GNU Lesser General Public License v2.1 or later
* which is available at
* http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html.
*
* SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later
*/
package org.jgrapht.alg.clique;
import org.jgrapht.*;
import org.jgrapht.alg.connectivity.*;
import org.jgrapht.graph.builder.*;
import java.util.*;
import java.util.Map.*;
/**
* Clique Minimal Separator Decomposition using MCS-M+ and Atoms algorithm as described in Berry et
* al. An Introduction to Clique Minimal Separator Decomposition (2010), DOI:10.3390/a3020197,
* http://www.mdpi.com/1999-4893/3/2/197
*
*
* The Clique Minimal Separator (CMS) Decomposition is a procedure that splits a graph into a set of
* subgraphs separated by minimal clique separators, adding the separating clique to each component
* produced by the separation. At the end we have a set of atoms. The CMS decomposition is unique
* and yields the set of the atoms independent of the order of the decomposition.
*
* @param the graph vertex type
* @param the graph edge type
*
* @author Florian Buenzli ([email protected])
* @author Thomas Tschager ([email protected])
* @author Tomas Hruz ([email protected])
* @author Philipp Hoppen
*/
public class CliqueMinimalSeparatorDecomposition
{
/**
* Source graph to operate on
*/
private Graph graph;
/**
* Minimal triangulation of graph
*/
private Graph chordalGraph;
/**
* Fill edges
*/
private Set fillEdges;
/**
* Minimal elimination ordering on the vertices of graph
*/
private LinkedList meo;
/**
* List of all vertices that generate a minimal separator of
* chordGraph
*/
private List generators;
/**
* Set of clique minimal separators
*/
private Set> separators;
/**
* The atoms generated by the decomposition
*/
private Set> atoms;
/**
* Map for each separator how many components it produces.
*/
private Map, Integer> fullComponentCount = new HashMap<>();
/**
* Setup a clique minimal separator decomposition on undirected graph
* g
. Loops and multiple (parallel) edges are removed, i.e. the graph is transformed to a
* simple graph.
*
* @param g The graph to decompose.
*/
public CliqueMinimalSeparatorDecomposition(Graph g)
{
this.graph = GraphTests.requireUndirected(g);
this.fillEdges = new HashSet<>();
}
/**
* Compute the minimal triangulation of the graph. Implementation of Algorithm MCS-M+ as
* described in Berry et al. (2010), DOI:10.3390/a3020197
* http://www.mdpi.com/1999-4893/3/2/197
*/
private void computeMinimalTriangulation()
{
// initialize chordGraph with same vertices as graph
chordalGraph = GraphTypeBuilder
. undirected().edgeSupplier(graph.getEdgeSupplier())
.vertexSupplier(graph.getVertexSupplier()).allowingMultipleEdges(false)
.allowingSelfLoops(false).buildGraph();
for (V v : graph.vertexSet()) {
chordalGraph.addVertex(v);
}
// initialize g' as subgraph of graph (same vertices and edges)
final Graph gprime = copyAsSimpleGraph(graph);
int s = -1;
generators = new ArrayList<>();
meo = new LinkedList<>();
final Map vertexLabels = new HashMap<>();
for (V v : gprime.vertexSet()) {
vertexLabels.put(v, 0);
}
for (int i = 1, n = graph.vertexSet().size(); i <= n; i++) {
V v = getMaxLabelVertex(vertexLabels);
LinkedList neighborsY = new LinkedList<>(Graphs.neighborListOf(gprime, v));
if (vertexLabels.get(v) <= s) {
generators.add(v);
}
s = vertexLabels.get(v);
// Mark x reached and all other vertices of gprime unreached
HashSet reached = new HashSet<>();
reached.add(v);
// mark neighborhood of x reached and add to reach(label(y))
HashMap> reach = new HashMap<>();
// mark y reached and add y to reach
for (V y : neighborsY) {
reached.add(y);
addToReach(vertexLabels.get(y), y, reach);
}
for (int j = 0; j < graph.vertexSet().size(); j++) {
if (!reach.containsKey(j)) {
continue;
}
while (reach.get(j).size() > 0) {
// remove a vertex y from reach(j)
V y = reach.get(j).iterator().next();
reach.get(j).remove(y);
for (V z : Graphs.neighborListOf(gprime, y)) {
if (!reached.contains(z)) {
reached.add(z);
if (vertexLabels.get(z) > j) {
neighborsY.add(z);
E fillEdge = graph.getEdgeSupplier().get();
fillEdges.add(fillEdge);
addToReach(vertexLabels.get(z), z, reach);
} else {
addToReach(j, z, reach);
}
}
}
}
}
for (V y : neighborsY) {
chordalGraph.addEdge(v, y);
vertexLabels.put(y, vertexLabels.get(y) + 1);
}
meo.addLast(v);
gprime.removeVertex(v);
vertexLabels.remove(v);
}
}
/**
* Get the vertex with the maximal label.
*
* @param vertexLabels Map that gives a label for each vertex.
*
* @return Vertex with the maximal label.
*/
private V getMaxLabelVertex(Map vertexLabels)
{
Iterator> iterator = vertexLabels.entrySet().iterator();
Entry max = iterator.next();
while (iterator.hasNext()) {
Entry e = iterator.next();
if (e.getValue() > max.getValue()) {
max = e;
}
}
return max.getKey();
}
/**
* Add a vertex to reach.
*
* @param k vertex' label
* @param v the vertex
* @param r the reach structure.
*/
private void addToReach(Integer k, V v, HashMap> r)
{
if (r.containsKey(k)) {
r.get(k).add(v);
} else {
HashSet set = new HashSet<>();
set.add(v);
r.put(k, set);
}
}
/**
* Compute the unique decomposition of the input graph $G$ (atoms of $G$). Implementation of
* algorithm Atoms as described in Berry et al. (2010), DOI:10.3390/a3020197,
* http://www.mdpi.com/1999-4893/3/2/197
*/
private void computeAtoms()
{
if (chordalGraph == null) {
computeMinimalTriangulation();
}
separators = new HashSet<>();
// initialize g' as subgraph of graph (same vertices and edges)
Graph gprime = copyAsSimpleGraph(graph);
// initialize h' as subgraph of chordalGraph (same vertices and edges)
Graph hprime = copyAsSimpleGraph(chordalGraph);
atoms = new HashSet<>();
Iterator iterator = meo.descendingIterator();
while (iterator.hasNext()) {
V v = iterator.next();
if (generators.contains(v)) {
Set separator = new HashSet<>(Graphs.neighborListOf(hprime, v));
if (isClique(graph, separator)) {
if (separator.size() > 0) {
if (separators.contains(separator)) {
fullComponentCount
.put(separator, fullComponentCount.get(separator) + 1);
} else {
fullComponentCount.put(separator, 2);
separators.add(separator);
}
}
Graph tmpGraph = copyAsSimpleGraph(gprime);
tmpGraph.removeAllVertices(separator);
ConnectivityInspector con = new ConnectivityInspector<>(tmpGraph);
if (con.isConnected()) {
throw new RuntimeException("separator did not separate the graph");
}
for (Set component : con.connectedSets()) {
if (component.contains(v)) {
gprime.removeAllVertices(component);
component.addAll(separator);
atoms.add(new HashSet<>(component));
assert (component.size() > 0);
break;
}
}
}
}
hprime.removeVertex(v);
}
if (gprime.vertexSet().size() > 0) {
atoms.add(new HashSet<>(gprime.vertexSet()));
}
}
/**
* Check whether the subgraph of graph
induced by the given vertices
* is complete, i.e. a clique.
*
* @param graph the graph.
* @param vertices the vertices to induce the subgraph from.
*
* @return true if the induced subgraph is a clique.
*/
private static boolean isClique(Graph graph, Set vertices)
{
for (V v1 : vertices) {
for (V v2 : vertices) {
if (!v1.equals(v2) && (graph.getEdge(v1, v2) == null)) {
return false;
}
}
}
return true;
}
/**
* Create a copy of a graph for internal use.
*
* @param graph the graph to copy.
*
* @return A copy of the graph projected to a SimpleGraph.
*/
private static Graph copyAsSimpleGraph(Graph graph)
{
Graph copy = GraphTypeBuilder
. undirected().edgeSupplier(graph.getEdgeSupplier())
.vertexSupplier(graph.getVertexSupplier()).allowingMultipleEdges(false)
.allowingSelfLoops(false).buildGraph();
if (graph.getType().isSimple()) {
Graphs.addGraph(copy, graph);
} else {
// project graph to SimpleGraph
Graphs.addAllVertices(copy, graph.vertexSet());
for (E e : graph.edgeSet()) {
V v1 = graph.getEdgeSource(e);
V v2 = graph.getEdgeTarget(e);
if (!v1.equals(v2) && !copy.containsEdge(e)) {
copy.addEdge(v1, v2);
}
}
}
return copy;
}
/**
* Check if the graph is chordal.
*
* @return true if the graph is chordal, false otherwise.
*/
public boolean isChordal()
{
if (chordalGraph == null) {
computeMinimalTriangulation();
}
return (chordalGraph.edgeSet().size() == graph.edgeSet().size());
}
/**
* Get the fill edges generated by the triangulation.
*
* @return Set of fill edges.
*/
public Set getFillEdges()
{
if (fillEdges == null) {
computeMinimalTriangulation();
}
return fillEdges;
}
/**
* Get the minimal triangulation of the graph.
*
* @return Triangulated graph.
*/
public Graph getMinimalTriangulation()
{
if (chordalGraph == null) {
computeMinimalTriangulation();
}
return chordalGraph;
}
/**
* Get the generators of the separators of the triangulated graph, i.e. all vertices that
* generate a minimal separator of triangulated graph.
*
* @return List of generators.
*/
public List getGenerators()
{
if (generators == null) {
computeMinimalTriangulation();
}
return generators;
}
/**
* Get the minimal elimination ordering produced by the triangulation.
*
* @return The minimal elimination ordering.
*/
public LinkedList getMeo()
{
if (meo == null) {
computeMinimalTriangulation();
}
return meo;
}
/**
* Get a map to know for each separator how many components it produces.
*
* @return A map from separators to integers (component count).
*/
public Map, Integer> getFullComponentCount()
{
if (fullComponentCount == null) {
computeAtoms();
}
return fullComponentCount;
}
/**
* Get the atoms generated by the decomposition.
*
* @return Set of atoms, where each atom is described as the set of its vertices.
*/
public Set> getAtoms()
{
if (atoms == null) {
computeAtoms();
}
return atoms;
}
/**
* Get the clique minimal separators.
*
* @return Set of separators, where each separator is described as the set of its vertices.
*/
public Set> getSeparators()
{
if (separators == null) {
computeAtoms();
}
return separators;
}
/**
* Get the original graph.
*
* @return Original graph.
*/
public Graph getGraph()
{
return graph;
}
}