-
-
Notifications
You must be signed in to change notification settings - Fork 5.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP : ENH: Refactored _hungarian.py for speed and added a minimize/maximize… #9800
WIP : ENH: Refactored _hungarian.py for speed and added a minimize/maximize… #9800
Conversation
… switch A refactoring of scipy.optimize.linear_sum_assignment in _hungarian.py. The use of state machine has been removed and the algorithm more closely follows the standard exposition in the literature (e.g. Wikipedia). This yields a performance improvement. Benchmarking can be found in my personal development repository: https://github.com/batconjurer/munkres-algorithm Also, there is now an optional parameter to specify if minimization or maximization of the assignment is desired.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @batconjurer, a performance increase would be nice here. I made a lot of detailed comments; at a high level:
- could you add your benchmarks to https://github.com/scipy/scipy/tree/master/benchmarks/benchmarks (in a new file probably, look at
optimize_zeros.py
there for an example)? - could you address the many style issues (use e.g.
pycodestyle
orautopep8
to check)?
I haven't looked at the algorithm yet, too many other things need fixing first
* Corrected header copyright and authorship info * Typeset math properly in main docstring * Fixed style issues * Added clearer references to where the algorithm was taken * Removed np.matrix * Refactored tests to use public API as much as possible * Move the public docstring inside of main public API function * Corrected the copy method for arrays * Added benchmarking
…urer/scipy into performance_refactored_hungarian
…my formatting as a section called 'Definitions' which it was not expecting
switch A refactoring of scipy.optimize.linear_sum_assignment in _hungarian.py. The use of state machine has been removed and the algorithm more closely follows the standard exposition in the literature (e.g. Wikipedia). This yields a performance improvement. Benchmarking has also been added. Also, there is now an optional parameter to specify if minimization or maximization of the assignment is desired.
This looks much better now. Pinging @larsmans as the original author of this code, in case he's still around and wants to have a look. |
switch A refactoring of scipy.optimize.linear_sum_assignment in _hungarian.py. The use of state machine has been removed and the algorithm more closely follows the standard exposition in the literature (e.g. Wikipedia). This yields a performance improvement. Benchmarking has also been added. Also, there is now an optional parameter to specify if minimization or maximization of the assignment is desired.
…eswitch A refactoring of scipy.optimize.linear_sum_assignment in _hungarian.py. The use of state machine has been removed and the algorithm more closely follows the standard exposition in the literature (e.g. Wikipedia). This yields a performance improvement. Benchmarking can be found in my personal development repository: https://github.com/batconjurer/munkres-algorithm Also, there is now an optional parameter to specify if minimization or maximization of the assignment is desired.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- test coverage looks solid & no old tests modified
- reasonably positive feedback from Ralf, though detailed review of the algorithm may not have happened
- rebase might be good given 25 days since last push
- even better would be a resident
optimize
expert signing off
for row in np.nonzero(self.row_saturated == False)[0]: | ||
path_row, path_col = self._aug_path(row) | ||
if not path_col: | ||
continue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The continue
is never hit in the tests
path_row, path_col = self._aug_path(row) | ||
if not path_col: | ||
continue | ||
if not len(path_row + path_col) % 2: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the if not
is always true in tests
break | ||
|
||
# Recreate augmented path from linked list if possible | ||
if last_col is not None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
always true in tests
Bumping milestone, but let's try to get this in soon. I noticed yesterday that attempting to rebase on latest master was a little messy, probably because of all the commits. |
…eswitch A refactoring of scipy.optimize.linear_sum_assignment in _hungarian.py. The use of state machine has been removed and the algorithm more closely follows the standard exposition in the literature (e.g. Wikipedia). This yields a performance improvement. Benchmarking can be found in my personal development repository: https://github.com/batconjurer/munkres-algorithm Also, there is now an optional parameter to specify if minimization or maximization of the assignment is desired. ** Update ** A logic error was discovered in the Wikipedia entry of the Hungarian algorithm which was propagated into the code. This has now been fixed and covered with a test.
Bad news. In reviewing tests, I discovered an error that originated in Wikipedia and that I unfortunately duplicated in the code. It is fixed now, but the speed improvement is evaporated. So this pull request should be deleted or put on hold (not sure the proper protocol here) while I work on speeding it up if possible. |
That's too bad. Good you caught it though! You can just change your PR title to WIP, that's a sign that it's not ready to merge. Then you can just leave it open and come back to it later. |
Is there any appetite for a C extension for this algorithm? The Hungarian algorithm is important for many applications, and it appears that we could get orders of magnitude better performance with C code. The py-lapsolver looks like a good candidate. It it MIT licensed and the benchmarks demonstrate its performance: https://github.com/cheind/py-lapsolver @rgommers What do you think? |
@pmla those benchmarks do seem to indicate that
|
@rgommers It turns out that Instead, I have written an implementation of the Jonker-Volgenant algorithm in C++. It supports everything the current implementation does, as well as the maximization option proposed in this pull request and the feasible infinities I proposed in #10293. I will open a separate pull request when I have finished writing tests and documentation. |
Hi, I'm the author of
[1] |
Hi All,
|
@cheind apologies for misrepresenting your package. I was browsing your repository and saw that the Stanford ACM-ICPC code only supports square matrices. I see now that you pad the cost matrix. I have written some benchmarks and tests for a selection of solvers. The code is a bit preliminary but you can find it here: https://github.com/pmla/linear-assignment-benchmarks Any replacement for the current scipy implementation should have the same functionality. I have defined this as:
Other than the scipy implementation itself, none of the other modules I tested met these requirements. The C++ module I have written, Here are the results of my tests and benchmarks:
The fastest package varies from test to test. I think correctness and backwards compatibility are more important than speed though. Any C or C++ replacement is also a lot faster than the current implementation. |
The error is about |
I wanted to raise some concerns about the benchmarks from @pmla. My concerns stem from probably (1) I'm wondering why the tests stop after 400x800, which I consider a rather moderate matrix size? ad (2) I recommend using a package such as timeit or pytest-benchmarks. (3) How is correctness measured? I've found the following related snippet
but this does not account for the true cost, does it? |
@rgommers Regarding the hard fail: if the elements of the cost matrix are sufficiently small, @cheind Thanks for your comments. The benchmark sizes are indeed fairly arbitrary. I have included one square and one rectangular size. The times are taken as the mean of 10 runs. I have only included the call to the solver in the timing, not the generation of the cost matrix or the post-processing needed to get the results into the scipy format. Correctness is determined by the result with the lowest sum-of-costs. The Here are some benchmarks for larger matrices:
I have not included the current scipy implementation as it is slow. |
Ah okay it's a scaling issue. We've had that a lot with other parts of |
A complete rewrite in C++ that's way faster was merged in gh-10296, so closing this. Thanks @batconjurer for this PR and everyone for the interesting discussion. |
… switch
Important Update :
After uncovering and fixing a serious bug, there is no longer a speed improvement. This pull request should not be approved for now unless I can speed it up again, which will take time.
A refactoring of scipy.optimize.linear_sum_assignment in _hungarian.py.
The use of state machine has been removed and the algorithm more
closely follows the standard exposition in the literature (e.g. Wikipedia).
This yields a performance improvement. Benchmarking has also been added.
Also, there is now an optional parameter to specify if minimization or
maximization of the assignment is desired.
The results of the benchmarking for hungarian.py in v1.2.2:
[ 0.00%] · For scipy commit a3e96ed <v1.2.2>:
[ 0.00%] ·· Benchmarking virtualenv-py3.6-Cython0.27.3-Tempita-numpy-pytest-six
[ 50.00%] ··· Running (hungarian.Hungarian.time_solve--).
[100.00%] ··· hungarian.Hungarian.time_solve ok
[100.00%] ··· ============= ============
matrix_size
------------- ------------
100 45.7±0.2ms
300 1.31±0.1s
500 7.51±0.01s
700 25.6±0.01s
============= ============
The results of the benchmarking for hungarian.py in v1.2.2:
[ 0.00%] · For scipy commit 832e219 <performance_refactored_hungarian~1>:
[ 0.00%] ·· Benchmarking virtualenv-py3.6-Cython0.27.3-Tempita-numpy-pytest-six
[ 0.00%] ··· Running (hungarian.Hungarian.time_solve--).
[ 0.01%] ··· hungarian.Hungarian.time_solve ok
[ 0.01%] ··· ============= ============
matrix_size
------------- ------------
100 48.8±0.1ms
300 565±3ms
500 2.58±0.01s
700 7.75±0s
============= ============