1285 lines
56 KiB
Java
1285 lines
56 KiB
Java
import org.jgrapht.Graph;
|
|
import org.jgrapht.GraphTests;
|
|
import org.jgrapht.GraphType;
|
|
import org.jgrapht.Graphs;
|
|
import org.jgrapht.alg.connectivity.GabowStrongConnectivityInspector;
|
|
import org.jgrapht.alg.interfaces.MatchingAlgorithm;
|
|
|
|
import java.io.Serializable;
|
|
import java.math.BigInteger;
|
|
import java.math.BigDecimal;
|
|
import java.math.RoundingMode;
|
|
|
|
import org.jgrapht.alg.shortestpath.DijkstraShortestPath;
|
|
import org.jgrapht.graph.*;
|
|
import org.jgrapht.graph.builder.GraphBuilder;
|
|
import org.jheaps.*;
|
|
import org.jheaps.tree.*;
|
|
import java.util.*;
|
|
import java.util.function.*;
|
|
import java.util.stream.Collectors;
|
|
|
|
/**
|
|
* Maximum weight matching in bipartite graphs with strictly integer edge weights, using a scaling algorithm
|
|
* via Duan and Su with O(m * sqrt(n) * log(N)) running time, where m is the number of edges, n is the number
|
|
* of vertices in the larger partition of the graph, and N is the maximum integer edge weight.
|
|
*
|
|
* Behavior when given a graph without strictly integer weights is undefined.
|
|
*
|
|
* See:
|
|
* "A Scaling Algorithm for Maximum Weight Matching in Bipartite Graphs"
|
|
* Ran Duan and Hsin-Hao Su, Proceedings of the Twenty-Third Annual ACM-SIAM Symposium on Discrete Algorithms, p. 1413-1424. (2012)
|
|
* https://web.eecs.umich.edu/~pettie/matching/Duan-Su-scaling-bipartite-matching.pdf
|
|
*
|
|
* @param <V> the graph vertex type
|
|
* @param <E> the graph edge type
|
|
*
|
|
* @author Eugene Fischer
|
|
*/
|
|
|
|
public class MaximumIntegerWeightBipartiteMatching<V, E> implements MatchingAlgorithm<V, E> {
|
|
|
|
private final Graph<V, E> graph;
|
|
private final Set<V> partition1;
|
|
private final Set<V> partition2;
|
|
private final Set<E> matching;
|
|
private final BigInteger minPartitionSize;
|
|
private final BigDecimal maxEdgeWeight; //must be an integer value
|
|
private BigInteger matchingWeight = BigInteger.ZERO;
|
|
//the number three as a BigDecimal
|
|
private final BigDecimal THREE = new BigDecimal(3);
|
|
// the initial potential constant
|
|
private final BigDecimal delta_0;
|
|
// the potential constant used in a given scaling phase
|
|
private BigDecimal delta;
|
|
// the number of scaling steps to use
|
|
private final Integer numberOfScales;
|
|
// the current scaling step
|
|
private Integer currentScale = 0;
|
|
// vertex potentials, not necessarily integers
|
|
private Map<V, BigDecimal> potentials = new HashMap<>();
|
|
;
|
|
// the matched edge of a vertex, also used to check if a vertex is free
|
|
private Map<V, E> matchedEdge;
|
|
private final Comparator<BigDecimal> decimalComparator;
|
|
private final Comparator<BigInteger> integerComparator;
|
|
private final Function<Comparator<BigDecimal>, AddressableHeap<BigDecimal, V>> heapSupplier;
|
|
// data structures for finding alternating paths
|
|
private AddressableHeap<BigDecimal, V> heap;
|
|
private Map<V, AddressableHeap.Handle<BigDecimal, V>> nodeInHeap;
|
|
private Map<V, E> predecessor;
|
|
private Map<V, BigDecimal> distance;
|
|
|
|
/*
|
|
* Constructor without a given maximum edge weight or addressable heap supplier
|
|
*
|
|
* @param graph the input graph
|
|
* @param partition1 the first partition of the vertex set
|
|
* @param partition2 the second partition of the vertex set
|
|
* @throws IllegalArgumentException if graph is not undirected
|
|
* @throws NumberFormatException if there are non-integer edge weights
|
|
*/
|
|
public MaximumIntegerWeightBipartiteMatching(Graph<V, E> graph, Set<V> partition1, Set<V> partition2) {
|
|
this(graph, partition1, partition2, new BigDecimal(String.valueOf(graph
|
|
.edgeSet()
|
|
.stream()
|
|
.mapToDouble(graph::getEdgeWeight)
|
|
// .peek( w -> new BigInteger(String.valueOf(w)))
|
|
.max())),
|
|
FibonacciHeap::new);
|
|
}
|
|
|
|
/*
|
|
* Constructor without a given maximum edge weight
|
|
*
|
|
* @param graph the input graph
|
|
* @param partition1 the first partition of the vertex set
|
|
* @param partition2 the second partition of the vertex set
|
|
* @param heapSupplier a supplier for the addressable heap to use in the algorithm
|
|
* @throws IllegalArgumentException if graph is not undirected
|
|
* @throws NumberFormatException if there are non-integer edge weights
|
|
*/
|
|
public MaximumIntegerWeightBipartiteMatching(Graph<V, E> graph, Set<V> partition1, Set<V> partition2,
|
|
Function<Comparator<BigDecimal>, AddressableHeap<BigDecimal, V>> heapSupplier) {
|
|
this(graph, partition1, partition2, new BigDecimal(String.valueOf(graph
|
|
.edgeSet()
|
|
.stream()
|
|
.mapToDouble(graph::getEdgeWeight)
|
|
.peek(w -> new BigInteger(String.valueOf(w)))
|
|
.max())),
|
|
heapSupplier);
|
|
}
|
|
|
|
/*
|
|
* Constructor without a given addressable heap supplier
|
|
*
|
|
* @param graph is the input graph
|
|
* @param partition1 is the first partition of the vertex set
|
|
* @param partition2 is the second partition of the vertex set
|
|
* @param maxEdgeWeight the maximum edge weight present in the graph (which must be an integer value)
|
|
* @throws IllegalArgumentException if graph is not undirected
|
|
* @throws NumberFormatException if there are non-integer edge weights
|
|
*/
|
|
public MaximumIntegerWeightBipartiteMatching(Graph<V, E> graph, Set<V> partition1, Set<V> partition2,
|
|
BigDecimal maxEdgeWeight) {
|
|
this(graph, partition1, partition2, maxEdgeWeight, FibonacciHeap::new);
|
|
}
|
|
|
|
|
|
/*
|
|
* Constructor.
|
|
*
|
|
* Does not check to confirm that all edge weights are integer values. Behavior for a graph with
|
|
* non-integer weights is undefined.
|
|
*
|
|
* @param graph is the input graph
|
|
* @param partition1 is the first partition of the vertex set
|
|
* @param partition2 is the second partition of the vertex set
|
|
* @param maxEdgeWeight the maximum edge weight present in the graph (which must be an integer value)
|
|
* @param heapSupplier a supplier for the addressable heap to use in the algorithm
|
|
* @throws IllegalArgumentException if graph is not undirected
|
|
* @throws IllegalArgumentException maxEdgeWeight is not an integer value
|
|
*/
|
|
public MaximumIntegerWeightBipartiteMatching(Graph<V, E> graph, Set<V> partition1, Set<V> partition2,
|
|
BigDecimal maxEdgeWeight,
|
|
Function<Comparator<BigDecimal>, AddressableHeap<BigDecimal, V>> heapSupplier) {
|
|
this.graph = GraphTests.requireUndirected(graph);
|
|
if (!GraphTests.isBipartitePartition(graph, partition1, partition2)) {
|
|
throw new IllegalArgumentException("Graph partition is not bipartite");
|
|
}
|
|
try {
|
|
int test = maxEdgeWeight.intValueExact();
|
|
} catch (ArithmeticException error) {
|
|
throw new IllegalArgumentException("Maximum edge weight must be an integer value small enough to fit in an int");
|
|
}
|
|
// Ensure that partition1 is smaller or equal in size compared to partition 2
|
|
if (partition1.size() <= partition2.size()) {
|
|
this.partition1 = Objects.requireNonNull(partition1, "Partition 1 cannot be null");
|
|
this.partition2 = Objects.requireNonNull(partition2, "Partition 2 cannot be null");
|
|
} else { // else, swap
|
|
this.partition1 = Objects.requireNonNull(partition2, "Partition 2 cannot be null");
|
|
this.partition2 = Objects.requireNonNull(partition1, "Partition 1 cannot be null");
|
|
}
|
|
this.decimalComparator = Comparator.<BigDecimal>naturalOrder();
|
|
this.integerComparator = Comparator.<BigInteger>naturalOrder();
|
|
this.heapSupplier = Objects.requireNonNull(heapSupplier, "Heap supplier cannot be null");
|
|
Integer n = Math.min(partition1.size(), partition2.size());
|
|
this.minPartitionSize = new BigInteger(String.valueOf(Math.max(partition1.size(), partition2.size())));
|
|
this.maxEdgeWeight = maxEdgeWeight;
|
|
this.matching = new LinkedHashSet<>();
|
|
this.matchedEdge = new HashMap<>();
|
|
Double exponent = maxEdgeWeight.doubleValue() / Math.sqrt(Double.parseDouble(n.toString()));
|
|
exponent = Math.log(exponent) / Math.log(2); //log base two. log_b(n) = log(n) / log(b)
|
|
exponent = Math.floor(exponent);
|
|
// the initial potential constant
|
|
this.delta_0 = new BigDecimal(BigInteger.TWO.pow(Double.valueOf(exponent.toString()).intValue()));
|
|
this.delta = this.delta_0;
|
|
Double numberOfScales = Math.log(Double.parseDouble(maxEdgeWeight.toString())) / Math.log(2);
|
|
numberOfScales = Math.ceil(numberOfScales);
|
|
this.numberOfScales = Double.valueOf(numberOfScales.toString()).intValue();
|
|
}
|
|
|
|
public Map<V, BigDecimal> getPotentials() {
|
|
if (potentials == null) {
|
|
return Collections.emptyMap();
|
|
} else {
|
|
return Collections.unmodifiableMap(potentials);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public Matching<V, E> getMatching() {
|
|
|
|
/*
|
|
* Test input instance
|
|
*/
|
|
if (!GraphTests.isSimple(graph)) {
|
|
throw new IllegalArgumentException("Only simple graphs supported");
|
|
}
|
|
if (!GraphTests.isBipartitePartition(graph, partition1, partition2)) {
|
|
throw new IllegalArgumentException("Graph partition is not bipartite");
|
|
}
|
|
|
|
// empty graph
|
|
if (graph.edgeSet().isEmpty()) {
|
|
return new MatchingImpl<>(graph, matching, matchingWeight.doubleValue());
|
|
}
|
|
|
|
/*
|
|
* ALGORITHM PHASE 1
|
|
* operates on scale 0
|
|
* in this phase an augmenting path must have free endpoints
|
|
*/
|
|
// initialize vertex potentials
|
|
for (V v : partition1) {
|
|
potentials.put(v, delta.multiply(bigDecimalFloor(maxEdgeWeight.divide(delta))));
|
|
}
|
|
for (V v : partition2) {
|
|
potentials.put(v, BigDecimal.ZERO);
|
|
}
|
|
while (numberOfFreeVertices(partition1) != 0 && !isZeroPotentialForFreeVertices(partition1)) {
|
|
// AUGMENTATION and DUAL ADJUSTMENT
|
|
phase1AugmentationAndDualAdjustment(BigDecimal.ONE, BigDecimal.ONE);
|
|
}
|
|
incrementScale();
|
|
|
|
/*
|
|
* ALGORITHM PHASE II
|
|
* operates on scale 1 - numberOfScales
|
|
* in this phase an augmenting path may start or end with matched edges
|
|
*/
|
|
while (currentScale <= numberOfScales) {
|
|
|
|
//update potentials for left vertices
|
|
partition1.forEach(v -> potentials.put(v, potentials.get(v).add(delta)));
|
|
/*
|
|
* Run one iteration of the Phase I Augmentation and Dual Adjustment procedures on G[1, 3].
|
|
* This is to correct the free-vertex duals condition.
|
|
*/
|
|
phase1AugmentationAndDualAdjustment(BigDecimal.ONE, THREE);
|
|
|
|
//AUGMENTATION
|
|
// stage 1 - eliminate augmenting cycles
|
|
// find a maximal set of vertex-disjoint augmenting cycles via modified DFS
|
|
Set<V> matchedVertices = matchedEdge.keySet();
|
|
Set<V> visitedVertices = new HashSet<>();
|
|
List<List<E>> cycles = new ArrayList<>();
|
|
for (V vertex : matchedVertices) {
|
|
//skip any vertices that have been visited already
|
|
if (visitedVertices.contains(vertex)) {
|
|
continue;
|
|
}
|
|
Stack<V> stack = new Stack<>();
|
|
Map<V, E> predecessor = new HashMap<V, E>();
|
|
stack.push(vertex);
|
|
while (!stack.empty()) {
|
|
V v = stack.pop();
|
|
if (!matchedVertices.contains(v)) {
|
|
//if this is a free vertex, ignore it
|
|
continue;
|
|
}
|
|
visitedVertices.add(v);
|
|
E edge = matchedEdge.get(v);
|
|
V v_mate = Graphs.getOppositeVertex(graph, edge, v);
|
|
visitedVertices.add(v_mate);
|
|
predecessor.put(v_mate, edge);
|
|
for (E e : graph.edgesOf(v_mate)) {
|
|
//ignore matched edges
|
|
if (matching.contains(e)) {
|
|
continue;
|
|
}
|
|
V nextVertex = Graphs.getOppositeVertex(graph, e, v_mate);
|
|
if (!visitedVertices.contains(nextVertex)) {
|
|
//if this vertex wasn't previously visited, add it to the stack
|
|
stack.push(nextVertex);
|
|
} else {
|
|
//check if nextVertex is an ancestor of v
|
|
V testVertex = v;
|
|
List<E> cycle = new ArrayList<>();
|
|
cycle.add(0, e);
|
|
cycle.add(0, edge);
|
|
while (predecessor.containsKey(testVertex)) {
|
|
E nextEdge = predecessor.get(testVertex);
|
|
cycle.add(0, nextEdge);
|
|
testVertex = Graphs.getOppositeVertex(graph, nextEdge, testVertex);
|
|
if (testVertex.equals(nextVertex)) {
|
|
//this is a cycle, add it
|
|
cycles.add(cycle);
|
|
//back up the search to before this cycle
|
|
V parent;
|
|
if (predecessor.containsKey(nextVertex)) {
|
|
parent = Graphs.getOppositeVertex(graph, predecessor.get(nextVertex), nextVertex);
|
|
} else {
|
|
parent = v;
|
|
}
|
|
while (!stack.empty() && !stack.peek().equals(parent)) {
|
|
stack.pop();
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
augmentMatching(cycles);
|
|
// stage 2 - eliminate augmenting paths
|
|
//starting vertices are zero potential vertices that are free if in partition1 or matched if in partition2
|
|
Set<V> startingVertices = partition1.stream()
|
|
.filter(v -> !matchedEdge.containsKey(v))
|
|
.filter(v -> potentials.get(v).compareTo(BigDecimal.ZERO) == 0)
|
|
.collect(Collectors.toCollection(HashSet::new));
|
|
startingVertices.addAll(partition2.stream()
|
|
.filter(v -> matchedEdge.containsKey(v))
|
|
.filter(v -> potentials.get(v).compareTo(BigDecimal.ZERO) == 0)
|
|
.collect(Collectors.toCollection(HashSet::new)));
|
|
List<List<E>> maximalAugmentingPaths = new ArrayList<>();
|
|
System.out.println("Starting vertex count: " + startingVertices.size());
|
|
visitedVertices = new HashSet<>();
|
|
Graph<V, E> directedGraph = new asDirectedGraph<V, E>(graph);
|
|
Graph<V, E> subgraphG13 = new MaskSubgraph<>(directedGraph, v -> true, e -> isEligibleEdge(e, BigDecimal.ONE, THREE));
|
|
for (V vertex : startingVertices) {
|
|
if (visitedVertices.contains(vertex)) {
|
|
continue;
|
|
}
|
|
Stack<V> stack = new Stack<>();
|
|
Map<V, E> predecessor = new HashMap<V, E>();
|
|
if (!matchedEdge.containsKey(vertex)) {
|
|
stack.push(vertex);
|
|
} else {
|
|
stack.push(Graphs.getOppositeVertex(graph, matchedEdge.get(vertex), vertex));
|
|
}
|
|
boolean stillSearching = true;
|
|
while (!stack.empty() && stillSearching) {
|
|
V v = stack.pop();
|
|
if(!subgraphG13.vertexSet().contains(v)){
|
|
continue;
|
|
}
|
|
visitedVertices.add(v);
|
|
for (E e : subgraphG13.edgesOf(v)) {
|
|
if (matching.contains(e)) {
|
|
continue;
|
|
}
|
|
V nextVertex = Graphs.getOppositeVertex(graph, e, v);
|
|
predecessor.put(nextVertex, e);
|
|
if (!matchedEdge.containsKey(nextVertex)) {
|
|
List<E> path = new ArrayList<>();
|
|
V testVertex = nextVertex;
|
|
while (predecessor.containsKey(testVertex)) {
|
|
E edge = predecessor.get(testVertex);
|
|
path.add(0, edge);
|
|
testVertex = Graphs.getOppositeVertex(graph, edge, testVertex);
|
|
}
|
|
maximalAugmentingPaths.add(path);
|
|
stillSearching = false;
|
|
} else {
|
|
E edge = matchedEdge.get(nextVertex);
|
|
stack.push(Graphs.getOppositeVertex(graph, edge, nextVertex));
|
|
}
|
|
}
|
|
if (stillSearching && potentials.get(v).compareTo(BigDecimal.ZERO) == 0) {
|
|
List<E> path = new ArrayList<>();
|
|
V testVertex = v;
|
|
while (predecessor.containsKey(testVertex)) {
|
|
E edge = predecessor.get(testVertex);
|
|
path.add(0, edge);
|
|
testVertex = Graphs.getOppositeVertex(graph, edge, testVertex);
|
|
}
|
|
maximalAugmentingPaths.add(path);
|
|
stillSearching = false;
|
|
}
|
|
}
|
|
}
|
|
augmentMatching(maximalAugmentingPaths);
|
|
|
|
//DUAL ADJUSTMENT
|
|
// --ANTICHAIN CASE
|
|
//get the set of violated edges
|
|
Set<E> violatedEdges = phase2GetViolatedEdges();
|
|
//get the set of adjustable vertices
|
|
//first find an intermediate set of vertices
|
|
Set<V> intermediateVertices = new HashSet<>();
|
|
for (V v : graph.vertexSet()) {
|
|
if (!matchedEdge.containsKey(v)) {
|
|
//add all free vertices
|
|
intermediateVertices.add(v);
|
|
} else if (potentials.get(Graphs.getOppositeVertex(graph, matchedEdge.get(v), v))
|
|
.compareTo(BigDecimal.ZERO) == 0) {
|
|
//add all matched vertices with a zero-potential mate
|
|
intermediateVertices.add(v);
|
|
}
|
|
}
|
|
//get V_odd(intermediateVertices, G[1, 3])
|
|
Set<V> nonAdjustableVertices = getEvenOddVertices(intermediateVertices, BigDecimal.ONE, THREE).get(1);
|
|
//finally, get the set of adjustable vertices
|
|
Set<V> adjustableVertices = graph.vertexSet()
|
|
.stream()
|
|
.filter(v -> !nonAdjustableVertices.contains(v))
|
|
.collect(Collectors.toCollection(HashSet::new));
|
|
//construct vertex sets X_L and X_R
|
|
Set<V> intermediateAdjustableSubsetLeft = adjustableVertices.stream()
|
|
.filter(partition1::contains)
|
|
.collect(Collectors.toCollection(HashSet::new));
|
|
Set<V> adjustableSubsetLeft = new HashSet<>();
|
|
for (V v : intermediateAdjustableSubsetLeft) {
|
|
if (graph.edgesOf(v).stream().anyMatch(violatedEdges::contains)) {
|
|
adjustableSubsetLeft.add(v);
|
|
}
|
|
}
|
|
Set<V> intermediateAdjustableSubsetRight = adjustableVertices.stream()
|
|
.filter(partition2::contains)
|
|
.collect(Collectors.toCollection(HashSet::new));
|
|
Set<V> adjustableSubsetRight = new HashSet<>();
|
|
for (V v : intermediateAdjustableSubsetRight) {
|
|
if (graph.edgesOf(v).stream().anyMatch(violatedEdges::contains)) {
|
|
adjustableSubsetRight.add(v);
|
|
}
|
|
}
|
|
Set<V> adjustableSubset = adjustableSubsetRight.size() > adjustableSubsetLeft.size() ?
|
|
adjustableSubsetRight :
|
|
adjustableSubsetLeft;
|
|
List<Set<V>> evenOddVertices = getEvenOddVertices(adjustableSubset, BigDecimal.ONE, THREE);
|
|
for (V v : evenOddVertices.get(0)) {
|
|
potentials.put(v, potentials.get(v).subtract(delta));
|
|
}
|
|
for (V v : evenOddVertices.get(1)) {
|
|
potentials.put(v, potentials.get(v).add(delta));
|
|
}
|
|
|
|
// --CHAIN CASE
|
|
//get a set of all the free vertices, these will be the "roots" of Dial's algorithm
|
|
Set<V> freeVertices = graph.vertexSet()
|
|
.stream()
|
|
.filter(v -> !matchedEdge.containsKey(v))
|
|
.collect(Collectors.toCollection(HashSet::new));
|
|
//create a set of buckets for each possible path distance. This can be a fixed array
|
|
//with a length of the number of vertices-1 times the maximum edge weight.
|
|
int numberOfBuckets;
|
|
//TODO: remove this
|
|
int maxIndex = 0; //this is just to test something, remove later
|
|
// try {
|
|
// numberOfBuckets = (graph.vertexSet().size() - 1) * maxEdgeWeight.intValueExact();
|
|
// } catch (ArithmeticException error) {
|
|
// //if the weights are too big for the above product to fit in an int,
|
|
// numberOfBuckets = Integer.MAX_VALUE - 1;
|
|
// }
|
|
numberOfBuckets = bigDecimalCeil(delta).intValueExact();
|
|
List<List<E>> shortestPaths = new ArrayList<>();
|
|
for (V v : freeVertices) {
|
|
//This needs to be a list of lists, because there can be more than one vertex with the same distance
|
|
List<List<V>> buckets = new ArrayList<>(numberOfBuckets);
|
|
while (buckets.size() < numberOfBuckets) {
|
|
buckets.add(new ArrayList<>());
|
|
}
|
|
// Collections.fill(buckets, new ArrayList<V>());
|
|
Map<V, V> previousVertex = new HashMap<>();
|
|
Map<V, BigDecimal> distances = new HashMap<>();
|
|
buckets.get(0).add(v);
|
|
distances.put(v, BigDecimal.ZERO);
|
|
System.out.println("Actual number of buckets: " + buckets.size());
|
|
for (int i = 0; i < buckets.size(); i++) {
|
|
List<V> vertices = buckets.get(i);
|
|
if (vertices.size() == 0) {
|
|
continue;
|
|
}
|
|
//TODO: remove this
|
|
maxIndex = i;
|
|
i--; //reduce i because we need to check this bucket again next iteration.
|
|
V vertex = vertices.remove(0);
|
|
Set<E> outgoingEdges = phase2GetOutgoingEdges(v, vertex);
|
|
for (E e : outgoingEdges) {
|
|
if (!isEligibleEdge(e, BigDecimal.ZERO, THREE)) {
|
|
continue;
|
|
}
|
|
BigDecimal edgeWeight = phase2GetEdgeWeight(e);
|
|
V nextVertex = Graphs.getOppositeVertex(graph, e, vertex);
|
|
BigDecimal distance = distances.get(vertex).add(edgeWeight);
|
|
if (!distances.containsKey(nextVertex)) {
|
|
distances.put(nextVertex, distance);
|
|
buckets.get(distance.intValue()).add(nextVertex);
|
|
previousVertex.put(nextVertex, vertex);
|
|
} else {
|
|
BigDecimal prevDistance = distances.get(nextVertex);
|
|
if (distance.compareTo(prevDistance) < 0) {
|
|
distances.put(nextVertex, distance);
|
|
buckets.get(prevDistance.intValue()).remove(nextVertex);
|
|
buckets.get(distance.intValue()).add(nextVertex);
|
|
previousVertex.put(nextVertex, vertex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(maxIndex == 0) {
|
|
continue;
|
|
}
|
|
//check which side of the graph v is on
|
|
boolean vInPartition1 = partition1.contains(v);
|
|
BigDecimal dualAdjustAmount = new BigDecimal(numberOfBuckets + 1);
|
|
V endVertex = v;
|
|
for (Map.Entry<V, BigDecimal> entry : distances.entrySet()) {
|
|
BigDecimal newAmount;
|
|
if (entry.getKey().equals(v)) {
|
|
continue;
|
|
}
|
|
if (partition1.contains(entry.getKey()) != vInPartition1 && !matchedEdge.containsKey(entry.getKey())) {
|
|
newAmount = entry.getValue();
|
|
} else if (partition1.contains(entry.getKey()) == vInPartition1) {
|
|
newAmount = entry.getValue().add(potentials.get(entry.getKey()));
|
|
} else {
|
|
newAmount = new BigDecimal(numberOfBuckets + 1);
|
|
}
|
|
if (newAmount.compareTo(dualAdjustAmount) < 0) {
|
|
dualAdjustAmount = newAmount;
|
|
endVertex = entry.getKey();
|
|
}
|
|
}
|
|
dualAdjustAmount = dualAdjustAmount.subtract(distances.get(endVertex));
|
|
if (dualAdjustAmount.compareTo(BigDecimal.ZERO) < 1) {
|
|
dualAdjustAmount = BigDecimal.ZERO;
|
|
}
|
|
if (partition1.contains(endVertex) == vInPartition1) {
|
|
potentials.put(endVertex, potentials.get(endVertex).subtract(dualAdjustAmount));
|
|
} else {
|
|
potentials.put(endVertex, potentials.get(endVertex).add(dualAdjustAmount));
|
|
}
|
|
List<E> path = new ArrayList<>();
|
|
V testVertex = endVertex;
|
|
while (previousVertex.containsKey(testVertex)) {
|
|
E edge = graph.getEdge(testVertex, previousVertex.get(testVertex));
|
|
path.add(0, edge);
|
|
testVertex = previousVertex.get(testVertex);
|
|
}
|
|
shortestPaths.add(path);
|
|
}
|
|
augmentMatching(shortestPaths);
|
|
//TODO: remove this
|
|
System.out.println("max distance reached: " + maxIndex);
|
|
if (currentScale == numberOfScales) {
|
|
break;
|
|
}
|
|
incrementScale();
|
|
}
|
|
|
|
/*
|
|
* ALGORITHM PHASE III
|
|
* operates on scale numberOfScales
|
|
*/
|
|
//detect cycles
|
|
Graph<V, E> directedGraph = new asDirectedGraph<V, E>(graph);
|
|
Graph<V, E> subgraphG01 = new MaskSubgraph<>(directedGraph, v -> true, e -> isEligibleEdge(e, BigDecimal.ZERO, BigDecimal.ONE));
|
|
GabowStrongConnectivityInspector<V, E> sscInspectorCycles = new GabowStrongConnectivityInspector<V, E>(subgraphG01);
|
|
List<Set<V>> stronglyConnectedComponents01 = sscInspectorCycles.stronglyConnectedSets();
|
|
Set<E> nonTightEdges = new HashSet<>();
|
|
for (E e: subgraphG01.edgeSet()) {
|
|
if (matching.contains(e) && getEdgePotential(e) != getScaledWeight(e)) {
|
|
nonTightEdges.add(e);
|
|
}
|
|
}
|
|
Set<E> nonTightEdgesInCycles = new HashSet<>();
|
|
for (E e: nonTightEdges) {
|
|
V source = subgraphG01.getEdgeSource(e);
|
|
V target = subgraphG01.getEdgeTarget(e);
|
|
for (Set<V> vertices: stronglyConnectedComponents01) {
|
|
if (vertices.contains(source) && vertices.contains(target)) {
|
|
nonTightEdgesInCycles.add(e);
|
|
}
|
|
}
|
|
}
|
|
// find a maximal set of vertex-disjoint augmenting cycles via modified DFS
|
|
List<List<E>> cycles = new ArrayList<>();
|
|
for(E e: nonTightEdgesInCycles) {
|
|
List<E> edgeList = DijkstraShortestPath.findPathBetween(subgraphG01, subgraphG01.getEdgeTarget(e), subgraphG01.getEdgeSource(e)).getEdgeList();
|
|
edgeList.add(e);
|
|
cycles.add(edgeList);
|
|
}
|
|
augmentMatching(cycles);
|
|
//detect paths
|
|
Set<V> nonAdjustableVertices = graph.vertexSet()
|
|
.stream()
|
|
.filter(v-> (!matchedEdge.containsKey(v) ||
|
|
(matchedEdge.containsKey(v) && potentials.get(Graphs.getOppositeVertex(graph, matchedEdge.get(v), v) ).equals(BigDecimal.ZERO))))
|
|
.collect(Collectors.toSet());
|
|
Set<E> nonTightMatchedEdges = matching.stream()
|
|
.filter(e -> !getScaledWeight(e).equals(getEdgePotential(e)))
|
|
.collect(Collectors.toSet());
|
|
boolean augmentingPathsExist = false;
|
|
for (E e: nonTightEdges) {
|
|
if (nonAdjustableVertices.contains(graph.getEdgeSource(e)) && nonAdjustableVertices.contains(graph.getEdgeTarget(e))) {
|
|
augmentingPathsExist = true;
|
|
break;
|
|
}
|
|
}
|
|
if (augmentingPathsExist) {
|
|
//largely repeated code, abstract this
|
|
Set<V> startingVertices = partition1.stream()
|
|
.filter(v -> !matchedEdge.containsKey(v))
|
|
.filter(v -> potentials.get(v).compareTo(BigDecimal.ZERO) == 0)
|
|
.collect(Collectors.toCollection(HashSet::new));
|
|
startingVertices.addAll(partition2.stream()
|
|
.filter(v -> matchedEdge.containsKey(v))
|
|
.filter(v -> potentials.get(v).compareTo(BigDecimal.ZERO) == 0)
|
|
.collect(Collectors.toCollection(HashSet::new)));
|
|
List<List<E>> maximalAugmentingPaths = new ArrayList<>();
|
|
System.out.println("Starting vertex count: " + startingVertices.size());
|
|
Set <V> visitedVertices = new HashSet<>();
|
|
for (V vertex : startingVertices) {
|
|
if (visitedVertices.contains(vertex)) {
|
|
continue;
|
|
}
|
|
Stack<V> stack = new Stack<>();
|
|
Map<V, E> predecessor = new HashMap<V, E>();
|
|
if (!matchedEdge.containsKey(vertex)) {
|
|
stack.push(vertex);
|
|
} else {
|
|
stack.push(Graphs.getOppositeVertex(graph, matchedEdge.get(vertex), vertex));
|
|
}
|
|
boolean stillSearching = true;
|
|
while (!stack.empty() && stillSearching) {
|
|
V v = stack.pop();
|
|
if(!subgraphG01.vertexSet().contains(v)){
|
|
continue;
|
|
}
|
|
visitedVertices.add(v);
|
|
for (E e : subgraphG01.edgesOf(v)) {
|
|
if (matching.contains(e)) {
|
|
continue;
|
|
}
|
|
V nextVertex = Graphs.getOppositeVertex(graph, e, v);
|
|
predecessor.put(nextVertex, e);
|
|
if (!matchedEdge.containsKey(nextVertex)) {
|
|
List<E> path = new ArrayList<>();
|
|
V testVertex = nextVertex;
|
|
while (predecessor.containsKey(testVertex)) {
|
|
E edge = predecessor.get(testVertex);
|
|
path.add(0, edge);
|
|
testVertex = Graphs.getOppositeVertex(graph, edge, testVertex);
|
|
}
|
|
maximalAugmentingPaths.add(path);
|
|
stillSearching = false;
|
|
} else {
|
|
E edge = matchedEdge.get(nextVertex);
|
|
stack.push(Graphs.getOppositeVertex(graph, edge, nextVertex));
|
|
}
|
|
}
|
|
if (stillSearching && potentials.get(v).compareTo(BigDecimal.ZERO) == 0) {
|
|
List<E> path = new ArrayList<>();
|
|
V testVertex = v;
|
|
while (predecessor.containsKey(testVertex)) {
|
|
E edge = predecessor.get(testVertex);
|
|
path.add(0, edge);
|
|
testVertex = Graphs.getOppositeVertex(graph, edge, testVertex);
|
|
}
|
|
maximalAugmentingPaths.add(path);
|
|
stillSearching = false;
|
|
}
|
|
}
|
|
}
|
|
augmentMatching(maximalAugmentingPaths);
|
|
}
|
|
//dual adjustment
|
|
Graph<V, E> subgraphG00 = new MaskSubgraph<>(directedGraph, v -> true, e -> isEligibleEdge(e, BigDecimal.ZERO, BigDecimal.ZERO));
|
|
GabowStrongConnectivityInspector<V, E> sscInspectorPaths = new GabowStrongConnectivityInspector<V, E>(subgraphG00);
|
|
List<Set<V>> stronglyConnectedComponents00 = sscInspectorPaths.stronglyConnectedSets();
|
|
Map<Integer, V> dummyVertices = new HashMap<>();
|
|
for (int i = 0; i < stronglyConnectedComponents00.size(); i++) {
|
|
dummyVertices.put(i, stronglyConnectedComponents00.get(i).iterator().next());
|
|
}
|
|
Graph<V, DefaultEdge> condensedG01 = new SimpleDirectedGraph<V, DefaultEdge>(DefaultEdge.class);
|
|
for(V v: subgraphG01.vertexSet()) {
|
|
boolean condenseThis = false;
|
|
for (int i = 0; i < stronglyConnectedComponents00.size(); i++) {
|
|
if (stronglyConnectedComponents00.get(i).contains(v)) {
|
|
condenseThis = true;
|
|
condensedG01.addVertex(dummyVertices.get(i));
|
|
break;
|
|
}
|
|
}
|
|
if (!condenseThis) {
|
|
condensedG01.addVertex(v);
|
|
}
|
|
}
|
|
for (E e: subgraphG01.edgeSet()) {
|
|
boolean replaceThis = false;
|
|
V source = subgraphG01.getEdgeSource(e);
|
|
V target = subgraphG01.getEdgeTarget(e);
|
|
for (int i = 0; i < stronglyConnectedComponents00.size(); i++) {
|
|
if (stronglyConnectedComponents00.get(i).contains(source)
|
|
&& stronglyConnectedComponents00.get(i).contains(target)) {
|
|
replaceThis = true;
|
|
break;
|
|
}
|
|
else if (stronglyConnectedComponents00.get(i).contains(source)) {
|
|
replaceThis = true;
|
|
condensedG01.addEdge(dummyVertices.get(i), target);
|
|
break;
|
|
}
|
|
else if (stronglyConnectedComponents00.get(i).contains(target)) {
|
|
replaceThis = true;
|
|
condensedG01.addEdge(source, dummyVertices.get(i));
|
|
break;
|
|
}
|
|
}
|
|
if (!replaceThis) {
|
|
condensedG01.addEdge(source, target);
|
|
}
|
|
}
|
|
//longest path must start from a vertex with in degree of 0
|
|
Set<V> zeroInDegreeVertices = condensedG01.vertexSet().stream().filter(v -> condensedG01.inDegreeOf(v) == 0).collect(Collectors.toSet());
|
|
//TODO: finish this
|
|
|
|
//print bad edge count
|
|
long badEdges = matching.stream().filter(e -> getEdgePotential(e).subtract(getScaledWeight(e)).equals(delta)).count();
|
|
System.out.println("Bad edge count: "+ badEdges);
|
|
return new MatchingImpl<>(graph, this.matching, this.matching.stream().map(graph::getEdgeWeight).reduce(0.0, (a, b) -> a + b));
|
|
|
|
|
|
}
|
|
|
|
/*
|
|
* Returns the set of violated edges in the current matching. These are the edges for which the
|
|
* sum of the
|
|
*/
|
|
private Set<E> phase2GetViolatedEdges() {
|
|
Set<E> violatedEdges = new HashSet<E>();
|
|
for (E e : matching) {
|
|
BigDecimal edgePotential = getEdgePotential(e);
|
|
BigDecimal scaledWeight = getScaledWeight(e);
|
|
BigDecimal difference = edgePotential.subtract(scaledWeight);
|
|
if (difference.compareTo(delta) > 0) {
|
|
violatedEdges.add(e);
|
|
}
|
|
}
|
|
return violatedEdges;
|
|
}
|
|
|
|
/*
|
|
* Returns the weight for an edge as needed for the search function in Phase 2
|
|
*/
|
|
private BigDecimal phase2GetEdgeWeight(E e) {
|
|
if (matching.contains(e)) {
|
|
return BigDecimal.ZERO;
|
|
} else {
|
|
return getEdgePotential(e).subtract(getScaledWeight(e));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Return outgoing edges of the associated directed graph, as needed for the search
|
|
* function in Phase 2
|
|
*/
|
|
private Set<E> phase2GetOutgoingEdges(V startingVector, V v) {
|
|
if (partition2.contains(startingVector)) {
|
|
return getOutgoingEdges(v);
|
|
} else {
|
|
return getOutgoingEdgesTranspose(v);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The Augmentation and Dual Adjustment steps of Phase I of the algorithm, using eligibility
|
|
* subgraph G[c, d]. This is abstracted out because these steps are used again in Phase II with
|
|
* a different eligibility subgraph than in Phase I.
|
|
*/
|
|
private void phase1AugmentationAndDualAdjustment(BigDecimal c, BigDecimal d) {
|
|
// AUGMENTATION
|
|
// Find a maximal set of augmenting paths in G[c, d]
|
|
List<List<E>> augmentingPaths = new ArrayList<>();
|
|
Set<V> usedVertices = new HashSet<>();
|
|
//in phase 1, the endpoints of an augmenting path must be free vertices
|
|
for (V v : partition1.stream()
|
|
.filter(this::isFreeVertex)
|
|
.collect(Collectors.toCollection(HashSet::new))) {
|
|
augmentingPaths.add(getAugmentingPathPhase1(v, usedVertices, c, d));
|
|
}
|
|
augmentMatching(augmentingPaths);
|
|
//DUAL ADJUSTMENT
|
|
Set<V> leftFreeVertices = partition1.stream()
|
|
.filter(this::isFreeVertex)
|
|
.collect(Collectors.toCollection(HashSet::new));
|
|
List<Set<V>> evenOddVertices = getEvenOddVertices(leftFreeVertices, c, d);
|
|
Set<V> evenVertices = evenOddVertices.get(0);
|
|
Set<V> oddVertices = evenOddVertices.get(1);
|
|
for (V v : evenVertices) {
|
|
potentials.put(v, potentials.get(v).subtract(delta));
|
|
}
|
|
for (V v : oddVertices) {
|
|
potentials.put(v, potentials.get(v).add(delta));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* In Phase 1 an augmenting path is an alternating path in G[c, d] with free endpoints.
|
|
* Function returns a maximal set of vertex-disjoint, shortest augmenting paths via breadth
|
|
* first search, as in Hopcroft-Karp
|
|
*/
|
|
private List<E> getAugmentingPathPhase1(V v, Set<V> usedVertices, BigDecimal c, BigDecimal d) {
|
|
List<E> path = new ArrayList<>();
|
|
Set<V> visited = new HashSet<>();
|
|
Deque<V> queue = new ArrayDeque<>();
|
|
Map<V, E> predecessor = new HashMap<V, E>();
|
|
if (!usedVertices.contains(v)) {
|
|
queue.add(v);
|
|
}
|
|
while (queue.size() > 0) {
|
|
V vertex = queue.removeFirst();
|
|
for (E e : graph.edgesOf(vertex)) {
|
|
if (predecessor.containsKey(vertex)) {
|
|
if (isMatchedEdge(e) == isMatchedEdge(predecessor.get(vertex))) { //ensure this is an alternating path
|
|
continue;
|
|
}
|
|
}
|
|
if (!isEligibleEdge(e, c, d)) { //ensure this edge is in G[1, 1]
|
|
continue;
|
|
}
|
|
V nextVertex = Graphs.getOppositeVertex(graph, e, vertex);
|
|
if (visited.contains(nextVertex)) { //we've already found a shorter path here
|
|
continue;
|
|
}
|
|
if (usedVertices.contains(nextVertex)) { //this vertex is already part of a path
|
|
continue;
|
|
}
|
|
visited.add(nextVertex);
|
|
predecessor.put(nextVertex, e);
|
|
if (isFreeVertex(nextVertex)) {
|
|
//this is the shortest augmenting path
|
|
while (predecessor.containsKey(nextVertex)) {
|
|
usedVertices.add(nextVertex);
|
|
E edge = predecessor.get(nextVertex);
|
|
path.add(0, edge);
|
|
nextVertex = Graphs.getOppositeVertex(graph, edge, nextVertex);
|
|
}
|
|
usedVertices.add(nextVertex);
|
|
return path;
|
|
} else {
|
|
queue.add(nextVertex);
|
|
}
|
|
}
|
|
}
|
|
return path;
|
|
}
|
|
|
|
/*
|
|
* Given a set of vertices, returns how many vertices in the set are not currently matched.
|
|
*/
|
|
private long numberOfFreeVertices(Set<V> vertices) {
|
|
return vertices.stream().filter(this::isFreeVertex).count();
|
|
}
|
|
|
|
/*
|
|
* Given a set of vertices, returns whether every unmatched vertex in the set
|
|
* has a potential value of 0.
|
|
*/
|
|
private boolean isZeroPotentialForFreeVertices(Set<V> vertices) {
|
|
for (V v : vertices) {
|
|
if (matchedEdge.containsKey(v)) {
|
|
continue;
|
|
}
|
|
if (potentials.get(v).compareTo(BigDecimal.ZERO) != 0) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Given a set of augmenting paths, passed as a List of Lists of edges, augments the current
|
|
* matching along all included paths.
|
|
*/
|
|
private void augmentMatching(List<List<E>> augmentingPaths) {
|
|
for (List<E> path : augmentingPaths) {
|
|
for (E edge : path) {
|
|
V s = graph.getEdgeSource(edge);
|
|
V t = graph.getEdgeTarget(edge);
|
|
if (matching.contains(edge)) {
|
|
matchedEdge.remove(s);
|
|
matchedEdge.remove(t);
|
|
matching.remove(edge);
|
|
} else {
|
|
matchedEdge.put(s, edge);
|
|
matchedEdge.put(t, edge);
|
|
matching.add(edge);
|
|
}
|
|
}
|
|
}
|
|
System.out.println("matching size: " + matching.size());
|
|
}
|
|
|
|
private Graph<V, E> getEligibilitySubgraph(BigDecimal c, BigDecimal d) {
|
|
return new MaskSubgraph<>(graph, v -> true, e -> ((matching.contains(e)
|
|
&& getEdgePotential(e).equals(getScaledWeight(e))
|
|
|| (!matching.contains(e)
|
|
&& getScaledWeight(e).add(delta.multiply(c)).compareTo(getEdgePotential(e)) < 1
|
|
&& getEdgePotential(e).compareTo(getScaledWeight(e).add(delta.multiply(d))) < 1))));
|
|
}
|
|
|
|
private List<Set<V>> getEvenOddVertices(Set<V> startingVertices, Graph<V, E> graph) {
|
|
Set<V> visitedVertices = new HashSet<>();
|
|
Set<V> oddVertices = new HashSet<>();
|
|
Set<V> evenVertices = new HashSet<>();
|
|
for (V vertex : startingVertices) {
|
|
//depth first search
|
|
Stack<V> stack = new Stack<>();
|
|
Map<V, Integer> distance = new HashMap<>();
|
|
if (!visitedVertices.contains(vertex)) {
|
|
stack.push(vertex);
|
|
distance.put(vertex, 0);
|
|
}
|
|
while (!stack.isEmpty()) {
|
|
V v = stack.pop();
|
|
if (!visitedVertices.contains(v)) {
|
|
visitedVertices.add(v);
|
|
if (distance.get(v) % 2 == 0) {
|
|
// reachable via an even-length path
|
|
evenVertices.add(v);
|
|
} else {
|
|
// reachable via an odd-length path
|
|
oddVertices.add(v);
|
|
}
|
|
for (E edge : graph.edgesOf(v)) {
|
|
// if this is a vertex in the starting set, only consider unmatched edges
|
|
if (startingVertices.contains(v) && matching.contains(edge)) {
|
|
continue;
|
|
}
|
|
// only consider edges in G[c, d]
|
|
V nextVertex = Graphs.getOppositeVertex(graph, edge, v);
|
|
stack.push(nextVertex);
|
|
distance.put(nextVertex, distance.get(v) + 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ArrayList<Set<V>> evenOddVertices = new ArrayList<>(2);
|
|
evenOddVertices.add(evenVertices);
|
|
evenOddVertices.add(oddVertices);
|
|
return evenOddVertices;
|
|
}
|
|
|
|
/*
|
|
* Function to obtain V_odd(X, G[c, d]) and V_even(X, G[c, d]), where X a subset of vertices and
|
|
* G[c, d] is the eligibility subgraph (as defined for the function isEligibleEdge). V_odd is the
|
|
* set of vertices reachable via an odd-length alternating path in G[c, d] starting with an unmatched
|
|
* edge that incidents a vertex in X, and V_even is the set reachable by an even-length alternating path
|
|
*
|
|
* For efficiency, the algorithm returns both V_even and V_odd as an array of vertex sets, with
|
|
* V_even at index 0 and V_odd at index 1.
|
|
*/
|
|
private List<Set<V>> getEvenOddVertices(Set<V> startingVertices, BigDecimal c, BigDecimal d) {
|
|
Set<V> visitedVertices = new HashSet<>();
|
|
Set<V> oddVertices = new HashSet<>();
|
|
Set<V> evenVertices = new HashSet<>();
|
|
for (V vertex : startingVertices) {
|
|
//depth first search
|
|
Stack<V> stack = new Stack<>();
|
|
Map<V, Integer> distance = new HashMap<>();
|
|
if (!visitedVertices.contains(vertex)) {
|
|
stack.push(vertex);
|
|
distance.put(vertex, 0);
|
|
}
|
|
while (!stack.isEmpty()) {
|
|
V v = stack.pop();
|
|
if (!visitedVertices.contains(v)) {
|
|
visitedVertices.add(v);
|
|
if (distance.get(v) % 2 == 0) {
|
|
// reachable via an even-length path
|
|
evenVertices.add(v);
|
|
} else {
|
|
// reachable via an odd-length path
|
|
oddVertices.add(v);
|
|
}
|
|
for (E edge : graph.edgesOf(v)) {
|
|
// if this is a vertex in the starting set, only consider unmatched edges
|
|
if (startingVertices.contains(v) && matching.contains(edge)) {
|
|
continue;
|
|
}
|
|
// only consider edges in G[c, d]
|
|
if (isEligibleEdge(edge, c, d)) {
|
|
V nextVertex = Graphs.getOppositeVertex(graph, edge, v);
|
|
stack.push(nextVertex);
|
|
distance.put(nextVertex, distance.get(v) + 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ArrayList<Set<V>> evenOddVertices = new ArrayList<>(2);
|
|
evenOddVertices.add(evenVertices);
|
|
evenOddVertices.add(oddVertices);
|
|
return evenOddVertices;
|
|
}
|
|
|
|
/*
|
|
* Returns the outgoing edge set of a vertex as though the graph is a directed graph,
|
|
* with edges going from partition2 to partition1 if they are unmatched and
|
|
* from partition1 to partition2 if they are matched.
|
|
*/
|
|
private Set<E> getOutgoingEdgesTranspose(V v) {
|
|
if (partition1.contains(v)) {
|
|
return graph.edgesOf(v)
|
|
.stream()
|
|
.filter(matching::contains)
|
|
.collect(Collectors.toCollection(HashSet::new));
|
|
} else {
|
|
return graph.edgesOf(v)
|
|
.stream()
|
|
.filter(e -> !matching.contains(e))
|
|
.collect(Collectors.toCollection(HashSet::new));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Returns the outgoing edge set of a vertex as though the graph is a directed graph,
|
|
* with edges going from partition1 to partition2 if they are unmatched and
|
|
* from partition2 to partition1 if they are matched.
|
|
*/
|
|
private Set<E> getOutgoingEdges(V v) {
|
|
if (partition2.contains(v)) {
|
|
return graph.edgesOf(v)
|
|
.stream()
|
|
.filter(matching::contains)
|
|
.collect(Collectors.toCollection(HashSet::new));
|
|
} else {
|
|
return graph.edgesOf(v)
|
|
.stream()
|
|
.filter(e -> !matching.contains(e))
|
|
.collect(Collectors.toCollection(HashSet::new));
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns whether an edge is both in the eligibility subgraph G[c, d] and,
|
|
* from starting vector v, an outgoing edge of the directed graph obtained by
|
|
* orienting edges from partition1 to partition2 if they are unmatched and from
|
|
* partition2 to partition1 if they are matched.
|
|
*/
|
|
private boolean isEligibleDirectedEdge(V v, E e, BigDecimal c, BigDecimal d) {
|
|
if (partition1.contains(v) && matching.contains(e)) {
|
|
return false;
|
|
}
|
|
if (partition2.contains(v) && !matching.contains(e)) {
|
|
return false;
|
|
}
|
|
return isEligibleEdge(e, c, d);
|
|
}
|
|
|
|
/*
|
|
* Returns whether an edge is in the eligibility subgraph G[c, d]. Given an edge e, let y(e) be the sum
|
|
* of the vertex potential of the source and target of e. Let w_i(e) be the scaled weight of e (as given
|
|
* by the scaledWeight function). Then G[c, d] is defined as the subgraph of G containing all edges e
|
|
* satisfying either of the following conditions:
|
|
* * e is not in the current matching and y(e) = w_i(e)
|
|
* or
|
|
* * e is in the current matching and w_i(e) + c * delta <= y(e) <= w_i(e) + d * delta.
|
|
*/
|
|
private boolean isEligibleEdge(E e, BigDecimal c, BigDecimal d) {
|
|
BigDecimal scaledEdgeWeight = getScaledWeight(e);
|
|
BigDecimal edgePotential = getEdgePotential(e);
|
|
if (matching.contains(e)) {
|
|
boolean c_eligible = scaledEdgeWeight.add(c.multiply(delta)).compareTo(edgePotential) <= 0;
|
|
boolean d_eligible = edgePotential.compareTo(scaledEdgeWeight.add(d.multiply(delta))) <= 0;
|
|
return c_eligible && d_eligible;
|
|
}
|
|
return edgePotential.compareTo(scaledEdgeWeight) == 0;
|
|
}
|
|
|
|
/*
|
|
* Given an edge, returns whether it is in the current matching
|
|
*/
|
|
private boolean isMatchedEdge(E e) {
|
|
return matching.contains(e);
|
|
}
|
|
|
|
/*
|
|
* Given a vertex, returns whether its edge set contains a matched edge
|
|
*/
|
|
private boolean isFreeVertex(V v) {
|
|
return !matchedEdge.containsKey(v);
|
|
}
|
|
|
|
/*
|
|
* Given an edge e with weight w(e), returns the scaled weight w_i(e) of that edge,
|
|
* where w_i(e) = delta * floor(w(e) / delta).
|
|
*/
|
|
private BigDecimal getScaledWeight(E e) {
|
|
BigDecimal weight = BigDecimal.valueOf(graph.getEdgeWeight(e));
|
|
return delta.multiply(bigDecimalFloor(weight.divide(delta)));
|
|
}
|
|
|
|
/*
|
|
* Given a vertex, returns the sum of the potentials of its endpoints
|
|
*/
|
|
private BigDecimal getEdgePotential(E e) {
|
|
return potentials.get(graph.getEdgeSource(e)).add(potentials.get(graph.getEdgeTarget(e)));
|
|
}
|
|
|
|
private void incrementScale() {
|
|
if (currentScale < numberOfScales) {
|
|
System.out.println("prev scale: " + currentScale);
|
|
|
|
currentScale = currentScale + 1;
|
|
System.out.println("new scale: " + currentScale);
|
|
|
|
System.out.println("prev delta_i: " + delta);
|
|
delta = delta.divide(new BigDecimal(2));
|
|
System.out.println("new delta_i: " + delta);
|
|
}
|
|
}
|
|
|
|
// Ceiling function for BigDecimal values
|
|
private BigDecimal bigDecimalCeil(BigDecimal num) {
|
|
return num.setScale(0, RoundingMode.CEILING);
|
|
}
|
|
|
|
// Floor function for BigDecimal values
|
|
private BigDecimal bigDecimalFloor(BigDecimal num) {
|
|
return num.setScale(0, RoundingMode.FLOOR);
|
|
}
|
|
|
|
|
|
class asDirectedGraph<V, E> extends GraphDelegator<V, E>
|
|
implements Serializable, Graph<V, E> {
|
|
//edges oriented from partition1 to partition2 if not in the matching, from partition2 to partition1 if
|
|
//in the matching.
|
|
|
|
private static final String NO_EDGE_ADD = "this graph does not support edge addition";
|
|
|
|
public asDirectedGraph(Graph<V, E> g)
|
|
{
|
|
super(g);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public Set<E> getAllEdges(V sourceVertex, V targetVertex)
|
|
{
|
|
Set<E> undirectedList = super.getAllEdges(sourceVertex, targetVertex);
|
|
if (undirectedList.size() == 0) {
|
|
return undirectedList;
|
|
}
|
|
boolean leftSource = partition1.contains(sourceVertex);
|
|
Set<E> directedList;
|
|
if (leftSource) {
|
|
directedList = undirectedList.stream()
|
|
.filter(e -> !matching.contains(e))
|
|
.collect(Collectors.toCollection(HashSet::new));
|
|
}
|
|
else {
|
|
directedList = undirectedList.stream()
|
|
.filter(matching::contains)
|
|
.collect(Collectors.toCollection(HashSet::new));
|
|
}
|
|
return directedList;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public E getEdge(V sourceVertex, V targetVertex)
|
|
{
|
|
E edge = super.getEdge(sourceVertex, targetVertex);
|
|
|
|
if (matching.contains(edge) && partition1.contains(sourceVertex)) {
|
|
return null;
|
|
}
|
|
else if (!matching.contains(edge) && partition2.contains(sourceVertex)) {
|
|
return null;
|
|
}
|
|
else {
|
|
return edge;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* @throws UnsupportedOperationException always, since operation is unsupported
|
|
*/
|
|
@Override
|
|
public E addEdge(V sourceVertex, V targetVertex)
|
|
{
|
|
throw new UnsupportedOperationException(NO_EDGE_ADD);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* @throws UnsupportedOperationException always, since operation is unsupported
|
|
*/
|
|
@Override
|
|
public boolean addEdge(V sourceVertex, V targetVertex, E e)
|
|
{
|
|
throw new UnsupportedOperationException(NO_EDGE_ADD);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public int degreeOf(V vertex)
|
|
{
|
|
return super.degreeOf(vertex);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public Set<E> incomingEdgesOf(V vertex)
|
|
{
|
|
Set<E> incomingEdges = super.edgesOf(vertex);
|
|
if (partition1.contains(vertex)) {
|
|
return incomingEdges.stream()
|
|
.filter(matching::contains)
|
|
.collect(Collectors.toCollection(HashSet::new));
|
|
}
|
|
else {
|
|
return incomingEdges.stream()
|
|
.filter(a -> !matching.contains(a))
|
|
.collect(Collectors.toCollection(HashSet::new));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public int inDegreeOf(V vertex)
|
|
{
|
|
return incomingEdgesOf(vertex).size();
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public Set<E> outgoingEdgesOf(V vertex)
|
|
{
|
|
Set<E> outgoingEdges = super.edgesOf(vertex);
|
|
if (partition2.contains(vertex)) {
|
|
return outgoingEdges.stream()
|
|
.filter(matching::contains)
|
|
.collect(Collectors.toCollection(HashSet::new));
|
|
}
|
|
else {
|
|
return outgoingEdges.stream()
|
|
.filter(a -> !matching.contains(a))
|
|
.collect(Collectors.toCollection(HashSet::new));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public int outDegreeOf(V vertex)
|
|
{
|
|
return super.outgoingEdgesOf(vertex).size();
|
|
}
|
|
|
|
public GraphType getType() {
|
|
return super.getType().asDirected();
|
|
}
|
|
}
|
|
|
|
class asTransposeDirectedGraph extends asDirectedGraph {
|
|
|
|
private final Set<V> partition1;
|
|
private final Set<V> partition2;
|
|
|
|
public asTransposeDirectedGraph(Graph<V, E> g, Set<V> partition1, Set<V> partition2) {
|
|
super(g);
|
|
this.partition1 = partition2;
|
|
this.partition2 = partition1;
|
|
}
|
|
}
|
|
}
|
|
|