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