1use crate::boundary::Boundary3D;
15use crate::geometry::Geometry3D;
16use crate::packing_utils::{
17 build_instances, build_unplaced_list, layer_place_items, packing_fitness, InstanceInfo,
18 PlacementItem,
19};
20use std::sync::atomic::{AtomicBool, Ordering};
21use std::sync::Arc;
22use u_nesting_core::sa::{
23 NeighborhoodOperator, PermutationSolution, SaConfig, SaProblem, SaRunner, SaSolution,
24};
25use u_nesting_core::solver::Config;
26use u_nesting_core::SolveResult;
27
28pub struct SaPackingProblem {
30 geometries: Vec<Geometry3D>,
32 boundary: Boundary3D,
34 config: Config,
36 instances: Vec<InstanceInfo>,
38 max_orientation_count: usize,
40 cancelled: Arc<AtomicBool>,
42}
43
44impl SaPackingProblem {
45 pub fn new(
47 geometries: Vec<Geometry3D>,
48 boundary: Boundary3D,
49 config: Config,
50 cancelled: Arc<AtomicBool>,
51 ) -> Self {
52 let instances = build_instances(&geometries);
53 let max_orientation_count = instances
54 .iter()
55 .map(|i| i.orientation_count)
56 .max()
57 .unwrap_or(1);
58
59 Self {
60 geometries,
61 boundary,
62 config,
63 instances,
64 max_orientation_count,
65 cancelled,
66 }
67 }
68
69 pub fn num_instances(&self) -> usize {
71 self.instances.len()
72 }
73
74 pub fn decode(
76 &self,
77 solution: &PermutationSolution,
78 ) -> (Vec<u_nesting_core::Placement<f64>>, f64, usize) {
79 let n = self.instances.len();
80 if n == 0 || solution.sequence.is_empty() {
81 return (Vec::new(), 0.0, 0);
82 }
83
84 let items: Vec<PlacementItem> = solution
85 .sequence
86 .iter()
87 .enumerate()
88 .map(|(seq_idx, &instance_idx)| PlacementItem {
89 instance_idx,
90 orientation_idx: solution.rotations.get(seq_idx).copied().unwrap_or(0),
91 })
92 .collect();
93
94 let result = layer_place_items(
95 &items,
96 &self.instances,
97 &self.geometries,
98 &self.boundary,
99 &self.config,
100 &self.cancelled,
101 );
102 (result.placements, result.utilization, result.placed_count)
103 }
104}
105
106impl SaProblem for SaPackingProblem {
107 type Solution = PermutationSolution;
108
109 fn initial_solution<R: rand::Rng>(&self, rng: &mut R) -> Self::Solution {
110 PermutationSolution::random(self.instances.len(), self.max_orientation_count, rng)
111 }
112
113 fn neighbor<R: rand::Rng>(
114 &self,
115 solution: &Self::Solution,
116 operator: NeighborhoodOperator,
117 rng: &mut R,
118 ) -> Self::Solution {
119 match operator {
120 NeighborhoodOperator::Swap => solution.apply_swap(rng),
121 NeighborhoodOperator::Relocate => solution.apply_relocate(rng),
122 NeighborhoodOperator::Inversion => solution.apply_inversion(rng),
123 NeighborhoodOperator::Rotation => solution.apply_rotation(rng),
124 NeighborhoodOperator::Chain => solution.apply_chain(rng),
125 }
126 }
127
128 fn evaluate(&self, solution: &mut Self::Solution) {
129 let (_, utilization, placed_count) = self.decode(solution);
130 let fitness = packing_fitness(placed_count, self.instances.len(), utilization);
131 solution.set_objective(fitness);
132 }
133
134 fn available_operators(&self) -> Vec<NeighborhoodOperator> {
135 if self.max_orientation_count > 1 {
136 vec![
137 NeighborhoodOperator::Swap,
138 NeighborhoodOperator::Relocate,
139 NeighborhoodOperator::Inversion,
140 NeighborhoodOperator::Rotation,
141 NeighborhoodOperator::Chain,
142 ]
143 } else {
144 vec![
145 NeighborhoodOperator::Swap,
146 NeighborhoodOperator::Relocate,
147 NeighborhoodOperator::Inversion,
148 NeighborhoodOperator::Chain,
149 ]
150 }
151 }
152
153 fn on_temperature_change(
154 &self,
155 temperature: f64,
156 iteration: u64,
157 best: &Self::Solution,
158 _current: &Self::Solution,
159 ) {
160 log::debug!(
161 "SA 3D Packing Iteration {}: temp={:.4}, best_fitness={:.4}",
162 iteration,
163 temperature,
164 best.objective()
165 );
166 }
167}
168
169pub fn run_sa_packing(
171 geometries: &[Geometry3D],
172 boundary: &Boundary3D,
173 config: &Config,
174 sa_config: SaConfig,
175 cancelled: Arc<AtomicBool>,
176) -> SolveResult<f64> {
177 let problem = SaPackingProblem::new(
178 geometries.to_vec(),
179 boundary.clone(),
180 config.clone(),
181 cancelled.clone(),
182 );
183
184 let runner = SaRunner::new(sa_config, problem);
185
186 let cancel_handle = runner.cancel_handle();
188 let cancelled_clone = cancelled.clone();
189 std::thread::spawn(move || {
190 while !cancelled_clone.load(Ordering::Relaxed) {
191 std::thread::sleep(std::time::Duration::from_millis(100));
192 }
193 cancel_handle.store(true, Ordering::Relaxed);
194 });
195
196 let sa_result = runner.run();
197
198 let problem = SaPackingProblem::new(
200 geometries.to_vec(),
201 boundary.clone(),
202 config.clone(),
203 Arc::new(AtomicBool::new(false)),
204 );
205
206 let (placements, utilization, _placed_count) = problem.decode(&sa_result.best);
207 let unplaced = build_unplaced_list(&placements, geometries);
208
209 let mut result = SolveResult::new();
210 result.placements = placements;
211 result.unplaced = unplaced;
212 result.boundaries_used = 1;
213 result.utilization = utilization;
214 result.computation_time_ms = sa_result.elapsed.as_millis() as u64;
215 result.iterations = Some(sa_result.iterations);
216 result.best_fitness = Some(sa_result.best.objective());
217 result.fitness_history = Some(sa_result.history);
218 result.strategy = Some("SimulatedAnnealing".to_string());
219 result.cancelled = cancelled.load(Ordering::Relaxed);
220 result.target_reached = sa_result.target_reached;
221
222 result
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
230 fn test_sa_packing_basic() {
231 let geometries = vec![
232 Geometry3D::new("B1", 20.0, 20.0, 20.0).with_quantity(2),
233 Geometry3D::new("B2", 15.0, 15.0, 15.0).with_quantity(2),
234 ];
235
236 let boundary = Boundary3D::new(100.0, 80.0, 50.0);
237 let config = Config::default();
238 let sa_config = SaConfig::default()
239 .with_initial_temp(100.0)
240 .with_final_temp(0.1)
241 .with_cooling_rate(0.9)
242 .with_iterations_per_temp(20)
243 .with_max_iterations(500);
244
245 let result = run_sa_packing(
246 &geometries,
247 &boundary,
248 &config,
249 sa_config,
250 Arc::new(AtomicBool::new(false)),
251 );
252
253 assert!(result.utilization > 0.0);
254 assert!(!result.placements.is_empty());
255 assert_eq!(result.strategy, Some("SimulatedAnnealing".to_string()));
256 }
257
258 #[test]
259 fn test_sa_packing_all_placed() {
260 let geometries = vec![Geometry3D::new("B1", 20.0, 20.0, 20.0).with_quantity(4)];
261
262 let boundary = Boundary3D::new(100.0, 100.0, 100.0);
263 let config = Config::default();
264 let sa_config = SaConfig::default()
265 .with_initial_temp(100.0)
266 .with_final_temp(0.1)
267 .with_max_iterations(1000);
268
269 let result = run_sa_packing(
270 &geometries,
271 &boundary,
272 &config,
273 sa_config,
274 Arc::new(AtomicBool::new(false)),
275 );
276
277 assert_eq!(result.placements.len(), 4);
279 assert!(result.unplaced.is_empty());
280 }
281
282 #[test]
283 fn test_sa_packing_with_orientations() {
284 use crate::geometry::OrientationConstraint;
285
286 let geometries = vec![Geometry3D::new("B1", 50.0, 10.0, 10.0)
288 .with_quantity(3)
289 .with_orientation(OrientationConstraint::Any)];
290
291 let boundary = Boundary3D::new(60.0, 60.0, 60.0);
292 let config = Config::default();
293 let sa_config = SaConfig::default()
294 .with_initial_temp(100.0)
295 .with_final_temp(0.1)
296 .with_max_iterations(500);
297
298 let result = run_sa_packing(
299 &geometries,
300 &boundary,
301 &config,
302 sa_config,
303 Arc::new(AtomicBool::new(false)),
304 );
305
306 assert!(result.utilization > 0.0);
307 assert!(!result.placements.is_empty());
308 }
309
310 #[test]
311 fn test_sa_problem_decode() {
312 let geometries = vec![Geometry3D::new("B1", 20.0, 20.0, 20.0).with_quantity(2)];
313
314 let boundary = Boundary3D::new(100.0, 100.0, 100.0);
315 let config = Config::default();
316 let cancelled = Arc::new(AtomicBool::new(false));
317
318 let problem = SaPackingProblem::new(geometries, boundary, config, cancelled);
319
320 assert_eq!(problem.num_instances(), 2);
321
322 let mut rng = rand::rng();
324 let solution = PermutationSolution::random(problem.num_instances(), 1, &mut rng);
325 let (placements, utilization, placed_count) = problem.decode(&solution);
326
327 assert!(placed_count >= 1);
329 assert_eq!(placements.len(), placed_count);
330 if placed_count > 0 {
331 assert!(utilization > 0.0);
332 }
333 }
334
335 #[test]
336 fn test_sa_packing_mass_constraint() {
337 let geometries = vec![Geometry3D::new("B1", 20.0, 20.0, 20.0)
338 .with_quantity(10)
339 .with_mass(100.0)];
340
341 let boundary = Boundary3D::new(100.0, 100.0, 100.0).with_max_mass(350.0);
342 let config = Config::default();
343 let sa_config = SaConfig::default()
344 .with_initial_temp(100.0)
345 .with_final_temp(0.1)
346 .with_max_iterations(500);
347
348 let result = run_sa_packing(
349 &geometries,
350 &boundary,
351 &config,
352 sa_config,
353 Arc::new(AtomicBool::new(false)),
354 );
355
356 assert!(result.placements.len() <= 3);
358 }
359}