From fcca22a2f05b582d0d9bc87eaf529275e141bef9 Mon Sep 17 00:00:00 2001 From: eugenefischer <66030419+eugenefischer@users.noreply.github.com> Date: Sat, 22 Oct 2022 13:18:43 -0500 Subject: [PATCH] Rename class, modify bidding to include marginal item value --- ...IntegerWeightBipartiteAuctionMatching.java | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 src/main/java/MaximumIntegerWeightBipartiteAuctionMatching.java diff --git a/src/main/java/MaximumIntegerWeightBipartiteAuctionMatching.java b/src/main/java/MaximumIntegerWeightBipartiteAuctionMatching.java new file mode 100644 index 0000000..aee42e1 --- /dev/null +++ b/src/main/java/MaximumIntegerWeightBipartiteAuctionMatching.java @@ -0,0 +1,157 @@ +import org.jgrapht.Graph; +import org.jgrapht.GraphTests; +import org.jgrapht.alg.interfaces.MatchingAlgorithm; + +import java.math.BigDecimal; +import java.util.*; + +/* +Maximum weight matching in bipartite graphs using a forward auction algorithm. This implementation uses the Gauss-Seidel +version of the forward auction algorithm, in which bids are submitted one at a time. For any weighted bipartite graph +with n vertices in the smaller partition, this algorithm will produce a matching that is within n*epsilon of being +optimal. For a weighted bipartite graph with strictly integer weights, this matching will always be optimal, and thus +is a maximum weight matching. + */ + +public class MaximumIntegerWeightBipartiteAuctionMatching implements MatchingAlgorithm { + + private final Graph graph; + private final Set partition1; + private final Set partition2; + private final BigDecimal epsilon; + private final Set matching; + private BigDecimal matchingWeight; + private boolean swappedPartitions = false; + + public MaximumIntegerWeightBipartiteAuctionMatching(Graph graph, Set partition1, Set 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"); + int n = Math.max(partition1.size(), partition2.size()); + this.epsilon = BigDecimal.valueOf(1 / ((double) n + 1)); + this.matching = new LinkedHashSet<>(); + this.matchingWeight = BigDecimal.ZERO; + } + + + /* + Method coded using MaximumWeightBipartiteMatching.class from JgraphT as a model + */ + @Override + public Matching 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"); + } + + /* + If the two partitions are different sizes, the bidders must be the smaller of the two partitions. + */ + Set items; + Set bidders; + if (partition2.size() >= partition1.size()) { + bidders = partition1; + items = partition2; + } + else { + bidders = partition2; + items = partition1; + swappedPartitions = true; + } + + /* + Create a map to track the owner of each item, which is initially null, + and a map to track the price of each item, which is initially 0. + */ + Map owners = new HashMap<>(); + Map prices = new HashMap<>(); + for(V item: items) { + owners.put(item, null); + prices.put(item, BigDecimal.ZERO); + } + + //Initialize queue of all bidders that don't currently own an item + Queue unmatchedBidders = new ArrayDeque<>(); + for(V bidder: bidders) { + unmatchedBidders.offer(bidder); + } + + while (unmatchedBidders.size() > 0) { + V bidder = unmatchedBidders.poll(); + V item = null; + BigDecimal bestValue = BigDecimal.valueOf(-1.0); + BigDecimal runnerUpValue = BigDecimal.valueOf(-1.0); + /* + Find the items that offer the best and second-best value for the bidder, + then submit a bid equal to the price of the best item plus the marginal value over + the second-best item plus delta. + */ + for (E edge: graph.edgesOf(bidder)) { + double weight = graph.getEdgeWeight(edge); + if(weight == 0.0) { + continue; + } + V tmp = getItem(bidder, edge); + BigDecimal value = BigDecimal.valueOf(weight).subtract(prices.get(tmp)); + if (value.compareTo(bestValue) >= 0) { + runnerUpValue = bestValue; + bestValue = value; + item = tmp; + } + else if (value.compareTo(runnerUpValue) >= 0) { + runnerUpValue = value; + } + } + if(bestValue.compareTo(BigDecimal.ZERO) >= 0) { + V formerOwner = owners.get(item); + BigDecimal price = prices.get(item); + BigDecimal bid = price.add(bestValue).subtract(runnerUpValue).add(epsilon); + if (formerOwner != null) { + unmatchedBidders.offer(formerOwner); + } + owners.put(item, bidder); + prices.put(item, bid); + } + } + for (V item: owners.keySet()) { + if (owners.get(item) != null) { + matching.add(graph.getEdge(item, owners.get(item))); + } + } + + for(E edge: matching) { + this.matchingWeight = this.matchingWeight.add(BigDecimal.valueOf(graph.getEdgeWeight(edge))); + } + + + return new MatchingImpl<>(graph, matching, matchingWeight.doubleValue()); + } + + private V getItem(V bidder, E edge) { + if (swappedPartitions) { + return graph.getEdgeSource(edge); + } + else { + return graph.getEdgeTarget(edge); + } + } + + private V getBidder(V item, E edge) { + if (swappedPartitions) { + return graph.getEdgeTarget(edge); + } + else { + return graph.getEdgeSource(edge); + } + } + + public BigDecimal getMatchingWeight() { + return matchingWeight; + } +}