Source code for tryalgo.kuhn_munkres

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""\
Maximum profit bipartite matching by Kuhn-Munkres

jill-jenn vie, christoph durr and samuel tardieu - 2014-2019

primal LP

    max sum_{u,v} w[u,v] * x[u,v]

    such that
    for all u in U: sum_v x[u,v] == 1   (l[u])

    for all v in V: sum_u x[u,v] <= 1   (l[v])

    for all u,v: x[u,v] >= 0


dual LP

    min sum_u l[u] + sum_v l[v]

    such that
    for all u,v:  l[u] + l[v] >= w[u,v]   (*)

    for all u in U: l[u] is arbitrary (free variable)
    for all v in V: l[v] >= 0


primal-dual algorithm:

    Start with trivial solution l for dual and with trivial
    non-solution x for primal.

    Iteratively improve primal or dual solution, maintaining complementary
    slackness conditions.

"""


# snip{
# pylint: disable=too-many-locals, too-many-branches
[docs] def kuhn_munkres(G, TOLERANCE=1e-6): """Maximum profit bipartite matching by Kuhn-Munkres :param G: weight matrix where G[u][v] is the weight of edge (u,v), :param TOLERANCE: a value with absolute value below tolerance is considered as being zero. If G consists of integer or fractional values then TOLERANCE can be chosen 0. :requires: graph (U,V,E) is complete bi-partite graph with len(U) <= len(V) float('-inf') or float('inf') entries in G are allowed but not None. :returns: matching table from U to V, value of matching :complexity: :math:`O(|U|^2 |V|)` """ nU = len(G) U = range(nU) nV = len(G[0]) V = range(nV) assert nU <= nV mu = [None] * nU # empty matching mv = [None] * nV lu = [max(row) for row in G] # trivial labels lv = [0] * nV for root in U: # build an alternate tree au = [False] * nU # au, av mark nodes... au[root] = True # ... covered by the tree Av = [None] * nV # Av[v] successor of v in the tree # for every vertex u, slack[u] := (val, v) such that # val is the smallest slack on the constraints (*) # with fixed u and v being the corresponding vertex slack = [(lu[root] + lv[v] - G[root][v], root) for v in V] while True: (delta, u), v = min((slack[v], v) for v in V if Av[v] is None) assert au[u] if delta > TOLERANCE: # tree is full for u0 in U: # improve labels if au[u0]: lu[u0] -= delta for v0 in V: if Av[v0] is not None: lv[v0] += delta else: (val, arg) = slack[v0] slack[v0] = (val - delta, arg) assert abs(lu[u] + lv[v] - G[u][v]) <= TOLERANCE # equality Av[v] = u # add (u, v) to A if mv[v] is None: break # alternating path found u1 = mv[v] assert not au[u1] au[u1] = True # add (u1, v) to A for v1 in V: if Av[v1] is None: # update margins alt = (lu[u1] + lv[v1] - G[u1][v1], u1) if slack[v1] > alt: slack[v1] = alt while v is not None: # ... alternating path found u = Av[v] # along path to root prec = mu[u] mv[v] = u # augment matching mu[u] = v v = prec return (mu, sum(lu) + sum(lv))
# snip}