1use crate::boundary::Boundary2D;
7use crate::clamp_placement_to_boundary;
8use crate::geometry::Geometry2D;
9use crate::nfp::{compute_ifp, compute_nfp, find_bottom_left_placement, Nfp, PlacedGeometry};
10use rand::prelude::*;
11use std::sync::atomic::{AtomicBool, Ordering};
12use std::sync::Arc;
13use u_nesting_core::ga::{GaConfig, GaProblem, GaProgress, GaRunner, Individual};
14use u_nesting_core::geometry::{Boundary, Geometry};
15use u_nesting_core::solver::{Config, ProgressCallback, ProgressInfo};
16use u_nesting_core::{Placement, SolveResult};
17
18#[derive(Debug, Clone)]
20pub struct NestingChromosome {
21 pub order: Vec<usize>,
23 pub rotations: Vec<usize>,
25 fitness: f64,
27 placed_count: usize,
29 total_count: usize,
31}
32
33impl NestingChromosome {
34 pub fn new(num_instances: usize, _rotation_options: usize) -> Self {
36 Self {
37 order: (0..num_instances).collect(),
38 rotations: vec![0; num_instances],
39 fitness: f64::NEG_INFINITY,
40 placed_count: 0,
41 total_count: num_instances,
42 }
43 }
44
45 pub fn random_with_options<R: Rng>(
47 num_instances: usize,
48 rotation_options: usize,
49 rng: &mut R,
50 ) -> Self {
51 let mut order: Vec<usize> = (0..num_instances).collect();
52 order.shuffle(rng);
53
54 let rotations: Vec<usize> = (0..num_instances)
55 .map(|_| rng.gen_range(0..rotation_options.max(1)))
56 .collect();
57
58 Self {
59 order,
60 rotations,
61 fitness: f64::NEG_INFINITY,
62 placed_count: 0,
63 total_count: num_instances,
64 }
65 }
66
67 pub fn set_fitness(&mut self, fitness: f64, placed_count: usize) {
69 self.fitness = fitness;
70 self.placed_count = placed_count;
71 }
72
73 pub fn order_crossover<R: Rng>(&self, other: &Self, rng: &mut R) -> Self {
75 let n = self.order.len();
76 if n < 2 {
77 return self.clone();
78 }
79
80 let (mut p1, mut p2) = (rng.gen_range(0..n), rng.gen_range(0..n));
82 if p1 > p2 {
83 std::mem::swap(&mut p1, &mut p2);
84 }
85
86 let mut child_order = vec![usize::MAX; n];
88 let mut used = vec![false; n];
89
90 for i in p1..=p2 {
91 child_order[i] = self.order[i];
92 used[self.order[i]] = true;
93 }
94
95 let mut j = (p2 + 1) % n;
97 for i in 0..n {
98 let idx = (p2 + 1 + i) % n;
99 if child_order[idx] == usize::MAX {
100 while used[other.order[j]] {
101 j = (j + 1) % n;
102 }
103 child_order[idx] = other.order[j];
104 used[other.order[j]] = true;
105 j = (j + 1) % n;
106 }
107 }
108
109 let rotations: Vec<usize> = self
111 .rotations
112 .iter()
113 .zip(&other.rotations)
114 .map(|(a, b)| if rng.gen() { *a } else { *b })
115 .collect();
116
117 Self {
118 order: child_order,
119 rotations,
120 fitness: f64::NEG_INFINITY,
121 placed_count: 0,
122 total_count: self.total_count,
123 }
124 }
125
126 pub fn swap_mutate<R: Rng>(&mut self, rng: &mut R) {
128 if self.order.len() < 2 {
129 return;
130 }
131
132 let i = rng.gen_range(0..self.order.len());
133 let j = rng.gen_range(0..self.order.len());
134 self.order.swap(i, j);
135 self.fitness = f64::NEG_INFINITY;
136 }
137
138 pub fn rotation_mutate<R: Rng>(&mut self, rotation_options: usize, rng: &mut R) {
140 if self.rotations.is_empty() || rotation_options <= 1 {
141 return;
142 }
143
144 let idx = rng.gen_range(0..self.rotations.len());
145 self.rotations[idx] = rng.gen_range(0..rotation_options);
146 self.fitness = f64::NEG_INFINITY;
147 }
148
149 pub fn inversion_mutate<R: Rng>(&mut self, rng: &mut R) {
151 let n = self.order.len();
152 if n < 2 {
153 return;
154 }
155
156 let (mut p1, mut p2) = (rng.gen_range(0..n), rng.gen_range(0..n));
157 if p1 > p2 {
158 std::mem::swap(&mut p1, &mut p2);
159 }
160
161 self.order[p1..=p2].reverse();
162 self.fitness = f64::NEG_INFINITY;
163 }
164}
165
166impl Individual for NestingChromosome {
167 type Fitness = f64;
168
169 fn fitness(&self) -> f64 {
170 self.fitness
171 }
172
173 fn random<R: Rng>(rng: &mut R) -> Self {
174 Self::random_with_options(0, 1, rng)
176 }
177
178 fn crossover<R: Rng>(&self, other: &Self, rng: &mut R) -> Self {
179 self.order_crossover(other, rng)
180 }
181
182 fn mutate<R: Rng>(&mut self, rng: &mut R) {
183 let r: f64 = rng.gen();
185 if r < 0.5 {
186 self.swap_mutate(rng);
187 } else if r < 0.8 {
188 self.inversion_mutate(rng);
189 } else {
190 self.rotation_mutate(4, rng);
192 }
193 }
194}
195
196#[derive(Debug, Clone)]
198struct InstanceInfo {
199 geometry_idx: usize,
201 instance_num: usize,
203}
204
205pub struct NestingProblem {
207 geometries: Vec<Geometry2D>,
209 boundary: Boundary2D,
211 config: Config,
213 instances: Vec<InstanceInfo>,
215 rotation_angles: Vec<Vec<f64>>,
217 rotation_options: usize,
219 cancelled: Arc<AtomicBool>,
221}
222
223impl NestingProblem {
224 pub fn new(
226 geometries: Vec<Geometry2D>,
227 boundary: Boundary2D,
228 config: Config,
229 cancelled: Arc<AtomicBool>,
230 ) -> Self {
231 let mut instances = Vec::new();
233 let mut rotation_angles = Vec::new();
234
235 for (geom_idx, geom) in geometries.iter().enumerate() {
236 let angles = geom.rotations();
238 let angles = if angles.is_empty() { vec![0.0] } else { angles };
239 rotation_angles.push(angles);
240
241 for instance_num in 0..geom.quantity() {
243 instances.push(InstanceInfo {
244 geometry_idx: geom_idx,
245 instance_num,
246 });
247 }
248 }
249
250 let rotation_options = rotation_angles.iter().map(|a| a.len()).max().unwrap_or(1);
252
253 Self {
254 geometries,
255 boundary,
256 config,
257 instances,
258 rotation_angles,
259 rotation_options,
260 cancelled,
261 }
262 }
263
264 pub fn num_instances(&self) -> usize {
266 self.instances.len()
267 }
268
269 pub fn rotation_options(&self) -> usize {
271 self.rotation_options
272 }
273
274 pub fn decode(&self, chromosome: &NestingChromosome) -> (Vec<Placement<f64>>, f64, usize) {
276 let mut placements = Vec::new();
277 let mut placed_geometries: Vec<PlacedGeometry> = Vec::new();
278 let mut total_placed_area = 0.0;
279 let mut placed_count = 0;
280
281 let margin = self.config.margin;
282 let spacing = self.config.spacing;
283
284 let boundary_polygon = self.get_boundary_polygon_with_margin(margin);
286
287 let sample_step = self.compute_sample_step();
289
290 for &instance_idx in chromosome.order.iter() {
292 if self.cancelled.load(Ordering::Relaxed) {
293 break;
294 }
295
296 if instance_idx >= self.instances.len() {
297 continue;
298 }
299
300 let info = &self.instances[instance_idx];
301 let geom = &self.geometries[info.geometry_idx];
302
303 let rotation_idx = chromosome.rotations.get(instance_idx).copied().unwrap_or(0);
305 let rotation_angle = self
306 .rotation_angles
307 .get(info.geometry_idx)
308 .and_then(|angles| angles.get(rotation_idx % angles.len()))
309 .copied()
310 .unwrap_or(0.0);
311
312 let ifp = match compute_ifp(&boundary_polygon, geom, rotation_angle) {
314 Ok(ifp) => ifp,
315 Err(_) => {
316 continue;
317 }
318 };
319
320 if ifp.is_empty() {
321 continue;
322 }
323
324 let mut nfps: Vec<Nfp> = Vec::new();
326 for placed in &placed_geometries {
327 let placed_exterior = placed.translated_exterior();
328 let placed_geom = Geometry2D::new(format!("_placed_{}", placed.geometry.id()))
329 .with_polygon(placed_exterior);
330
331 if let Ok(nfp) = compute_nfp(&placed_geom, geom, rotation_angle) {
332 let expanded = self.expand_nfp(&nfp, spacing);
333 nfps.push(expanded);
334 }
335 }
336
337 let ifp_shrunk = self.shrink_ifp(&ifp, spacing);
339
340 let nfp_refs: Vec<&Nfp> = nfps.iter().collect();
344 let placement_result = find_bottom_left_placement(&ifp_shrunk, &nfp_refs, sample_step);
345 if let Some((x, y)) = placement_result {
346 let geom_aabb = geom.aabb_at_rotation(rotation_angle);
348 let boundary_aabb = self.boundary.aabb();
349
350 if let Some((clamped_x, clamped_y)) =
351 clamp_placement_to_boundary(x, y, geom_aabb, boundary_aabb)
352 {
353 let placement = Placement::new_2d(
354 geom.id().clone(),
355 info.instance_num,
356 clamped_x,
357 clamped_y,
358 rotation_angle,
359 );
360
361 placements.push(placement);
362 placed_geometries.push(PlacedGeometry::new(
363 geom.clone(),
364 (clamped_x, clamped_y),
365 rotation_angle,
366 ));
367 total_placed_area += geom.measure();
368 placed_count += 1;
369 }
370 }
371 }
372
373 let utilization = total_placed_area / self.boundary.measure();
374 (placements, utilization, placed_count)
375 }
376
377 fn get_boundary_polygon_with_margin(&self, margin: f64) -> Vec<(f64, f64)> {
379 let (b_min, b_max) = self.boundary.aabb();
380 vec![
381 (b_min[0] + margin, b_min[1] + margin),
382 (b_max[0] - margin, b_min[1] + margin),
383 (b_max[0] - margin, b_max[1] - margin),
384 (b_min[0] + margin, b_max[1] - margin),
385 ]
386 }
387
388 fn compute_sample_step(&self) -> f64 {
390 if self.geometries.is_empty() {
391 return 1.0;
392 }
393
394 let mut min_dim = f64::INFINITY;
395 for geom in &self.geometries {
396 let (g_min, g_max) = geom.aabb();
397 let width = g_max[0] - g_min[0];
398 let height = g_max[1] - g_min[1];
399 min_dim = min_dim.min(width).min(height);
400 }
401
402 (min_dim / 4.0).clamp(0.5, 10.0)
403 }
404
405 fn expand_nfp(&self, nfp: &Nfp, spacing: f64) -> Nfp {
407 if spacing <= 0.0 {
408 return nfp.clone();
409 }
410
411 let expanded_polygons: Vec<Vec<(f64, f64)>> = nfp
412 .polygons
413 .iter()
414 .map(|polygon| {
415 let (cx, cy) = polygon_centroid(polygon);
416 polygon
417 .iter()
418 .map(|&(x, y)| {
419 let dx = x - cx;
420 let dy = y - cy;
421 let dist = (dx * dx + dy * dy).sqrt();
422 if dist > 1e-10 {
423 let scale = (dist + spacing) / dist;
424 (cx + dx * scale, cy + dy * scale)
425 } else {
426 (x, y)
427 }
428 })
429 .collect()
430 })
431 .collect();
432
433 Nfp::from_polygons(expanded_polygons)
434 }
435
436 fn shrink_ifp(&self, ifp: &Nfp, spacing: f64) -> Nfp {
438 if spacing <= 0.0 {
439 return ifp.clone();
440 }
441
442 let shrunk_polygons: Vec<Vec<(f64, f64)>> = ifp
443 .polygons
444 .iter()
445 .filter_map(|polygon| {
446 let (cx, cy) = polygon_centroid(polygon);
447 let shrunk: Vec<(f64, f64)> = polygon
448 .iter()
449 .map(|&(x, y)| {
450 let dx = x - cx;
451 let dy = y - cy;
452 let dist = (dx * dx + dy * dy).sqrt();
453 if dist > spacing + 1e-10 {
454 let scale = (dist - spacing) / dist;
455 (cx + dx * scale, cy + dy * scale)
456 } else {
457 (cx, cy)
458 }
459 })
460 .collect();
461
462 if shrunk.len() >= 3 {
463 Some(shrunk)
464 } else {
465 None
466 }
467 })
468 .collect();
469
470 Nfp::from_polygons(shrunk_polygons)
471 }
472}
473
474fn polygon_centroid(polygon: &[(f64, f64)]) -> (f64, f64) {
476 if polygon.is_empty() {
477 return (0.0, 0.0);
478 }
479
480 let sum: (f64, f64) = polygon
481 .iter()
482 .fold((0.0, 0.0), |acc, &(x, y)| (acc.0 + x, acc.1 + y));
483 let n = polygon.len() as f64;
484 (sum.0 / n, sum.1 / n)
485}
486
487impl GaProblem for NestingProblem {
488 type Individual = NestingChromosome;
489
490 fn evaluate(&self, individual: &mut Self::Individual) {
491 let (_, utilization, placed_count) = self.decode(individual);
492
493 let placement_ratio = placed_count as f64 / individual.total_count.max(1) as f64;
495
496 let fitness = placement_ratio * 100.0 + utilization * 10.0;
499
500 individual.set_fitness(fitness, placed_count);
501 }
502
503 fn initialize_population<R: Rng>(&self, size: usize, rng: &mut R) -> Vec<Self::Individual> {
504 (0..size)
505 .map(|_| {
506 NestingChromosome::random_with_options(
507 self.num_instances(),
508 self.rotation_options(),
509 rng,
510 )
511 })
512 .collect()
513 }
514
515 fn on_generation(
516 &self,
517 generation: u32,
518 best: &Self::Individual,
519 _population: &[Self::Individual],
520 ) {
521 log::debug!(
522 "GA Generation {}: fitness={:.4}, placed={}/{}",
523 generation,
524 best.fitness(),
525 best.placed_count,
526 best.total_count
527 );
528 }
529}
530
531pub fn run_ga_nesting(
533 geometries: &[Geometry2D],
534 boundary: &Boundary2D,
535 config: &Config,
536 ga_config: GaConfig,
537 cancelled: Arc<AtomicBool>,
538) -> SolveResult<f64> {
539 let problem = NestingProblem::new(
540 geometries.to_vec(),
541 boundary.clone(),
542 config.clone(),
543 cancelled.clone(),
544 );
545
546 let runner = GaRunner::new(ga_config, problem);
547
548 let cancel_handle = runner.cancel_handle();
550 let cancelled_clone = cancelled.clone();
551 std::thread::spawn(move || {
552 while !cancelled_clone.load(Ordering::Relaxed) {
553 std::thread::sleep(std::time::Duration::from_millis(100));
554 }
555 cancel_handle.store(true, Ordering::Relaxed);
556 });
557
558 let ga_result = runner.run();
559
560 let problem = NestingProblem::new(
562 geometries.to_vec(),
563 boundary.clone(),
564 config.clone(),
565 Arc::new(AtomicBool::new(false)),
566 );
567
568 let (placements, utilization, _placed_count) = problem.decode(&ga_result.best);
569
570 let mut unplaced = Vec::new();
572 let mut placed_ids: std::collections::HashSet<String> = std::collections::HashSet::new();
573 for p in &placements {
574 placed_ids.insert(p.geometry_id.clone());
575 }
576 for geom in geometries {
577 if !placed_ids.contains(geom.id()) {
578 unplaced.push(geom.id().clone());
579 }
580 }
581
582 let mut result = SolveResult::new();
583 result.placements = placements;
584 result.unplaced = unplaced;
585 result.boundaries_used = 1;
586 result.utilization = utilization;
587 result.computation_time_ms = ga_result.elapsed.as_millis() as u64;
588 result.generations = Some(ga_result.generations);
589 result.best_fitness = Some(ga_result.best.fitness());
590 result.fitness_history = Some(ga_result.history);
591 result.strategy = Some("GeneticAlgorithm".to_string());
592 result.cancelled = cancelled.load(Ordering::Relaxed);
593 result.target_reached = ga_result.target_reached;
594
595 result
596}
597
598pub fn run_ga_nesting_with_progress(
600 geometries: &[Geometry2D],
601 boundary: &Boundary2D,
602 config: &Config,
603 ga_config: GaConfig,
604 cancelled: Arc<AtomicBool>,
605 progress_callback: ProgressCallback,
606) -> SolveResult<f64> {
607 let total_items = geometries.iter().map(|g| g.quantity()).sum::<usize>();
608
609 let problem = NestingProblem::new(
610 geometries.to_vec(),
611 boundary.clone(),
612 config.clone(),
613 cancelled.clone(),
614 );
615
616 let runner = GaRunner::new(ga_config.clone(), problem);
617
618 let cancel_handle = runner.cancel_handle();
620 let cancelled_clone = cancelled.clone();
621 std::thread::spawn(move || {
622 while !cancelled_clone.load(Ordering::Relaxed) {
623 std::thread::sleep(std::time::Duration::from_millis(100));
624 }
625 cancel_handle.store(true, Ordering::Relaxed);
626 });
627
628 let max_generations = ga_config.max_generations;
630 let ga_result = runner.run_with_progress(move |ga_progress: GaProgress<f64>| {
631 let info = ProgressInfo::new()
632 .with_iteration(ga_progress.generation, max_generations)
633 .with_fitness(ga_progress.best_fitness)
634 .with_utilization(ga_progress.best_fitness) .with_items(0, total_items) .with_elapsed(ga_progress.elapsed.as_millis() as u64)
637 .with_phase("Genetic Algorithm".to_string());
638
639 let info = if !ga_progress.running {
640 info.finished()
641 } else {
642 info
643 };
644
645 progress_callback(info);
646 });
647
648 let problem = NestingProblem::new(
650 geometries.to_vec(),
651 boundary.clone(),
652 config.clone(),
653 Arc::new(AtomicBool::new(false)),
654 );
655
656 let (placements, utilization, _placed_count) = problem.decode(&ga_result.best);
657
658 let mut unplaced = Vec::new();
660 let mut placed_ids: std::collections::HashSet<String> = std::collections::HashSet::new();
661 for p in &placements {
662 placed_ids.insert(p.geometry_id.clone());
663 }
664 for geom in geometries {
665 if !placed_ids.contains(geom.id()) {
666 unplaced.push(geom.id().clone());
667 }
668 }
669
670 let mut result = SolveResult::new();
671 result.placements = placements;
672 result.unplaced = unplaced;
673 result.boundaries_used = 1;
674 result.utilization = utilization;
675 result.computation_time_ms = ga_result.elapsed.as_millis() as u64;
676 result.generations = Some(ga_result.generations);
677 result.best_fitness = Some(ga_result.best.fitness());
678 result.fitness_history = Some(ga_result.history);
679 result.strategy = Some("GeneticAlgorithm".to_string());
680 result.cancelled = cancelled.load(Ordering::Relaxed);
681 result.target_reached = ga_result.target_reached;
682
683 result
684}
685
686#[cfg(test)]
687mod tests {
688 use super::*;
689
690 #[test]
691 fn test_nesting_chromosome_crossover() {
692 let mut rng = thread_rng();
693 let parent1 = NestingChromosome::random_with_options(10, 4, &mut rng);
694 let parent2 = NestingChromosome::random_with_options(10, 4, &mut rng);
695
696 let child = parent1.order_crossover(&parent2, &mut rng);
697
698 assert_eq!(child.order.len(), 10);
700 let mut sorted = child.order.clone();
701 sorted.sort();
702 assert_eq!(sorted, (0..10).collect::<Vec<_>>());
703 }
704
705 #[test]
706 fn test_nesting_chromosome_mutation() {
707 let mut rng = thread_rng();
708 let mut chromosome = NestingChromosome::random_with_options(10, 4, &mut rng);
709
710 chromosome.swap_mutate(&mut rng);
711
712 let mut sorted = chromosome.order.clone();
714 sorted.sort();
715 assert_eq!(sorted, (0..10).collect::<Vec<_>>());
716 }
717
718 #[test]
719 fn test_ga_nesting_basic() {
720 let geometries = vec![
721 Geometry2D::rectangle("R1", 20.0, 10.0).with_quantity(2),
722 Geometry2D::rectangle("R2", 15.0, 15.0).with_quantity(2),
723 ];
724
725 let boundary = Boundary2D::rectangle(100.0, 50.0);
726 let config = Config::default();
727 let ga_config = GaConfig::default()
728 .with_population_size(20)
729 .with_max_generations(10);
730
731 let result = run_ga_nesting(
732 &geometries,
733 &boundary,
734 &config,
735 ga_config,
736 Arc::new(AtomicBool::new(false)),
737 );
738
739 assert!(result.utilization > 0.0);
740 assert!(!result.placements.is_empty());
741 }
742
743 #[test]
744 fn test_ga_nesting_all_placed() {
745 let geometries = vec![Geometry2D::rectangle("R1", 20.0, 20.0).with_quantity(4)];
746
747 let boundary = Boundary2D::rectangle(100.0, 100.0);
748 let config = Config::default();
749 let ga_config = GaConfig::default()
750 .with_population_size(30)
751 .with_max_generations(20);
752
753 let result = run_ga_nesting(
754 &geometries,
755 &boundary,
756 &config,
757 ga_config,
758 Arc::new(AtomicBool::new(false)),
759 );
760
761 assert_eq!(result.placements.len(), 4);
763 assert!(result.unplaced.is_empty());
764 }
765
766 #[test]
767 fn test_ga_nesting_with_rotation() {
768 let geometries = vec![Geometry2D::rectangle("R1", 30.0, 10.0)
770 .with_quantity(3)
771 .with_rotations(vec![0.0, 90.0])];
772
773 let boundary = Boundary2D::rectangle(50.0, 50.0);
774 let config = Config::default();
775 let ga_config = GaConfig::default()
776 .with_population_size(30)
777 .with_max_generations(20);
778
779 let result = run_ga_nesting(
780 &geometries,
781 &boundary,
782 &config,
783 ga_config,
784 Arc::new(AtomicBool::new(false)),
785 );
786
787 assert!(result.utilization > 0.0);
788 assert!(!result.placements.is_empty());
790 }
791
792 #[test]
793 fn test_nesting_problem_decode() {
794 let geometries = vec![Geometry2D::rectangle("R1", 20.0, 10.0).with_quantity(2)];
795
796 let boundary = Boundary2D::rectangle(100.0, 50.0);
797 let config = Config::default();
798 let cancelled = Arc::new(AtomicBool::new(false));
799
800 let problem = NestingProblem::new(geometries, boundary, config, cancelled);
801
802 assert_eq!(problem.num_instances(), 2);
803
804 let chromosome = NestingChromosome::new(2, 1);
806 let (placements, utilization, placed_count) = problem.decode(&chromosome);
807
808 assert_eq!(placed_count, 2);
809 assert_eq!(placements.len(), 2);
810 assert!(utilization > 0.0);
811 }
812}