Source code for gasm.lap.auction
"""Bertsekas auction algorithm with epsilon-scaling (CPU reference).
This is a NumPy reference implementation, used as a CPU fallback and to validate
the native OpenCL auction kernel. With epsilon-scaling the auction converges to
an optimal assignment (identical total score to Jonker-Volgenant); the GASM
noise term lifts ties so both solvers return the same matching. For production
CPU use prefer the ``'jv'`` solver, which is considerably faster in pure Python.
"""
from __future__ import annotations
import numpy as np
def _auction_round(C: np.ndarray, eps: float, prices: np.ndarray):
nr, nc = C.shape
person_to_obj = np.full(nr, -1, dtype=np.int64)
obj_to_person = np.full(nc, -1, dtype=np.int64)
unassigned = list(range(nr))
while unassigned:
i = unassigned.pop()
values = C[i] - prices
j = int(np.argmax(values))
best = values[j]
if nc > 1:
saved = values[j]
values[j] = -np.inf
second = values.max()
values[j] = saved
else:
second = -np.inf
bid = best - second + eps
prices[j] += bid
prev = obj_to_person[j]
if prev != -1:
person_to_obj[prev] = -1
unassigned.append(int(prev))
obj_to_person[j] = i
person_to_obj[i] = j
return person_to_obj
def _auction(C: np.ndarray) -> np.ndarray:
nr, nc = C.shape
maxabs = max(1.0, float(np.abs(C).max()))
eps = maxabs
# The final assignment is within ``nr * eps`` of the optimum, so eps_final
# must be small relative to the score scale to recover the exact optimum.
eps_final = maxabs * 1e-9 / max(nr, 1)
prices = np.zeros(nc, dtype=np.float64)
assign = np.full(nr, -1, dtype=np.int64)
while True:
assign = _auction_round(C, eps, prices)
if eps <= eps_final:
break
eps = max(eps / 4.0, eps_final)
return assign
[docs]
def solve(score: np.ndarray):
"""Return a maximum-score assignment of rows to columns.
Parameters
----------
score:
Dense score matrix of shape ``(nA, nB)``; higher is better.
Returns
-------
rows, cols:
Index arrays of the assignment, of size ``min(nA, nB)``.
"""
score = np.asarray(score, dtype=np.float64)
nr, nc = score.shape
transposed = False
if nr > nc:
score = score.T
transposed = True
nr, nc = nc, nr
assign = _auction(score)
rows = np.arange(nr)
cols = assign
if transposed:
rows, cols = cols, rows
order = np.argsort(rows)
return rows[order], cols[order]