1use crate::boundary::Boundary2D;
15use crate::clamp_placement_to_boundary;
16use crate::geometry::Geometry2D;
17use crate::nfp::{
18 compute_ifp, compute_nfp, find_bottom_left_placement, verify_no_overlap, Nfp, PlacedGeometry,
19};
20use std::sync::atomic::{AtomicBool, Ordering};
21use std::sync::Arc;
22use u_nesting_core::geometry::{Boundary, Geometry};
23use u_nesting_core::sa::{
24 NeighborhoodOperator, PermutationSolution, SaConfig, SaProblem, SaRunner, SaSolution,
25};
26use u_nesting_core::solver::Config;
27use u_nesting_core::{Placement, SolveResult};
28
29use crate::placement_utils::{expand_nfp, nesting_fitness, shrink_ifp, InstanceInfo};
30
31pub struct SaNestingProblem {
33 geometries: Vec<Geometry2D>,
35 boundary: Boundary2D,
37 config: Config,
39 instances: Vec<InstanceInfo>,
41 rotation_angles: Vec<Vec<f64>>,
43 max_rotation_options: usize,
45 cancelled: Arc<AtomicBool>,
47}
48
49impl SaNestingProblem {
50 pub fn new(
52 geometries: Vec<Geometry2D>,
53 boundary: Boundary2D,
54 config: Config,
55 cancelled: Arc<AtomicBool>,
56 ) -> Self {
57 let mut instances = Vec::new();
59 let mut rotation_angles = Vec::new();
60 let mut max_rotation_options = 1;
61
62 for (geom_idx, geom) in geometries.iter().enumerate() {
63 let angles = geom.rotations();
65 let angles = if angles.is_empty() { vec![0.0] } else { angles };
66 max_rotation_options = max_rotation_options.max(angles.len());
67 rotation_angles.push(angles);
68
69 for instance_num in 0..geom.quantity() {
71 instances.push(InstanceInfo {
72 geometry_idx: geom_idx,
73 instance_num,
74 });
75 }
76 }
77
78 Self {
79 geometries,
80 boundary,
81 config,
82 instances,
83 rotation_angles,
84 max_rotation_options,
85 cancelled,
86 }
87 }
88
89 pub fn num_instances(&self) -> usize {
91 self.instances.len()
92 }
93
94 pub fn decode(&self, solution: &PermutationSolution) -> (Vec<Placement<f64>>, f64, usize) {
96 let n = self.instances.len();
97 if n == 0 || solution.sequence.is_empty() {
98 return (Vec::new(), 0.0, 0);
99 }
100
101 let mut placements = Vec::new();
102 let mut placed_geometries: Vec<PlacedGeometry> = Vec::new();
103 let mut total_placed_area = 0.0;
104 let mut placed_count = 0;
105
106 let margin = self.config.margin;
107 let spacing = self.config.spacing;
108
109 let boundary_polygon = self.get_boundary_polygon_with_margin(margin);
111
112 let sample_step = self.compute_sample_step();
114
115 for (seq_idx, &instance_idx) in solution.sequence.iter().enumerate() {
117 if self.cancelled.load(Ordering::Relaxed) {
118 break;
119 }
120
121 if instance_idx >= self.instances.len() {
122 continue;
123 }
124
125 let info = &self.instances[instance_idx];
126 let geom = &self.geometries[info.geometry_idx];
127
128 let rotation_idx = solution.rotations.get(seq_idx).copied().unwrap_or(0);
130 let num_rotations = self
131 .rotation_angles
132 .get(info.geometry_idx)
133 .map(|a| a.len())
134 .unwrap_or(1);
135
136 let rotation_angle = self
137 .rotation_angles
138 .get(info.geometry_idx)
139 .and_then(|angles| angles.get(rotation_idx % num_rotations))
140 .copied()
141 .unwrap_or(0.0);
142
143 let ifp = match compute_ifp(&boundary_polygon, geom, rotation_angle) {
145 Ok(ifp) => ifp,
146 Err(_) => continue,
147 };
148
149 if ifp.is_empty() {
150 continue;
151 }
152
153 let mut nfps: Vec<Nfp> = Vec::new();
155 for placed in &placed_geometries {
156 let placed_exterior = placed.translated_exterior();
157 let placed_geom = Geometry2D::new(format!("_placed_{}", placed.geometry.id()))
158 .with_polygon(placed_exterior);
159
160 if let Ok(nfp) = compute_nfp(&placed_geom, geom, rotation_angle) {
161 let expanded = expand_nfp(&nfp, spacing);
162 nfps.push(expanded);
163 }
164 }
165
166 let ifp_shrunk = shrink_ifp(&ifp, spacing);
168
169 let nfp_refs: Vec<&Nfp> = nfps.iter().collect();
173 if let Some((x, y)) = find_bottom_left_placement(&ifp_shrunk, &nfp_refs, sample_step) {
174 let geom_aabb = geom.aabb_at_rotation(rotation_angle);
176 let boundary_aabb = self.boundary.aabb();
177
178 if let Some((clamped_x, clamped_y)) =
179 clamp_placement_to_boundary(x, y, geom_aabb, boundary_aabb)
180 {
181 let was_clamped = (clamped_x - x).abs() > 1e-6 || (clamped_y - y).abs() > 1e-6;
184 if was_clamped {
185 if !verify_no_overlap(
187 geom,
188 (clamped_x, clamped_y),
189 rotation_angle,
190 &placed_geometries,
191 ) {
192 continue; }
194 }
195
196 let placement = Placement::new_2d(
197 geom.id().clone(),
198 info.instance_num,
199 clamped_x,
200 clamped_y,
201 rotation_angle,
202 );
203
204 placements.push(placement);
205 placed_geometries.push(PlacedGeometry::new(
206 geom.clone(),
207 (clamped_x, clamped_y),
208 rotation_angle,
209 ));
210 total_placed_area += geom.measure();
211 placed_count += 1;
212 }
213 }
214 }
215
216 let utilization = total_placed_area / self.boundary.measure();
217 (placements, utilization, placed_count)
218 }
219
220 fn get_boundary_polygon_with_margin(&self, margin: f64) -> Vec<(f64, f64)> {
222 let (b_min, b_max) = self.boundary.aabb();
223 vec![
224 (b_min[0] + margin, b_min[1] + margin),
225 (b_max[0] - margin, b_min[1] + margin),
226 (b_max[0] - margin, b_max[1] - margin),
227 (b_min[0] + margin, b_max[1] - margin),
228 ]
229 }
230
231 fn compute_sample_step(&self) -> f64 {
233 if self.geometries.is_empty() {
234 return 1.0;
235 }
236
237 let mut min_dim = f64::INFINITY;
238 for geom in &self.geometries {
239 let (g_min, g_max) = geom.aabb();
240 let width = g_max[0] - g_min[0];
241 let height = g_max[1] - g_min[1];
242 min_dim = min_dim.min(width).min(height);
243 }
244
245 (min_dim / 4.0).clamp(0.5, 10.0)
246 }
247}
248
249impl SaProblem for SaNestingProblem {
250 type Solution = PermutationSolution;
251
252 fn initial_solution<R: rand::Rng>(&self, rng: &mut R) -> Self::Solution {
253 PermutationSolution::random(self.instances.len(), self.max_rotation_options, rng)
254 }
255
256 fn neighbor<R: rand::Rng>(
257 &self,
258 solution: &Self::Solution,
259 operator: NeighborhoodOperator,
260 rng: &mut R,
261 ) -> Self::Solution {
262 match operator {
263 NeighborhoodOperator::Swap => solution.apply_swap(rng),
264 NeighborhoodOperator::Relocate => solution.apply_relocate(rng),
265 NeighborhoodOperator::Inversion => solution.apply_inversion(rng),
266 NeighborhoodOperator::Rotation => solution.apply_rotation(rng),
267 NeighborhoodOperator::Chain => solution.apply_chain(rng),
268 }
269 }
270
271 fn evaluate(&self, solution: &mut Self::Solution) {
272 let (_, utilization, placed_count) = self.decode(solution);
273 let fitness = nesting_fitness(placed_count, self.instances.len(), utilization);
274 solution.set_objective(fitness);
275 }
276
277 fn available_operators(&self) -> Vec<NeighborhoodOperator> {
278 if self.max_rotation_options > 1 {
279 vec![
280 NeighborhoodOperator::Swap,
281 NeighborhoodOperator::Relocate,
282 NeighborhoodOperator::Inversion,
283 NeighborhoodOperator::Rotation,
284 NeighborhoodOperator::Chain,
285 ]
286 } else {
287 vec![
288 NeighborhoodOperator::Swap,
289 NeighborhoodOperator::Relocate,
290 NeighborhoodOperator::Inversion,
291 NeighborhoodOperator::Chain,
292 ]
293 }
294 }
295
296 fn on_temperature_change(
297 &self,
298 temperature: f64,
299 iteration: u64,
300 best: &Self::Solution,
301 _current: &Self::Solution,
302 ) {
303 log::debug!(
304 "SA Iteration {}: temp={:.4}, best_fitness={:.4}",
305 iteration,
306 temperature,
307 best.objective()
308 );
309 }
310}
311
312pub fn run_sa_nesting(
314 geometries: &[Geometry2D],
315 boundary: &Boundary2D,
316 config: &Config,
317 sa_config: SaConfig,
318 cancelled: Arc<AtomicBool>,
319) -> SolveResult<f64> {
320 let problem = SaNestingProblem::new(
321 geometries.to_vec(),
322 boundary.clone(),
323 config.clone(),
324 cancelled.clone(),
325 );
326
327 let runner = SaRunner::new(sa_config, problem);
328
329 let cancel_handle = runner.cancel_handle();
331 let cancelled_clone = cancelled.clone();
332 std::thread::spawn(move || {
333 while !cancelled_clone.load(Ordering::Relaxed) {
334 std::thread::sleep(std::time::Duration::from_millis(100));
335 }
336 cancel_handle.store(true, Ordering::Relaxed);
337 });
338
339 let sa_result = runner.run();
340
341 let problem = SaNestingProblem::new(
343 geometries.to_vec(),
344 boundary.clone(),
345 config.clone(),
346 Arc::new(AtomicBool::new(false)),
347 );
348
349 let (placements, utilization, _placed_count) = problem.decode(&sa_result.best);
350
351 let mut unplaced = Vec::new();
353 let mut placed_ids: std::collections::HashSet<String> = std::collections::HashSet::new();
354 for p in &placements {
355 placed_ids.insert(p.geometry_id.clone());
356 }
357 for geom in geometries {
358 if !placed_ids.contains(geom.id()) {
359 unplaced.push(geom.id().clone());
360 }
361 }
362
363 let mut result = SolveResult::new();
364 result.placements = placements;
365 result.unplaced = unplaced;
366 result.boundaries_used = 1;
367 result.utilization = utilization;
368 result.computation_time_ms = sa_result.elapsed.as_millis() as u64;
369 result.iterations = Some(sa_result.iterations);
370 result.best_fitness = Some(sa_result.best.objective());
371 result.fitness_history = Some(sa_result.history);
372 result.strategy = Some("SimulatedAnnealing".to_string());
373 result.cancelled = cancelled.load(Ordering::Relaxed);
374 result.target_reached = sa_result.target_reached;
375
376 result
377}
378
379#[cfg(test)]
380mod tests {
381 use super::*;
382
383 #[test]
384 fn test_sa_nesting_basic() {
385 let geometries = vec![
386 Geometry2D::rectangle("R1", 20.0, 10.0).with_quantity(2),
387 Geometry2D::rectangle("R2", 15.0, 15.0).with_quantity(2),
388 ];
389
390 let boundary = Boundary2D::rectangle(100.0, 50.0);
391 let config = Config::default();
392 let sa_config = SaConfig::default()
393 .with_initial_temp(100.0)
394 .with_final_temp(0.1)
395 .with_cooling_rate(0.9)
396 .with_iterations_per_temp(20)
397 .with_max_iterations(500);
398
399 let result = run_sa_nesting(
400 &geometries,
401 &boundary,
402 &config,
403 sa_config,
404 Arc::new(AtomicBool::new(false)),
405 );
406
407 assert!(result.utilization > 0.0);
408 assert!(!result.placements.is_empty());
409 assert_eq!(result.strategy, Some("SimulatedAnnealing".to_string()));
410 }
411
412 #[test]
413 fn test_sa_nesting_all_placed() {
414 let geometries = vec![Geometry2D::rectangle("R1", 20.0, 20.0).with_quantity(4)];
415
416 let boundary = Boundary2D::rectangle(100.0, 100.0);
417 let config = Config::default();
418 let sa_config = SaConfig::default()
419 .with_initial_temp(100.0)
420 .with_final_temp(0.1)
421 .with_max_iterations(1000);
422
423 let result = run_sa_nesting(
424 &geometries,
425 &boundary,
426 &config,
427 sa_config,
428 Arc::new(AtomicBool::new(false)),
429 );
430
431 assert_eq!(result.placements.len(), 4);
433 assert!(result.unplaced.is_empty());
434 }
435
436 #[test]
437 fn test_sa_nesting_with_rotation() {
438 let geometries = vec![Geometry2D::rectangle("R1", 30.0, 10.0)
439 .with_quantity(3)
440 .with_rotations(vec![0.0, 90.0])];
441
442 let boundary = Boundary2D::rectangle(50.0, 50.0);
443 let config = Config::default();
444 let sa_config = SaConfig::default()
445 .with_initial_temp(100.0)
446 .with_final_temp(0.1)
447 .with_max_iterations(500);
448
449 let result = run_sa_nesting(
450 &geometries,
451 &boundary,
452 &config,
453 sa_config,
454 Arc::new(AtomicBool::new(false)),
455 );
456
457 assert!(result.utilization > 0.0);
458 assert!(!result.placements.is_empty());
459 }
460
461 #[test]
462 fn test_sa_problem_decode() {
463 let geometries = vec![Geometry2D::rectangle("R1", 20.0, 10.0).with_quantity(2)];
464
465 let boundary = Boundary2D::rectangle(100.0, 50.0);
466 let config = Config::default();
467 let cancelled = Arc::new(AtomicBool::new(false));
468
469 let problem = SaNestingProblem::new(geometries, boundary, config, cancelled);
470
471 assert_eq!(problem.num_instances(), 2);
472
473 let mut rng = rand::rng();
475 let solution = PermutationSolution::random(problem.num_instances(), 1, &mut rng);
476 let (placements, utilization, placed_count) = problem.decode(&solution);
477
478 assert!(placed_count >= 1);
480 assert_eq!(placements.len(), placed_count);
481 if placed_count > 0 {
482 assert!(utilization > 0.0);
483 }
484 }
485}