All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jgrapht.alg.flow.GusfieldGomoryHuCutTree Maven / Gradle / Ivy

The newest version!
/*
 * (C) Copyright 2016-2023, 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.*;

/**
 * This class computes a Gomory-Hu tree (GHT) using the algorithm proposed by Dan Gusfield. For a
 * definition of GHTs, refer to: Gomory, R., Hu, T. Multi-terminal network flows. Journal of the
 * Socieity for Industrial and Applied mathematics, 9(4), p551-570, 1961. GHTs can be used to
 * efficiently query the maximum flows and minimum cuts for all pairs of vertices. The algorithm is
 * described in: Gusfield, D, Very simple methods for all pairs network flow analysis. SIAM
 * Journal on Computing, 19(1), p142-155, 1990
* In an undirected graph, there exist $\frac{n(n-1)}{2}$ different vertex pairs. This class * computes the maximum flow/minimum cut between each of these pairs efficiently by performing * exactly $(n-1)$ minimum $s-t$ cut computations. If your application needs fewer than $n-1$ * flow/cut computations, consider computing the maximum flows/minimum cuts manually through * {@link MaximumFlowAlgorithm}/{@link MinimumSTCutAlgorithm}. * * *

* The runtime complexity of this class is $O((V-1)Q)$, where $Q$ is the runtime complexity of the * algorithm used to compute $s-t$ cuts in the graph. By default, this class uses the * {@link PushRelabelMFImpl} implementation to calculate minimum s-t cuts. This class has a runtime * complexity of $O(V^3)$, resulting in a $O(V^4)$ runtime complexity for the overall algorithm. * * *

* Note: this class performs calculations in a lazy manner. The GHT is not calculated until the * first invocation of {@link GusfieldGomoryHuCutTree#getMaximumFlowValue(Object, Object)} or * {@link GusfieldGomoryHuCutTree#getGomoryHuTree()}. Moreover, this class only calculates * the value of the maximum flow between a source-destination pair; it does not calculate the * corresponding flow per edge. If you need to know the exact flow through an edge, use one of the * alternative {@link MaximumFlowAlgorithm} implementations. * *

* In contrast to an Equivalent Flow Tree ({@link GusfieldEquivalentFlowTree}), Gomory-Hu trees also * provide all minimum cuts for all pairs of vertices! * *

* 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 GusfieldGomoryHuCutTree implements MaximumFlowAlgorithm, MinimumSTCutAlgorithm { private final Graph network; /* Number of vertices in the graph */ private final int n; /* Algorithm used to computed the Maximum $s-t$ flows */ private final MinimumSTCutAlgorithm minimumSTCutAlgorithm; /* Data structures for computations */ private List vertexList = new ArrayList<>(); private Map indexMap = new HashMap<>(); private int[] p; // See vector p in the paper description private double[] fl; // See vector fl in the paper description /* Matrix containing the flow values for every $s-t$ pair */ private double[][] flowMatrix = null; private V lastInvokedSource = null; private V lastInvokedTarget = null; private Set sourcePartitionLastInvokedSource = null; private SimpleWeightedGraph gomoryHuTree = null; /** * Constructs a new GusfieldEquivalentFlowTree instance. * * @param network input graph */ public GusfieldGomoryHuCutTree(Graph network) { this(network, MaximumFlowAlgorithmBase.DEFAULT_EPSILON); } /** * Constructs a new GusfieldEquivalentFlowTree instance. * * @param network input graph * @param epsilon precision */ public GusfieldGomoryHuCutTree(Graph network, double epsilon) { this(network, new PushRelabelMFImpl<>(network, epsilon)); } /** * Constructs a new GusfieldEquivalentFlowTree instance. * * @param network input graph * @param minimumSTCutAlgorithm algorithm used to compute the minimum s-t cuts */ public GusfieldGomoryHuCutTree( Graph network, MinimumSTCutAlgorithm minimumSTCutAlgorithm) { this.network = GraphTests.requireUndirected(network); this.n = network.vertexSet().size(); if (n < 2) throw new IllegalArgumentException("Graph must have at least 2 vertices"); this.minimumSTCutAlgorithm = minimumSTCutAlgorithm; vertexList.addAll(network.vertexSet()); for (int i = 0; i < vertexList.size(); i++) indexMap.put(vertexList.get(i), i); } /** * Runs the algorithm */ private void calculateGomoryHuTree() { flowMatrix = new double[n][n]; p = new int[n]; fl = new double[n]; for (int s = 1; s < n; s++) { int t = p[s]; double flowValue = minimumSTCutAlgorithm.calculateMinCut(vertexList.get(s), vertexList.get(t)); Set sourcePartition = minimumSTCutAlgorithm.getSourcePartition(); // Set X in the // paper fl[s] = flowValue; for (int i = 0; i < n; i++) if (i != s && sourcePartition.contains(vertexList.get(i)) && p[i] == t) p[i] = s; if (sourcePartition.contains(vertexList.get(p[t]))) { p[s] = p[t]; p[t] = s; fl[s] = fl[t]; fl[t] = flowValue; } // populate the flow matrix flowMatrix[s][t] = flowMatrix[t][s] = flowValue; for (int i = 0; i < s; i++) if (i != t) flowMatrix[s][i] = flowMatrix[i][s] = Math.min(flowMatrix[s][t], flowMatrix[t][i]); } } /** * Returns the Gomory-Hu Tree as an actual tree (graph). Note that this tree is not necessarily * unique. The edge weights represent the flow values/cut weights. This method runs in $O(n)$ * time. * * @return Gomory-Hu Tree */ public SimpleWeightedGraph getGomoryHuTree() { if (p == null) // Lazy invocation of the algorithm this.calculateGomoryHuTree(); // Compute the tree from scratch. Since we compute a new tree, the user is free to modify // this tree. SimpleWeightedGraph gomoryHuTree = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(gomoryHuTree, vertexList); for (int i = 1; i < n; i++) { Graphs.addEdge(gomoryHuTree, vertexList.get(i), vertexList.get(p[i]), fl[i]); } return gomoryHuTree; } /* ================== Maximum Flow ================== */ /** * Unsupported operation * * @param source source of the flow inside the network * @param sink sink of the flow inside the network * * @return nothing */ @Override public MaximumFlow getMaximumFlow(V source, V sink) { throw new UnsupportedOperationException( "Flows calculated via Gomory-Hu trees only provide a maximum flow value, not the exact flow per edge/arc."); } /** * Returns the Maximum flow between source and sink. The algorithm is only executed once; * successive invocations of this method will return in $O(1)$ time. * * @param source source vertex * @param sink sink vertex * @return the Maximum flow between source and sink. */ @Override public double getMaximumFlowValue(V source, V sink) { assert indexMap.containsKey(source) && indexMap.containsKey(sink); lastInvokedSource = source; lastInvokedTarget = sink; sourcePartitionLastInvokedSource = null; gomoryHuTree = null; if (p == null) // Lazy invocation of the algorithm this.calculateGomoryHuTree(); return flowMatrix[indexMap.get(source)][indexMap.get(sink)]; } /** * Unsupported operation * * @return nothing */ @Override public Map getFlowMap() { throw new UnsupportedOperationException( "Flows calculated via Gomory-Hu trees only provide a maximum flow value, not the exact flow per edge/arc."); } /** * Unsupported operation * * @param e edge * @return nothing */ @Override public V getFlowDirection(E e) { throw new UnsupportedOperationException( "Flows calculated via Gomory-Hu trees only provide a maximum flow value, not the exact flow per edge/arc."); } /* ================== Minimum Cut ================== */ @Override public double calculateMinCut(V source, V sink) { return getMaximumFlowValue(source, sink); } /** * Calculates the minimum cut in the graph, that is, the minimum cut over all $s-t$ pairs. The * same result can be obtained with the {@link org.jgrapht.alg.StoerWagnerMinimumCut} * implementation. After invoking this method, the source/sink partitions corresponding to the * minimum cut can be queried through the {@link #getSourcePartition()} and * {@link #getSinkPartition()} methods. After computing the Gomory-Hu Cut tree, this method runs * in $O(N)$ time. * * @return weight of the minimum cut in the graph */ public double calculateMinCut() { if (this.gomoryHuTree == null) this.gomoryHuTree = this.getGomoryHuTree(); DefaultWeightedEdge cheapestEdge = gomoryHuTree .edgeSet().stream().min(Comparator.comparing(gomoryHuTree::getEdgeWeight)) .orElseThrow(() -> new RuntimeException("graph is empty?!")); lastInvokedSource = gomoryHuTree.getEdgeSource(cheapestEdge); lastInvokedTarget = gomoryHuTree.getEdgeTarget(cheapestEdge); sourcePartitionLastInvokedSource = null; return gomoryHuTree.getEdgeWeight(cheapestEdge); } @Override public double getCutCapacity() { return calculateMinCut(lastInvokedSource, lastInvokedTarget); } @Override public Set getSourcePartition() { if (sourcePartitionLastInvokedSource != null) return sourcePartitionLastInvokedSource; if (this.gomoryHuTree == null) this.gomoryHuTree = this.getGomoryHuTree(); Set pathEdges = this.findPathBetween(gomoryHuTree, lastInvokedSource, lastInvokedTarget); DefaultWeightedEdge cheapestEdge = pathEdges.stream().min(Comparator.comparing(gomoryHuTree::getEdgeWeight)).orElseThrow( () -> new RuntimeException("path is empty?!")); // Remove the selected edge from the gomoryHuTree graph. The resulting graph consists of 2 // components V source = gomoryHuTree.getEdgeSource(cheapestEdge); V target = gomoryHuTree.getEdgeTarget(cheapestEdge); gomoryHuTree.removeEdge(cheapestEdge); // Return the vertices in the component with the source vertex sourcePartitionLastInvokedSource = new ConnectivityInspector<>(gomoryHuTree).connectedSetOf(lastInvokedSource); // Restore the internal tree structure by putting the edge back gomoryHuTree.addEdge(source, target, cheapestEdge); return sourcePartitionLastInvokedSource; } /** * BFS method to find the edges in the shortest path from a source to a target vertex in a tree * graph. * * @param tree input graph * @param source source * @param target target * @return edges constituting the shortest path between source and target */ private Set findPathBetween( SimpleWeightedGraph tree, V source, V target) { boolean[] visited = new boolean[vertexList.size()]; Map predecessorMap = new HashMap(); Queue queue = new ArrayDeque<>(); queue.add(source); boolean found = false; while (!found && !queue.isEmpty()) { V next = queue.poll(); for (V v : Graphs.neighborListOf(tree, next)) { if (!visited[indexMap.get(v)]) { predecessorMap.put(v, next); queue.add(v); } if (v == target) { found = true; break; } } visited[indexMap.get(next)] = true; } Set edges = new LinkedHashSet<>(); V v = target; while (v != source) { V pred = predecessorMap.get(v); edges.add(tree.getEdge(v, pred)); v = pred; } return edges; } @Override public Set getSinkPartition() { Set sinkPartition = new LinkedHashSet<>(network.vertexSet()); sinkPartition.removeAll(this.getSourcePartition()); return sinkPartition; } @Override public Set getCutEdges() { Set cutEdges = new LinkedHashSet<>(); Set sourcePartion = this.getSourcePartition(); for (E e : network.edgeSet()) { V source = network.getEdgeSource(e); V sink = network.getEdgeTarget(e); if (sourcePartion.contains(source) ^ sourcePartion.contains(sink)) cutEdges.add(e); } return cutEdges; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy