Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
hungarian.cc
Go to the documentation of this file.
1// Copyright 2010-2024 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <cmath>
18#include <cstdio>
19#include <limits>
20#include <vector>
21
22#include "absl/strings/str_format.h"
23#include "absl/types/span.h"
25
26namespace operations_research {
27
29 static constexpr int kHungarianOptimizerRowNotFound = -1;
30 static constexpr int kHungarianOptimizerColNotFound = -2;
31
32 public:
33 // Setup the initial conditions for the algorithm.
34
35 // Parameters: costs is a matrix of the cost of assigning each agent to
36 // each task. costs[i][j] is the cost of assigning agent i to task j.
37 // All the costs must be non-negative. This matrix does not have to
38 // be square (i.e. we can have different numbers of agents and tasks), but it
39 // must be regular (i.e. there must be the same number of entries in each row
40 // of the matrix).
41 explicit HungarianOptimizer(absl::Span<const std::vector<double>> costs);
42
43 // Find an assignment which maximizes the total cost.
44 // Returns the assignment in the two vectors passed as argument.
45 // preimage[i] is assigned to image[i].
46 void Maximize(std::vector<int>* preimage, std::vector<int>* image);
47
48 // Like Maximize(), but minimizing the cost instead.
49 void Minimize(std::vector<int>* preimage, std::vector<int>* image);
50
51 private:
52 typedef void (HungarianOptimizer::*Step)();
53
54 typedef enum { NONE, PRIME, STAR } Mark;
55
56 // Convert the final cost matrix into a set of assignments of preimage->image.
57 // Returns the assignment in the two vectors passed as argument, the same as
58 // Minimize and Maximize
59 void FindAssignments(std::vector<int>* preimage, std::vector<int>* image);
60
61 // Is the cell (row, col) starred?
62 bool IsStarred(int row, int col) const { return marks_[row][col] == STAR; }
63
64 // Mark cell (row, col) with a star
65 void Star(int row, int col) {
66 marks_[row][col] = STAR;
67 stars_in_col_[col]++;
68 }
69
70 // Remove a star from cell (row, col)
71 void UnStar(int row, int col) {
72 marks_[row][col] = NONE;
73 stars_in_col_[col]--;
74 }
75
76 // Find a column in row 'row' containing a star, or return
77 // kHungarianOptimizerColNotFound if no such column exists.
78 int FindStarInRow(int row) const;
79
80 // Find a row in column 'col' containing a star, or return
81 // kHungarianOptimizerRowNotFound if no such row exists.
82 int FindStarInCol(int col) const;
83
84 // Is cell (row, col) marked with a prime?
85 bool IsPrimed(int row, int col) const { return marks_[row][col] == PRIME; }
86
87 // Mark cell (row, col) with a prime.
88 void Prime(int row, int col) { marks_[row][col] = PRIME; }
89
90 // Find a column in row containing a prime, or return
91 // kHungarianOptimizerColNotFound if no such column exists.
92 int FindPrimeInRow(int row) const;
93
94 // Remove the prime marks_ from every cell in the matrix.
95 void ClearPrimes();
96
97 // Does column col contain a star?
98 bool ColContainsStar(int col) const { return stars_in_col_[col] > 0; }
99
100 // Is row 'row' covered?
101 bool RowCovered(int row) const { return rows_covered_[row]; }
102
103 // Cover row 'row'.
104 void CoverRow(int row) { rows_covered_[row] = true; }
105
106 // Uncover row 'row'.
107 void UncoverRow(int row) { rows_covered_[row] = false; }
108
109 // Is column col covered?
110 bool ColCovered(int col) const { return cols_covered_[col]; }
111
112 // Cover column col.
113 void CoverCol(int col) { cols_covered_[col] = true; }
114
115 // Uncover column col.
116 void UncoverCol(int col) { cols_covered_[col] = false; }
117
118 // Uncover ever row and column in the matrix.
119 void ClearCovers();
120
121 // Find the smallest uncovered cell in the matrix.
122 double FindSmallestUncovered() const;
123
124 // Find an uncovered zero and store its coordinates in (zeroRow_, zeroCol_)
125 // and return true, or return false if no such cell exists.
126 bool FindZero(int* zero_row, int* zero_col) const;
127
128 // Print the matrix to stdout (for debugging.)
129 void PrintMatrix();
130
131 // Run the Munkres algorithm!
132 void DoMunkres();
133
134 // Step 1.
135 // For each row of the matrix, find the smallest element and subtract it
136 // from every element in its row. Go to Step 2.
137 void ReduceRows();
138
139 // Step 2.
140 // Find a zero (Z) in the matrix. If there is no starred zero in its row
141 // or column, star Z. Repeat for every element in the matrix. Go to step 3.
142 // Note: profiling shows this method to use 9.2% of the CPU - the next
143 // slowest step takes 0.6%. I can't think of a way of speeding it up though.
144 void StarZeroes();
145
146 // Step 3.
147 // Cover each column containing a starred zero. If all columns are
148 // covered, the starred zeros describe a complete set of unique assignments.
149 // In this case, terminate the algorithm. Otherwise, go to step 4.
150 void CoverStarredZeroes();
151
152 // Step 4.
153 // Find a noncovered zero and prime it. If there is no starred zero in the
154 // row containing this primed zero, Go to Step 5. Otherwise, cover this row
155 // and uncover the column containing the starred zero. Continue in this manner
156 // until there are no uncovered zeros left, then go to Step 6.
157 void PrimeZeroes();
158
159 // Step 5.
160 // Construct a series of alternating primed and starred zeros as follows.
161 // Let Z0 represent the uncovered primed zero found in Step 4. Let Z1 denote
162 // the starred zero in the column of Z0 (if any). Let Z2 denote the primed
163 // zero in the row of Z1 (there will always be one). Continue until the
164 // series terminates at a primed zero that has no starred zero in its column.
165 // Unstar each starred zero of the series, star each primed zero of the
166 // series, erase all primes and uncover every line in the matrix. Return to
167 // Step 3.
168 void MakeAugmentingPath();
169
170 // Step 6.
171 // Add the smallest uncovered value in the matrix to every element of each
172 // covered row, and subtract it from every element of each uncovered column.
173 // Return to Step 4 without altering any stars, primes, or covered lines.
174 void AugmentPath();
175
176 // The size of the problem, i.e. max(#agents, #tasks).
177 int matrix_size_;
178
179 // The expanded cost matrix.
180 std::vector<std::vector<double>> costs_;
181
182 // The greatest cost in the initial cost matrix.
183 double max_cost_;
184
185 // Which rows and columns are currently covered.
186 std::vector<bool> rows_covered_;
187 std::vector<bool> cols_covered_;
188
189 // The marks_ (star/prime/none) on each element of the cost matrix.
190 std::vector<std::vector<Mark>> marks_;
191
192 // The number of stars in each column - used to speed up coverStarredZeroes.
193 std::vector<int> stars_in_col_;
194
195 // Representation of a path_ through the matrix - used in step 5.
196 std::vector<int> preimage_; // i.e. the agents
197 std::vector<int> image_; // i.e. the tasks
198
199 // The width_ and height_ of the initial (non-expanded) cost matrix.
200 int width_;
201 int height_;
202
203 // The current state of the algorithm
204 HungarianOptimizer::Step state_;
205};
206
208 absl::Span<const std::vector<double>> costs)
209 : matrix_size_(0),
210 costs_(),
211 max_cost_(0),
212 rows_covered_(),
213 cols_covered_(),
214 marks_(),
215 stars_in_col_(),
216 preimage_(),
217 image_(),
218 width_(0),
219 height_(0),
220 state_(nullptr) {
221 width_ = costs.size();
222
223 if (width_ > 0) {
224 height_ = costs[0].size();
225 } else {
226 height_ = 0;
227 }
228
229 matrix_size_ = std::max(width_, height_);
230 max_cost_ = 0;
231
232 // Generate the expanded cost matrix by adding extra 0-valued elements in
233 // order to make a square matrix. At the same time, find the greatest cost
234 // in the matrix (used later if we want to maximize rather than minimize the
235 // overall cost.)
236 costs_.resize(matrix_size_);
237 for (int row = 0; row < matrix_size_; ++row) {
238 costs_[row].resize(matrix_size_);
239 }
240
241 for (int row = 0; row < matrix_size_; ++row) {
242 for (int col = 0; col < matrix_size_; ++col) {
243 if ((row >= width_) || (col >= height_)) {
244 costs_[row][col] = 0;
245 } else {
246 costs_[row][col] = costs[row][col];
247 max_cost_ = std::max(max_cost_, costs_[row][col]);
248 }
249 }
250 }
251
252 // Initially, none of the cells of the matrix are marked.
253 marks_.resize(matrix_size_);
254 for (int row = 0; row < matrix_size_; ++row) {
255 marks_[row].resize(matrix_size_);
256 for (int col = 0; col < matrix_size_; ++col) {
257 marks_[row][col] = NONE;
258 }
259 }
260
261 stars_in_col_.resize(matrix_size_);
262
263 rows_covered_.resize(matrix_size_);
264 cols_covered_.resize(matrix_size_);
265
266 preimage_.resize(matrix_size_ * 2);
267 image_.resize(matrix_size_ * 2);
268}
269
270// Find an assignment which maximizes the total cost.
271// Return an array of pairs of integers. Each pair (i, j) corresponds to
272// assigning agent i to task j.
273void HungarianOptimizer::Maximize(std::vector<int>* preimage,
274 std::vector<int>* image) {
275 // Find a maximal assignment by subtracting each of the
276 // original costs from max_cost_ and then minimizing.
277 for (int row = 0; row < width_; ++row) {
278 for (int col = 0; col < height_; ++col) {
279 costs_[row][col] = max_cost_ - costs_[row][col];
280 }
281 }
282 Minimize(preimage, image);
283}
284
285// Find an assignment which minimizes the total cost.
286// Return an array of pairs of integers. Each pair (i, j) corresponds to
287// assigning agent i to task j.
288void HungarianOptimizer::Minimize(std::vector<int>* preimage,
289 std::vector<int>* image) {
290 DoMunkres();
291 FindAssignments(preimage, image);
292}
293
294// Convert the final cost matrix into a set of assignments of agents -> tasks.
295// Return an array of pairs of integers, the same as the return values of
296// Minimize() and Maximize()
297void HungarianOptimizer::FindAssignments(std::vector<int>* preimage,
298 std::vector<int>* image) {
299 preimage->clear();
300 image->clear();
301 for (int row = 0; row < width_; ++row) {
302 for (int col = 0; col < height_; ++col) {
303 if (IsStarred(row, col)) {
304 preimage->push_back(row);
305 image->push_back(col);
306 break;
307 }
308 }
309 }
310 // TODO(user)
311 // result_size = min(width_, height_);
312 // CHECK image.size() == result_size
313 // CHECK preimage.size() == result_size
314}
315
316// Find a column in row 'row' containing a star, or return
317// kHungarianOptimizerColNotFound if no such column exists.
318int HungarianOptimizer::FindStarInRow(int row) const {
319 for (int col = 0; col < matrix_size_; ++col) {
320 if (IsStarred(row, col)) {
321 return col;
322 }
323 }
324
325 return kHungarianOptimizerColNotFound;
326}
327
328// Find a row in column 'col' containing a star, or return
329// kHungarianOptimizerRowNotFound if no such row exists.
330int HungarianOptimizer::FindStarInCol(int col) const {
331 if (!ColContainsStar(col)) {
332 return kHungarianOptimizerRowNotFound;
333 }
334
335 for (int row = 0; row < matrix_size_; ++row) {
336 if (IsStarred(row, col)) {
337 return row;
338 }
339 }
340
341 // NOTREACHED
342 return kHungarianOptimizerRowNotFound;
343}
344
345// Find a column in row containing a prime, or return
346// kHungarianOptimizerColNotFound if no such column exists.
347int HungarianOptimizer::FindPrimeInRow(int row) const {
348 for (int col = 0; col < matrix_size_; ++col) {
349 if (IsPrimed(row, col)) {
350 return col;
351 }
352 }
353
354 return kHungarianOptimizerColNotFound;
355}
356
357// Remove the prime marks from every cell in the matrix.
358void HungarianOptimizer::ClearPrimes() {
359 for (int row = 0; row < matrix_size_; ++row) {
360 for (int col = 0; col < matrix_size_; ++col) {
361 if (IsPrimed(row, col)) {
362 marks_[row][col] = NONE;
363 }
364 }
365 }
366}
367
368// Uncovery ever row and column in the matrix.
369void HungarianOptimizer::ClearCovers() {
370 for (int x = 0; x < matrix_size_; x++) {
371 UncoverRow(x);
372 UncoverCol(x);
373 }
374}
375
376// Find the smallest uncovered cell in the matrix.
377double HungarianOptimizer::FindSmallestUncovered() const {
378 double minval = std::numeric_limits<double>::max();
379
380 for (int row = 0; row < matrix_size_; ++row) {
381 if (RowCovered(row)) {
382 continue;
383 }
384
385 for (int col = 0; col < matrix_size_; ++col) {
386 if (ColCovered(col)) {
387 continue;
388 }
389
390 minval = std::min(minval, costs_[row][col]);
391 }
392 }
393
394 return minval;
395}
396
397// Find an uncovered zero and store its co-ordinates in (zeroRow, zeroCol)
398// and return true, or return false if no such cell exists.
399bool HungarianOptimizer::FindZero(int* zero_row, int* zero_col) const {
400 for (int row = 0; row < matrix_size_; ++row) {
401 if (RowCovered(row)) {
402 continue;
403 }
404
405 for (int col = 0; col < matrix_size_; ++col) {
406 if (ColCovered(col)) {
407 continue;
408 }
409
410 if (costs_[row][col] == 0) {
411 *zero_row = row;
412 *zero_col = col;
413 return true;
414 }
415 }
416 }
417
418 return false;
419}
420
421// Print the matrix to stdout (for debugging.)
422void HungarianOptimizer::PrintMatrix() {
423 for (int row = 0; row < matrix_size_; ++row) {
424 for (int col = 0; col < matrix_size_; ++col) {
425 absl::PrintF("%g ", costs_[row][col]);
426
427 if (IsStarred(row, col)) {
428 absl::PrintF("*");
429 }
430
431 if (IsPrimed(row, col)) {
432 absl::PrintF("'");
433 }
434 }
435 absl::PrintF("\n");
436 }
437}
438
439// Run the Munkres algorithm!
440void HungarianOptimizer::DoMunkres() {
441 state_ = &HungarianOptimizer::ReduceRows;
442 while (state_ != nullptr) {
443 (this->*state_)();
444 }
445}
446
447// Step 1.
448// For each row of the matrix, find the smallest element and subtract it
449// from every element in its row. Go to Step 2.
450void HungarianOptimizer::ReduceRows() {
451 for (int row = 0; row < matrix_size_; ++row) {
452 double min_cost = costs_[row][0];
453 for (int col = 1; col < matrix_size_; ++col) {
454 min_cost = std::min(min_cost, costs_[row][col]);
455 }
456 for (int col = 0; col < matrix_size_; ++col) {
457 costs_[row][col] -= min_cost;
458 }
459 }
460 state_ = &HungarianOptimizer::StarZeroes;
461}
462
463// Step 2.
464// Find a zero (Z) in the matrix. If there is no starred zero in its row
465// or column, star Z. Repeat for every element in the matrix. Go to step 3.
466void HungarianOptimizer::StarZeroes() {
467 // Since no rows or columns are covered on entry to this step, we use the
468 // covers as a quick way of marking which rows & columns have stars in them.
469 for (int row = 0; row < matrix_size_; ++row) {
470 if (RowCovered(row)) {
471 continue;
472 }
473
474 for (int col = 0; col < matrix_size_; ++col) {
475 if (ColCovered(col)) {
476 continue;
477 }
478
479 if (costs_[row][col] == 0) {
480 Star(row, col);
481 CoverRow(row);
482 CoverCol(col);
483 break;
484 }
485 }
486 }
487
488 ClearCovers();
489 state_ = &HungarianOptimizer::CoverStarredZeroes;
490}
491
492// Step 3.
493// Cover each column containing a starred zero. If all columns are
494// covered, the starred zeros describe a complete set of unique assignments.
495// In this case, terminate the algorithm. Otherwise, go to step 4.
496void HungarianOptimizer::CoverStarredZeroes() {
497 int num_covered = 0;
498
499 for (int col = 0; col < matrix_size_; ++col) {
500 if (ColContainsStar(col)) {
501 CoverCol(col);
502 num_covered++;
503 }
504 }
505
506 if (num_covered >= matrix_size_) {
507 state_ = nullptr;
508 return;
509 }
510 state_ = &HungarianOptimizer::PrimeZeroes;
511}
512
513// Step 4.
514// Find a noncovered zero and prime it. If there is no starred zero in the
515// row containing this primed zero, Go to Step 5. Otherwise, cover this row
516// and uncover the column containing the starred zero. Continue in this manner
517// until there are no uncovered zeros left, then go to Step 6.
518
519void HungarianOptimizer::PrimeZeroes() {
520 // This loop is guaranteed to terminate in at most matrix_size_ iterations,
521 // as findZero() returns a location only if there is at least one uncovered
522 // zero in the matrix. Each iteration, either one row is covered or the
523 // loop terminates. Since there are matrix_size_ rows, after that many
524 // iterations there are no uncovered cells and hence no uncovered zeroes,
525 // so the loop terminates.
526 for (;;) {
527 int zero_row, zero_col;
528 if (!FindZero(&zero_row, &zero_col)) {
529 // No uncovered zeroes.
530 state_ = &HungarianOptimizer::AugmentPath;
531 return;
532 }
533
534 Prime(zero_row, zero_col);
535 int star_col = FindStarInRow(zero_row);
536
537 if (star_col != kHungarianOptimizerColNotFound) {
538 CoverRow(zero_row);
539 UncoverCol(star_col);
540 } else {
541 preimage_[0] = zero_row;
542 image_[0] = zero_col;
543 state_ = &HungarianOptimizer::MakeAugmentingPath;
544 return;
545 }
546 }
547}
548
549// Step 5.
550// Construct a series of alternating primed and starred zeros as follows.
551// Let Z0 represent the uncovered primed zero found in Step 4. Let Z1 denote
552// the starred zero in the column of Z0 (if any). Let Z2 denote the primed
553// zero in the row of Z1 (there will always be one). Continue until the
554// series terminates at a primed zero that has no starred zero in its column.
555// Unstar each starred zero of the series, star each primed zero of the
556// series, erase all primes and uncover every line in the matrix. Return to
557// Step 3.
558void HungarianOptimizer::MakeAugmentingPath() {
559 bool done = false;
560 int count = 0;
561
562 // Note: this loop is guaranteed to terminate within matrix_size_ iterations
563 // because:
564 // 1) on entry to this step, there is at least 1 column with no starred zero
565 // (otherwise we would have terminated the algorithm already.)
566 // 2) each row containing a star also contains exactly one primed zero.
567 // 4) each column contains at most one starred zero.
568 //
569 // Since the path_ we construct visits primed and starred zeroes alternately,
570 // and terminates if we reach a primed zero in a column with no star, our
571 // path_ must either contain matrix_size_ or fewer stars (in which case the
572 // loop iterates fewer than matrix_size_ times), or it contains more. In
573 // that case, because (1) implies that there are fewer than
574 // matrix_size_ stars, we must have visited at least one star more than once.
575 // Consider the first such star that we visit more than once; it must have
576 // been reached immediately after visiting a prime in the same row. By (2),
577 // this prime is unique and so must have also been visited more than once.
578 // Therefore, that prime must be in the same column as a star that has been
579 // visited more than once, contradicting the assumption that we chose the
580 // first multiply visited star, or it must be in the same column as more
581 // than one star, contradicting (3). Therefore, we never visit any star
582 // more than once and the loop terminates within matrix_size_ iterations.
583
584 while (!done) {
585 // First construct the alternating path...
586 int row = FindStarInCol(image_[count]);
587
588 if (row != kHungarianOptimizerRowNotFound) {
589 count++;
590 preimage_[count] = row;
591 image_[count] = image_[count - 1];
592 } else {
593 done = true;
594 }
595
596 if (!done) {
597 int col = FindPrimeInRow(preimage_[count]);
598 count++;
599 preimage_[count] = preimage_[count - 1];
600 image_[count] = col;
601 }
602 }
603
604 // Then modify it.
605 for (int i = 0; i <= count; ++i) {
606 int row = preimage_[i];
607 int col = image_[i];
608
609 if (IsStarred(row, col)) {
610 UnStar(row, col);
611 } else {
612 Star(row, col);
613 }
614 }
615
616 ClearCovers();
617 ClearPrimes();
618 state_ = &HungarianOptimizer::CoverStarredZeroes;
619}
620
621// Step 6
622// Add the smallest uncovered value in the matrix to every element of each
623// covered row, and subtract it from every element of each uncovered column.
624// Return to Step 4 without altering any stars, primes, or covered lines.
625void HungarianOptimizer::AugmentPath() {
626 double minval = FindSmallestUncovered();
627
628 for (int row = 0; row < matrix_size_; ++row) {
629 for (int col = 0; col < matrix_size_; ++col) {
630 if (RowCovered(row)) {
631 costs_[row][col] += minval;
632 }
633
634 if (!ColCovered(col)) {
635 costs_[row][col] -= minval;
636 }
637 }
638 }
639
640 state_ = &HungarianOptimizer::PrimeZeroes;
641}
642
643bool InputContainsNan(absl::Span<const std::vector<double>> input) {
644 for (const auto& subvector : input) {
645 for (const auto& num : subvector) {
646 if (std::isnan(num)) {
647 LOG(ERROR) << "The provided input contains " << num << ".";
648 return true;
649 }
650 }
651 }
652 return false;
653}
654
656 absl::Span<const std::vector<double>> cost,
657 absl::flat_hash_map<int, int>* direct_assignment,
658 absl::flat_hash_map<int, int>* reverse_assignment) {
659 if (InputContainsNan(cost)) {
660 LOG(ERROR) << "Returning before invoking the Hungarian optimizer.";
661 return;
662 }
663 std::vector<int> agent;
664 std::vector<int> task;
665 HungarianOptimizer hungarian_optimizer(cost);
666 hungarian_optimizer.Minimize(&agent, &task);
667 for (int i = 0; i < agent.size(); ++i) {
668 (*direct_assignment)[agent[i]] = task[i];
669 (*reverse_assignment)[task[i]] = agent[i];
670 }
671}
672
674 absl::Span<const std::vector<double>> cost,
675 absl::flat_hash_map<int, int>* direct_assignment,
676 absl::flat_hash_map<int, int>* reverse_assignment) {
677 if (InputContainsNan(cost)) {
678 LOG(ERROR) << "Returning before invoking the Hungarian optimizer.";
679 return;
680 }
681 std::vector<int> agent;
682 std::vector<int> task;
683 HungarianOptimizer hungarian_optimizer(cost);
684 hungarian_optimizer.Maximize(&agent, &task);
685 for (int i = 0; i < agent.size(); ++i) {
686 (*direct_assignment)[agent[i]] = task[i];
687 (*reverse_assignment)[task[i]] = agent[i];
688 }
689}
690
691} // namespace operations_research
void Minimize(std::vector< int > *preimage, std::vector< int > *image)
Like Maximize(), but minimizing the cost instead.
Definition hungarian.cc:288
HungarianOptimizer(absl::Span< const std::vector< double > > costs)
Setup the initial conditions for the algorithm.
Definition hungarian.cc:207
void Maximize(std::vector< int > *preimage, std::vector< int > *image)
Definition hungarian.cc:273
ColIndex col
Definition markowitz.cc:187
RowIndex row
Definition markowitz.cc:186
In SWIG mode, we don't want anything besides these top-level includes.
bool InputContainsNan(absl::Span< const std::vector< double > > input)
Definition hungarian.cc:643
void MinimizeLinearAssignment(absl::Span< const std::vector< double > > cost, absl::flat_hash_map< int, int > *direct_assignment, absl::flat_hash_map< int, int > *reverse_assignment)
See IMPORTANT NOTE at the top of the file.
Definition hungarian.cc:655
void MaximizeLinearAssignment(absl::Span< const std::vector< double > > cost, absl::flat_hash_map< int, int > *direct_assignment, absl::flat_hash_map< int, int > *reverse_assignment)
See IMPORTANT NOTE at the top of the file.
Definition hungarian.cc:673
static int input(yyscan_t yyscanner)
const Variable x
Definition qp_tests.cc:127