1use crate::boundary::Boundary2D;
21use crate::clamp_placement_to_boundary;
22use crate::geometry::Geometry2D;
23use crate::nfp::{
24 compute_ifp, compute_nfp, find_bottom_left_placement, verify_no_overlap, Nfp, PlacedGeometry,
25};
26use std::sync::atomic::{AtomicBool, Ordering};
27use std::sync::Arc;
28use std::time::Instant;
29use u_nesting_core::alns::{
30 AlnsConfig, AlnsProblem, AlnsResult, AlnsRunner, AlnsSolution, DestroyOperatorId,
31 DestroyResult, RepairOperatorId, RepairResult,
32};
33use u_nesting_core::geometry::{Boundary, Geometry};
34use u_nesting_core::solver::Config;
35use u_nesting_core::{Placement, SolveResult};
36
37use crate::placement_utils::{expand_nfp, shrink_ifp, InstanceInfo};
38use rand::prelude::*;
39
40#[derive(Debug, Clone)]
42pub struct PlacedItem {
43 pub instance_idx: usize,
45 pub x: f64,
47 pub y: f64,
49 pub rotation: f64,
51 pub score: f64,
53}
54
55#[derive(Debug, Clone)]
57pub struct AlnsNestingSolution {
58 pub placed: Vec<PlacedItem>,
60 pub unplaced: Vec<usize>,
62 pub total_instances: usize,
64 pub placed_area: f64,
66 pub boundary_area: f64,
68 pub max_y: f64,
70}
71
72impl AlnsNestingSolution {
73 pub fn new(total_instances: usize, boundary_area: f64) -> Self {
75 Self {
76 placed: Vec::new(),
77 unplaced: (0..total_instances).collect(),
78 total_instances,
79 placed_area: 0.0,
80 boundary_area,
81 max_y: 0.0,
82 }
83 }
84}
85
86impl AlnsSolution for AlnsNestingSolution {
87 fn fitness(&self) -> f64 {
88 let unplaced_penalty = self.unplaced.len() as f64 * 1000.0;
90 let utilization_penalty = if self.placed_area > 0.0 {
91 1.0 - (self.placed_area / self.boundary_area)
92 } else {
93 1.0
94 };
95 let height_penalty = self.max_y / 1000.0;
96
97 unplaced_penalty + utilization_penalty + height_penalty
98 }
99
100 fn placed_count(&self) -> usize {
101 self.placed.len()
102 }
103
104 fn total_count(&self) -> usize {
105 self.total_instances
106 }
107}
108
109pub struct AlnsNestingProblem {
111 geometries: Vec<Geometry2D>,
113 boundary: Boundary2D,
115 config: Config,
117 instances: Vec<InstanceInfo>,
119 rotation_angles: Vec<Vec<f64>>,
121 geometry_areas: Vec<f64>,
123 cancelled: Arc<AtomicBool>,
125 start_time: Instant,
127 time_limit_ms: u64,
129}
130
131impl AlnsNestingProblem {
132 pub fn new(
134 geometries: Vec<Geometry2D>,
135 boundary: Boundary2D,
136 config: Config,
137 cancelled: Arc<AtomicBool>,
138 time_limit_ms: u64,
139 ) -> Self {
140 let mut instances = Vec::new();
141 let mut rotation_angles = Vec::new();
142 let mut geometry_areas = Vec::new();
143
144 for (geom_idx, geom) in geometries.iter().enumerate() {
145 let angles = geom.rotations();
146 let angles = if angles.is_empty() { vec![0.0] } else { angles };
147 rotation_angles.push(angles);
148
149 let area = geom.measure();
150 geometry_areas.push(area);
151
152 for instance_num in 0..geom.quantity() {
153 instances.push(InstanceInfo {
154 geometry_idx: geom_idx,
155 instance_num,
156 });
157 }
158 }
159
160 Self {
161 geometries,
162 boundary,
163 config,
164 instances,
165 rotation_angles,
166 geometry_areas,
167 cancelled,
168 start_time: Instant::now(),
169 time_limit_ms,
170 }
171 }
172
173 fn is_timed_out(&self) -> bool {
175 if self.time_limit_ms == 0 {
176 return false;
177 }
178 self.start_time.elapsed().as_millis() as u64 >= self.time_limit_ms
179 }
180
181 pub fn num_instances(&self) -> usize {
183 self.instances.len()
184 }
185
186 fn get_boundary_polygon_with_margin(&self, margin: f64) -> Vec<(f64, f64)> {
188 let (min, max) = self.boundary.aabb();
189 vec![
190 (min[0] + margin, min[1] + margin),
191 (max[0] - margin, min[1] + margin),
192 (max[0] - margin, max[1] - margin),
193 (min[0] + margin, max[1] - margin),
194 ]
195 }
196
197 fn compute_sample_step(&self) -> f64 {
199 let (min, max) = self.boundary.aabb();
200 let width = max[0] - min[0];
201 (width / 100.0).max(1.0)
202 }
203
204 fn try_place_item(
206 &self,
207 instance_idx: usize,
208 placed_geometries: &[PlacedGeometry],
209 boundary_polygon: &[(f64, f64)],
210 sample_step: f64,
211 ) -> Option<PlacedItem> {
212 let info = &self.instances[instance_idx];
213 let geom = &self.geometries[info.geometry_idx];
214 let angles = &self.rotation_angles[info.geometry_idx];
215
216 let mut best_placement: Option<PlacedItem> = None;
217 let mut best_y = f64::MAX;
218
219 for &rotation in angles {
220 let ifp = match compute_ifp(boundary_polygon, geom, rotation) {
221 Ok(ifp) => ifp,
222 Err(_) => continue,
223 };
224
225 if ifp.is_empty() {
226 continue;
227 }
228
229 let spacing = self.config.spacing;
230 let mut nfps: Vec<Nfp> = Vec::new();
231
232 for pg in placed_geometries {
233 let placed_exterior = pg.translated_exterior();
234 let placed_geom = Geometry2D::new(format!("_placed_{}", pg.geometry.id()))
235 .with_polygon(placed_exterior);
236
237 if let Ok(nfp) = compute_nfp(&placed_geom, geom, rotation) {
238 let expanded = expand_nfp(&nfp, spacing);
239 nfps.push(expanded);
240 }
241 }
242
243 let ifp_shrunk = shrink_ifp(&ifp, spacing);
244 let nfp_refs: Vec<&Nfp> = nfps.iter().collect();
245
246 if let Some((x, y)) = find_bottom_left_placement(&ifp_shrunk, &nfp_refs, sample_step) {
249 let geom_aabb = geom.aabb_at_rotation(rotation);
251 let boundary_aabb = self.boundary.aabb();
252
253 if let Some((clamped_x, clamped_y)) =
254 clamp_placement_to_boundary(x, y, geom_aabb, boundary_aabb)
255 {
256 let was_clamped = (clamped_x - x).abs() > 1e-6 || (clamped_y - y).abs() > 1e-6;
259 if was_clamped {
260 if !verify_no_overlap(
262 geom,
263 (clamped_x, clamped_y),
264 rotation,
265 placed_geometries,
266 ) {
267 continue; }
269 }
270
271 if clamped_y < best_y {
272 best_y = clamped_y;
273 best_placement = Some(PlacedItem {
274 instance_idx,
275 x: clamped_x,
276 y: clamped_y,
277 rotation,
278 score: clamped_y,
279 });
280 }
281 }
282 }
283 }
284
285 best_placement
286 }
287
288 fn place_items_blf(&self, items: &[usize], solution: &mut AlnsNestingSolution) {
290 let margin = self.config.margin;
291 let boundary_polygon = self.get_boundary_polygon_with_margin(margin);
292 let sample_step = self.compute_sample_step();
293
294 let mut placed_geometries: Vec<PlacedGeometry> = Vec::new();
295 for item in &solution.placed {
296 let info = &self.instances[item.instance_idx];
297 let geom = &self.geometries[info.geometry_idx];
298 placed_geometries.push(PlacedGeometry {
299 geometry: geom.clone(),
300 position: (item.x, item.y),
301 rotation: item.rotation,
302 });
303 }
304
305 let mut sorted_items = items.to_vec();
307 sorted_items.sort_by(|&a, &b| {
308 let area_a = self.geometry_areas[self.instances[a].geometry_idx];
309 let area_b = self.geometry_areas[self.instances[b].geometry_idx];
310 area_b
311 .partial_cmp(&area_a)
312 .unwrap_or(std::cmp::Ordering::Equal)
313 });
314
315 for &instance_idx in &sorted_items {
316 if self.cancelled.load(Ordering::Relaxed) || self.is_timed_out() {
318 break;
319 }
320
321 if let Some(placement) = self.try_place_item(
322 instance_idx,
323 &placed_geometries,
324 &boundary_polygon,
325 sample_step,
326 ) {
327 let info = &self.instances[instance_idx];
328 let area = self.geometry_areas[info.geometry_idx];
329
330 solution.placed_area += area;
331 solution.max_y = solution.max_y.max(placement.y);
332
333 let geom = &self.geometries[info.geometry_idx];
334 placed_geometries.push(PlacedGeometry {
335 geometry: geom.clone(),
336 position: (placement.x, placement.y),
337 rotation: placement.rotation,
338 });
339
340 solution.placed.push(placement);
341 solution.unplaced.retain(|&idx| idx != instance_idx);
342 }
343 }
344 }
345
346 fn remove_item(&self, solution: &mut AlnsNestingSolution, instance_idx: usize) {
348 if let Some(pos) = solution
349 .placed
350 .iter()
351 .position(|p| p.instance_idx == instance_idx)
352 {
353 let item = solution.placed.remove(pos);
354 let info = &self.instances[item.instance_idx];
355 solution.placed_area -= self.geometry_areas[info.geometry_idx];
356 solution.unplaced.push(item.instance_idx);
357 }
358
359 solution.max_y = solution.placed.iter().map(|p| p.y).fold(0.0, f64::max);
361 }
362}
363
364impl AlnsProblem for AlnsNestingProblem {
365 type Solution = AlnsNestingSolution;
366
367 fn create_initial_solution(&mut self) -> AlnsNestingSolution {
368 let boundary_area = self.boundary.measure();
369 let mut solution = AlnsNestingSolution::new(self.instances.len(), boundary_area);
370
371 let all_items: Vec<usize> = (0..self.instances.len()).collect();
372 self.place_items_blf(&all_items, &mut solution);
373
374 solution
375 }
376
377 fn clone_solution(&self, solution: &AlnsNestingSolution) -> AlnsNestingSolution {
378 solution.clone()
379 }
380
381 fn destroy_operators(&self) -> Vec<DestroyOperatorId> {
382 vec![
383 DestroyOperatorId::Random,
384 DestroyOperatorId::Worst,
385 DestroyOperatorId::Related,
386 DestroyOperatorId::Shaw,
387 ]
388 }
389
390 fn repair_operators(&self) -> Vec<RepairOperatorId> {
391 vec![
392 RepairOperatorId::Greedy,
393 RepairOperatorId::BottomLeftFill,
394 RepairOperatorId::Random,
395 ]
396 }
397
398 fn destroy(
399 &mut self,
400 solution: &mut AlnsNestingSolution,
401 operator: DestroyOperatorId,
402 degree: f64,
403 rng: &mut rand::rngs::StdRng,
404 ) -> DestroyResult {
405 let num_to_remove = ((solution.placed.len() as f64 * degree).ceil() as usize).max(1);
406 let mut removed_indices = Vec::new();
407
408 if solution.placed.is_empty() {
409 return DestroyResult {
410 removed_indices,
411 operator,
412 };
413 }
414
415 match operator {
416 DestroyOperatorId::Random => {
417 let mut indices: Vec<usize> =
419 solution.placed.iter().map(|p| p.instance_idx).collect();
420 indices.shuffle(rng);
421
422 for &idx in indices.iter().take(num_to_remove) {
423 removed_indices.push(idx);
424 }
425 }
426 DestroyOperatorId::Worst => {
427 let mut items_with_score: Vec<(usize, f64)> = solution
429 .placed
430 .iter()
431 .map(|p| (p.instance_idx, p.score))
432 .collect();
433
434 items_with_score
435 .sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
436
437 for (idx, _) in items_with_score.iter().take(num_to_remove) {
438 removed_indices.push(*idx);
439 }
440 }
441 DestroyOperatorId::Related | DestroyOperatorId::Shaw => {
442 let seed_idx = rng.random_range(0..solution.placed.len());
444 let seed = &solution.placed[seed_idx];
445 let seed_x = seed.x;
446 let seed_y = seed.y;
447
448 let mut items_with_distance: Vec<(usize, f64)> = solution
449 .placed
450 .iter()
451 .map(|item| {
452 let dx = item.x - seed_x;
453 let dy = item.y - seed_y;
454 (item.instance_idx, (dx * dx + dy * dy).sqrt())
455 })
456 .collect();
457
458 items_with_distance
459 .sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
460
461 for (idx, _) in items_with_distance.iter().take(num_to_remove) {
462 removed_indices.push(*idx);
463 }
464 }
465 DestroyOperatorId::Custom(_) => {
466 let mut indices: Vec<usize> =
468 solution.placed.iter().map(|p| p.instance_idx).collect();
469 indices.shuffle(rng);
470
471 for &idx in indices.iter().take(num_to_remove) {
472 removed_indices.push(idx);
473 }
474 }
475 }
476
477 for &idx in &removed_indices {
479 self.remove_item(solution, idx);
480 }
481
482 DestroyResult {
483 removed_indices,
484 operator,
485 }
486 }
487
488 fn repair(
489 &mut self,
490 solution: &mut AlnsNestingSolution,
491 _destroyed: &DestroyResult,
492 operator: RepairOperatorId,
493 ) -> RepairResult {
494 let items_to_place = solution.unplaced.clone();
495 let initial_placed = solution.placed.len();
496
497 match operator {
498 RepairOperatorId::Greedy | RepairOperatorId::BottomLeftFill => {
499 self.place_items_blf(&items_to_place, solution);
501 }
502 RepairOperatorId::Regret => {
503 self.place_items_blf(&items_to_place, solution);
505 }
506 RepairOperatorId::Random => {
507 let mut shuffled = items_to_place.clone();
509 use rand::SeedableRng;
510 let mut rng = rand::rngs::StdRng::from_os_rng();
511 shuffled.shuffle(&mut rng);
512 self.place_items_blf(&shuffled, solution);
513 }
514 RepairOperatorId::Custom(_) => {
515 self.place_items_blf(&items_to_place, solution);
516 }
517 }
518
519 RepairResult {
520 placed_count: solution.placed.len() - initial_placed,
521 unplaced_count: solution.unplaced.len(),
522 operator,
523 }
524 }
525
526 fn relatedness(&self, solution: &AlnsNestingSolution, i: usize, j: usize) -> f64 {
527 let item_i = solution.placed.iter().find(|p| p.instance_idx == i);
529 let item_j = solution.placed.iter().find(|p| p.instance_idx == j);
530
531 match (item_i, item_j) {
532 (Some(a), Some(b)) => {
533 let dx = a.x - b.x;
534 let dy = a.y - b.y;
535 1.0 / (1.0 + (dx * dx + dy * dy).sqrt())
536 }
537 _ => 0.0,
538 }
539 }
540}
541
542pub fn run_alns_nesting(
544 geometries: &[Geometry2D],
545 boundary: &Boundary2D,
546 config: &Config,
547 alns_config: &AlnsConfig,
548 cancelled: Arc<AtomicBool>,
549) -> SolveResult<f64> {
550 let mut problem = AlnsNestingProblem::new(
551 geometries.to_vec(),
552 boundary.clone(),
553 config.clone(),
554 cancelled,
555 alns_config.time_limit_ms,
556 );
557
558 let runner = AlnsRunner::new(alns_config.clone());
559 let alns_result: AlnsResult<AlnsNestingSolution> = runner.run(&mut problem, |_progress| {
560 });
562
563 let mut result = SolveResult::new();
564
565 for item in &alns_result.best_solution.placed {
566 let info = &problem.instances[item.instance_idx];
567 let geom = &problem.geometries[info.geometry_idx];
568
569 result.placements.push(Placement::new_2d(
570 geom.id().to_string(),
571 info.instance_num,
572 item.x,
573 item.y,
574 item.rotation,
575 ));
576 }
577
578 result.boundaries_used = if result.placements.is_empty() { 0 } else { 1 };
579 result.utilization =
580 alns_result.best_solution.placed_area / alns_result.best_solution.boundary_area;
581 result.computation_time_ms = alns_result.elapsed_ms;
582 result.iterations = Some(alns_result.iterations as u64);
583 result.best_fitness = Some(alns_result.best_fitness);
584 result.strategy = Some("ALNS".to_string());
585
586 result
587}
588
589#[cfg(test)]
590mod tests {
591 use super::*;
592
593 fn create_test_geometries() -> Vec<Geometry2D> {
594 vec![
595 Geometry2D::rectangle("rect1", 50.0, 30.0).with_quantity(3),
596 Geometry2D::rectangle("rect2", 40.0, 40.0).with_quantity(2),
597 Geometry2D::rectangle("rect3", 60.0, 20.0).with_quantity(2),
598 ]
599 }
600
601 fn create_test_boundary() -> Boundary2D {
602 Boundary2D::rectangle(300.0, 200.0)
603 }
604
605 #[test]
606 fn test_alns_nesting_problem_creation() {
607 let geometries = create_test_geometries();
608 let boundary = create_test_boundary();
609 let config = Config::default();
610 let cancelled = Arc::new(AtomicBool::new(false));
611
612 let problem = AlnsNestingProblem::new(geometries, boundary, config, cancelled, 60000);
613
614 assert_eq!(problem.num_instances(), 7);
615 }
616
617 #[test]
618 fn test_alns_nesting_initial_solution() {
619 let geometries = create_test_geometries();
620 let boundary = create_test_boundary();
621 let config = Config::default();
622 let cancelled = Arc::new(AtomicBool::new(false));
623
624 let mut problem = AlnsNestingProblem::new(geometries, boundary, config, cancelled, 60000);
625 let solution = problem.create_initial_solution();
626
627 assert!(!solution.placed.is_empty());
628 assert!(solution.placed_area > 0.0);
629 }
630
631 #[test]
632 fn test_alns_nesting_solution_fitness() {
633 let solution = AlnsNestingSolution {
634 placed: vec![
635 PlacedItem {
636 instance_idx: 0,
637 x: 10.0,
638 y: 10.0,
639 rotation: 0.0,
640 score: 10.0,
641 },
642 PlacedItem {
643 instance_idx: 1,
644 x: 60.0,
645 y: 10.0,
646 rotation: 0.0,
647 score: 10.0,
648 },
649 ],
650 unplaced: vec![2],
651 total_instances: 3,
652 placed_area: 3000.0,
653 boundary_area: 60000.0,
654 max_y: 50.0,
655 };
656
657 let fitness = solution.fitness();
658 assert!(fitness > 0.0);
659 assert!(fitness >= 1000.0); }
661
662 #[test]
663 fn test_alns_nesting_destroy_random() {
664 use rand::SeedableRng;
665
666 let geometries = create_test_geometries();
667 let boundary = create_test_boundary();
668 let config = Config::default();
669 let cancelled = Arc::new(AtomicBool::new(false));
670
671 let mut problem = AlnsNestingProblem::new(geometries, boundary, config, cancelled, 60000);
672 let mut solution = problem.create_initial_solution();
673
674 let initial_placed = solution.placed.len();
675 let mut rng = rand::rngs::StdRng::seed_from_u64(42);
676
677 let result = problem.destroy(&mut solution, DestroyOperatorId::Random, 0.3, &mut rng);
678
679 assert!(!result.removed_indices.is_empty());
680 assert_eq!(result.operator, DestroyOperatorId::Random);
681 assert!(solution.placed.len() < initial_placed);
682 }
683
684 #[test]
685 fn test_alns_nesting_destroy_worst() {
686 use rand::SeedableRng;
687
688 let geometries = create_test_geometries();
689 let boundary = create_test_boundary();
690 let config = Config::default();
691 let cancelled = Arc::new(AtomicBool::new(false));
692
693 let mut problem = AlnsNestingProblem::new(geometries, boundary, config, cancelled, 60000);
694 let mut solution = problem.create_initial_solution();
695
696 let initial_placed = solution.placed.len();
697 let mut rng = rand::rngs::StdRng::seed_from_u64(42);
698
699 let result = problem.destroy(&mut solution, DestroyOperatorId::Worst, 0.3, &mut rng);
700
701 assert!(!result.removed_indices.is_empty());
702 assert_eq!(result.operator, DestroyOperatorId::Worst);
703 assert!(solution.placed.len() < initial_placed);
704 }
705
706 #[test]
707 fn test_alns_nesting_repair() {
708 use rand::SeedableRng;
709
710 let geometries = create_test_geometries();
711 let boundary = create_test_boundary();
712 let config = Config::default();
713 let cancelled = Arc::new(AtomicBool::new(false));
714
715 let mut problem = AlnsNestingProblem::new(geometries, boundary, config, cancelled, 60000);
716 let mut solution = problem.create_initial_solution();
717
718 let mut rng = rand::rngs::StdRng::seed_from_u64(42);
719
720 let destroy_result =
721 problem.destroy(&mut solution, DestroyOperatorId::Random, 0.5, &mut rng);
722 let after_destroy_placed = solution.placed.len();
723
724 let repair_result =
725 problem.repair(&mut solution, &destroy_result, RepairOperatorId::Greedy);
726
727 assert!(repair_result.placed_count > 0 || after_destroy_placed == solution.placed.len());
728 assert_eq!(repair_result.operator, RepairOperatorId::Greedy);
729 }
730
731 #[test]
732 fn test_run_alns_nesting() {
733 let geometries = create_test_geometries();
734 let boundary = create_test_boundary();
735 let config = Config::default();
736 let alns_config = AlnsConfig::new()
737 .with_max_iterations(50)
738 .with_time_limit_ms(5000);
739 let cancelled = Arc::new(AtomicBool::new(false));
740
741 let result = run_alns_nesting(&geometries, &boundary, &config, &alns_config, cancelled);
742
743 assert!(!result.placements.is_empty());
744 assert!(result.utilization > 0.0);
745 }
746
747 #[test]
748 fn test_alns_nesting_full_cycle() {
749 let geometries = create_test_geometries();
750 let boundary = create_test_boundary();
751 let config = Config::default();
752 let cancelled = Arc::new(AtomicBool::new(false));
753
754 let mut problem = AlnsNestingProblem::new(geometries, boundary, config, cancelled, 60000);
755
756 let alns_config = AlnsConfig::new().with_max_iterations(10).with_seed(42);
757
758 let runner = AlnsRunner::new(alns_config);
759 let result: AlnsResult<AlnsNestingSolution> = runner.run(&mut problem, |progress| {
760 assert!(progress.iteration <= 10);
761 });
762
763 assert!(result.iterations <= 10);
764 assert!(!result.best_solution.placed.is_empty());
765 }
766
767 #[test]
768 fn test_alns_destroy_operators() {
769 let geometries = create_test_geometries();
770 let boundary = create_test_boundary();
771 let config = Config::default();
772 let cancelled = Arc::new(AtomicBool::new(false));
773
774 let problem = AlnsNestingProblem::new(geometries, boundary, config, cancelled, 60000);
775 let operators = problem.destroy_operators();
776
777 assert!(operators.contains(&DestroyOperatorId::Random));
778 assert!(operators.contains(&DestroyOperatorId::Worst));
779 assert!(operators.contains(&DestroyOperatorId::Related));
780 assert!(operators.contains(&DestroyOperatorId::Shaw));
781 }
782
783 #[test]
784 fn test_alns_repair_operators() {
785 let geometries = create_test_geometries();
786 let boundary = create_test_boundary();
787 let config = Config::default();
788 let cancelled = Arc::new(AtomicBool::new(false));
789
790 let problem = AlnsNestingProblem::new(geometries, boundary, config, cancelled, 60000);
791 let operators = problem.repair_operators();
792
793 assert!(operators.contains(&RepairOperatorId::Greedy));
794 assert!(operators.contains(&RepairOperatorId::BottomLeftFill));
795 assert!(operators.contains(&RepairOperatorId::Random));
796 }
797}