Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
Hungarian.H
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
72#ifndef HUNGARIAN_H
73#define HUNGARIAN_H
74
75#include <cmath>
76#include <limits>
77#include <type_traits>
78#include <utility>
79#include <initializer_list>
80#include <tpl_dynMat.H>
81#include <tpl_array.H>
82#include <htlist.H>
83#include <ah-errors.H>
84#include <ahFunction.H>
85
86namespace Aleph {
96template <typename Cost_Type = double>
98{
99 static_assert(std::is_signed_v<std::remove_cv_t<Cost_Type>> or
100 std::is_floating_point_v<std::remove_cv_t<Cost_Type>>,
101 "Cost_Type must be signed or floating-point to avoid "
102 "underflow when negating costs");
106 size_t orig_rows = 0;
107 size_t orig_cols = 0;
108
117 {
119 for (size_t i = 0; i < orig_rows; ++i)
120 if (const long j = row_to_col[i]; j >= 0 and static_cast<size_t>(j) < orig_cols)
121 pairs.append(std::make_pair(i, static_cast<size_t>(j)));
122 return pairs;
123 }
124};
125
140template <typename Cost_Type = double>
142
143{
144 static_assert(std::is_signed_v<std::remove_cv_t<Cost_Type>> or
145 std::is_floating_point_v<std::remove_cv_t<Cost_Type>>,
146 "Cost_Type must be signed or floating-point to avoid "
147 "underflow when negating costs");
148 size_t n_ = 0; // Padded square dimension
149 size_t orig_rows_ = 0; // Original row count
150 size_t orig_cols_ = 0; // Original column count
152 Array<long> row_to_col_; // row i -> column (or -1 if dummy)
153 Array<long> col_to_row_; // column j -> row (or -1 if dummy)
154
160 void solve(const DynMatrix<Cost_Type> &cost)
161 {
162 const long n = static_cast<long>(n_);
163 const auto sz = static_cast<size_t>(n + 1);
164
165 // Dual variables (potentials), 1-indexed
166 Array<Cost_Type> u(sz, Cost_Type{0});
167 Array<Cost_Type> v(sz, Cost_Type{0});
168
169 // p[j] = row matched to column j (0 = unmatched)
170 Array<long> p(sz, 0L);
171
172 const Cost_Type Inf = std::numeric_limits<Cost_Type>::max() / 2;
173
174 // For each row, find the shortest augmenting path
175 for (long i = 1; i <= n; ++i)
176 {
177 // "Virtual" column 0 is matched to row i
178 p(0) = i;
179
180 // Minimum reduced cost to reach each column
181 Array<Cost_Type> dist(sz, Inf);
182 dist(0) = Cost_Type{0};
183
184 // way[j] = previous column on the shortest path to j
185 Array<long> way(sz, 0L);
186
187 // visited[j] = true if column j is in the "tree"
188 Array<bool> visited(sz, false);
189
190 // Dijkstra-like scan
191 long j0 = 0; // current column (start at virtual column 0)
192 do
193 {
194 visited(j0) = true;
195 const long i0 = p(j0); // row matched to current column
196 Cost_Type delta = Inf;
197 long j1 = -1;
198
199 for (long j = 1; j <= n; ++j)
200 {
201 if (visited(j))
202 continue;
203
204 // Reduced cost: c[i0][j] - u[i0] - v[j]
205 const Cost_Type reduced =
206 cost.read(static_cast<size_t>(i0 - 1), static_cast<size_t>(j - 1)) - u(i0) - v(j);
207
208 if (reduced < dist(j))
209 {
210 dist(j) = reduced;
211 way(j) = j0;
212 }
213
214 if (dist(j) < delta)
215 {
216 delta = dist(j);
217 j1 = j;
218 }
219 }
220
221 // Update potentials
222 for (long j = 0; j <= n; ++j)
223 if (visited(j))
224 {
225 u(p(j)) += delta;
226 v(j) -= delta;
227 }
228 else
229 dist(j) -= delta;
230
231 j0 = j1;
232 } while (p(j0) != 0); // until we reach a free column
233
234 // Augment along the path
235 do
236 {
237 const long j1 = way(j0);
238 p(j0) = p(j1);
239 j0 = j1;
240 } while (j0 != 0);
241 }
242
243 // Extract results
244 total_cost_ = -v(0); // v[0] accumulates the total cost
245
248
249 for (long j = 1; j <= n; ++j)
250 {
251 const long row = p(j) - 1; // convert to 0-based
252 const long col = j - 1;
253
254 if (row >= 0 and static_cast<size_t>(row) < orig_rows_ and col >= 0 and
255 static_cast<size_t>(col) < orig_cols_)
256 {
257 row_to_col_(static_cast<size_t>(row)) = col;
258 col_to_row_(static_cast<size_t>(col)) = row;
259 }
260 }
261 }
262
263public:
273 {
274 orig_rows_ = cost.rows();
275 orig_cols_ = cost.cols();
277 << "Hungarian_Assignment: cost matrix must not be empty";
278
279 if constexpr (std::is_floating_point_v<Cost_Type>)
280 for (size_t i = 0; i < orig_rows_; ++i)
281 for (size_t j = 0; j < orig_cols_; ++j)
282 ah_invalid_argument_if(not std::isfinite(cost.read(i, j)))
283 << "Hungarian_Assignment: cost[" << i << "][" << j << "] is not finite";
284
286
287 // Build padded square matrix (zeros for padding)
290 for (size_t i = 0; i < orig_rows_; ++i)
291 for (size_t j = 0; j < orig_cols_; ++j)
292 padded(i, j) = cost.read(i, j);
293
294 solve(padded);
295 }
296
315 Hungarian_Assignment(std::initializer_list<std::initializer_list<Cost_Type>> rows)
316 {
317 ah_invalid_argument_if(rows.size() == 0)
318 << "Hungarian_Assignment: cost matrix must not be empty";
319
320 orig_rows_ = rows.size();
321 orig_cols_ = rows.begin()->size();
323 << "Hungarian_Assignment: cost matrix must not have zero columns";
325
328
329 size_t i = 0;
330 for (const auto &row : rows)
331 {
333 << "Hungarian_Assignment: all rows must have the same length";
334 size_t j = 0;
335 for (const auto &val : row)
336 {
337 if constexpr (std::is_floating_point_v<Cost_Type>)
338 ah_invalid_argument_if(not std::isfinite(val))
339 << "Hungarian_Assignment: non-finite cost value at row " << i;
340 padded(i, j++) = val;
341 }
342 ++i;
343 }
344
345 solve(padded);
346 }
347
355
362 [[nodiscard]] long get_assignment(const size_t row) const
363 {
364 ah_out_of_range_error_if(row >= orig_rows_) << "Hungarian_Assignment::get_assignment: row "
365 << row << " out of range [0, " << orig_rows_ << ")";
366 return row_to_col_[row];
367 }
368
377 {
379 for (size_t i = 0; i < orig_rows_; ++i)
380 if (const long j = row_to_col_[i]; j >= 0 and static_cast<size_t>(j) < orig_cols_)
381 pairs.append(std::make_pair(i, static_cast<size_t>(j)));
382 return pairs;
383 }
384
393
402
411 {
412 return std::move(row_to_col_);
413 }
414
423 {
424 return std::move(col_to_row_);
425 }
426
431 {
432 return n_;
433 }
434
439 {
440 return orig_rows_;
441 }
442
447 {
448 return orig_cols_;
449 }
450};
451
469template <typename Cost_Type>
471{
472 static_assert(std::is_signed_v<std::remove_cv_t<Cost_Type>> or
473 std::is_floating_point_v<std::remove_cv_t<Cost_Type>>,
474 "Cost_Type must be signed or floating-point to avoid "
475 "underflow when negating costs");
476
479 // Read scalar fields before moving from ha.
480 result.total_cost = ha.get_total_cost();
481 result.orig_rows = ha.rows();
482 result.orig_cols = ha.cols();
483 result.row_to_col = std::move(ha).extract_row_assignments();
484 result.col_to_row = std::move(ha).extract_col_assignments();
485 return result;
486}
487
507template <typename Cost_Type>
509{
510 static_assert(std::is_signed_v<std::remove_cv_t<Cost_Type>> or
511 std::is_floating_point_v<std::remove_cv_t<Cost_Type>>,
512 "Cost_Type must be signed or floating-point to avoid "
513 "underflow when negating costs");
514
515 // Use a promoted signed type for negation to avoid overflow when
516 // Cost_Type is a signed integer and a cell contains its minimum value.
517 using Common = std::common_type_t<Cost_Type, long long>;
518 using Promoted =
519 std::conditional_t<std::is_floating_point_v<Common>, Common, std::make_signed_t<Common>>;
520
521 const size_t rows = cost.rows();
522 const size_t cols = cost.cols();
524 negated.allocate();
525 for (size_t i = 0; i < rows; ++i)
526 for (size_t j = 0; j < cols; ++j)
527 {
528 if constexpr (std::is_integral_v<Promoted> and std::is_signed_v<Promoted>)
529 {
530 auto v = static_cast<Promoted>(cost.read(i, j));
531 ah_overflow_error_if(v == std::numeric_limits<Promoted>::min())
532 << "Cannot negate minimum integer value";
533 negated(i, j) = -v;
534 }
535 else
536 negated(i, j) = -static_cast<Promoted>(cost.read(i, j));
537 }
538
539 auto inner = hungarian_assignment(negated);
540
542 result.total_cost = static_cast<Cost_Type>(-inner.total_cost);
543 result.row_to_col = std::move(inner.row_to_col);
544 result.col_to_row = std::move(inner.col_to_row);
545 result.orig_rows = inner.orig_rows;
546 result.orig_cols = inner.orig_cols;
547 return result;
548}
549} // end namespace Aleph
550
551#endif // HUNGARIAN_H
Exception handling system with formatted messages for Aleph-w.
#define ah_out_of_range_error_if(C)
Throws std::out_of_range if condition holds.
Definition ah-errors.H:584
#define ah_overflow_error_if(C)
Throws std::overflow_error if condition holds.
Definition ah-errors.H:468
#define ah_invalid_argument_if(C)
Throws std::invalid_argument if condition holds.
Definition ah-errors.H:644
Standard functor implementations and comparison objects.
size_t row
Definition ca-c-api.h:115
size_t * rows
Definition ca-c-api.h:112
size_t size_t col
Definition ca-c-api.h:116
size_t cols
Definition ca-c-api.h:105
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:138
Doubly-linked list (defined in tpl_dynList.H).
Definition htlist.H:1155
T & append(const T &item)
Definition htlist.H:1271
Dynamic matrix with sparse storage.
Definition tpl_dynMat.H:120
const T & read(const size_t i, const size_t j) const
Read the entry at position (i, j).
Definition tpl_dynMat.H:452
constexpr size_t cols() const noexcept
Get the number of columns.
Definition tpl_dynMat.H:419
void allocate()
Pre-allocate memory for the entire matrix.
Definition tpl_dynMat.H:249
constexpr size_t rows() const noexcept
Get the number of rows.
Definition tpl_dynMat.H:413
Implementation of the Hungarian (Munkres) algorithm.
Definition Hungarian.H:143
DynList< std::pair< size_t, size_t > > get_assignments() const
Get all assignment pairs.
Definition Hungarian.H:376
long get_assignment(const size_t row) const
Get the column assigned to a given row.
Definition Hungarian.H:362
const Array< long > & get_row_assignments() const noexcept
Get the row-to-column assignment array.
Definition Hungarian.H:389
Hungarian_Assignment(std::initializer_list< std::initializer_list< Cost_Type > > rows)
Construct and solve from an initializer list of rows.
Definition Hungarian.H:315
Array< long > extract_col_assignments() &&noexcept
Move out the column-to-row assignment array.
Definition Hungarian.H:422
const Array< long > & get_col_assignments() const noexcept
Get the column-to-row assignment array.
Definition Hungarian.H:398
size_t cols() const noexcept
Get the original number of columns.
Definition Hungarian.H:446
size_t dimension() const noexcept
Get the padded square dimension.
Definition Hungarian.H:430
Array< long > extract_row_assignments() &&noexcept
Move out the row-to-column assignment array.
Definition Hungarian.H:410
size_t rows() const noexcept
Get the original number of rows.
Definition Hungarian.H:438
void solve(const DynMatrix< Cost_Type > &cost)
Core algorithm: shortest augmenting paths with dual variables.
Definition Hungarian.H:160
Hungarian_Assignment(const DynMatrix< Cost_Type > &cost)
Construct and solve from a DynMatrix cost matrix.
Definition Hungarian.H:272
Cost_Type get_total_cost() const noexcept
Get the optimal total cost.
Definition Hungarian.H:351
__gmp_expr< T, __gmp_unary_expr< __gmp_expr< T, U >, __gmp_j0_function > > j0(const __gmp_expr< T, U > &expr)
Definition gmpfrxx.h:4110
__gmp_expr< T, __gmp_unary_expr< __gmp_expr< T, U >, __gmp_j1_function > > j1(const __gmp_expr< T, U > &expr)
Definition gmpfrxx.h:4111
Hungarian_Result< Cost_Type > hungarian_max_assignment(const DynMatrix< Cost_Type > &cost)
Compute maximum-profit assignment (free function).
Definition Hungarian.H:508
Hungarian_Result< Cost_Type > hungarian_assignment(const DynMatrix< Cost_Type > &cost)
Compute minimum-cost assignment (free function).
Definition Hungarian.H:470
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
Singly linked list implementations with head-tail access.
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
and
Check uniqueness with explicit hash + equality functors.
Result of the Hungarian assignment algorithm.
Definition Hungarian.H:98
Array< long > col_to_row
column j is assigned to row col_to_row[j]
Definition Hungarian.H:105
Cost_Type total_cost
Optimal total cost.
Definition Hungarian.H:103
Array< long > row_to_col
row i is assigned to column row_to_col[i]
Definition Hungarian.H:104
DynList< std::pair< size_t, size_t > > get_pairs() const
Get the assignment pairs, excluding dummy entries.
Definition Hungarian.H:116
size_t orig_rows
Original number of rows.
Definition Hungarian.H:106
size_t orig_cols
Original number of columns.
Definition Hungarian.H:107
Dynamic array container with automatic resizing.
Dynamic matrix with lazy allocation.