From 0071cafbbd1632a1ca46c115e6c04f1ea24380c7 Mon Sep 17 00:00:00 2001 From: eugenefischer <66030419+eugenefischer@users.noreply.github.com> Date: Wed, 9 Apr 2025 10:17:13 -0500 Subject: [PATCH] Rough implementation, missing final dual adjustment step, and may have other bugs as well as it does not yet output a maximum weight matching --- readme.md | 1 + src/main/java/InteractiveInterface.java | 22 +- ...MaximumIntegerWeightBipartiteMatching.java | 1288 ++++++++++++++++- src/main/java/Simulator.java | 6 + 4 files changed, 1241 insertions(+), 76 deletions(-) diff --git a/readme.md b/readme.md index 17e72ef..727f5dc 100644 --- a/readme.md +++ b/readme.md @@ -650,6 +650,7 @@ the file of distinct cells may enable better simulated replication of this exper * Melhorn, K., Näher, St. [The LEDA Platform of Combinatorial and Geometric Computing.](https://people.mpi-inf.mpg.de/~mehlhorn/LEDAbook.html) Cambridge University Press. Chapter 7, Graph Algorithms; p. 132-162 (1999) * Fredman, M., Tarjan, R. ["Fibonacci heaps and their uses in improved network optimization algorithms."](https://www.cl.cam.ac.uk/teaching/1011/AlgorithII/1987-FredmanTar-fibonacci.pdf) J. ACM, 34(3):596–615 (1987)) * Bertsekas, D., Castañon, D. ["A forward/reverse auction algorithm for asymmetric assignment problems"](https://www.mit.edu/~dimitrib/For_Rev_Asym_Auction.pdf) Computational Optimization and Applications 1, 277-297 (1992) +* Dimitrios Michail, Joris Kinable, Barak Naveh, and John V. Sichi. 2020. JGraphT—A Java Library for Graph Data Structures and Algorithms. ACM Trans. Math. Softw. 46, 2, Article 16 ## EXTERNAL LIBRARIES USED * [JGraphT](https://jgrapht.org) -- Graph theory data structures and algorithms diff --git a/src/main/java/InteractiveInterface.java b/src/main/java/InteractiveInterface.java index 7859b4d..a0f28fb 100644 --- a/src/main/java/InteractiveInterface.java +++ b/src/main/java/InteractiveInterface.java @@ -582,35 +582,35 @@ public class InteractiveInterface { boolean backToOptions = false; while(!backToOptions) { System.out.println("\n---------ALGORITHM OPTIONS----------"); - System.out.println("1) Use integer weight scaling algorithm by Duan and Su."); - System.out.println("2) Use Hungarian algorithm with Fibonacci heap priority queue"); - System.out.println("3) Use Hungarian algorithm with pairing heap priority queue"); - System.out.println("4) Use auction algorithm"); + System.out.println("1) Use Hungarian algorithm with Fibonacci heap priority queue"); + System.out.println("2) Use Hungarian algorithm with pairing heap priority queue"); + System.out.println("3) Use auction algorithm"); + System.out.println("4) Use integer weight scaling algorithm by Duan and Su. (buggy, not yet fully implemented!)"); System.out.println("0) Return to Options menu"); try { input = sc.nextInt(); switch (input) { case 1 -> { - BiGpairSEQ.setIntegerWeightScalingAlgorithm(); - System.out.println("MWM algorithm set to integer weight scaling algorithm of Duan and Su"); - backToOptions = true; - } - case 2 -> { BiGpairSEQ.setHungarianAlgorithm(); BiGpairSEQ.setFibonacciHeap(); System.out.println("MWM algorithm set to Hungarian with Fibonacci heap"); backToOptions = true; } - case 3 -> { + case 2 -> { BiGpairSEQ.setHungarianAlgorithm(); BiGpairSEQ.setPairingHeap(); System.out.println("MWM algorithm set to Hungarian with pairing heap"); backToOptions = true; } - case 4 -> { + case 3 -> { BiGpairSEQ.setAuctionAlgorithm(); System.out.println("MWM algorithm set to auction"); } + case 4 -> { + BiGpairSEQ.setIntegerWeightScalingAlgorithm(); + System.out.println("MWM algorithm set to integer weight scaling algorithm of Duan and Su"); + backToOptions = true; + } case 0 -> backToOptions = true; default -> System.out.println("Invalid input"); } diff --git a/src/main/java/MaximumIntegerWeightBipartiteMatching.java b/src/main/java/MaximumIntegerWeightBipartiteMatching.java index bb1beb5..003c03b 100644 --- a/src/main/java/MaximumIntegerWeightBipartiteMatching.java +++ b/src/main/java/MaximumIntegerWeightBipartiteMatching.java @@ -1,15 +1,31 @@ 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 org.jgrapht.alg.shortestpath.DijkstraShortestPath; + +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) @@ -27,71 +43,153 @@ public class MaximumIntegerWeightBipartiteMatching implements MatchingAlgo private final Set partition1; private final Set partition2; private final Set matching; - private final BigInteger maxPartitionSize; - private final BigInteger maxEdgeWeight; - private BigInteger matchingWeight; - private BigInteger delta; + 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; - public final double LOG_2 = Math.log(2.0); //constant for logBigInteger function - private final int MAX_DIGITS_2 = 977; //constant for logBigInteger function + // the current scaling step + private Integer currentScale = 0; + // vertex potentials, not necessarily integers + private Map potentials = new HashMap<>(); + ; + // the matched edge of a vertex, also used to check if a vertex is free + private Map matchedEdge; + private final Comparator decimalComparator; + private final Comparator integerComparator; + private final Function, AddressableHeap> heapSupplier; + // data structures for finding alternating paths + private AddressableHeap heap; + private Map> nodeInHeap; + private Map predecessor; + private Map 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 graph, Set partition1, Set 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 graph, Set partition1, Set partition2, + Function, AddressableHeap> 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 graph, Set partition1, Set 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 graph, Set partition1, Set partition2, + BigDecimal maxEdgeWeight, + Function, AddressableHeap> heapSupplier) { 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); + if (!GraphTests.isBipartitePartition(graph, partition1, partition2)) { + throw new IllegalArgumentException("Graph partition is not bipartite"); } - 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 graph, Set partition1, Set 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 graph, Set partition1, Set 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()))); + 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.naturalOrder(); + this.integerComparator = Comparator.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.matchingWeight = BigInteger.ZERO; + this.matchedEdge = new HashMap<>(); Double exponent = maxEdgeWeight.doubleValue() / Math.sqrt(Double.parseDouble(n.toString())); - exponent = Math.log10(exponent); + exponent = Math.log(exponent) / Math.log(2); //log base two. log_b(n) = log(n) / log(b) 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()); + // 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 getPotentials() { + if (potentials == null) { + return Collections.emptyMap(); + } else { + return Collections.unmodifiableMap(potentials); + } + } + + /** + * {@inheritDoc} + */ @Override public Matching getMatching() { @@ -105,22 +203,1082 @@ public class MaximumIntegerWeightBipartiteMatching implements MatchingAlgo 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 matchedVertices = matchedEdge.keySet(); + Set visitedVertices = new HashSet<>(); + List> cycles = new ArrayList<>(); + for (V vertex : matchedVertices) { + //skip any vertices that have been visited already + if (visitedVertices.contains(vertex)) { + continue; + } + Stack stack = new Stack<>(); + Map predecessor = new HashMap(); + 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 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 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> maximalAugmentingPaths = new ArrayList<>(); + System.out.println("Starting vertex count: " + startingVertices.size()); + visitedVertices = new HashSet<>(); + Graph directedGraph = new asDirectedGraph(graph); + Graph subgraphG13 = new MaskSubgraph<>(directedGraph, v -> true, e -> isEligibleEdge(e, BigDecimal.ONE, THREE)); + for (V vertex : startingVertices) { + if (visitedVertices.contains(vertex)) { + continue; + } + Stack stack = new Stack<>(); + Map predecessor = new HashMap(); + 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 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 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 violatedEdges = phase2GetViolatedEdges(); + //get the set of adjustable vertices + //first find an intermediate set of vertices + Set 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 nonAdjustableVertices = getEvenOddVertices(intermediateVertices, BigDecimal.ONE, THREE).get(1); + //finally, get the set of adjustable vertices + Set adjustableVertices = graph.vertexSet() + .stream() + .filter(v -> !nonAdjustableVertices.contains(v)) + .collect(Collectors.toCollection(HashSet::new)); + //construct vertex sets X_L and X_R + Set intermediateAdjustableSubsetLeft = adjustableVertices.stream() + .filter(partition1::contains) + .collect(Collectors.toCollection(HashSet::new)); + Set adjustableSubsetLeft = new HashSet<>(); + for (V v : intermediateAdjustableSubsetLeft) { + if (graph.edgesOf(v).stream().anyMatch(violatedEdges::contains)) { + adjustableSubsetLeft.add(v); + } + } + Set intermediateAdjustableSubsetRight = adjustableVertices.stream() + .filter(partition2::contains) + .collect(Collectors.toCollection(HashSet::new)); + Set adjustableSubsetRight = new HashSet<>(); + for (V v : intermediateAdjustableSubsetRight) { + if (graph.edgesOf(v).stream().anyMatch(violatedEdges::contains)) { + adjustableSubsetRight.add(v); + } + } + Set adjustableSubset = adjustableSubsetRight.size() > adjustableSubsetLeft.size() ? + adjustableSubsetRight : + adjustableSubsetLeft; + List> 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 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> 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> buckets = new ArrayList<>(numberOfBuckets); + while (buckets.size() < numberOfBuckets) { + buckets.add(new ArrayList<>()); + } +// Collections.fill(buckets, new ArrayList()); + Map previousVertex = new HashMap<>(); + Map 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 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 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 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 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 directedGraph = new asDirectedGraph(graph); + Graph subgraphG01 = new MaskSubgraph<>(directedGraph, v -> true, e -> isEligibleEdge(e, BigDecimal.ZERO, BigDecimal.ONE)); + GabowStrongConnectivityInspector sscInspectorCycles = new GabowStrongConnectivityInspector(subgraphG01); + List> stronglyConnectedComponents01 = sscInspectorCycles.stronglyConnectedSets(); + Set nonTightEdges = new HashSet<>(); + for (E e: subgraphG01.edgeSet()) { + if (matching.contains(e) && getEdgePotential(e) != getScaledWeight(e)) { + nonTightEdges.add(e); + } + } + Set nonTightEdgesInCycles = new HashSet<>(); + for (E e: nonTightEdges) { + V source = subgraphG01.getEdgeSource(e); + V target = subgraphG01.getEdgeTarget(e); + for (Set 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> cycles = new ArrayList<>(); + for(E e: nonTightEdgesInCycles) { + List edgeList = DijkstraShortestPath.findPathBetween(subgraphG01, subgraphG01.getEdgeTarget(e), subgraphG01.getEdgeSource(e)).getEdgeList(); + edgeList.add(e); + cycles.add(edgeList); + } + augmentMatching(cycles); + //detect paths + Set 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 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 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> maximalAugmentingPaths = new ArrayList<>(); + System.out.println("Starting vertex count: " + startingVertices.size()); + Set visitedVertices = new HashSet<>(); + for (V vertex : startingVertices) { + if (visitedVertices.contains(vertex)) { + continue; + } + Stack stack = new Stack<>(); + Map predecessor = new HashMap(); + 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 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 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 subgraphG00 = new MaskSubgraph<>(directedGraph, v -> true, e -> isEligibleEdge(e, BigDecimal.ZERO, BigDecimal.ZERO)); + GabowStrongConnectivityInspector sscInspectorPaths = new GabowStrongConnectivityInspector(subgraphG00); + List> stronglyConnectedComponents00 = sscInspectorPaths.stronglyConnectedSets(); + Map dummyVertices = new HashMap<>(); + for (int i = 0; i < stronglyConnectedComponents00.size(); i++) { + dummyVertices.put(i, stronglyConnectedComponents00.get(i).iterator().next()); + } + Graph condensedG01 = new SimpleDirectedGraph(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 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)); } - //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; + /* + * Returns the set of violated edges in the current matching. These are the edges for which the + * sum of the + */ + private Set phase2GetViolatedEdges() { + Set violatedEdges = new HashSet(); + 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 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> augmentingPaths = new ArrayList<>(); + Set 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 leftFreeVertices = partition1.stream() + .filter(this::isFreeVertex) + .collect(Collectors.toCollection(HashSet::new)); + List> evenOddVertices = getEvenOddVertices(leftFreeVertices, c, d); + Set evenVertices = evenOddVertices.get(0); + Set 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 getAugmentingPathPhase1(V v, Set usedVertices, BigDecimal c, BigDecimal d) { + List path = new ArrayList<>(); + Set visited = new HashSet<>(); + Deque queue = new ArrayDeque<>(); + Map predecessor = new HashMap(); + 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 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 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> augmentingPaths) { + for (List 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 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> getEvenOddVertices(Set startingVertices, Graph graph) { + Set visitedVertices = new HashSet<>(); + Set oddVertices = new HashSet<>(); + Set evenVertices = new HashSet<>(); + for (V vertex : startingVertices) { + //depth first search + Stack stack = new Stack<>(); + Map 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> 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> getEvenOddVertices(Set startingVertices, BigDecimal c, BigDecimal d) { + Set visitedVertices = new HashSet<>(); + Set oddVertices = new HashSet<>(); + Set evenVertices = new HashSet<>(); + for (V vertex : startingVertices) { + //depth first search + Stack stack = new Stack<>(); + Map 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> 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 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 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 extends GraphDelegator + implements Serializable, Graph { + //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 g) + { + super(g); + } + + /** + * {@inheritDoc} + */ + @Override + public Set getAllEdges(V sourceVertex, V targetVertex) + { + Set undirectedList = super.getAllEdges(sourceVertex, targetVertex); + if (undirectedList.size() == 0) { + return undirectedList; + } + boolean leftSource = partition1.contains(sourceVertex); + Set 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 incomingEdgesOf(V vertex) + { + Set 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 outgoingEdgesOf(V vertex) + { + Set 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 partition1; + private final Set partition2; + + public asTransposeDirectedGraph(Graph g, Set partition1, Set partition2) { + super(g); + this.partition1 = partition2; + this.partition2 = partition1; + } + } } + diff --git a/src/main/java/Simulator.java b/src/main/java/Simulator.java index 5f52f66..ab0da02 100644 --- a/src/main/java/Simulator.java +++ b/src/main/java/Simulator.java @@ -193,6 +193,9 @@ public class Simulator implements GraphModificationFunctions { //create a new MaximumIntegerWeightBipartiteAuctionMatching maxWeightMatching = new MaximumIntegerWeightBipartiteAuctionMatching<>(graph, alphas, betas); } + case INTEGER_WEIGHT_SCALING -> { + maxWeightMatching = new MaximumIntegerWeightBipartiteMatching<>(graph, alphas, betas, new BigDecimal(highThreshold)); + } default -> { //HUNGARIAN //use selected heap type for priority queue HeapType heap = BiGpairSEQ.getPriorityQueueHeapType(); @@ -273,6 +276,9 @@ public class Simulator implements GraphModificationFunctions { case AUCTION -> { algoType = "Auction algorithm"; } + case INTEGER_WEIGHT_SCALING -> { + algoType = "Integer weight scaling algorithm from Duan and Su (not yet perfectly implemented)"; + } default -> { //HUNGARIAN algoType = "Hungarian algorithm with heap: " + BiGpairSEQ.getPriorityQueueHeapType().name(); }