1use crate::boundary::Boundary3D;
15use crate::geometry::Geometry3D;
16use std::sync::atomic::{AtomicBool, Ordering};
17use std::sync::Arc;
18use u_nesting_core::geometry::{Boundary, Geometry};
19use u_nesting_core::sa::{
20 NeighborhoodOperator, PermutationSolution, SaConfig, SaProblem, SaRunner, SaSolution,
21};
22use u_nesting_core::solver::Config;
23use u_nesting_core::{Placement, SolveResult};
24
25#[derive(Debug, Clone)]
27struct InstanceInfo {
28 geometry_idx: usize,
30 instance_num: usize,
32 orientation_count: usize,
34}
35
36pub struct SaPackingProblem {
38 geometries: Vec<Geometry3D>,
40 boundary: Boundary3D,
42 config: Config,
44 instances: Vec<InstanceInfo>,
46 max_orientation_count: usize,
48 cancelled: Arc<AtomicBool>,
50}
51
52impl SaPackingProblem {
53 pub fn new(
55 geometries: Vec<Geometry3D>,
56 boundary: Boundary3D,
57 config: Config,
58 cancelled: Arc<AtomicBool>,
59 ) -> Self {
60 let mut instances = Vec::new();
62 let mut max_orientation_count = 1;
63
64 for (geom_idx, geom) in geometries.iter().enumerate() {
65 let orient_count = geom.allowed_orientations().len();
66 max_orientation_count = max_orientation_count.max(orient_count);
67
68 for instance_num in 0..geom.quantity() {
69 instances.push(InstanceInfo {
70 geometry_idx: geom_idx,
71 instance_num,
72 orientation_count: orient_count,
73 });
74 }
75 }
76
77 Self {
78 geometries,
79 boundary,
80 config,
81 instances,
82 max_orientation_count,
83 cancelled,
84 }
85 }
86
87 pub fn num_instances(&self) -> usize {
89 self.instances.len()
90 }
91
92 pub fn decode(&self, solution: &PermutationSolution) -> (Vec<Placement<f64>>, f64, usize) {
94 let n = self.instances.len();
95 if n == 0 || solution.sequence.is_empty() {
96 return (Vec::new(), 0.0, 0);
97 }
98
99 let mut placements = Vec::new();
100
101 let margin = self.config.margin;
102 let spacing = self.config.spacing;
103
104 let bound_max_x = self.boundary.width() - margin;
105 let bound_max_y = self.boundary.depth() - margin;
106 let bound_max_z = self.boundary.height() - margin;
107
108 let mut current_x = margin;
110 let mut current_y = margin;
111 let mut current_z = margin;
112 let mut row_depth = 0.0_f64;
113 let mut layer_height = 0.0_f64;
114
115 let mut total_placed_volume = 0.0;
116 let mut total_placed_mass = 0.0;
117 let mut placed_count = 0;
118
119 for (seq_idx, &instance_idx) in solution.sequence.iter().enumerate() {
121 if self.cancelled.load(Ordering::Relaxed) {
122 break;
123 }
124
125 if instance_idx >= self.instances.len() {
126 continue;
127 }
128
129 let info = &self.instances[instance_idx];
130 let geom = &self.geometries[info.geometry_idx];
131
132 let orientation_idx = solution.rotations.get(seq_idx).copied().unwrap_or(0);
134 let orientation_idx = orientation_idx % info.orientation_count.max(1);
135
136 let dims = geom.dimensions_for_orientation(orientation_idx);
138 let g_width = dims.x;
139 let g_depth = dims.y;
140 let g_height = dims.z;
141
142 if let (Some(max_mass), Some(item_mass)) = (self.boundary.max_mass(), geom.mass()) {
144 if total_placed_mass + item_mass > max_mass {
145 continue;
146 }
147 }
148
149 if current_x + g_width > bound_max_x {
151 current_x = margin;
153 current_y += row_depth + spacing;
154 row_depth = 0.0;
155 }
156
157 if current_y + g_depth > bound_max_y {
159 current_x = margin;
161 current_y = margin;
162 current_z += layer_height + spacing;
163 row_depth = 0.0;
164 layer_height = 0.0;
165 }
166
167 if current_z + g_height > bound_max_z {
169 continue;
170 }
171
172 let placement = Placement::new_3d(
174 geom.id().clone(),
175 info.instance_num,
176 current_x,
177 current_y,
178 current_z,
179 0.0,
180 0.0,
181 0.0, );
183
184 placements.push(placement);
185 total_placed_volume += geom.measure();
186 if let Some(mass) = geom.mass() {
187 total_placed_mass += mass;
188 }
189 placed_count += 1;
190
191 current_x += g_width + spacing;
193 row_depth = row_depth.max(g_depth);
194 layer_height = layer_height.max(g_height);
195 }
196
197 let utilization = total_placed_volume / self.boundary.measure();
198 (placements, utilization, placed_count)
199 }
200}
201
202impl SaProblem for SaPackingProblem {
203 type Solution = PermutationSolution;
204
205 fn initial_solution<R: rand::Rng>(&self, rng: &mut R) -> Self::Solution {
206 PermutationSolution::random(self.instances.len(), self.max_orientation_count, rng)
207 }
208
209 fn neighbor<R: rand::Rng>(
210 &self,
211 solution: &Self::Solution,
212 operator: NeighborhoodOperator,
213 rng: &mut R,
214 ) -> Self::Solution {
215 match operator {
216 NeighborhoodOperator::Swap => solution.apply_swap(rng),
217 NeighborhoodOperator::Relocate => solution.apply_relocate(rng),
218 NeighborhoodOperator::Inversion => solution.apply_inversion(rng),
219 NeighborhoodOperator::Rotation => solution.apply_rotation(rng),
220 NeighborhoodOperator::Chain => solution.apply_chain(rng),
221 }
222 }
223
224 fn evaluate(&self, solution: &mut Self::Solution) {
225 let total_instances = self.instances.len();
226 let (_, utilization, placed_count) = self.decode(solution);
227
228 let placement_ratio = placed_count as f64 / total_instances.max(1) as f64;
230 let fitness = placement_ratio * 100.0 + utilization * 10.0;
231
232 solution.set_objective(fitness);
233 }
234
235 fn available_operators(&self) -> Vec<NeighborhoodOperator> {
236 if self.max_orientation_count > 1 {
237 vec![
238 NeighborhoodOperator::Swap,
239 NeighborhoodOperator::Relocate,
240 NeighborhoodOperator::Inversion,
241 NeighborhoodOperator::Rotation,
242 NeighborhoodOperator::Chain,
243 ]
244 } else {
245 vec![
246 NeighborhoodOperator::Swap,
247 NeighborhoodOperator::Relocate,
248 NeighborhoodOperator::Inversion,
249 NeighborhoodOperator::Chain,
250 ]
251 }
252 }
253
254 fn on_temperature_change(
255 &self,
256 temperature: f64,
257 iteration: u64,
258 best: &Self::Solution,
259 _current: &Self::Solution,
260 ) {
261 log::debug!(
262 "SA 3D Packing Iteration {}: temp={:.4}, best_fitness={:.4}",
263 iteration,
264 temperature,
265 best.objective()
266 );
267 }
268}
269
270pub fn run_sa_packing(
272 geometries: &[Geometry3D],
273 boundary: &Boundary3D,
274 config: &Config,
275 sa_config: SaConfig,
276 cancelled: Arc<AtomicBool>,
277) -> SolveResult<f64> {
278 let problem = SaPackingProblem::new(
279 geometries.to_vec(),
280 boundary.clone(),
281 config.clone(),
282 cancelled.clone(),
283 );
284
285 let runner = SaRunner::new(sa_config, problem);
286
287 let cancel_handle = runner.cancel_handle();
289 let cancelled_clone = cancelled.clone();
290 std::thread::spawn(move || {
291 while !cancelled_clone.load(Ordering::Relaxed) {
292 std::thread::sleep(std::time::Duration::from_millis(100));
293 }
294 cancel_handle.store(true, Ordering::Relaxed);
295 });
296
297 let sa_result = runner.run();
298
299 let problem = SaPackingProblem::new(
301 geometries.to_vec(),
302 boundary.clone(),
303 config.clone(),
304 Arc::new(AtomicBool::new(false)),
305 );
306
307 let (placements, utilization, _placed_count) = problem.decode(&sa_result.best);
308
309 let mut unplaced = Vec::new();
311 let mut placed_ids: std::collections::HashSet<String> = std::collections::HashSet::new();
312 for p in &placements {
313 placed_ids.insert(p.geometry_id.clone());
314 }
315 for geom in geometries {
316 if !placed_ids.contains(geom.id()) {
317 unplaced.push(geom.id().clone());
318 }
319 }
320
321 let mut result = SolveResult::new();
322 result.placements = placements;
323 result.unplaced = unplaced;
324 result.boundaries_used = 1;
325 result.utilization = utilization;
326 result.computation_time_ms = sa_result.elapsed.as_millis() as u64;
327 result.iterations = Some(sa_result.iterations);
328 result.best_fitness = Some(sa_result.best.objective());
329 result.fitness_history = Some(sa_result.history);
330 result.strategy = Some("SimulatedAnnealing".to_string());
331 result.cancelled = cancelled.load(Ordering::Relaxed);
332 result.target_reached = sa_result.target_reached;
333
334 result
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340
341 #[test]
342 fn test_sa_packing_basic() {
343 let geometries = vec![
344 Geometry3D::new("B1", 20.0, 20.0, 20.0).with_quantity(2),
345 Geometry3D::new("B2", 15.0, 15.0, 15.0).with_quantity(2),
346 ];
347
348 let boundary = Boundary3D::new(100.0, 80.0, 50.0);
349 let config = Config::default();
350 let sa_config = SaConfig::default()
351 .with_initial_temp(100.0)
352 .with_final_temp(0.1)
353 .with_cooling_rate(0.9)
354 .with_iterations_per_temp(20)
355 .with_max_iterations(500);
356
357 let result = run_sa_packing(
358 &geometries,
359 &boundary,
360 &config,
361 sa_config,
362 Arc::new(AtomicBool::new(false)),
363 );
364
365 assert!(result.utilization > 0.0);
366 assert!(!result.placements.is_empty());
367 assert_eq!(result.strategy, Some("SimulatedAnnealing".to_string()));
368 }
369
370 #[test]
371 fn test_sa_packing_all_placed() {
372 let geometries = vec![Geometry3D::new("B1", 20.0, 20.0, 20.0).with_quantity(4)];
373
374 let boundary = Boundary3D::new(100.0, 100.0, 100.0);
375 let config = Config::default();
376 let sa_config = SaConfig::default()
377 .with_initial_temp(100.0)
378 .with_final_temp(0.1)
379 .with_max_iterations(1000);
380
381 let result = run_sa_packing(
382 &geometries,
383 &boundary,
384 &config,
385 sa_config,
386 Arc::new(AtomicBool::new(false)),
387 );
388
389 assert_eq!(result.placements.len(), 4);
391 assert!(result.unplaced.is_empty());
392 }
393
394 #[test]
395 fn test_sa_packing_with_orientations() {
396 use crate::geometry::OrientationConstraint;
397
398 let geometries = vec![Geometry3D::new("B1", 50.0, 10.0, 10.0)
400 .with_quantity(3)
401 .with_orientation(OrientationConstraint::Any)];
402
403 let boundary = Boundary3D::new(60.0, 60.0, 60.0);
404 let config = Config::default();
405 let sa_config = SaConfig::default()
406 .with_initial_temp(100.0)
407 .with_final_temp(0.1)
408 .with_max_iterations(500);
409
410 let result = run_sa_packing(
411 &geometries,
412 &boundary,
413 &config,
414 sa_config,
415 Arc::new(AtomicBool::new(false)),
416 );
417
418 assert!(result.utilization > 0.0);
419 assert!(!result.placements.is_empty());
420 }
421
422 #[test]
423 fn test_sa_problem_decode() {
424 use rand::prelude::*;
425
426 let geometries = vec![Geometry3D::new("B1", 20.0, 20.0, 20.0).with_quantity(2)];
427
428 let boundary = Boundary3D::new(100.0, 100.0, 100.0);
429 let config = Config::default();
430 let cancelled = Arc::new(AtomicBool::new(false));
431
432 let problem = SaPackingProblem::new(geometries, boundary, config, cancelled);
433
434 assert_eq!(problem.num_instances(), 2);
435
436 let mut rng = thread_rng();
438 let solution = PermutationSolution::random(problem.num_instances(), 1, &mut rng);
439 let (placements, utilization, placed_count) = problem.decode(&solution);
440
441 assert!(placed_count >= 1);
443 assert_eq!(placements.len(), placed_count);
444 if placed_count > 0 {
445 assert!(utilization > 0.0);
446 }
447 }
448
449 #[test]
450 fn test_sa_packing_mass_constraint() {
451 let geometries = vec![Geometry3D::new("B1", 20.0, 20.0, 20.0)
452 .with_quantity(10)
453 .with_mass(100.0)];
454
455 let boundary = Boundary3D::new(100.0, 100.0, 100.0).with_max_mass(350.0);
456 let config = Config::default();
457 let sa_config = SaConfig::default()
458 .with_initial_temp(100.0)
459 .with_final_temp(0.1)
460 .with_max_iterations(500);
461
462 let result = run_sa_packing(
463 &geometries,
464 &boundary,
465 &config,
466 sa_config,
467 Arc::new(AtomicBool::new(false)),
468 );
469
470 assert!(result.placements.len() <= 3);
472 }
473}