org.jgrapht.alg.flow.PadbergRaoOddMinimumCutset Maven / Gradle / Ivy
/*
* (C) Copyright 2016-2021, by Joris Kinable 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.flow;
import org.jgrapht.*;
import org.jgrapht.alg.connectivity.*;
import org.jgrapht.alg.interfaces.*;
import org.jgrapht.graph.*;
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
/**
* Implementation of the algorithm by Padberg and Rao to compute Odd Minimum Cut-Sets. Let $G=(V,E)$
* be an undirected, simple weighted graph, where all edge weights are positive. Let $T \subset V$
* with $|T|$ even, be a set of vertices that are labelled odd. A cut-set $(U:V-U)$ is called
* odd if $|T \cap U|$ is an odd number. Let $c(U:V-U)$ be the weight of the cut, that is, the sum
* of weights of the edges which have exactly one endpoint in $U$ and one endpoint in $V-U$. The
* problem of finding an odd minimum cut-set in $G$ is stated as follows: Find $W \subseteq V$ such
* that $c(W:V-W)=min(c(U:V-U)|U \subseteq V, |T \cap U|$ is odd).
*
*
* The algorithm has been published in: Padberg, M. Rao, M. Odd Minimum Cut-Sets and b-Matchings.
* Mathematics of Operations Research, 7(1), p67-80, 1982. A more concise description is published
* in: Letchford, A. Reinelt, G. Theis, D. Odd minimum cut-sets and b-matchings revisited. SIAM
* Journal of Discrete Mathematics, 22(4), p1480-1487, 2008.
*
*
* The runtime complexity of this algorithm is dominated by the runtime complexity of the algorithm
* used to compute A Gomory-Hu tree on graph $G$. Consequently, the runtime complexity of this class
* is $O(V^4)$.
*
*
* This class does not support changes to the underlying graph. The behavior of this class is
* undefined when the graph is modified after instantiating this class.
*
* @param the graph vertex type
* @param the graph edge type
*
* @author Joris Kinable
*/
public class PadbergRaoOddMinimumCutset
{
/* Input graph */
private final Graph network;
/* Set of vertices which are labeled 'odd' (set T in the paper) */
private Set oddVertices;
/* Algorithm used to calculate the Gomory-Hu Cut-tree */
private final GusfieldGomoryHuCutTree gusfieldGomoryHuCutTreeAlgorithm;
/* The Gomory-Hu tree */
private SimpleWeightedGraph gomoryHuTree;
/* Weight of the minimum odd cut-set */
private double minimumCutWeight = Double.MAX_VALUE;
/* Source partition constituting the minimum odd cut-set */
private Set sourcePartitionMinimumCut;
/**
* Creates a new instance of the PadbergRaoOddMinimumCutset algorithm.
*
* @param network input graph
*/
public PadbergRaoOddMinimumCutset(Graph network)
{
this(network, MaximumFlowAlgorithmBase.DEFAULT_EPSILON);
}
/**
* Creates a new instance of the PadbergRaoOddMinimumCutset algorithm.
*
* @param network input graph
* @param epsilon tolerance
*/
public PadbergRaoOddMinimumCutset(Graph network, double epsilon)
{
this(network, new PushRelabelMFImpl<>(network, epsilon));
}
/**
* Creates a new instance of the PadbergRaoOddMinimumCutset algorithm.
*
* @param network input graph
* @param minimumSTCutAlgorithm algorithm used to calculate the Gomory-Hu tree
*/
public PadbergRaoOddMinimumCutset(
Graph network, MinimumSTCutAlgorithm minimumSTCutAlgorithm)
{
this.network = GraphTests.requireUndirected(network);
gusfieldGomoryHuCutTreeAlgorithm =
new GusfieldGomoryHuCutTree<>(network, minimumSTCutAlgorithm);
}
/**
* Calculates the minimum odd cut. The implementation follows Algorithm 1 in the paper Odd
* minimum cut sets and b-matchings revisited by Adam Letchford, Gerhard Reinelt and Dirk Theis.
* The original algorithm runs on a compressed Gomory-Hu tree: a cut-tree with the odd vertices
* as terminal vertices. This tree has $|T|-1$ edges as opposed to $|V|-1$ for a Gomory-Hu tree
* defined on the input graph $G$. This compression step can however be skipped. If you want to
* run the original algorithm in the paper, set the parameter useTreeCompression
to
* true. Alternatively, experiment which setting of this parameter produces the fastest results.
* Both settings are guaranteed to find the optimal cut. Experiments on random graphs showed
* that setting useTreeCompression
to false was on average a bit faster.
*
* @param oddVertices Set of vertices which are labeled 'odd'. Note that the number of vertices
* in this set must be even!
* @param useTreeCompression parameter indicating whether tree compression should be used
* (recommended: false).
* @return weight of the minimum odd cut.
*/
public double calculateMinCut(Set oddVertices, boolean useTreeCompression)
{
minimumCutWeight = Double.MAX_VALUE;
this.oddVertices = oddVertices;
if (oddVertices.size() % 2 == 1)
throw new IllegalArgumentException("There needs to be an even number of odd vertices");
assert network.vertexSet().containsAll(oddVertices); // All odd vertices must be contained
// in the graph
// all edge weights must be non-negative
assert network.edgeSet().stream().noneMatch(e -> network.getEdgeWeight(e) < 0);
gomoryHuTree = gusfieldGomoryHuCutTreeAlgorithm.getGomoryHuTree();
if (useTreeCompression)
return calculateMinCutWithTreeCompression();
else
return calculateMinCutWithoutTreeCompression();
}
/**
* Modified implementation of the algorithm proposed in Odd Minimum Cut-sets and b-matchings by
* Padberg and Rao. The optimal cut is directly computed on the Gomory-Hu tree computed for
* graph $G$. This approach iterates efficiently over all possible cuts of the graph (there are
* $|V|$ such cuts).
*
* @return weight of the minimum odd cut.
*/
private double calculateMinCutWithoutTreeCompression()
{
Set edges = new LinkedHashSet<>(gomoryHuTree.edgeSet());
for (DefaultWeightedEdge edge : edges) {
V source = gomoryHuTree.getEdgeSource(edge);
V target = gomoryHuTree.getEdgeTarget(edge);
double edgeWeight = gomoryHuTree.getEdgeWeight(edge);
if (edgeWeight >= minimumCutWeight)
continue;
gomoryHuTree.removeEdge(edge); // Temporarily remove edge
Set sourcePartition =
new ConnectivityInspector<>(gomoryHuTree).connectedSetOf(source);
if (PadbergRaoOddMinimumCutset.isOddVertexSet(sourcePartition, oddVertices)) { // If the
// source
// partition
// forms
// an odd
// cutset,
// check
// whether
// the
// cut
// isn't
// better
// than
// the
// one we
// already
// found.
minimumCutWeight = edgeWeight;
sourcePartitionMinimumCut = sourcePartition;
}
gomoryHuTree.addEdge(source, target, edge); // Place edge back
}
return minimumCutWeight;
}
/**
* Implementation of the algorithm proposed in Odd Minimum Cut-sets and b-matchings by Padberg
* and Rao. The algorithm evaluates at most $|T|$ cuts in the Gomory-Hu tree.
*
* @return weight of the minimum odd cut.
*/
private double calculateMinCutWithTreeCompression()
{
Queue> queue = new ArrayDeque<>();
queue.add(oddVertices);
// Keep splitting the clusters until each resulting cluster containes exactly one vertex.
while (!queue.isEmpty()) {
Set nextCluster = queue.poll();
this.splitCluster(nextCluster, queue);
}
return minimumCutWeight;
}
/**
* Takes a set of odd vertices with cardinality $2$ or more, and splits them into $2$ new
* non-empty sets.
*
* @param cluster group of odd vertices
* @param queue clusters with cardinality $2$ or more
*/
private void splitCluster(Set cluster, Queue> queue)
{
assert cluster.size() >= 2;
// Choose 2 random odd nodes
Iterator iterator = cluster.iterator();
V oddNode1 = iterator.next();
V oddNode2 = iterator.next();
// Calculate the minimum cut separating these two nodes.
double cutWeight = gusfieldGomoryHuCutTreeAlgorithm.calculateMinCut(oddNode1, oddNode2);
Set sourcePartition = null;
if (cutWeight < minimumCutWeight) {
sourcePartition = gusfieldGomoryHuCutTreeAlgorithm.getSourcePartition();
if (PadbergRaoOddMinimumCutset.isOddVertexSet(sourcePartition, oddVertices)) {
this.minimumCutWeight = cutWeight;
this.sourcePartitionMinimumCut = sourcePartition;
}
}
if (cluster.size() == 2)
return;
if (sourcePartition == null)
sourcePartition = gusfieldGomoryHuCutTreeAlgorithm.getSourcePartition();
Set split1 = this.intersection(cluster, sourcePartition);
Set split2 = new HashSet<>(cluster);
split2.removeAll(split1);
if (split1.size() > 1)
queue.add(split1);
if (split2.size() > 1)
queue.add(split2);
}
/**
* Efficient way to compute the intersection between two sets
*
* @param set1 set $1$
* @param set2 set $2$
* @return intersection of set $1$ and $2$
*/
private Set intersection(Set set1, Set set2)
{
Set a;
Set b;
if (set1.size() <= set2.size()) {
a = set1;
b = set2;
} else {
a = set2;
b = set1;
}
return a.stream().filter(b::contains).collect(Collectors.toSet());
}
/**
* Convenience method which test whether the given set contains an odd number of odd-labeled
* nodes.
*
* @param vertex type
* @param vertices input set
* @param oddVertices subset of vertices which are labeled odd
* @return true if the given set contains an odd number of odd-labeled nodes.
*/
public static boolean isOddVertexSet(Set vertices, Set oddVertices)
{
if (vertices.size() < oddVertices.size())
return vertices.stream().filter(oddVertices::contains).count() % 2 == 1;
else
return oddVertices.stream().filter(vertices::contains).count() % 2 == 1;
}
/**
* Returns partition $W$ of the cut obtained after the last invocation of
* {@link #calculateMinCut(Set, boolean)}
*
* @return partition $W$
*/
public Set getSourcePartition()
{
return sourcePartitionMinimumCut;
}
/**
* Returns partition $V-W$ of the cut obtained after the last invocation of
* {@link #calculateMinCut(Set, boolean)}
*
* @return partition $V-W$
*/
public Set getSinkPartition()
{
Set sinkPartition = new LinkedHashSet<>(network.vertexSet());
sinkPartition.removeAll(sourcePartitionMinimumCut);
return sinkPartition;
}
/**
* Returns the set of edges which run from the source partition to the sink partition, in the
* $s-t$ cut obtained after the last invocation of {@link #calculateMinCut(Set, boolean)}
*
* @return set of edges which have one endpoint in the source partition and one endpoint in the
* sink partition.
*/
public Set getCutEdges()
{
Predicate predicate = e -> sourcePartitionMinimumCut.contains(network.getEdgeSource(e))
^ sourcePartitionMinimumCut.contains(network.getEdgeTarget(e));
return network
.edgeSet().stream().filter(predicate)
.collect(Collectors.toCollection(LinkedHashSet::new));
}
}