1use crate::boundary::Boundary2D;
19use crate::clamp_placement_to_boundary;
20use crate::geometry::Geometry2D;
21use crate::nfp::{
22 compute_ifp, compute_nfp, find_bottom_left_placement, verify_no_overlap, Nfp, PlacedGeometry,
23};
24use std::sync::atomic::{AtomicBool, Ordering};
25use std::sync::Arc;
26use std::time::Instant;
27use u_nesting_core::gdrr::{
28 GdrrConfig, GdrrProblem, GdrrResult, GdrrRunner, GdrrSolution, RecreateResult, RecreateType,
29 RuinResult, RuinType, RuinedItem,
30};
31use u_nesting_core::geometry::{Boundary, Geometry};
32use u_nesting_core::solver::Config;
33use u_nesting_core::{Placement, SolveResult};
34
35use crate::placement_utils::{expand_nfp, shrink_ifp, InstanceInfo};
36use rand::prelude::*;
37
38#[derive(Debug, Clone)]
40pub struct PlacedItem {
41 pub instance_idx: usize,
43 pub x: f64,
45 pub y: f64,
47 pub rotation: f64,
49 pub score: f64,
51}
52
53#[derive(Debug, Clone)]
55pub struct GdrrNestingSolution {
56 pub placed: Vec<PlacedItem>,
58 pub unplaced: Vec<usize>,
60 pub total_instances: usize,
62 pub placed_area: f64,
64 pub boundary_area: f64,
66 pub max_y: f64,
68}
69
70impl GdrrNestingSolution {
71 pub fn new(total_instances: usize, boundary_area: f64) -> Self {
73 Self {
74 placed: Vec::new(),
75 unplaced: (0..total_instances).collect(),
76 total_instances,
77 placed_area: 0.0,
78 boundary_area,
79 max_y: 0.0,
80 }
81 }
82}
83
84impl GdrrSolution for GdrrNestingSolution {
85 fn fitness(&self) -> f64 {
86 let unplaced_penalty = self.unplaced.len() as f64 * 1000.0;
91 let utilization_penalty = if self.placed_area > 0.0 {
92 1.0 - (self.placed_area / self.boundary_area)
93 } else {
94 1.0
95 };
96 let height_penalty = self.max_y / 1000.0; unplaced_penalty + utilization_penalty + height_penalty
99 }
100
101 fn placed_count(&self) -> usize {
102 self.placed.len()
103 }
104
105 fn total_count(&self) -> usize {
106 self.total_instances
107 }
108
109 fn utilization(&self) -> f64 {
110 if self.boundary_area > 0.0 {
111 self.placed_area / self.boundary_area
112 } else {
113 0.0
114 }
115 }
116
117 fn fits_goal(&self, goal: f64) -> bool {
118 self.fitness() <= goal
120 }
121}
122
123pub struct GdrrNestingProblem {
125 geometries: Vec<Geometry2D>,
127 boundary: Boundary2D,
129 config: Config,
131 instances: Vec<InstanceInfo>,
133 rotation_angles: Vec<Vec<f64>>,
135 geometry_areas: Vec<f64>,
137 cancelled: Arc<AtomicBool>,
139 start_time: Instant,
141 time_limit_ms: u64,
143}
144
145impl GdrrNestingProblem {
146 pub fn new(
148 geometries: Vec<Geometry2D>,
149 boundary: Boundary2D,
150 config: Config,
151 cancelled: Arc<AtomicBool>,
152 time_limit_ms: u64,
153 ) -> Self {
154 let mut instances = Vec::new();
156 let mut rotation_angles = Vec::new();
157 let mut geometry_areas = Vec::new();
158
159 for (geom_idx, geom) in geometries.iter().enumerate() {
160 let angles = geom.rotations();
162 let angles = if angles.is_empty() { vec![0.0] } else { angles };
163 rotation_angles.push(angles);
164
165 let area = geom.measure();
167 geometry_areas.push(area);
168
169 for instance_num in 0..geom.quantity() {
171 instances.push(InstanceInfo {
172 geometry_idx: geom_idx,
173 instance_num,
174 });
175 }
176 }
177
178 Self {
179 geometries,
180 boundary,
181 config,
182 instances,
183 rotation_angles,
184 geometry_areas,
185 cancelled,
186 start_time: Instant::now(),
187 time_limit_ms,
188 }
189 }
190
191 fn is_timed_out(&self) -> bool {
193 if self.time_limit_ms == 0 {
194 return false;
195 }
196 self.start_time.elapsed().as_millis() as u64 >= self.time_limit_ms
197 }
198
199 pub fn num_instances(&self) -> usize {
201 self.instances.len()
202 }
203
204 fn get_boundary_polygon_with_margin(&self, margin: f64) -> Vec<(f64, f64)> {
206 let (min, max) = self.boundary.aabb();
207 vec![
208 (min[0] + margin, min[1] + margin),
209 (max[0] - margin, min[1] + margin),
210 (max[0] - margin, max[1] - margin),
211 (min[0] + margin, max[1] - margin),
212 ]
213 }
214
215 fn compute_sample_step(&self) -> f64 {
217 let (min, max) = self.boundary.aabb();
218 let width = max[0] - min[0];
219 (width / 100.0).max(1.0)
220 }
221
222 fn try_place_item(
224 &self,
225 instance_idx: usize,
226 placed_geometries: &[PlacedGeometry],
227 boundary_polygon: &[(f64, f64)],
228 sample_step: f64,
229 ) -> Option<PlacedItem> {
230 let info = &self.instances[instance_idx];
231 let geom = &self.geometries[info.geometry_idx];
232 let angles = &self.rotation_angles[info.geometry_idx];
233
234 let mut best_placement: Option<PlacedItem> = None;
235 let mut best_y = f64::MAX;
236
237 for &rotation in angles {
238 let ifp = match compute_ifp(boundary_polygon, geom, rotation) {
240 Ok(ifp) => ifp,
241 Err(_) => continue,
242 };
243
244 if ifp.is_empty() {
245 continue;
246 }
247
248 let spacing = self.config.spacing;
250 let mut nfps: Vec<Nfp> = Vec::new();
251
252 for pg in placed_geometries {
253 let placed_exterior = pg.translated_exterior();
255 let placed_geom = Geometry2D::new(format!("_placed_{}", pg.geometry.id()))
256 .with_polygon(placed_exterior);
257
258 if let Ok(nfp) = compute_nfp(&placed_geom, geom, rotation) {
259 let expanded = expand_nfp(&nfp, spacing);
260 nfps.push(expanded);
261 }
262 }
263
264 let ifp_shrunk = shrink_ifp(&ifp, spacing);
266
267 let nfp_refs: Vec<&Nfp> = nfps.iter().collect();
271 if let Some((x, y)) = find_bottom_left_placement(&ifp_shrunk, &nfp_refs, sample_step) {
272 let geom_aabb = geom.aabb_at_rotation(rotation);
274 let boundary_aabb = self.boundary.aabb();
275
276 if let Some((clamped_x, clamped_y)) =
277 clamp_placement_to_boundary(x, y, geom_aabb, boundary_aabb)
278 {
279 let was_clamped = (clamped_x - x).abs() > 1e-6 || (clamped_y - y).abs() > 1e-6;
282 if was_clamped {
283 if !verify_no_overlap(
285 geom,
286 (clamped_x, clamped_y),
287 rotation,
288 placed_geometries,
289 ) {
290 continue; }
292 }
293
294 if clamped_y < best_y {
295 best_y = clamped_y;
296 best_placement = Some(PlacedItem {
297 instance_idx,
298 x: clamped_x,
299 y: clamped_y,
300 rotation,
301 score: clamped_y, });
303 }
304 }
305 }
306 }
307
308 best_placement
309 }
310
311 fn place_items_blf(&self, items: &[usize], solution: &mut GdrrNestingSolution) {
313 let margin = self.config.margin;
314 let boundary_polygon = self.get_boundary_polygon_with_margin(margin);
315 let sample_step = self.compute_sample_step();
316
317 let mut placed_geometries: Vec<PlacedGeometry> = Vec::new();
319 for item in &solution.placed {
320 let info = &self.instances[item.instance_idx];
321 let geom = &self.geometries[info.geometry_idx];
322 placed_geometries.push(PlacedGeometry {
323 geometry: geom.clone(),
324 position: (item.x, item.y),
325 rotation: item.rotation,
326 });
327 }
328
329 let mut sorted_items = items.to_vec();
331 sorted_items.sort_by(|&a, &b| {
332 let area_a = self.geometry_areas[self.instances[a].geometry_idx];
333 let area_b = self.geometry_areas[self.instances[b].geometry_idx];
334 area_b
335 .partial_cmp(&area_a)
336 .unwrap_or(std::cmp::Ordering::Equal)
337 });
338
339 for &instance_idx in &sorted_items {
340 if self.cancelled.load(Ordering::Relaxed) || self.is_timed_out() {
342 break;
343 }
344
345 if let Some(placement) = self.try_place_item(
346 instance_idx,
347 &placed_geometries,
348 &boundary_polygon,
349 sample_step,
350 ) {
351 let info = &self.instances[instance_idx];
353 let area = self.geometry_areas[info.geometry_idx];
354
355 solution.placed_area += area;
356 solution.max_y = solution.max_y.max(placement.y);
357
358 let geom = &self.geometries[info.geometry_idx];
360 placed_geometries.push(PlacedGeometry {
361 geometry: geom.clone(),
362 position: (placement.x, placement.y),
363 rotation: placement.rotation,
364 });
365
366 solution.placed.push(placement);
367 solution.unplaced.retain(|&idx| idx != instance_idx);
368 }
369 }
370 }
371}
372
373impl GdrrProblem for GdrrNestingProblem {
374 type Solution = GdrrNestingSolution;
375
376 fn create_initial_solution(&mut self) -> GdrrNestingSolution {
377 let boundary_area = self.boundary.measure();
378 let mut solution = GdrrNestingSolution::new(self.instances.len(), boundary_area);
379
380 let all_items: Vec<usize> = (0..self.instances.len()).collect();
382 self.place_items_blf(&all_items, &mut solution);
383
384 solution
385 }
386
387 fn clone_solution(&self, solution: &GdrrNestingSolution) -> GdrrNestingSolution {
388 solution.clone()
389 }
390
391 fn ruin_random(
392 &mut self,
393 solution: &mut GdrrNestingSolution,
394 ratio: f64,
395 rng: &mut rand::rngs::StdRng,
396 ) -> RuinResult {
397 let num_to_remove = ((solution.placed.len() as f64 * ratio).ceil() as usize).max(1);
398 let mut removed_items = Vec::new();
399
400 if solution.placed.is_empty() {
401 return RuinResult {
402 removed_items,
403 ruin_type: RuinType::Random,
404 };
405 }
406
407 let mut indices: Vec<usize> = (0..solution.placed.len()).collect();
409 indices.shuffle(rng);
410
411 for &idx in indices.iter().take(num_to_remove) {
412 let item = &solution.placed[idx];
413 let info = &self.instances[item.instance_idx];
414
415 removed_items.push(RuinedItem {
416 index: item.instance_idx,
417 geometry_id: self.geometries[info.geometry_idx].id().to_string(),
418 position: vec![item.x, item.y],
419 rotation: item.rotation,
420 score: item.score,
421 });
422 }
423
424 let removed_instance_indices: Vec<usize> = removed_items.iter().map(|r| r.index).collect();
426
427 for idx in &removed_instance_indices {
428 if let Some(pos) = solution.placed.iter().position(|p| p.instance_idx == *idx) {
429 let item = solution.placed.remove(pos);
430 let info = &self.instances[item.instance_idx];
431 solution.placed_area -= self.geometry_areas[info.geometry_idx];
432 solution.unplaced.push(item.instance_idx);
433 }
434 }
435
436 solution.max_y = solution.placed.iter().map(|p| p.y).fold(0.0, f64::max);
438
439 RuinResult {
440 removed_items,
441 ruin_type: RuinType::Random,
442 }
443 }
444
445 fn ruin_cluster(
446 &mut self,
447 solution: &mut GdrrNestingSolution,
448 ratio: f64,
449 rng: &mut rand::rngs::StdRng,
450 ) -> RuinResult {
451 let num_to_remove = ((solution.placed.len() as f64 * ratio).ceil() as usize).max(1);
452 let mut removed_items = Vec::new();
453
454 if solution.placed.is_empty() {
455 return RuinResult {
456 removed_items,
457 ruin_type: RuinType::Cluster,
458 };
459 }
460
461 let seed_idx = rng.random_range(0..solution.placed.len());
463 let seed = &solution.placed[seed_idx];
464 let seed_x = seed.x;
465 let seed_y = seed.y;
466
467 let mut items_with_distance: Vec<(usize, f64)> = solution
469 .placed
470 .iter()
471 .enumerate()
472 .map(|(idx, item)| {
473 let dx = item.x - seed_x;
474 let dy = item.y - seed_y;
475 (idx, (dx * dx + dy * dy).sqrt())
476 })
477 .collect();
478
479 items_with_distance
480 .sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
481
482 for (idx, _) in items_with_distance.iter().take(num_to_remove) {
484 let item = &solution.placed[*idx];
485 let info = &self.instances[item.instance_idx];
486
487 removed_items.push(RuinedItem {
488 index: item.instance_idx,
489 geometry_id: self.geometries[info.geometry_idx].id().to_string(),
490 position: vec![item.x, item.y],
491 rotation: item.rotation,
492 score: item.score,
493 });
494 }
495
496 let removed_instance_indices: Vec<usize> = removed_items.iter().map(|r| r.index).collect();
498
499 for idx in &removed_instance_indices {
500 if let Some(pos) = solution.placed.iter().position(|p| p.instance_idx == *idx) {
501 let item = solution.placed.remove(pos);
502 let info = &self.instances[item.instance_idx];
503 solution.placed_area -= self.geometry_areas[info.geometry_idx];
504 solution.unplaced.push(item.instance_idx);
505 }
506 }
507
508 solution.max_y = solution.placed.iter().map(|p| p.y).fold(0.0, f64::max);
510
511 RuinResult {
512 removed_items,
513 ruin_type: RuinType::Cluster,
514 }
515 }
516
517 fn ruin_worst(
518 &mut self,
519 solution: &mut GdrrNestingSolution,
520 ratio: f64,
521 _rng: &mut rand::rngs::StdRng,
522 ) -> RuinResult {
523 let num_to_remove = ((solution.placed.len() as f64 * ratio).ceil() as usize).max(1);
524 let mut removed_items = Vec::new();
525
526 if solution.placed.is_empty() {
527 return RuinResult {
528 removed_items,
529 ruin_type: RuinType::Worst,
530 };
531 }
532
533 let mut items_with_score: Vec<(usize, f64)> = solution
535 .placed
536 .iter()
537 .enumerate()
538 .map(|(idx, item)| (idx, item.score))
539 .collect();
540
541 items_with_score.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
542
543 for (idx, _) in items_with_score.iter().take(num_to_remove) {
545 let item = &solution.placed[*idx];
546 let info = &self.instances[item.instance_idx];
547
548 removed_items.push(RuinedItem {
549 index: item.instance_idx,
550 geometry_id: self.geometries[info.geometry_idx].id().to_string(),
551 position: vec![item.x, item.y],
552 rotation: item.rotation,
553 score: item.score,
554 });
555 }
556
557 let removed_instance_indices: Vec<usize> = removed_items.iter().map(|r| r.index).collect();
559
560 for idx in &removed_instance_indices {
561 if let Some(pos) = solution.placed.iter().position(|p| p.instance_idx == *idx) {
562 let item = solution.placed.remove(pos);
563 let info = &self.instances[item.instance_idx];
564 solution.placed_area -= self.geometry_areas[info.geometry_idx];
565 solution.unplaced.push(item.instance_idx);
566 }
567 }
568
569 solution.max_y = solution.placed.iter().map(|p| p.y).fold(0.0, f64::max);
571
572 RuinResult {
573 removed_items,
574 ruin_type: RuinType::Worst,
575 }
576 }
577
578 fn recreate_best_fit(
579 &mut self,
580 solution: &mut GdrrNestingSolution,
581 _ruined: &RuinResult,
582 ) -> RecreateResult {
583 let items_to_place = solution.unplaced.clone();
584 let initial_placed = solution.placed.len();
585
586 self.place_items_blf(&items_to_place, solution);
587
588 RecreateResult {
589 placed_count: solution.placed.len() - initial_placed,
590 unplaced_count: solution.unplaced.len(),
591 recreate_type: RecreateType::BestFit,
592 }
593 }
594
595 fn recreate_blf(
596 &mut self,
597 solution: &mut GdrrNestingSolution,
598 _ruined: &RuinResult,
599 ) -> RecreateResult {
600 let items_to_place = solution.unplaced.clone();
601 let initial_placed = solution.placed.len();
602
603 self.place_items_blf(&items_to_place, solution);
604
605 RecreateResult {
606 placed_count: solution.placed.len() - initial_placed,
607 unplaced_count: solution.unplaced.len(),
608 recreate_type: RecreateType::BottomLeftFill,
609 }
610 }
611
612 fn recreate_nfp(
613 &mut self,
614 solution: &mut GdrrNestingSolution,
615 _ruined: &RuinResult,
616 ) -> RecreateResult {
617 let items_to_place = solution.unplaced.clone();
619 let initial_placed = solution.placed.len();
620
621 self.place_items_blf(&items_to_place, solution);
622
623 RecreateResult {
624 placed_count: solution.placed.len() - initial_placed,
625 unplaced_count: solution.unplaced.len(),
626 recreate_type: RecreateType::NfpGuided,
627 }
628 }
629
630 fn placement_score(&self, solution: &GdrrNestingSolution, item_index: usize) -> f64 {
631 solution
632 .placed
633 .iter()
634 .find(|p| p.instance_idx == item_index)
635 .map(|p| p.score)
636 .unwrap_or(f64::MAX)
637 }
638
639 fn get_neighbors(
640 &self,
641 solution: &GdrrNestingSolution,
642 item_index: usize,
643 radius: f64,
644 ) -> Vec<usize> {
645 let item = match solution
646 .placed
647 .iter()
648 .find(|p| p.instance_idx == item_index)
649 {
650 Some(i) => i,
651 None => return vec![],
652 };
653
654 solution
655 .placed
656 .iter()
657 .filter(|p| {
658 if p.instance_idx == item_index {
659 return false;
660 }
661 let dx = p.x - item.x;
662 let dy = p.y - item.y;
663 (dx * dx + dy * dy).sqrt() <= radius
664 })
665 .map(|p| p.instance_idx)
666 .collect()
667 }
668}
669
670pub fn run_gdrr_nesting(
672 geometries: &[Geometry2D],
673 boundary: &Boundary2D,
674 config: &Config,
675 gdrr_config: &GdrrConfig,
676 cancelled: Arc<AtomicBool>,
677) -> SolveResult<f64> {
678 let mut problem = GdrrNestingProblem::new(
679 geometries.to_vec(),
680 boundary.clone(),
681 config.clone(),
682 cancelled,
683 gdrr_config.time_limit_ms,
684 );
685
686 let runner = GdrrRunner::new(gdrr_config.clone());
687 let gdrr_result: GdrrResult<GdrrNestingSolution> = runner.run(&mut problem, |_progress| {
688 });
690
691 let mut result = SolveResult::new();
693
694 for item in &gdrr_result.best_solution.placed {
695 let info = &problem.instances[item.instance_idx];
696 let geom = &problem.geometries[info.geometry_idx];
697
698 result.placements.push(Placement::new_2d(
699 geom.id().to_string(),
700 info.instance_num,
701 item.x,
702 item.y,
703 item.rotation,
704 ));
705 }
706
707 result.boundaries_used = if result.placements.is_empty() { 0 } else { 1 };
708 result.utilization = gdrr_result.best_solution.utilization();
709 result.computation_time_ms = gdrr_result.elapsed_ms;
710 result.iterations = Some(gdrr_result.iterations as u64);
711 result.best_fitness = Some(gdrr_result.best_fitness);
712 result.strategy = Some("GDRR".to_string());
713
714 result
715}
716
717#[cfg(test)]
718mod tests {
719 use super::*;
720
721 fn create_test_geometries() -> Vec<Geometry2D> {
722 vec![
723 Geometry2D::rectangle("rect1", 50.0, 30.0).with_quantity(3),
724 Geometry2D::rectangle("rect2", 40.0, 40.0).with_quantity(2),
725 Geometry2D::rectangle("rect3", 60.0, 20.0).with_quantity(2),
726 ]
727 }
728
729 fn create_test_boundary() -> Boundary2D {
730 Boundary2D::rectangle(300.0, 200.0)
731 }
732
733 #[test]
734 fn test_gdrr_nesting_problem_creation() {
735 let geometries = create_test_geometries();
736 let boundary = create_test_boundary();
737 let config = Config::default();
738 let cancelled = Arc::new(AtomicBool::new(false));
739
740 let problem = GdrrNestingProblem::new(geometries, boundary, config, cancelled, 60000);
741
742 assert_eq!(problem.num_instances(), 7); }
744
745 #[test]
746 fn test_gdrr_nesting_initial_solution() {
747 let geometries = create_test_geometries();
748 let boundary = create_test_boundary();
749 let config = Config::default();
750 let cancelled = Arc::new(AtomicBool::new(false));
751
752 let mut problem = GdrrNestingProblem::new(geometries, boundary, config, cancelled, 60000);
753 let solution = problem.create_initial_solution();
754
755 assert!(!solution.placed.is_empty());
756 assert!(solution.placed_area > 0.0);
757 }
758
759 #[test]
760 fn test_gdrr_nesting_solution_fitness() {
761 let solution = GdrrNestingSolution {
762 placed: vec![
763 PlacedItem {
764 instance_idx: 0,
765 x: 10.0,
766 y: 10.0,
767 rotation: 0.0,
768 score: 10.0,
769 },
770 PlacedItem {
771 instance_idx: 1,
772 x: 60.0,
773 y: 10.0,
774 rotation: 0.0,
775 score: 10.0,
776 },
777 ],
778 unplaced: vec![2],
779 total_instances: 3,
780 placed_area: 3000.0,
781 boundary_area: 60000.0,
782 max_y: 50.0,
783 };
784
785 let fitness = solution.fitness();
786 assert!(fitness > 0.0);
787 assert!(fitness >= 1000.0);
789 }
790
791 #[test]
792 fn test_gdrr_nesting_ruin_random() {
793 use rand::SeedableRng;
794
795 let geometries = create_test_geometries();
796 let boundary = create_test_boundary();
797 let config = Config::default();
798 let cancelled = Arc::new(AtomicBool::new(false));
799
800 let mut problem = GdrrNestingProblem::new(geometries, boundary, config, cancelled, 60000);
801 let mut solution = problem.create_initial_solution();
802
803 let initial_placed = solution.placed.len();
804 let mut rng = rand::rngs::StdRng::seed_from_u64(42);
805
806 let result = problem.ruin_random(&mut solution, 0.3, &mut rng);
807
808 assert!(!result.removed_items.is_empty());
809 assert_eq!(result.ruin_type, RuinType::Random);
810 assert!(solution.placed.len() < initial_placed);
811 }
812
813 #[test]
814 fn test_gdrr_nesting_ruin_cluster() {
815 use rand::SeedableRng;
816
817 let geometries = create_test_geometries();
818 let boundary = create_test_boundary();
819 let config = Config::default();
820 let cancelled = Arc::new(AtomicBool::new(false));
821
822 let mut problem = GdrrNestingProblem::new(geometries, boundary, config, cancelled, 60000);
823 let mut solution = problem.create_initial_solution();
824
825 let initial_placed = solution.placed.len();
826 let mut rng = rand::rngs::StdRng::seed_from_u64(42);
827
828 let result = problem.ruin_cluster(&mut solution, 0.3, &mut rng);
829
830 assert!(!result.removed_items.is_empty());
831 assert_eq!(result.ruin_type, RuinType::Cluster);
832 assert!(solution.placed.len() < initial_placed);
833 }
834
835 #[test]
836 fn test_gdrr_nesting_ruin_worst() {
837 use rand::SeedableRng;
838
839 let geometries = create_test_geometries();
840 let boundary = create_test_boundary();
841 let config = Config::default();
842 let cancelled = Arc::new(AtomicBool::new(false));
843
844 let mut problem = GdrrNestingProblem::new(geometries, boundary, config, cancelled, 60000);
845 let mut solution = problem.create_initial_solution();
846
847 let initial_placed = solution.placed.len();
848 let mut rng = rand::rngs::StdRng::seed_from_u64(42);
849
850 let result = problem.ruin_worst(&mut solution, 0.3, &mut rng);
851
852 assert!(!result.removed_items.is_empty());
853 assert_eq!(result.ruin_type, RuinType::Worst);
854 assert!(solution.placed.len() < initial_placed);
855 }
856
857 #[test]
858 fn test_gdrr_nesting_recreate() {
859 use rand::SeedableRng;
860
861 let geometries = create_test_geometries();
862 let boundary = create_test_boundary();
863 let config = Config::default();
864 let cancelled = Arc::new(AtomicBool::new(false));
865
866 let mut problem = GdrrNestingProblem::new(geometries, boundary, config, cancelled, 60000);
867 let mut solution = problem.create_initial_solution();
868
869 let mut rng = rand::rngs::StdRng::seed_from_u64(42);
870
871 let ruin_result = problem.ruin_random(&mut solution, 0.5, &mut rng);
873 let after_ruin_placed = solution.placed.len();
874
875 let recreate_result = problem.recreate_best_fit(&mut solution, &ruin_result);
877
878 assert!(recreate_result.placed_count > 0 || after_ruin_placed == solution.placed.len());
879 assert_eq!(recreate_result.recreate_type, RecreateType::BestFit);
880 }
881
882 #[test]
883 fn test_run_gdrr_nesting() {
884 let geometries = create_test_geometries();
885 let boundary = create_test_boundary();
886 let config = Config::default();
887 let gdrr_config = GdrrConfig::new()
888 .with_max_iterations(50)
889 .with_time_limit_ms(5000);
890 let cancelled = Arc::new(AtomicBool::new(false));
891
892 let result = run_gdrr_nesting(&geometries, &boundary, &config, &gdrr_config, cancelled);
893
894 assert!(!result.placements.is_empty());
895 assert!(result.utilization > 0.0);
896 }
897
898 #[test]
899 fn test_gdrr_nesting_full_cycle() {
900 let geometries = create_test_geometries();
901 let boundary = create_test_boundary();
902 let config = Config::default();
903 let cancelled = Arc::new(AtomicBool::new(false));
904
905 let mut problem = GdrrNestingProblem::new(geometries, boundary, config, cancelled, 60000);
906
907 let gdrr_config = GdrrConfig::new().with_max_iterations(10).with_seed(42);
908
909 let runner = GdrrRunner::new(gdrr_config);
910 let result: GdrrResult<GdrrNestingSolution> = runner.run(&mut problem, |progress| {
911 assert!(progress.iteration <= 10);
912 });
913
914 assert!(result.iterations <= 10);
915 assert!(!result.best_solution.placed.is_empty());
916 }
917}