1use crate::boundary::Boundary2D;
7use crate::clamp_placement_to_boundary;
8use crate::geometry::Geometry2D;
9use crate::nfp::{
10 compute_ifp_with_margin_and_mirror, compute_nfp_mirrored, find_bottom_left_placement,
11 verify_no_overlap_mirrored, Nfp, PlacedGeometry,
12};
13use rand::prelude::*;
14use std::sync::atomic::{AtomicBool, Ordering};
15use std::sync::Arc;
16use u_nesting_core::ga::{GaConfig, GaProblem, GaProgress, GaRunner, Individual};
17use u_nesting_core::geometry::{Boundary, Geometry};
18use u_nesting_core::solver::{Config, ProgressCallback, ProgressInfo};
19use u_nesting_core::{Placement, SolveResult};
20
21use crate::placement_utils::{expand_nfp, nesting_fitness, shrink_ifp, InstanceInfo};
22
23#[derive(Debug, Clone)]
25pub struct NestingChromosome {
26 pub order: Vec<usize>,
28 pub rotations: Vec<usize>,
30 pub mirrors: Vec<bool>,
38 fitness: f64,
40 placed_count: usize,
42 total_count: usize,
44}
45
46impl NestingChromosome {
47 pub fn new(num_instances: usize, _rotation_options: usize) -> Self {
49 Self {
50 order: (0..num_instances).collect(),
51 rotations: vec![0; num_instances],
52 mirrors: vec![false; num_instances],
53 fitness: f64::NEG_INFINITY,
54 placed_count: 0,
55 total_count: num_instances,
56 }
57 }
58
59 pub fn random_with_options<R: Rng>(
61 num_instances: usize,
62 rotation_options: usize,
63 rng: &mut R,
64 ) -> Self {
65 let mut order: Vec<usize> = (0..num_instances).collect();
66 order.shuffle(rng);
67
68 let rotations: Vec<usize> = (0..num_instances)
69 .map(|_| rng.random_range(0..rotation_options.max(1)))
70 .collect();
71
72 let mirrors: Vec<bool> = (0..num_instances).map(|_| rng.random()).collect();
73
74 Self {
75 order,
76 rotations,
77 mirrors,
78 fitness: f64::NEG_INFINITY,
79 placed_count: 0,
80 total_count: num_instances,
81 }
82 }
83
84 pub fn set_fitness(&mut self, fitness: f64, placed_count: usize) {
86 self.fitness = fitness;
87 self.placed_count = placed_count;
88 }
89
90 pub fn order_crossover<R: Rng>(&self, other: &Self, rng: &mut R) -> Self {
92 let n = self.order.len();
93 if n < 2 {
94 return self.clone();
95 }
96
97 let (mut p1, mut p2) = (rng.random_range(0..n), rng.random_range(0..n));
99 if p1 > p2 {
100 std::mem::swap(&mut p1, &mut p2);
101 }
102
103 let mut child_order = vec![usize::MAX; n];
105 let mut used = vec![false; n];
106
107 for i in p1..=p2 {
108 child_order[i] = self.order[i];
109 used[self.order[i]] = true;
110 }
111
112 let mut j = (p2 + 1) % n;
114 for i in 0..n {
115 let idx = (p2 + 1 + i) % n;
116 if child_order[idx] == usize::MAX {
117 while used[other.order[j]] {
118 j = (j + 1) % n;
119 }
120 child_order[idx] = other.order[j];
121 used[other.order[j]] = true;
122 j = (j + 1) % n;
123 }
124 }
125
126 let rotations: Vec<usize> = self
128 .rotations
129 .iter()
130 .zip(&other.rotations)
131 .map(|(a, b)| if rng.random() { *a } else { *b })
132 .collect();
133
134 let mirrors: Vec<bool> = self
136 .mirrors
137 .iter()
138 .zip(&other.mirrors)
139 .map(|(a, b)| if rng.random() { *a } else { *b })
140 .collect();
141
142 Self {
143 order: child_order,
144 rotations,
145 mirrors,
146 fitness: f64::NEG_INFINITY,
147 placed_count: 0,
148 total_count: self.total_count,
149 }
150 }
151
152 pub fn swap_mutate<R: Rng>(&mut self, rng: &mut R) {
154 if self.order.len() < 2 {
155 return;
156 }
157
158 let i = rng.random_range(0..self.order.len());
159 let j = rng.random_range(0..self.order.len());
160 self.order.swap(i, j);
161 self.fitness = f64::NEG_INFINITY;
162 }
163
164 pub fn rotation_mutate<R: Rng>(&mut self, rotation_options: usize, rng: &mut R) {
166 if self.rotations.is_empty() || rotation_options <= 1 {
167 return;
168 }
169
170 let idx = rng.random_range(0..self.rotations.len());
171 self.rotations[idx] = rng.random_range(0..rotation_options);
172 self.fitness = f64::NEG_INFINITY;
173 }
174
175 pub fn mirror_mutate<R: Rng>(&mut self, rng: &mut R) {
180 if self.mirrors.is_empty() {
181 return;
182 }
183
184 let idx = rng.random_range(0..self.mirrors.len());
185 self.mirrors[idx] = !self.mirrors[idx];
186 self.fitness = f64::NEG_INFINITY;
187 }
188
189 pub fn inversion_mutate<R: Rng>(&mut self, rng: &mut R) {
191 let n = self.order.len();
192 if n < 2 {
193 return;
194 }
195
196 let (mut p1, mut p2) = (rng.random_range(0..n), rng.random_range(0..n));
197 if p1 > p2 {
198 std::mem::swap(&mut p1, &mut p2);
199 }
200
201 self.order[p1..=p2].reverse();
202 self.fitness = f64::NEG_INFINITY;
203 }
204}
205
206impl Individual for NestingChromosome {
207 type Fitness = f64;
208
209 fn fitness(&self) -> f64 {
210 self.fitness
211 }
212
213 fn random<R: Rng>(rng: &mut R) -> Self {
214 Self::random_with_options(0, 1, rng)
216 }
217
218 fn crossover<R: Rng>(&self, other: &Self, rng: &mut R) -> Self {
219 self.order_crossover(other, rng)
220 }
221
222 fn mutate<R: Rng>(&mut self, rng: &mut R) {
223 let r: f64 = rng.random();
225 if r < 0.45 {
226 self.swap_mutate(rng);
227 } else if r < 0.70 {
228 self.inversion_mutate(rng);
229 } else if r < 0.85 {
230 self.rotation_mutate(4, rng);
232 } else {
233 self.mirror_mutate(rng);
234 }
235 }
236}
237
238pub struct NestingProblem {
240 geometries: Vec<Geometry2D>,
242 boundary: Boundary2D,
244 config: Config,
246 instances: Vec<InstanceInfo>,
248 rotation_angles: Vec<Vec<f64>>,
250 rotation_options: usize,
252 cancelled: Arc<AtomicBool>,
254}
255
256impl NestingProblem {
257 pub fn new(
259 geometries: Vec<Geometry2D>,
260 boundary: Boundary2D,
261 config: Config,
262 cancelled: Arc<AtomicBool>,
263 ) -> Self {
264 let mut instances = Vec::new();
266 let mut rotation_angles = Vec::new();
267
268 for (geom_idx, geom) in geometries.iter().enumerate() {
269 let angles = geom.rotations();
271 let angles = if angles.is_empty() { vec![0.0] } else { angles };
272 rotation_angles.push(angles);
273
274 for instance_num in 0..geom.quantity() {
276 instances.push(InstanceInfo {
277 geometry_idx: geom_idx,
278 instance_num,
279 });
280 }
281 }
282
283 let rotation_options = rotation_angles.iter().map(|a| a.len()).max().unwrap_or(1);
285
286 Self {
287 geometries,
288 boundary,
289 config,
290 instances,
291 rotation_angles,
292 rotation_options,
293 cancelled,
294 }
295 }
296
297 pub fn num_instances(&self) -> usize {
299 self.instances.len()
300 }
301
302 pub fn rotation_options(&self) -> usize {
304 self.rotation_options
305 }
306
307 pub fn decode(&self, chromosome: &NestingChromosome) -> (Vec<Placement<f64>>, f64, usize) {
309 let mut placements = Vec::new();
310 let mut placed_geometries: Vec<PlacedGeometry> = Vec::new();
311 let mut total_placed_area = 0.0;
312 let mut placed_count = 0;
313
314 let margin = self.config.margin;
315 let spacing = self.config.spacing;
316
317 let boundary_polygon = self.get_boundary_polygon_with_margin(margin);
319
320 let sample_step = self.compute_sample_step();
322
323 for &instance_idx in chromosome.order.iter() {
325 if self.cancelled.load(Ordering::Relaxed) {
326 break;
327 }
328
329 if instance_idx >= self.instances.len() {
330 continue;
331 }
332
333 let info = &self.instances[instance_idx];
334 let geom = &self.geometries[info.geometry_idx];
335
336 let rotation_idx = chromosome.rotations.get(instance_idx).copied().unwrap_or(0);
338 let rotation_angle = self
339 .rotation_angles
340 .get(info.geometry_idx)
341 .and_then(|angles| angles.get(rotation_idx % angles.len()))
342 .copied()
343 .unwrap_or(0.0);
344
345 let mirror = chromosome
348 .mirrors
349 .get(instance_idx)
350 .copied()
351 .unwrap_or(false)
352 && geom.allow_flip();
353
354 let ifp = match compute_ifp_with_margin_and_mirror(
356 &boundary_polygon,
357 geom,
358 rotation_angle,
359 0.0,
360 mirror,
361 ) {
362 Ok(ifp) => ifp,
363 Err(_) => {
364 continue;
365 }
366 };
367
368 if ifp.is_empty() {
369 continue;
370 }
371
372 let mut nfps: Vec<Nfp> = Vec::new();
374 for placed in &placed_geometries {
375 let placed_exterior = placed.translated_exterior();
378 let placed_geom = Geometry2D::new(format!("_placed_{}", placed.geometry.id()))
379 .with_polygon(placed_exterior);
380
381 if let Ok(nfp) =
382 compute_nfp_mirrored(&placed_geom, geom, rotation_angle, false, mirror)
383 {
384 let expanded = self.expand_nfp(&nfp, spacing);
385 nfps.push(expanded);
386 }
387 }
388
389 let ifp_shrunk = self.shrink_ifp(&ifp, spacing);
391
392 let nfp_refs: Vec<&Nfp> = nfps.iter().collect();
395 let placement_result = find_bottom_left_placement(&ifp_shrunk, &nfp_refs, sample_step);
396 if let Some((x, y)) = placement_result {
397 let geom_aabb = geom.aabb_at_rotation_mirrored(rotation_angle, mirror);
401 let boundary_aabb = self.boundary.aabb();
402
403 if let Some((clamped_x, clamped_y)) =
404 clamp_placement_to_boundary(x, y, geom_aabb, boundary_aabb)
405 {
406 let was_clamped = (clamped_x - x).abs() > 1e-6 || (clamped_y - y).abs() > 1e-6;
409 if was_clamped {
410 if !verify_no_overlap_mirrored(
412 geom,
413 (clamped_x, clamped_y),
414 rotation_angle,
415 mirror,
416 &placed_geometries,
417 ) {
418 continue; }
420 }
421
422 let placement = Placement::new_2d(
423 geom.id().clone(),
424 info.instance_num,
425 clamped_x,
426 clamped_y,
427 rotation_angle,
428 )
429 .with_mirrored(mirror);
430
431 placements.push(placement);
432 placed_geometries.push(
433 PlacedGeometry::new(geom.clone(), (clamped_x, clamped_y), rotation_angle)
434 .with_mirrored(mirror),
435 );
436 total_placed_area += geom.measure();
437 placed_count += 1;
438 }
439 }
440 }
441
442 let utilization = total_placed_area / self.boundary.measure();
443 (placements, utilization, placed_count)
444 }
445
446 fn get_boundary_polygon_with_margin(&self, margin: f64) -> Vec<(f64, f64)> {
448 let (b_min, b_max) = self.boundary.aabb();
449 vec![
450 (b_min[0] + margin, b_min[1] + margin),
451 (b_max[0] - margin, b_min[1] + margin),
452 (b_max[0] - margin, b_max[1] - margin),
453 (b_min[0] + margin, b_max[1] - margin),
454 ]
455 }
456
457 fn compute_sample_step(&self) -> f64 {
459 if self.geometries.is_empty() {
460 return 1.0;
461 }
462
463 let mut min_dim = f64::INFINITY;
464 for geom in &self.geometries {
465 let (g_min, g_max) = geom.aabb();
466 let width = g_max[0] - g_min[0];
467 let height = g_max[1] - g_min[1];
468 min_dim = min_dim.min(width).min(height);
469 }
470
471 (min_dim / 4.0).clamp(0.5, 10.0)
472 }
473
474 fn expand_nfp(&self, nfp: &Nfp, spacing: f64) -> Nfp {
476 expand_nfp(nfp, spacing)
477 }
478
479 fn shrink_ifp(&self, ifp: &Nfp, spacing: f64) -> Nfp {
481 shrink_ifp(ifp, spacing)
482 }
483}
484
485impl GaProblem for NestingProblem {
486 type Individual = NestingChromosome;
487
488 fn evaluate(&self, individual: &mut Self::Individual) {
489 let (_, utilization, placed_count) = self.decode(individual);
490 let fitness = nesting_fitness(placed_count, individual.total_count, utilization);
491 individual.set_fitness(fitness, placed_count);
492 }
493
494 fn initialize_population<R: Rng>(&self, size: usize, rng: &mut R) -> Vec<Self::Individual> {
495 (0..size)
496 .map(|_| {
497 NestingChromosome::random_with_options(
498 self.num_instances(),
499 self.rotation_options(),
500 rng,
501 )
502 })
503 .collect()
504 }
505
506 fn on_generation(
507 &self,
508 generation: u32,
509 best: &Self::Individual,
510 _population: &[Self::Individual],
511 ) {
512 log::debug!(
513 "GA Generation {}: fitness={:.4}, placed={}/{}",
514 generation,
515 best.fitness(),
516 best.placed_count,
517 best.total_count
518 );
519 }
520}
521
522pub fn run_ga_nesting(
524 geometries: &[Geometry2D],
525 boundary: &Boundary2D,
526 config: &Config,
527 ga_config: GaConfig,
528 cancelled: Arc<AtomicBool>,
529) -> SolveResult<f64> {
530 let problem = NestingProblem::new(
531 geometries.to_vec(),
532 boundary.clone(),
533 config.clone(),
534 cancelled.clone(),
535 );
536
537 let runner = GaRunner::new(ga_config, problem);
538
539 #[cfg(not(target_arch = "wasm32"))]
541 {
542 let cancel_handle = runner.cancel_handle();
543 let cancelled_clone = cancelled.clone();
544 std::thread::spawn(move || {
545 while !cancelled_clone.load(Ordering::Relaxed) {
546 std::thread::sleep(std::time::Duration::from_millis(100));
547 }
548 cancel_handle.store(true, Ordering::Relaxed);
549 });
550 }
551
552 let ga_result = match config.seed {
555 Some(seed) => runner.run_with_rng(&mut rand::rngs::StdRng::seed_from_u64(seed)),
556 None => runner.run(),
557 };
558
559 let problem = NestingProblem::new(
561 geometries.to_vec(),
562 boundary.clone(),
563 config.clone(),
564 Arc::new(AtomicBool::new(false)),
565 );
566
567 let (placements, utilization, _placed_count) = problem.decode(&ga_result.best);
568
569 let mut unplaced = Vec::new();
571 let mut placed_ids: std::collections::HashSet<String> = std::collections::HashSet::new();
572 for p in &placements {
573 placed_ids.insert(p.geometry_id.clone());
574 }
575 for geom in geometries {
576 if !placed_ids.contains(geom.id()) {
577 unplaced.push(geom.id().clone());
578 }
579 }
580
581 let mut result = SolveResult::new();
582 result.placements = placements;
583 result.unplaced = unplaced;
584 result.boundaries_used = 1;
585 result.utilization = utilization;
586 result.computation_time_ms = ga_result.elapsed.as_millis() as u64;
587 result.generations = Some(ga_result.generations);
588 result.best_fitness = Some(ga_result.best.fitness());
589 result.fitness_history = Some(ga_result.history);
590 result.strategy = Some("GeneticAlgorithm".to_string());
591 result.cancelled = cancelled.load(Ordering::Relaxed);
592 result.target_reached = ga_result.target_reached;
593
594 result
595}
596
597pub fn run_ga_nesting_with_progress(
599 geometries: &[Geometry2D],
600 boundary: &Boundary2D,
601 config: &Config,
602 ga_config: GaConfig,
603 cancelled: Arc<AtomicBool>,
604 progress_callback: ProgressCallback,
605) -> SolveResult<f64> {
606 let total_items = geometries.iter().map(|g| g.quantity()).sum::<usize>();
607
608 let problem = NestingProblem::new(
609 geometries.to_vec(),
610 boundary.clone(),
611 config.clone(),
612 cancelled.clone(),
613 );
614
615 let runner = GaRunner::new(ga_config.clone(), problem);
616
617 #[cfg(not(target_arch = "wasm32"))]
619 {
620 let cancel_handle = runner.cancel_handle();
621 let cancelled_clone = cancelled.clone();
622 std::thread::spawn(move || {
623 while !cancelled_clone.load(Ordering::Relaxed) {
624 std::thread::sleep(std::time::Duration::from_millis(100));
625 }
626 cancel_handle.store(true, Ordering::Relaxed);
627 });
628 }
629
630 let max_generations = ga_config.max_generations;
636 let progress_adapter = move |ga_progress: GaProgress<f64>| {
637 let info = ProgressInfo::new()
638 .with_iteration(ga_progress.generation, max_generations)
639 .with_fitness(ga_progress.best_fitness)
640 .with_utilization(ga_progress.best_fitness) .with_items(0, total_items) .with_elapsed(ga_progress.elapsed.as_millis() as u64)
643 .with_phase("Genetic Algorithm".to_string());
644
645 let info = if !ga_progress.running {
646 info.finished()
647 } else {
648 info
649 };
650
651 progress_callback(info);
652 };
653 let ga_result = match config.seed {
654 Some(seed) => runner.run_with_rng_and_progress(
655 &mut rand::rngs::StdRng::seed_from_u64(seed),
656 Some(progress_adapter),
657 ),
658 None => runner.run_with_progress(progress_adapter),
659 };
660
661 let problem = NestingProblem::new(
663 geometries.to_vec(),
664 boundary.clone(),
665 config.clone(),
666 Arc::new(AtomicBool::new(false)),
667 );
668
669 let (placements, utilization, _placed_count) = problem.decode(&ga_result.best);
670
671 let mut unplaced = Vec::new();
673 let mut placed_ids: std::collections::HashSet<String> = std::collections::HashSet::new();
674 for p in &placements {
675 placed_ids.insert(p.geometry_id.clone());
676 }
677 for geom in geometries {
678 if !placed_ids.contains(geom.id()) {
679 unplaced.push(geom.id().clone());
680 }
681 }
682
683 let mut result = SolveResult::new();
684 result.placements = placements;
685 result.unplaced = unplaced;
686 result.boundaries_used = 1;
687 result.utilization = utilization;
688 result.computation_time_ms = ga_result.elapsed.as_millis() as u64;
689 result.generations = Some(ga_result.generations);
690 result.best_fitness = Some(ga_result.best.fitness());
691 result.fitness_history = Some(ga_result.history);
692 result.strategy = Some("GeneticAlgorithm".to_string());
693 result.cancelled = cancelled.load(Ordering::Relaxed);
694 result.target_reached = ga_result.target_reached;
695
696 result
697}
698
699#[cfg(test)]
700mod tests {
701 use super::*;
702
703 #[test]
704 fn test_nesting_chromosome_crossover() {
705 let mut rng = rand::rng();
706 let parent1 = NestingChromosome::random_with_options(10, 4, &mut rng);
707 let parent2 = NestingChromosome::random_with_options(10, 4, &mut rng);
708
709 let child = parent1.order_crossover(&parent2, &mut rng);
710
711 assert_eq!(child.order.len(), 10);
713 let mut sorted = child.order.clone();
714 sorted.sort();
715 assert_eq!(sorted, (0..10).collect::<Vec<_>>());
716 }
717
718 #[test]
719 fn test_nesting_chromosome_mutation() {
720 let mut rng = rand::rng();
721 let mut chromosome = NestingChromosome::random_with_options(10, 4, &mut rng);
722
723 chromosome.swap_mutate(&mut rng);
724
725 let mut sorted = chromosome.order.clone();
727 sorted.sort();
728 assert_eq!(sorted, (0..10).collect::<Vec<_>>());
729 }
730
731 #[test]
732 fn test_nesting_chromosome_mirrors_gene_present() {
733 let mut rng = rand::rng();
734 let chromosome = NestingChromosome::random_with_options(10, 4, &mut rng);
735 assert_eq!(chromosome.mirrors.len(), 10);
736
737 let fixed = NestingChromosome::new(10, 4);
738 assert_eq!(fixed.mirrors, vec![false; 10]);
739 }
740
741 #[test]
742 fn test_nesting_chromosome_mirror_crossover() {
743 let mut rng = rand::rng();
744 let mut parent1 = NestingChromosome::random_with_options(10, 4, &mut rng);
750 let mut parent2 = NestingChromosome::random_with_options(10, 4, &mut rng);
751 parent1.mirrors = vec![true; 10];
752 parent2.mirrors = vec![false; 10];
753
754 let child = parent1.order_crossover(&parent2, &mut rng);
755 assert_eq!(child.mirrors.len(), 10);
756 assert!(child.mirrors.iter().all(|&m| m || !m)); }
758
759 #[test]
760 fn test_mirror_mutate_flips_bit() {
761 let mut rng = rand::rng();
762 let mut chromosome = NestingChromosome::new(5, 1);
763 assert_eq!(chromosome.mirrors, vec![false; 5]);
764
765 let mut single = NestingChromosome::new(1, 1);
767 single.mirror_mutate(&mut rng);
768 assert!(single.mirrors[0]);
769 single.mirror_mutate(&mut rng);
770 assert!(!single.mirrors[0]);
771
772 chromosome.mirror_mutate(&mut rng);
773 assert_eq!(chromosome.mirrors.iter().filter(|&&m| m).count(), 1);
774 }
775
776 fn chiral_l(id: &str) -> Geometry2D {
779 Geometry2D::l_shape(id, 30.0, 20.0, 20.0, 10.0)
780 }
781
782 fn polygons_overlap(a: &[(f64, f64)], b: &[(f64, f64)]) -> bool {
783 for i in 0..a.len() {
784 let (a1, a2) = (a[i], a[(i + 1) % a.len()]);
785 for j in 0..b.len() {
786 let (b1, b2) = (b[j], b[(j + 1) % b.len()]);
787 if crate::polygon_ops::segments_intersect(a1, a2, b1, b2) {
788 return true;
789 }
790 }
791 }
792 false
793 }
794
795 #[test]
802 fn test_ga_decode_mirror_no_overlap() {
803 let geometries = vec![chiral_l("L").with_flip(true).with_quantity(2)];
804 let boundary = Boundary2D::rectangle(65.0, 45.0);
805 let config = Config::default().with_spacing(1.0);
806 let problem = NestingProblem::new(
807 geometries.clone(),
808 boundary,
809 config,
810 Arc::new(AtomicBool::new(false)),
811 );
812
813 let mut chromosome = NestingChromosome::new(2, 1);
818 chromosome.mirrors = vec![false, true];
819
820 let (placements, utilization, placed_count) = problem.decode(&chromosome);
821
822 assert_eq!(
823 placed_count, 2,
824 "both instances should fit in this boundary"
825 );
826 assert_eq!(placements.len(), 2);
827 assert!(utilization > 0.0);
828 assert!(!placements[0].mirrored);
829 assert!(
830 placements[1].mirrored,
831 "instance 1's mirror gene was true and allow_flip is set — decode() must honor it"
832 );
833
834 let poly0 = PlacedGeometry::new(
835 geometries[0].clone(),
836 (placements[0].x(), placements[0].y()),
837 placements[0].angle(),
838 )
839 .with_mirrored(placements[0].mirrored)
840 .translated_exterior();
841 let poly1 = PlacedGeometry::new(
842 geometries[0].clone(),
843 (placements[1].x(), placements[1].y()),
844 placements[1].angle(),
845 )
846 .with_mirrored(placements[1].mirrored)
847 .translated_exterior();
848 assert!(
849 !polygons_overlap(&poly0, &poly1),
850 "unmirrored instance 0 and mirrored instance 1 must not overlap"
851 );
852 }
853
854 #[test]
855 fn test_ga_decode_mirror_ignored_without_allow_flip() {
856 let geometries = vec![chiral_l("L").with_quantity(1)];
859 let boundary = Boundary2D::rectangle(65.0, 45.0);
860 let problem = NestingProblem::new(
861 geometries,
862 boundary,
863 Config::default(),
864 Arc::new(AtomicBool::new(false)),
865 );
866
867 let mut chromosome = NestingChromosome::new(1, 1);
868 chromosome.mirrors = vec![true];
869
870 let (placements, _utilization, placed_count) = problem.decode(&chromosome);
871 assert_eq!(placed_count, 1);
872 assert!(
873 !placements[0].mirrored,
874 "allow_flip=false must suppress the mirror gene"
875 );
876 }
877
878 #[test]
879 fn test_ga_nesting_basic() {
880 let geometries = vec![
881 Geometry2D::rectangle("R1", 20.0, 10.0).with_quantity(2),
882 Geometry2D::rectangle("R2", 15.0, 15.0).with_quantity(2),
883 ];
884
885 let boundary = Boundary2D::rectangle(100.0, 50.0);
886 let config = Config::default();
887 let ga_config = GaConfig::default()
888 .with_population_size(20)
889 .with_max_generations(10);
890
891 let result = run_ga_nesting(
892 &geometries,
893 &boundary,
894 &config,
895 ga_config,
896 Arc::new(AtomicBool::new(false)),
897 );
898
899 assert!(result.utilization > 0.0);
900 assert!(!result.placements.is_empty());
901 }
902
903 #[test]
904 fn test_ga_nesting_all_placed() {
905 let geometries = vec![Geometry2D::rectangle("R1", 20.0, 20.0).with_quantity(4)];
906
907 let boundary = Boundary2D::rectangle(100.0, 100.0);
908 let config = Config::default();
909 let ga_config = GaConfig::default()
910 .with_population_size(30)
911 .with_max_generations(20);
912
913 let result = run_ga_nesting(
914 &geometries,
915 &boundary,
916 &config,
917 ga_config,
918 Arc::new(AtomicBool::new(false)),
919 );
920
921 assert_eq!(result.placements.len(), 4);
923 assert!(result.unplaced.is_empty());
924 }
925
926 #[test]
927 fn test_ga_nesting_with_rotation() {
928 let geometries = vec![Geometry2D::rectangle("R1", 30.0, 10.0)
930 .with_quantity(3)
931 .with_rotations(vec![0.0, 90.0])];
932
933 let boundary = Boundary2D::rectangle(50.0, 50.0);
934 let config = Config::default();
935 let ga_config = GaConfig::default()
936 .with_population_size(30)
937 .with_max_generations(20);
938
939 let result = run_ga_nesting(
940 &geometries,
941 &boundary,
942 &config,
943 ga_config,
944 Arc::new(AtomicBool::new(false)),
945 );
946
947 assert!(result.utilization > 0.0);
948 assert!(!result.placements.is_empty());
950 }
951
952 #[test]
953 fn test_nesting_problem_decode() {
954 let geometries = vec![Geometry2D::rectangle("R1", 20.0, 10.0).with_quantity(2)];
955
956 let boundary = Boundary2D::rectangle(100.0, 50.0);
957 let config = Config::default();
958 let cancelled = Arc::new(AtomicBool::new(false));
959
960 let problem = NestingProblem::new(geometries, boundary, config, cancelled);
961
962 assert_eq!(problem.num_instances(), 2);
963
964 let chromosome = NestingChromosome::new(2, 1);
966 let (placements, utilization, placed_count) = problem.decode(&chromosome);
967
968 assert_eq!(placed_count, 2);
969 assert_eq!(placements.len(), 2);
970 assert!(utilization > 0.0);
971 }
972}