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
31
89# ifndef HUNGARIAN_H
90# define HUNGARIAN_H
91
92# include <limits>
93# include <type_traits>
94# include <utility>
95# include <initializer_list>
96# include <tpl_dynMat.H>
97# include <tpl_array.H>
98# include <htlist.H>
99# include <ah-errors.H>
100# include <ahFunction.H>
101
102namespace Aleph
103{
113 template <typename Cost_Type = double>
115 {
116 static_assert(std::is_signed_v<std::remove_cv_t<Cost_Type>> or
117 std::is_floating_point_v<std::remove_cv_t<Cost_Type>>,
118 "Cost_Type must be signed or floating-point to avoid "
119 "underflow when negating costs");
123 size_t orig_rows = 0;
124 size_t orig_cols = 0;
125
134 {
136 for (size_t i = 0; i < orig_rows; ++i)
137 if (const long j = row_to_col[i]; j >= 0 and static_cast<size_t>(j) < orig_cols)
138 pairs.append(std::make_pair(i, static_cast<size_t>(j)));
139 return pairs;
140 }
141 };
142
174 template <typename Cost_Type = double>
176 {
177 static_assert(std::is_signed_v<std::remove_cv_t<Cost_Type>> or
178 std::is_floating_point_v<std::remove_cv_t<Cost_Type>>,
179 "Cost_Type must be signed or floating-point to avoid "
180 "underflow when negating costs");
181 size_t n_ = 0; // Padded square dimension
182 size_t orig_rows_ = 0; // Original row count
183 size_t orig_cols_ = 0; // Original column count
185 Array<long> row_to_col_; // row i -> column (or -1 if dummy)
186 Array<long> col_to_row_; // column j -> row (or -1 if dummy)
187
193 void solve(const DynMatrix<Cost_Type> & cost)
194 {
195 const long n = static_cast<long>(n_);
196 const auto sz = static_cast<size_t>(n + 1);
197
198 // Dual variables (potentials), 1-indexed
199 Array<Cost_Type> u(sz, Cost_Type{0});
200 Array<Cost_Type> v(sz, Cost_Type{0});
201
202 // p[j] = row matched to column j (0 = unmatched)
203 Array<long> p(sz, 0L);
204
205 const Cost_Type Inf = std::numeric_limits<Cost_Type>::max() / 2;
206
207 // For each row, find the shortest augmenting path
208 for (long i = 1; i <= n; ++i)
209 {
210 // "Virtual" column 0 is matched to row i
211 p(0) = i;
212
213 // Minimum reduced cost to reach each column
214 Array<Cost_Type> dist(sz, Inf);
215 dist(0) = Cost_Type{0};
216
217 // way[j] = previous column on the shortest path to j
218 Array<long> way(sz, 0L);
219
220 // visited[j] = true if column j is in the "tree"
221 Array<bool> visited(sz, false);
222
223 // Dijkstra-like scan
224 long j0 = 0; // current column (start at virtual column 0)
225 do
226 {
227 visited(j0) = true;
228 const long i0 = p(j0); // row matched to current column
229 Cost_Type delta = Inf;
230 long j1 = -1;
231
232 for (long j = 1; j <= n; ++j)
233 {
234 if (visited(j))
235 continue;
236
237 // Reduced cost: c[i0][j] - u[i0] - v[j]
238 const Cost_Type reduced = cost.read(static_cast<size_t>(i0 - 1),
239 static_cast<size_t>(j - 1)) - u(i0) - v(j);
240
241 if (reduced < dist(j))
242 {
243 dist(j) = reduced;
244 way(j) = j0;
245 }
246
247 if (dist(j) < delta)
248 {
249 delta = dist(j);
250 j1 = j;
251 }
252 }
253
254 // Update potentials
255 for (long j = 0; j <= n; ++j)
256 if (visited(j))
257 {
258 u(p(j)) += delta;
259 v(j) -= delta;
260 }
261 else
262 dist(j) -= delta;
263
264 j0 = j1;
265 }
266 while (p(j0) != 0); // until we reach a free column
267
268 // Augment along the path
269 do
270 {
271 const long j1 = way(j0);
272 p(j0) = p(j1);
273 j0 = j1;
274 }
275 while (j0 != 0);
276 }
277
278 // Extract results
279 total_cost_ = -v(0); // v[0] accumulates the total cost
280
283
284 for (long j = 1; j <= n; ++j)
285 {
286 const long row = p(j) - 1; // convert to 0-based
287 const long col = j - 1;
288
289 if (row >= 0 and static_cast<size_t>(row) < orig_rows_ and
290 col >= 0 and static_cast<size_t>(col) < orig_cols_)
291 {
292 row_to_col_(static_cast<size_t>(row)) = col;
293 col_to_row_(static_cast<size_t>(col)) = row;
294 }
295 }
296 }
297
298 public:
308 {
309 orig_rows_ = cost.rows();
310 orig_cols_ = cost.cols();
312 << "Hungarian_Assignment: cost matrix must not be empty";
313
315
316 // Build padded square matrix (zeros for padding)
319 for (size_t i = 0; i < orig_rows_; ++i)
320 for (size_t j = 0; j < orig_cols_; ++j)
321 padded(i, j) = cost.read(i, j);
322
323 solve(padded);
324 }
325
344 Hungarian_Assignment(std::initializer_list<std::initializer_list<Cost_Type>> rows)
345 {
346 ah_invalid_argument_if(rows.size() == 0) <<
347 "Hungarian_Assignment: cost matrix must not be empty";
348
349 orig_rows_ = rows.size();
350 orig_cols_ = rows.begin()->size();
352
355
356 size_t i = 0;
357 for (const auto & row: rows)
358 {
360 << "Hungarian_Assignment: all rows must have the same length";
361 size_t j = 0;
362 for (const auto & val: row)
363 padded(i, j++) = val;
364 ++i;
365 }
366
367 solve(padded);
368 }
369
377
384 [[nodiscard]] long get_assignment(const size_t row) const
385 {
387 << "Hungarian_Assignment::get_assignment: row " << row
388 << " out of range [0, " << orig_rows_ << ")";
389 return row_to_col_[row];
390 }
391
400 {
402 for (size_t i = 0; i < orig_rows_; ++i)
403 if (const long j = row_to_col_[i]; j >= 0 and static_cast<size_t>(j) < orig_cols_)
404 pairs.append(std::make_pair(i, static_cast<size_t>(j)));
405 return pairs;
406 }
407
416
425
429 [[nodiscard]] size_t dimension() const noexcept { return n_; }
430
434 [[nodiscard]] size_t rows() const noexcept { return orig_rows_; }
435
439 [[nodiscard]] size_t cols() const noexcept { return orig_cols_; }
440 };
441
459 template <typename Cost_Type>
462 {
463 static_assert(std::is_signed_v<std::remove_cv_t<Cost_Type>> or
464 std::is_floating_point_v<std::remove_cv_t<Cost_Type>>,
465 "Cost_Type must be signed or floating-point to avoid "
466 "underflow when negating costs");
467
470 result.total_cost = ha.get_total_cost();
471 result.row_to_col = ha.get_row_assignments();
472 result.col_to_row = ha.get_col_assignments();
473 result.orig_rows = ha.rows();
474 result.orig_cols = ha.cols();
475 return result;
476 }
477
497 template <typename Cost_Type>
500 {
501 static_assert(std::is_signed_v<std::remove_cv_t<Cost_Type>> or
502 std::is_floating_point_v<std::remove_cv_t<Cost_Type>>,
503 "Cost_Type must be signed or floating-point to avoid "
504 "underflow when negating costs");
505
506 // Use a promoted signed type for negation to avoid overflow when
507 // Cost_Type is a signed integer and a cell contains its minimum value.
508 using Common = std::common_type_t<Cost_Type, long long>;
509 using Promoted = std::conditional_t<std::is_floating_point_v<Common>,
510 Common,
511 std::make_signed_t<Common>>;
512
513 const size_t rows = cost.rows();
514 const size_t cols = cost.cols();
515 DynMatrix<Promoted> negated(rows, cols);
516 negated.allocate();
517 for (size_t i = 0; i < rows; ++i)
518 for (size_t j = 0; j < cols; ++j)
519 {
520 if constexpr (std::is_signed_v<Promoted>)
521 {
522 auto v = static_cast<Promoted>(cost.read(i, j));
523 ah_overflow_error_if(v == std::numeric_limits<Promoted>::min())
524 << "Cannot negate minimum integer value";
525 negated(i, j) = -v;
526 }
527 else
528 {
529 negated(i, j) = -static_cast<Promoted>(cost.read(i, j));
530 }
531 }
532
534
536 result.total_cost = static_cast<Cost_Type>(-inner.total_cost);
537 result.row_to_col = std::move(inner.row_to_col);
538 result.col_to_row = std::move(inner.col_to_row);
539 result.orig_rows = inner.orig_rows;
540 result.orig_cols = inner.orig_cols;
541 return result;
542 }
543} // end namespace Aleph
544
545# 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:579
#define ah_overflow_error_if(C)
Throws std::overflow_error if condition holds.
Definition ah-errors.H:463
#define ah_invalid_argument_if(C)
Throws std::invalid_argument if condition holds.
Definition ah-errors.H:639
Standard functor implementations and comparison objects.
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:139
Dynamic singly linked list with functional programming support.
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
Hungarian (Munkres) algorithm for optimal assignment.
Definition Hungarian.H:176
DynList< std::pair< size_t, size_t > > get_assignments() const
Get all assignment pairs.
Definition Hungarian.H:399
long get_assignment(const size_t row) const
Get the column assigned to a given row.
Definition Hungarian.H:384
const Array< long > & get_row_assignments() const noexcept
Get the row-to-column assignment array.
Definition Hungarian.H:412
Hungarian_Assignment(std::initializer_list< std::initializer_list< Cost_Type > > rows)
Construct and solve from an initializer list of rows.
Definition Hungarian.H:344
const Array< long > & get_col_assignments() const noexcept
Get the column-to-row assignment array.
Definition Hungarian.H:421
size_t cols() const noexcept
Get the original number of columns.
Definition Hungarian.H:439
size_t dimension() const noexcept
Get the padded square dimension.
Definition Hungarian.H:429
size_t rows() const noexcept
Get the original number of rows.
Definition Hungarian.H:434
void solve(const DynMatrix< Cost_Type > &cost)
Core algorithm: shortest augmenting paths with dual variables.
Definition Hungarian.H:193
Hungarian_Assignment(const DynMatrix< Cost_Type > &cost)
Construct and solve from a DynMatrix cost matrix.
Definition Hungarian.H:307
Cost_Type get_total_cost() const noexcept
Get the optimal total cost.
Definition Hungarian.H:373
__gmp_expr< T, __gmp_unary_expr< __gmp_expr< T, U >, __gmp_j0_function > > j0(const __gmp_expr< T, U > &expr)
Definition gmpfrxx.h:4099
__gmp_expr< T, __gmp_unary_expr< __gmp_expr< T, U >, __gmp_j1_function > > j1(const __gmp_expr< T, U > &expr)
Definition gmpfrxx.h:4100
Hungarian_Result< Cost_Type > hungarian_max_assignment(const DynMatrix< Cost_Type > &cost)
Compute maximum-profit assignment (free function).
Definition Hungarian.H:499
Hungarian_Result< Cost_Type > hungarian_assignment(const DynMatrix< Cost_Type > &cost)
Compute minimum-cost assignment (free function).
Definition Hungarian.H:461
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.
Divide_Conquer_DP_Result< Cost > divide_and_conquer_partition_dp(const size_t groups, const size_t n, Transition_Cost_Fn transition_cost, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize partition DP using divide-and-conquer optimization.
Result of the Hungarian assignment algorithm.
Definition Hungarian.H:115
Array< long > col_to_row
column j is assigned to row col_to_row[j]
Definition Hungarian.H:122
Cost_Type total_cost
Optimal total cost.
Definition Hungarian.H:120
Array< long > row_to_col
row i is assigned to column row_to_col[i]
Definition Hungarian.H:121
DynList< std::pair< size_t, size_t > > get_pairs() const
Get the assignment pairs, excluding dummy entries.
Definition Hungarian.H:133
size_t orig_rows
Original number of rows.
Definition Hungarian.H:123
size_t orig_cols
Original number of columns.
Definition Hungarian.H:124
Dynamic array container with automatic resizing.
Dynamic matrix with lazy allocation.