1use crate::boundary::Boundary2D;
15use crate::clamp_placement_to_boundary;
16use crate::geometry::Geometry2D;
17use crate::nfp::{compute_ifp, compute_nfp, find_bottom_left_placement, Nfp, PlacedGeometry};
18use std::sync::atomic::{AtomicBool, Ordering};
19use std::sync::Arc;
20use u_nesting_core::geometry::{Boundary, Geometry};
21use u_nesting_core::sa::{
22 NeighborhoodOperator, PermutationSolution, SaConfig, SaProblem, SaRunner, SaSolution,
23};
24use u_nesting_core::solver::Config;
25use u_nesting_core::{Placement, SolveResult};
26
27#[derive(Debug, Clone)]
29struct InstanceInfo {
30 geometry_idx: usize,
32 instance_num: usize,
34}
35
36pub struct SaNestingProblem {
38 geometries: Vec<Geometry2D>,
40 boundary: Boundary2D,
42 config: Config,
44 instances: Vec<InstanceInfo>,
46 rotation_angles: Vec<Vec<f64>>,
48 max_rotation_options: usize,
50 cancelled: Arc<AtomicBool>,
52}
53
54impl SaNestingProblem {
55 pub fn new(
57 geometries: Vec<Geometry2D>,
58 boundary: Boundary2D,
59 config: Config,
60 cancelled: Arc<AtomicBool>,
61 ) -> Self {
62 let mut instances = Vec::new();
64 let mut rotation_angles = Vec::new();
65 let mut max_rotation_options = 1;
66
67 for (geom_idx, geom) in geometries.iter().enumerate() {
68 let angles = geom.rotations();
70 let angles = if angles.is_empty() { vec![0.0] } else { angles };
71 max_rotation_options = max_rotation_options.max(angles.len());
72 rotation_angles.push(angles);
73
74 for instance_num in 0..geom.quantity() {
76 instances.push(InstanceInfo {
77 geometry_idx: geom_idx,
78 instance_num,
79 });
80 }
81 }
82
83 Self {
84 geometries,
85 boundary,
86 config,
87 instances,
88 rotation_angles,
89 max_rotation_options,
90 cancelled,
91 }
92 }
93
94 pub fn num_instances(&self) -> usize {
96 self.instances.len()
97 }
98
99 pub fn decode(&self, solution: &PermutationSolution) -> (Vec<Placement<f64>>, f64, usize) {
101 let n = self.instances.len();
102 if n == 0 || solution.sequence.is_empty() {
103 return (Vec::new(), 0.0, 0);
104 }
105
106 let mut placements = Vec::new();
107 let mut placed_geometries: Vec<PlacedGeometry> = Vec::new();
108 let mut total_placed_area = 0.0;
109 let mut placed_count = 0;
110
111 let margin = self.config.margin;
112 let spacing = self.config.spacing;
113
114 let boundary_polygon = self.get_boundary_polygon_with_margin(margin);
116
117 let sample_step = self.compute_sample_step();
119
120 for (seq_idx, &instance_idx) in solution.sequence.iter().enumerate() {
122 if self.cancelled.load(Ordering::Relaxed) {
123 break;
124 }
125
126 if instance_idx >= self.instances.len() {
127 continue;
128 }
129
130 let info = &self.instances[instance_idx];
131 let geom = &self.geometries[info.geometry_idx];
132
133 let rotation_idx = solution.rotations.get(seq_idx).copied().unwrap_or(0);
135 let num_rotations = self
136 .rotation_angles
137 .get(info.geometry_idx)
138 .map(|a| a.len())
139 .unwrap_or(1);
140
141 let rotation_angle = self
142 .rotation_angles
143 .get(info.geometry_idx)
144 .and_then(|angles| angles.get(rotation_idx % num_rotations))
145 .copied()
146 .unwrap_or(0.0);
147
148 let ifp = match compute_ifp(&boundary_polygon, geom, rotation_angle) {
150 Ok(ifp) => ifp,
151 Err(_) => continue,
152 };
153
154 if ifp.is_empty() {
155 continue;
156 }
157
158 let mut nfps: Vec<Nfp> = Vec::new();
160 for placed in &placed_geometries {
161 let placed_exterior = placed.translated_exterior();
162 let placed_geom = Geometry2D::new(format!("_placed_{}", placed.geometry.id()))
163 .with_polygon(placed_exterior);
164
165 if let Ok(nfp) = compute_nfp(&placed_geom, geom, rotation_angle) {
166 let expanded = expand_nfp(&nfp, spacing);
167 nfps.push(expanded);
168 }
169 }
170
171 let ifp_shrunk = shrink_ifp(&ifp, spacing);
173
174 let nfp_refs: Vec<&Nfp> = nfps.iter().collect();
178 if let Some((x, y)) = find_bottom_left_placement(&ifp_shrunk, &nfp_refs, sample_step) {
179 let geom_aabb = geom.aabb_at_rotation(rotation_angle);
181 let boundary_aabb = self.boundary.aabb();
182
183 if let Some((clamped_x, clamped_y)) =
184 clamp_placement_to_boundary(x, y, geom_aabb, boundary_aabb)
185 {
186 let placement = Placement::new_2d(
187 geom.id().clone(),
188 info.instance_num,
189 clamped_x,
190 clamped_y,
191 rotation_angle,
192 );
193
194 placements.push(placement);
195 placed_geometries.push(PlacedGeometry::new(
196 geom.clone(),
197 (clamped_x, clamped_y),
198 rotation_angle,
199 ));
200 total_placed_area += geom.measure();
201 placed_count += 1;
202 }
203 }
204 }
205
206 let utilization = total_placed_area / self.boundary.measure();
207 (placements, utilization, placed_count)
208 }
209
210 fn get_boundary_polygon_with_margin(&self, margin: f64) -> Vec<(f64, f64)> {
212 let (b_min, b_max) = self.boundary.aabb();
213 vec![
214 (b_min[0] + margin, b_min[1] + margin),
215 (b_max[0] - margin, b_min[1] + margin),
216 (b_max[0] - margin, b_max[1] - margin),
217 (b_min[0] + margin, b_max[1] - margin),
218 ]
219 }
220
221 fn compute_sample_step(&self) -> f64 {
223 if self.geometries.is_empty() {
224 return 1.0;
225 }
226
227 let mut min_dim = f64::INFINITY;
228 for geom in &self.geometries {
229 let (g_min, g_max) = geom.aabb();
230 let width = g_max[0] - g_min[0];
231 let height = g_max[1] - g_min[1];
232 min_dim = min_dim.min(width).min(height);
233 }
234
235 (min_dim / 4.0).clamp(0.5, 10.0)
236 }
237}
238
239impl SaProblem for SaNestingProblem {
240 type Solution = PermutationSolution;
241
242 fn initial_solution<R: rand::Rng>(&self, rng: &mut R) -> Self::Solution {
243 PermutationSolution::random(self.instances.len(), self.max_rotation_options, rng)
244 }
245
246 fn neighbor<R: rand::Rng>(
247 &self,
248 solution: &Self::Solution,
249 operator: NeighborhoodOperator,
250 rng: &mut R,
251 ) -> Self::Solution {
252 match operator {
253 NeighborhoodOperator::Swap => solution.apply_swap(rng),
254 NeighborhoodOperator::Relocate => solution.apply_relocate(rng),
255 NeighborhoodOperator::Inversion => solution.apply_inversion(rng),
256 NeighborhoodOperator::Rotation => solution.apply_rotation(rng),
257 NeighborhoodOperator::Chain => solution.apply_chain(rng),
258 }
259 }
260
261 fn evaluate(&self, solution: &mut Self::Solution) {
262 let total_instances = self.instances.len();
263 let (_, utilization, placed_count) = self.decode(solution);
264
265 let placement_ratio = placed_count as f64 / total_instances.max(1) as f64;
267
268 let fitness = placement_ratio * 100.0 + utilization * 10.0;
271
272 solution.set_objective(fitness);
273 }
274
275 fn available_operators(&self) -> Vec<NeighborhoodOperator> {
276 if self.max_rotation_options > 1 {
277 vec![
278 NeighborhoodOperator::Swap,
279 NeighborhoodOperator::Relocate,
280 NeighborhoodOperator::Inversion,
281 NeighborhoodOperator::Rotation,
282 NeighborhoodOperator::Chain,
283 ]
284 } else {
285 vec![
286 NeighborhoodOperator::Swap,
287 NeighborhoodOperator::Relocate,
288 NeighborhoodOperator::Inversion,
289 NeighborhoodOperator::Chain,
290 ]
291 }
292 }
293
294 fn on_temperature_change(
295 &self,
296 temperature: f64,
297 iteration: u64,
298 best: &Self::Solution,
299 _current: &Self::Solution,
300 ) {
301 log::debug!(
302 "SA Iteration {}: temp={:.4}, best_fitness={:.4}",
303 iteration,
304 temperature,
305 best.objective()
306 );
307 }
308}
309
310fn expand_nfp(nfp: &Nfp, spacing: f64) -> Nfp {
312 if spacing <= 0.0 {
313 return nfp.clone();
314 }
315
316 let expanded_polygons: Vec<Vec<(f64, f64)>> = nfp
317 .polygons
318 .iter()
319 .map(|polygon| {
320 let (cx, cy) = polygon_centroid(polygon);
321 polygon
322 .iter()
323 .map(|&(x, y)| {
324 let dx = x - cx;
325 let dy = y - cy;
326 let dist = (dx * dx + dy * dy).sqrt();
327 if dist > 1e-10 {
328 let scale = (dist + spacing) / dist;
329 (cx + dx * scale, cy + dy * scale)
330 } else {
331 (x, y)
332 }
333 })
334 .collect()
335 })
336 .collect();
337
338 Nfp::from_polygons(expanded_polygons)
339}
340
341fn shrink_ifp(ifp: &Nfp, spacing: f64) -> Nfp {
343 if spacing <= 0.0 {
344 return ifp.clone();
345 }
346
347 let shrunk_polygons: Vec<Vec<(f64, f64)>> = ifp
348 .polygons
349 .iter()
350 .filter_map(|polygon| {
351 let (cx, cy) = polygon_centroid(polygon);
352 let shrunk: Vec<(f64, f64)> = polygon
353 .iter()
354 .map(|&(x, y)| {
355 let dx = x - cx;
356 let dy = y - cy;
357 let dist = (dx * dx + dy * dy).sqrt();
358 if dist > spacing + 1e-10 {
359 let scale = (dist - spacing) / dist;
360 (cx + dx * scale, cy + dy * scale)
361 } else {
362 (cx, cy)
363 }
364 })
365 .collect();
366
367 if shrunk.len() >= 3 {
368 Some(shrunk)
369 } else {
370 None
371 }
372 })
373 .collect();
374
375 Nfp::from_polygons(shrunk_polygons)
376}
377
378fn polygon_centroid(polygon: &[(f64, f64)]) -> (f64, f64) {
380 if polygon.is_empty() {
381 return (0.0, 0.0);
382 }
383
384 let sum: (f64, f64) = polygon
385 .iter()
386 .fold((0.0, 0.0), |acc, &(x, y)| (acc.0 + x, acc.1 + y));
387 let n = polygon.len() as f64;
388 (sum.0 / n, sum.1 / n)
389}
390
391pub fn run_sa_nesting(
393 geometries: &[Geometry2D],
394 boundary: &Boundary2D,
395 config: &Config,
396 sa_config: SaConfig,
397 cancelled: Arc<AtomicBool>,
398) -> SolveResult<f64> {
399 let problem = SaNestingProblem::new(
400 geometries.to_vec(),
401 boundary.clone(),
402 config.clone(),
403 cancelled.clone(),
404 );
405
406 let runner = SaRunner::new(sa_config, problem);
407
408 let cancel_handle = runner.cancel_handle();
410 let cancelled_clone = cancelled.clone();
411 std::thread::spawn(move || {
412 while !cancelled_clone.load(Ordering::Relaxed) {
413 std::thread::sleep(std::time::Duration::from_millis(100));
414 }
415 cancel_handle.store(true, Ordering::Relaxed);
416 });
417
418 let sa_result = runner.run();
419
420 let problem = SaNestingProblem::new(
422 geometries.to_vec(),
423 boundary.clone(),
424 config.clone(),
425 Arc::new(AtomicBool::new(false)),
426 );
427
428 let (placements, utilization, _placed_count) = problem.decode(&sa_result.best);
429
430 let mut unplaced = Vec::new();
432 let mut placed_ids: std::collections::HashSet<String> = std::collections::HashSet::new();
433 for p in &placements {
434 placed_ids.insert(p.geometry_id.clone());
435 }
436 for geom in geometries {
437 if !placed_ids.contains(geom.id()) {
438 unplaced.push(geom.id().clone());
439 }
440 }
441
442 let mut result = SolveResult::new();
443 result.placements = placements;
444 result.unplaced = unplaced;
445 result.boundaries_used = 1;
446 result.utilization = utilization;
447 result.computation_time_ms = sa_result.elapsed.as_millis() as u64;
448 result.iterations = Some(sa_result.iterations);
449 result.best_fitness = Some(sa_result.best.objective());
450 result.fitness_history = Some(sa_result.history);
451 result.strategy = Some("SimulatedAnnealing".to_string());
452 result.cancelled = cancelled.load(Ordering::Relaxed);
453 result.target_reached = sa_result.target_reached;
454
455 result
456}
457
458#[cfg(test)]
459mod tests {
460 use super::*;
461
462 #[test]
463 fn test_sa_nesting_basic() {
464 let geometries = vec![
465 Geometry2D::rectangle("R1", 20.0, 10.0).with_quantity(2),
466 Geometry2D::rectangle("R2", 15.0, 15.0).with_quantity(2),
467 ];
468
469 let boundary = Boundary2D::rectangle(100.0, 50.0);
470 let config = Config::default();
471 let sa_config = SaConfig::default()
472 .with_initial_temp(100.0)
473 .with_final_temp(0.1)
474 .with_cooling_rate(0.9)
475 .with_iterations_per_temp(20)
476 .with_max_iterations(500);
477
478 let result = run_sa_nesting(
479 &geometries,
480 &boundary,
481 &config,
482 sa_config,
483 Arc::new(AtomicBool::new(false)),
484 );
485
486 assert!(result.utilization > 0.0);
487 assert!(!result.placements.is_empty());
488 assert_eq!(result.strategy, Some("SimulatedAnnealing".to_string()));
489 }
490
491 #[test]
492 fn test_sa_nesting_all_placed() {
493 let geometries = vec![Geometry2D::rectangle("R1", 20.0, 20.0).with_quantity(4)];
494
495 let boundary = Boundary2D::rectangle(100.0, 100.0);
496 let config = Config::default();
497 let sa_config = SaConfig::default()
498 .with_initial_temp(100.0)
499 .with_final_temp(0.1)
500 .with_max_iterations(1000);
501
502 let result = run_sa_nesting(
503 &geometries,
504 &boundary,
505 &config,
506 sa_config,
507 Arc::new(AtomicBool::new(false)),
508 );
509
510 assert_eq!(result.placements.len(), 4);
512 assert!(result.unplaced.is_empty());
513 }
514
515 #[test]
516 fn test_sa_nesting_with_rotation() {
517 let geometries = vec![Geometry2D::rectangle("R1", 30.0, 10.0)
518 .with_quantity(3)
519 .with_rotations(vec![0.0, 90.0])];
520
521 let boundary = Boundary2D::rectangle(50.0, 50.0);
522 let config = Config::default();
523 let sa_config = SaConfig::default()
524 .with_initial_temp(100.0)
525 .with_final_temp(0.1)
526 .with_max_iterations(500);
527
528 let result = run_sa_nesting(
529 &geometries,
530 &boundary,
531 &config,
532 sa_config,
533 Arc::new(AtomicBool::new(false)),
534 );
535
536 assert!(result.utilization > 0.0);
537 assert!(!result.placements.is_empty());
538 }
539
540 #[test]
541 fn test_sa_problem_decode() {
542 use rand::prelude::*;
543
544 let geometries = vec![Geometry2D::rectangle("R1", 20.0, 10.0).with_quantity(2)];
545
546 let boundary = Boundary2D::rectangle(100.0, 50.0);
547 let config = Config::default();
548 let cancelled = Arc::new(AtomicBool::new(false));
549
550 let problem = SaNestingProblem::new(geometries, boundary, config, cancelled);
551
552 assert_eq!(problem.num_instances(), 2);
553
554 let mut rng = thread_rng();
556 let solution = PermutationSolution::random(problem.num_instances(), 1, &mut rng);
557 let (placements, utilization, placed_count) = problem.decode(&solution);
558
559 assert!(placed_count >= 1);
561 assert_eq!(placements.len(), placed_count);
562 if placed_count > 0 {
563 assert!(utilization > 0.0);
564 }
565 }
566}