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