-
Notifications
You must be signed in to change notification settings - Fork 3
/
_lap.py
111 lines (88 loc) · 4.03 KB
/
_lap.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# Wrapper for the Jonker-Volgenant algorithm for solving the rectangular
# linear sum assignment problem. The original code was an implementation of
# the Hungarian algorithm (Kuhn-Munkres) taken from scikit-learn, based on
# original code by Brian Clapper and adapted to NumPy by Gael Varoquaux.
# Further improvements by Ben Root, Vlad Niculae, Lars Buitinck,
# and Peter Larsen.
#
# Copyright (c) 2008 Brian M. Clapper <[email protected]>, Gael Varoquaux
# Author: Brian M. Clapper, Gael Varoquaux
# License: 3-clause BSD
import numpy as np
from jonkervolgenant import calculate_assignment
def linear_sum_assignment(cost_matrix, maximize=False):
"""Solve the linear sum assignment problem.
The linear sum assignment problem is also known as minimum weight matching
in bipartite graphs. A problem instance is described by a matrix C, where
each C[i,j] is the cost of matching vertex i of the first partite set
(a "worker") and vertex j of the second set (a "job"). The goal is to find
a complete assignment of workers to jobs of minimal cost.
Formally, let X be a boolean matrix where :math:`X[i,j] = 1` iff row i is
assigned to column j. Then the optimal assignment has cost
.. math::
\\min \\sum_i \\sum_j C_{i,j} X_{i,j}
s.t. each row is assignment to at most one column, and each column to at
most one row.
This function can also solve a generalization of the classic assignment
problem where the cost matrix is rectangular. If it has more rows than
columns, then not every row needs to be assigned to a column, and vice
versa.
The method used is the Jonker-Volgenant algorithm.
Parameters
----------
cost_matrix : array
The cost matrix of the bipartite graph.
maximize : bool (default: False)
Calculates a maximum weight matching if true.
Returns
-------
row_ind, col_ind : array
An array of row indices and one of corresponding column indices giving
the optimal assignment. The cost of the assignment can be computed
as ``cost_matrix[row_ind, col_ind].sum()``. The row indices will be
sorted; in the case of a square cost matrix they will be equal to
``numpy.arange(cost_matrix.shape[0])``.
Notes
-----
.. versionadded:: 0.17.0
Examples
--------
>>> cost = np.array([[4, 1, 3], [2, 0, 5], [3, 2, 2]])
>>> from scipy.optimize import linear_sum_assignment
>>> row_ind, col_ind = linear_sum_assignment(cost)
>>> col_ind
array([1, 0, 2])
>>> cost[row_ind, col_ind].sum()
5
References
----------
1. https://en.wikipedia.org/wiki/Assignment_problem
2. R. Jonker and A. Volgenant. A Shortest Augmenting Path Algorithm for
Dense and Sparse Linear Assignment Problems. *Computing*, 38:325-340
December 1987.
3. DF Crouse. On implementing 2D rectangular assignment algorithms.
*IEEE Transactions on Aerospace and Electronic Systems*,
52(4):1679-1696, August 2016, https://doi.org/10.1109/TAES.2016.140952
"""
cost_matrix = np.asarray(cost_matrix)
if len(cost_matrix.shape) != 2:
raise ValueError("expected a matrix (2-d array), got a %r array"
% (cost_matrix.shape,))
if not (np.issubdtype(cost_matrix.dtype, np.number) or
cost_matrix.dtype == np.dtype(np.bool)):
raise ValueError("expected a matrix containing numerical entries, got %s"
% (cost_matrix.dtype,))
if maximize:
cost_matrix = -cost_matrix
if np.any(np.isneginf(cost_matrix) | np.isnan(cost_matrix)):
raise ValueError("matrix contains invalid numeric entries")
cost_matrix = cost_matrix.astype(np.double)
a = np.arange(np.min(cost_matrix.shape))
# The algorithm expects more columns than rows in the cost matrix.
if cost_matrix.shape[1] < cost_matrix.shape[0]:
b = calculate_assignment(cost_matrix.T)
indices = np.argsort(b)
return (b[indices], a[indices])
else:
b = calculate_assignment(cost_matrix)
return (a, b)