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