converge_optimization/assignment/
auction.rs1use super::{AssignmentProblem, AssignmentSolution, AssignmentSolver};
16use crate::{Cost, Result, SolverParams, SolverStats, SolverStatus};
17use std::time::Instant;
18
19pub struct AuctionSolver {
21 pub epsilon: i64,
23}
24
25impl Default for AuctionSolver {
26 fn default() -> Self {
27 Self { epsilon: 1 }
28 }
29}
30
31impl AssignmentSolver for AuctionSolver {
32 fn solve(
33 &self,
34 problem: &AssignmentProblem,
35 params: &SolverParams,
36 ) -> Result<AssignmentSolution> {
37 solve_auction(problem, params, self.epsilon)
38 }
39
40 fn name(&self) -> &'static str {
41 "auction"
42 }
43}
44
45pub fn solve(problem: &AssignmentProblem) -> Result<AssignmentSolution> {
47 solve_auction(problem, &SolverParams::default(), 1)
48}
49
50fn solve_auction(
51 problem: &AssignmentProblem,
52 params: &SolverParams,
53 epsilon: i64,
54) -> Result<AssignmentSolution> {
55 let start = Instant::now();
56 let n = problem.num_agents;
57
58 if n == 0 || problem.num_tasks == 0 {
59 return Err(crate::Error::invalid_input("empty problem"));
60 }
61
62 if !problem.is_square() {
63 return super::hungarian::solve(problem);
65 }
66
67 let max_cost: i64 = problem
69 .costs
70 .iter()
71 .flat_map(|row| row.iter())
72 .max()
73 .copied()
74 .unwrap_or(0);
75
76 let benefit: Vec<Vec<i64>> = problem
77 .costs
78 .iter()
79 .map(|row| row.iter().map(|&c| max_cost - c).collect())
80 .collect();
81
82 let mut prices = vec![0i64; n];
84
85 let mut assignment = vec![-1i32; n];
87
88 let mut task_owner = vec![-1i32; n];
90
91 let mut iterations = 0;
92 let mut unassigned: Vec<usize> = (0..n).collect();
93
94 while !unassigned.is_empty() {
95 iterations += 1;
96
97 if params.has_time_limit() && start.elapsed().as_secs_f64() > params.time_limit_seconds {
99 return Err(crate::Error::timeout(params.time_limit_seconds));
100 }
101
102 let agent = unassigned.pop().unwrap();
104
105 let mut best_task = 0;
107 let mut best_value = i64::MIN;
108 let mut second_best_value = i64::MIN;
109
110 for task in 0..n {
111 let value = benefit[agent][task] - prices[task];
112 if value > best_value {
113 second_best_value = best_value;
114 best_value = value;
115 best_task = task;
116 } else if value > second_best_value {
117 second_best_value = value;
118 }
119 }
120
121 if second_best_value == i64::MIN {
123 second_best_value = best_value;
124 }
125
126 let bid_increment = best_value - second_best_value + epsilon;
128
129 let prev_owner = task_owner[best_task];
131 if prev_owner >= 0 {
132 assignment[prev_owner as usize] = -1;
133 unassigned.push(prev_owner as usize);
134 }
135
136 assignment[agent] = best_task as i32;
138 task_owner[best_task] = agent as i32;
139 prices[best_task] += bid_increment;
140 }
141
142 let assignments: Vec<usize> = assignment.iter().map(|&t| t as usize).collect();
144 let total_cost: Cost = assignments
145 .iter()
146 .enumerate()
147 .map(|(agent, &task)| problem.costs[agent][task])
148 .sum();
149
150 let elapsed = start.elapsed().as_secs_f64();
151
152 Ok(AssignmentSolution {
153 assignments,
154 total_cost,
155 status: SolverStatus::Optimal,
156 stats: SolverStats {
157 solve_time_seconds: elapsed,
158 iterations,
159 objective_value: Some(total_cost as f64),
160 ..Default::default()
161 },
162 })
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn test_3x3_auction() {
171 let problem =
172 AssignmentProblem::from_costs(vec![vec![10, 5, 13], vec![3, 9, 18], vec![14, 8, 7]]);
173 let solution = solve(&problem).unwrap();
174 assert_eq!(solution.total_cost, 15);
175 }
176
177 #[test]
178 fn test_2x2_auction() {
179 let problem = AssignmentProblem::from_costs(vec![vec![1, 2], vec![3, 4]]);
180 let solution = solve(&problem).unwrap();
181 assert_eq!(solution.total_cost, 5);
182 }
183}