org.jgrapht.alg.flow.EdmondsKarpMFImpl Maven / Gradle / Ivy
/*
* (C) Copyright 2008-2021, by Ilya Razenshteyn 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.util.extension.*;
import java.util.*;
/**
* This class computes a maximum flow in a
* flow network using
* Edmonds-Karp algorithm. Given
* is a weighted directed or undirected graph $G(V,E)$ with vertex set $V$ and edge set $E$. Each
* edge $e\in E$ has an associated non-negative capacity $u_e$. The maximum flow problem involves
* finding a feasible flow from a source vertex $s$ to a sink vertex $t$ which is maximum. The
* amount of flow $f_e$ through any edge $e$ cannot exceed capacity $u_e$. Moreover, flow
* conservation must hold: the sum of flows entering a node must equal the sum of flows exiting that
* node, except for the source and the sink nodes.
*
* Mathematically, the maximum flow problem is stated as follows: \[ \begin{align} \max~&
* \sum_{e\in \delta^+(s)}f_e &\\ \mbox{s.t. }&\sum_{e\in \delta^-(i)} f_e=\sum_{e\in
* \delta^+(i)} f_e & \forall i\in V\setminus\{s,t\}\\ &0\leq f_e \leq u_e & \forall
* e\in E \end{align} \] Here $\delta^+(i)$ resp $\delta^-(i)$ denote resp the outgoing and incoming
* edges of vertex $i$.
*
* When the input graph is undirected, an edge $(i,j)$ is treated as two directed arcs: $(i,j)$ and
* $(j,i)$. In such a case, there is the additional restriction that the flow can only go in one
* direction: the flow either goes form $i$ to $j$, or from $j$ to $i$, but there cannot be a
* positive flow on $(i,j)$ and $(j,i)$ simultaneously.
*
* The runtime complexity of this class is $O(nm^2)$, where $n$ is the number of vertices and $m$
* the number of edges in the graph. For a more efficient algorithm, consider using
* {@link PushRelabelMFImpl} instead.
*
*
* This class can also compute minimum s-t cuts. Effectively, to compute a minimum s-t cut, the
* implementation first computes a minimum s-t flow, after which a BFS is run on the residual graph.
*
*
* For more details see Andrew V. Goldberg's Combinatorial Optimization (Lecture Notes).
*
* Note: even though the algorithm accepts any kind of graph, currently only Simple directed and
* undirected graphs are supported (and tested!).
*
* @param the graph vertex type
* @param the graph edge type
*
* @author Ilya Razensteyn
*/
public final class EdmondsKarpMFImpl
extends
MaximumFlowAlgorithmBase
{
/* current source vertex */
private VertexExtension currentSource;
/* current sink vertex */
private VertexExtension currentSink;
private final ExtensionFactory vertexExtensionsFactory;
private final ExtensionFactory edgeExtensionsFactory;
/**
* Constructs MaximumFlow
instance to work with a copy of
* network
. Current source and sink are set to null
. If
* network
is weighted, then capacities are weights, otherwise all capacities are
* equal to one. Doubles are compared using
* DEFAULT_EPSILON
tolerance.
*
* @param network network, where maximum flow will be calculated
*/
public EdmondsKarpMFImpl(Graph network)
{
this(network, DEFAULT_EPSILON);
}
/**
* Constructs MaximumFlow
instance to work with a copy of
* network
. Current source and sink are set to null
. If
* network
is weighted, then capacities are weights, otherwise all capacities are
* equal to one.
*
* @param network network, where maximum flow will be calculated
* @param epsilon tolerance for comparing doubles
*/
public EdmondsKarpMFImpl(Graph network, double epsilon)
{
super(network, epsilon);
this.vertexExtensionsFactory = () -> new VertexExtension();
this.edgeExtensionsFactory = () -> new AnnotatedFlowEdge();
if (network == null) {
throw new NullPointerException("network is null");
}
if (epsilon <= 0) {
throw new IllegalArgumentException("invalid epsilon (must be positive)");
}
for (E e : network.edgeSet()) {
if (network.getEdgeWeight(e) < -epsilon) {
throw new IllegalArgumentException("invalid capacity (must be non-negative)");
}
}
}
/**
* Sets current source to source
, current sink to sink
, then
* calculates maximum flow from source
to sink
. Note, that
* source
and sink
must be vertices of the
* network
passed to the constructor, and they must be different.
*
* @param source source vertex
* @param sink sink vertex
*
* @return a maximum flow
*/
public MaximumFlow getMaximumFlow(V source, V sink)
{
this.calculateMaximumFlow(source, sink);
maxFlow = composeFlow();
return new MaximumFlowImpl<>(maxFlowValue, maxFlow);
}
/**
* Sets current source to source
, current sink to sink
, then
* calculates maximum flow from source
to sink
. Note, that
* source
and sink
must be vertices of the
* network
passed to the constructor, and they must be different. If desired, a flow map
* can be queried afterwards; this will not require a new invocation of the algorithm.
*
* @param source source vertex
* @param sink sink vertex
*
* @return the value of the maximum flow
*/
public double calculateMaximumFlow(V source, V sink)
{
super.init(source, sink, vertexExtensionsFactory, edgeExtensionsFactory);
if (!network.containsVertex(source)) {
throw new IllegalArgumentException("invalid source (null or not from this network)");
}
if (!network.containsVertex(sink)) {
throw new IllegalArgumentException("invalid sink (null or not from this network)");
}
if (source.equals(sink)) {
throw new IllegalArgumentException("source is equal to sink");
}
currentSource = getVertexExtension(source);
currentSink = getVertexExtension(sink);
for (;;) {
breadthFirstSearch();
if (!currentSink.visited) {
break;
}
maxFlowValue += augmentFlow();
}
return maxFlowValue;
}
/**
* Method which finds a path from source to sink the in the residual graph. Note that this
* method tries to find multiple paths at once. Once a single path has been discovered, no new
* nodes are added to the queue, but nodes which are already in the queue are fully explored. As
* such there's a chance that multiple paths are discovered.
*/
private void breadthFirstSearch()
{
for (V v : network.vertexSet()) {
getVertexExtension(v).visited = false;
getVertexExtension(v).lastArcs = null;
}
Queue queue = new ArrayDeque<>();
queue.offer(currentSource);
currentSource.visited = true;
currentSource.excess = Double.POSITIVE_INFINITY;
currentSink.excess = 0.0;
boolean seenSink = false;
while (queue.size() != 0) {
VertexExtension ux = queue.poll();
for (AnnotatedFlowEdge ex : ux.getOutgoing()) {
if (comparator.compare(ex.flow, ex.capacity) < 0) {
VertexExtension vx = ex.getTarget();
if (vx == currentSink) {
vx.visited = true;
if (vx.lastArcs == null) {
vx.lastArcs = new ArrayList<>();
}
vx.lastArcs.add(ex);
vx.excess += Math.min(ux.excess, ex.capacity - ex.flow);
seenSink = true;
} else if (!vx.visited) {
vx.visited = true;
vx.excess = Math.min(ux.excess, ex.capacity - ex.flow);
vx.lastArcs = Collections.singletonList(ex);
if (!seenSink) {
queue.add(vx);
}
}
}
}
}
}
/**
* For all paths which end in the sink. trace them back to the source and push flow through
* them.
*
* @return total increase in flow from source to sink
*/
private double augmentFlow()
{
double flowIncrease = 0;
Set seen = new HashSet<>();
for (AnnotatedFlowEdge ex : currentSink.lastArcs) {
double deltaFlow = Math.min(ex.getSource().excess, ex.capacity - ex.flow);
if (augmentFlowAlongInternal(deltaFlow, ex. getSource(), seen)) {
pushFlowThrough(ex, deltaFlow);
flowIncrease += deltaFlow;
}
}
return flowIncrease;
}
private boolean augmentFlowAlongInternal(
double deltaFlow, VertexExtension node, Set seen)
{
if (node == currentSource) {
return true;
}
if (seen.contains(node)) {
return false;
}
seen.add(node);
AnnotatedFlowEdge prev = node.lastArcs.get(0);
if (augmentFlowAlongInternal(deltaFlow, prev. getSource(), seen)) {
pushFlowThrough(prev, deltaFlow);
return true;
}
return false;
}
private VertexExtension getVertexExtension(V v)
{
return (VertexExtension) vertexExtensionManager.getExtension(v);
}
class VertexExtension
extends
VertexExtensionBase
{
boolean visited; // this mark is used during BFS to mark visited nodes
List lastArcs; // last arc(-s) in the shortest path used to reach this
// vertex
}
}