127 lines
6.2 KiB
Java
127 lines
6.2 KiB
Java
import org.jgrapht.Graph;
|
|
import org.jgrapht.GraphTests;
|
|
import org.jgrapht.alg.interfaces.MatchingAlgorithm;
|
|
import org.jgrapht.alg.shortestpath.DijkstraShortestPath;
|
|
import java.math.BigInteger;
|
|
import java.util.*;
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* 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 maxPartitionSize;
|
|
private final BigInteger maxEdgeWeight;
|
|
private BigInteger matchingWeight;
|
|
private BigInteger delta;
|
|
private final Integer numberOfScales;
|
|
public final double LOG_2 = Math.log(2.0); //constant for logBigInteger function
|
|
private final int MAX_DIGITS_2 = 977; //constant for logBigInteger function
|
|
|
|
public MaximumIntegerWeightBipartiteMatching(Graph<V, E> graph, Set<V> partition1, Set<V> partition2) {
|
|
this.graph = GraphTests.requireUndirected(graph);
|
|
this.partition1 = Objects.requireNonNull(partition1, "Partition 1 cannot be null");
|
|
this.partition2 = Objects.requireNonNull(partition2, "Partition 2 cannot be null");
|
|
Integer n = Math.max(partition1.size(), partition2.size());
|
|
this.maxPartitionSize = new BigInteger(String.valueOf(Math.max(partition1.size(), partition2.size())));
|
|
Integer maxEdgeWeight = 0;
|
|
for (E edge: graph.edgeSet()) {
|
|
Integer weight = Integer.valueOf(String.valueOf(graph.getEdgeWeight(edge)));
|
|
maxEdgeWeight = Math.max(maxEdgeWeight, weight);
|
|
}
|
|
this.maxEdgeWeight = new BigInteger(maxEdgeWeight.toString());
|
|
this.matching = new LinkedHashSet<>();
|
|
this.matchingWeight = BigInteger.ZERO;
|
|
//This takes the integer square root of the maxPartitionSize instead of the square root, not sure if this will introduce an error
|
|
Double exponent = maxEdgeWeight / Math.sqrt(Double.parseDouble(n.toString()));
|
|
exponent = Math.log10(exponent);
|
|
exponent = Math.floor(exponent);
|
|
this.delta = BigInteger.TWO.pow(Integer.parseInt(exponent.toString()));
|
|
Double numberOfScales = Math.ceil(Math.log10(Double.parseDouble(maxEdgeWeight.toString())));
|
|
this.numberOfScales = Integer.parseInt(numberOfScales.toString());
|
|
}
|
|
|
|
public MaximumIntegerWeightBipartiteMatching(Graph<V, E> graph, Set<V> partition1, Set<V> partition2, Integer maxEdgeWeight) {
|
|
this.graph = GraphTests.requireUndirected(graph);
|
|
this.partition1 = Objects.requireNonNull(partition1, "Partition 1 cannot be null");
|
|
this.partition2 = Objects.requireNonNull(partition2, "Partition 2 cannot be null");
|
|
Integer n = Math.max(partition1.size(), partition2.size());
|
|
this.maxPartitionSize = new BigInteger(String.valueOf(Math.max(partition1.size(), partition2.size())));
|
|
this.maxEdgeWeight = new BigInteger(maxEdgeWeight.toString());
|
|
this.matching = new LinkedHashSet<>();
|
|
this.matchingWeight = BigInteger.ZERO;
|
|
Double exponent = maxEdgeWeight / Math.sqrt(Double.parseDouble(n.toString()));
|
|
exponent = Math.log10(exponent);
|
|
exponent = Math.floor(exponent);
|
|
this.delta = BigInteger.TWO.pow(Integer.parseInt(exponent.toString()));
|
|
Double numberOfScales = Math.ceil(Math.log10(Double.parseDouble(maxEdgeWeight.toString())));
|
|
this.numberOfScales = Integer.parseInt(numberOfScales.toString());
|
|
}
|
|
|
|
public MaximumIntegerWeightBipartiteMatching(Graph<V, E> graph, Set<V> partition1, Set<V> partition2, BigInteger maxEdgeWeight) {
|
|
this.graph = GraphTests.requireUndirected(graph);
|
|
this.partition1 = Objects.requireNonNull(partition1, "Partition 1 cannot be null");
|
|
this.partition2 = Objects.requireNonNull(partition2, "Partition 2 cannot be null");
|
|
Integer n = Math.max(partition1.size(), partition2.size());
|
|
this.maxPartitionSize = new BigInteger(String.valueOf(Math.max(partition1.size(), partition2.size())));
|
|
this.maxEdgeWeight = maxEdgeWeight;
|
|
this.matching = new LinkedHashSet<>();
|
|
this.matchingWeight = BigInteger.ZERO;
|
|
Double exponent = maxEdgeWeight.doubleValue() / Math.sqrt(Double.parseDouble(n.toString()));
|
|
exponent = Math.log10(exponent);
|
|
exponent = Math.floor(exponent);
|
|
this.delta = BigInteger.TWO.pow(Integer.parseInt(exponent.toString()));
|
|
Double numberOfScales = Math.ceil(Math.log10(Double.parseDouble(maxEdgeWeight.toString())));
|
|
this.numberOfScales = Integer.parseInt(numberOfScales.toString());
|
|
}
|
|
|
|
@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");
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
//function to return the natural logarithm of an arbitrary BigInteger
|
|
//by leonbloy via https://stackoverflow.com/questions/6827516/logarithm-for-biginteger
|
|
private double logBigInteger(BigInteger val) {
|
|
if (val.signum() < 1)
|
|
return val.signum() < 0 ? Double.NaN : Double.NEGATIVE_INFINITY;
|
|
int blex = val.bitLength() - MAX_DIGITS_2; // any value in 60..1023 works here
|
|
if (blex > 0)
|
|
val = val.shiftRight(blex);
|
|
double res = Math.log(val.doubleValue());
|
|
return blex > 0 ? res + blex * LOG_2 : res;
|
|
}
|
|
|
|
|
|
}
|