converge_optimization/assignment/
hungarian.rs1use super::{AssignmentProblem, AssignmentSolution, AssignmentSolver};
15use crate::{Cost, Error, Result, SolverParams, SolverStats, SolverStatus};
16use std::time::Instant;
17
18pub struct HungarianSolver;
20
21impl AssignmentSolver for HungarianSolver {
22 fn solve(
23 &self,
24 problem: &AssignmentProblem,
25 params: &SolverParams,
26 ) -> Result<AssignmentSolution> {
27 solve_hungarian(problem, params)
28 }
29
30 fn name(&self) -> &'static str {
31 "hungarian"
32 }
33}
34
35pub fn solve(problem: &AssignmentProblem) -> Result<AssignmentSolution> {
37 solve_hungarian(problem, &SolverParams::default())
38}
39
40fn solve_hungarian(
41 problem: &AssignmentProblem,
42 params: &SolverParams,
43) -> Result<AssignmentSolution> {
44 let start = Instant::now();
45
46 let n = problem.num_agents;
47 let m = problem.num_tasks;
48
49 if n == 0 || m == 0 {
50 return Err(Error::invalid_input("empty problem"));
51 }
52
53 let size = n.max(m);
55 let mut cost = vec![vec![0i64; size]; size];
56
57 let large = problem
59 .costs
60 .iter()
61 .flat_map(|row| row.iter())
62 .max()
63 .copied()
64 .unwrap_or(0)
65 .saturating_add(1);
66
67 for i in 0..size {
68 for j in 0..size {
69 cost[i][j] = if i < n && j < m {
70 problem.costs[i][j]
71 } else {
72 large };
74 }
75 }
76
77 let mut u = vec![0i64; size + 1];
79 let mut v = vec![0i64; size + 1];
80
81 let mut p = vec![0usize; size + 1];
83
84 let mut way = vec![0usize; size + 1];
86
87 let mut iterations = 0;
88
89 for i in 1..=size {
90 if params.has_time_limit() && start.elapsed().as_secs_f64() > params.time_limit_seconds {
92 return Err(Error::timeout(params.time_limit_seconds));
93 }
94
95 p[0] = i;
96 let mut j0 = 0usize; let mut minv = vec![i64::MAX; size + 1];
99 let mut used = vec![false; size + 1];
100
101 loop {
103 iterations += 1;
104 used[j0] = true;
105 let i0 = p[j0];
106 let mut delta = i64::MAX;
107 let mut j1 = 0usize;
108
109 for j in 1..=size {
110 if !used[j] {
111 let cur = cost[i0 - 1][j - 1] - u[i0] - v[j];
112 if cur < minv[j] {
113 minv[j] = cur;
114 way[j] = j0;
115 }
116 if minv[j] < delta {
117 delta = minv[j];
118 j1 = j;
119 }
120 }
121 }
122
123 for j in 0..=size {
125 if used[j] {
126 u[p[j]] += delta;
127 v[j] -= delta;
128 } else {
129 minv[j] -= delta;
130 }
131 }
132
133 j0 = j1;
134 if p[j0] == 0 {
135 break;
136 }
137 }
138
139 loop {
141 let j1 = way[j0];
142 p[j0] = p[j1];
143 j0 = j1;
144 if j0 == 0 {
145 break;
146 }
147 }
148 }
149
150 let mut assignments = vec![0usize; n];
152 let mut total_cost: Cost = 0;
153
154 for j in 1..=size {
155 if p[j] != 0 && p[j] <= n && j <= m {
156 let agent = p[j] - 1;
157 let task = j - 1;
158 assignments[agent] = task;
159 total_cost += problem.costs[agent][task];
160 }
161 }
162
163 let elapsed = start.elapsed().as_secs_f64();
164
165 Ok(AssignmentSolution {
166 assignments,
167 total_cost,
168 status: SolverStatus::Optimal,
169 stats: SolverStats {
170 solve_time_seconds: elapsed,
171 iterations,
172 objective_value: Some(total_cost as f64),
173 ..Default::default()
174 },
175 })
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn test_3x3() {
184 let problem =
185 AssignmentProblem::from_costs(vec![vec![10, 5, 13], vec![3, 9, 18], vec![14, 8, 7]]);
186 let solution = solve(&problem).unwrap();
187 assert_eq!(solution.total_cost, 15);
188 assert_eq!(solution.status, SolverStatus::Optimal);
189 }
190
191 #[test]
192 fn test_1x1() {
193 let problem = AssignmentProblem::from_costs(vec![vec![42]]);
194 let solution = solve(&problem).unwrap();
195 assert_eq!(solution.total_cost, 42);
196 assert_eq!(solution.assignments, vec![0]);
197 }
198
199 #[test]
200 fn test_2x2() {
201 let problem = AssignmentProblem::from_costs(vec![vec![1, 2], vec![3, 4]]);
202 let solution = solve(&problem).unwrap();
203 assert_eq!(solution.total_cost, 5);
205 }
206
207 #[test]
208 fn test_negative_costs() {
209 let problem = AssignmentProblem::from_costs(vec![vec![-1, -2], vec![-3, -4]]);
210 let solution = solve(&problem).unwrap();
211 assert_eq!(solution.total_cost, -5);
213 }
214
215 #[test]
216 fn test_larger() {
217 let problem = AssignmentProblem::from_costs(vec![
218 vec![7, 53, 183, 439],
219 vec![497, 383, 563, 79],
220 vec![627, 343, 773, 959],
221 vec![447, 283, 463, 29],
222 ]);
223 let solution = solve(&problem).unwrap();
224 assert!(solution.total_cost <= 892);
228 }
229}