1use crate::boundary::Boundary3D;
7use crate::geometry::Geometry3D;
8use crate::packing_utils::{
9 build_instances, build_unplaced_list, layer_place_items, packing_fitness, InstanceInfo,
10 PlacementItem,
11};
12use rand::prelude::*;
13use std::sync::atomic::{AtomicBool, Ordering};
14use std::sync::Arc;
15use u_nesting_core::ga::{GaConfig, GaProblem, GaRunner, Individual};
16use u_nesting_core::solver::Config;
17use u_nesting_core::SolveResult;
18
19#[derive(Debug, Clone)]
21pub struct PackingChromosome {
22 pub order: Vec<usize>,
24 pub orientations: Vec<usize>,
26 fitness: f64,
28 placed_count: usize,
30 total_count: usize,
32}
33
34impl PackingChromosome {
35 pub fn new(num_instances: usize) -> Self {
37 Self {
38 order: (0..num_instances).collect(),
39 orientations: vec![0; num_instances],
40 fitness: f64::NEG_INFINITY,
41 placed_count: 0,
42 total_count: num_instances,
43 }
44 }
45
46 pub fn random_with_options<R: Rng>(
48 num_instances: usize,
49 orientation_counts: &[usize],
50 rng: &mut R,
51 ) -> Self {
52 let mut order: Vec<usize> = (0..num_instances).collect();
53 order.shuffle(rng);
54
55 let orientations: Vec<usize> = orientation_counts
56 .iter()
57 .map(|&count| rng.random_range(0..count.max(1)))
58 .collect();
59
60 Self {
61 order,
62 orientations,
63 fitness: f64::NEG_INFINITY,
64 placed_count: 0,
65 total_count: num_instances,
66 }
67 }
68
69 pub fn set_fitness(&mut self, fitness: f64, placed_count: usize) {
71 self.fitness = fitness;
72 self.placed_count = placed_count;
73 }
74
75 pub fn order_crossover<R: Rng>(
77 &self,
78 other: &Self,
79 orientation_counts: &[usize],
80 rng: &mut R,
81 ) -> Self {
82 let n = self.order.len();
83 if n < 2 {
84 return self.clone();
85 }
86
87 let (mut p1, mut p2) = (rng.random_range(0..n), rng.random_range(0..n));
89 if p1 > p2 {
90 std::mem::swap(&mut p1, &mut p2);
91 }
92
93 let mut child_order = vec![usize::MAX; n];
95 let mut used = vec![false; n];
96
97 for i in p1..=p2 {
98 child_order[i] = self.order[i];
99 used[self.order[i]] = true;
100 }
101
102 let mut j = (p2 + 1) % n;
104 for i in 0..n {
105 let idx = (p2 + 1 + i) % n;
106 if child_order[idx] == usize::MAX {
107 while used[other.order[j]] {
108 j = (j + 1) % n;
109 }
110 child_order[idx] = other.order[j];
111 used[other.order[j]] = true;
112 j = (j + 1) % n;
113 }
114 }
115
116 let orientations: Vec<usize> = self
118 .orientations
119 .iter()
120 .zip(&other.orientations)
121 .enumerate()
122 .map(|(i, (a, b))| {
123 let max_orient = orientation_counts.get(i).copied().unwrap_or(1);
124 let chosen = if rng.random() { *a } else { *b };
125 chosen % max_orient.max(1)
126 })
127 .collect();
128
129 Self {
130 order: child_order,
131 orientations,
132 fitness: f64::NEG_INFINITY,
133 placed_count: 0,
134 total_count: self.total_count,
135 }
136 }
137
138 pub fn swap_mutate<R: Rng>(&mut self, rng: &mut R) {
140 if self.order.len() < 2 {
141 return;
142 }
143
144 let i = rng.random_range(0..self.order.len());
145 let j = rng.random_range(0..self.order.len());
146 self.order.swap(i, j);
147 self.fitness = f64::NEG_INFINITY;
148 }
149
150 pub fn orientation_mutate<R: Rng>(&mut self, orientation_counts: &[usize], rng: &mut R) {
152 if self.orientations.is_empty() {
153 return;
154 }
155
156 let idx = rng.random_range(0..self.orientations.len());
157 let max_orient = orientation_counts.get(idx).copied().unwrap_or(1);
158 if max_orient > 1 {
159 self.orientations[idx] = rng.random_range(0..max_orient);
160 self.fitness = f64::NEG_INFINITY;
161 }
162 }
163
164 pub fn inversion_mutate<R: Rng>(&mut self, rng: &mut R) {
166 let n = self.order.len();
167 if n < 2 {
168 return;
169 }
170
171 let (mut p1, mut p2) = (rng.random_range(0..n), rng.random_range(0..n));
172 if p1 > p2 {
173 std::mem::swap(&mut p1, &mut p2);
174 }
175
176 self.order[p1..=p2].reverse();
177 self.fitness = f64::NEG_INFINITY;
178 }
179}
180
181impl Individual for PackingChromosome {
182 type Fitness = f64;
183
184 fn fitness(&self) -> f64 {
185 self.fitness
186 }
187
188 fn random<R: Rng>(_rng: &mut R) -> Self {
189 Self::new(0)
191 }
192
193 fn crossover<R: Rng>(&self, other: &Self, rng: &mut R) -> Self {
194 let fake_counts = vec![6; self.orientations.len()];
196 self.order_crossover(other, &fake_counts, rng)
197 }
198
199 fn mutate<R: Rng>(&mut self, rng: &mut R) {
200 let r: f64 = rng.random();
202 if r < 0.5 {
203 self.swap_mutate(rng);
204 } else if r < 0.8 {
205 self.inversion_mutate(rng);
206 } else {
207 let fake_counts = vec![6; self.orientations.len()];
209 self.orientation_mutate(&fake_counts, rng);
210 }
211 }
212}
213
214pub struct PackingProblem {
216 geometries: Vec<Geometry3D>,
218 boundary: Boundary3D,
220 config: Config,
222 instances: Vec<InstanceInfo>,
224 orientation_counts: Vec<usize>,
226 cancelled: Arc<AtomicBool>,
228}
229
230impl PackingProblem {
231 pub fn new(
233 geometries: Vec<Geometry3D>,
234 boundary: Boundary3D,
235 config: Config,
236 cancelled: Arc<AtomicBool>,
237 ) -> Self {
238 let instances = build_instances(&geometries);
239 let orientation_counts: Vec<usize> =
240 instances.iter().map(|i| i.orientation_count).collect();
241
242 Self {
243 geometries,
244 boundary,
245 config,
246 instances,
247 orientation_counts,
248 cancelled,
249 }
250 }
251
252 pub fn num_instances(&self) -> usize {
254 self.instances.len()
255 }
256
257 pub fn orientation_counts(&self) -> &[usize] {
259 &self.orientation_counts
260 }
261
262 pub fn decode(
264 &self,
265 chromosome: &PackingChromosome,
266 ) -> (Vec<u_nesting_core::Placement<f64>>, f64, usize) {
267 let items: Vec<PlacementItem> = chromosome
268 .order
269 .iter()
270 .map(|&idx| PlacementItem {
271 instance_idx: idx,
272 orientation_idx: chromosome.orientations.get(idx).copied().unwrap_or(0),
273 })
274 .collect();
275
276 let result = layer_place_items(
277 &items,
278 &self.instances,
279 &self.geometries,
280 &self.boundary,
281 &self.config,
282 &self.cancelled,
283 );
284 (result.placements, result.utilization, result.placed_count)
285 }
286}
287
288impl GaProblem for PackingProblem {
289 type Individual = PackingChromosome;
290
291 fn evaluate(&self, individual: &mut Self::Individual) {
292 let (_, utilization, placed_count) = self.decode(individual);
293 let fitness = packing_fitness(placed_count, individual.total_count, utilization);
294 individual.set_fitness(fitness, placed_count);
295 }
296
297 fn initialize_population<R: Rng>(&self, size: usize, rng: &mut R) -> Vec<Self::Individual> {
298 (0..size)
299 .map(|_| {
300 PackingChromosome::random_with_options(
301 self.num_instances(),
302 &self.orientation_counts,
303 rng,
304 )
305 })
306 .collect()
307 }
308
309 fn on_generation(
310 &self,
311 generation: u32,
312 best: &Self::Individual,
313 _population: &[Self::Individual],
314 ) {
315 log::debug!(
316 "GA 3D Packing Gen {}: fitness={:.4}, placed={}/{}",
317 generation,
318 best.fitness(),
319 best.placed_count,
320 best.total_count
321 );
322 }
323}
324
325pub fn run_ga_packing(
327 geometries: &[Geometry3D],
328 boundary: &Boundary3D,
329 config: &Config,
330 ga_config: GaConfig,
331 cancelled: Arc<AtomicBool>,
332) -> SolveResult<f64> {
333 let problem = PackingProblem::new(
334 geometries.to_vec(),
335 boundary.clone(),
336 config.clone(),
337 cancelled.clone(),
338 );
339
340 let runner = GaRunner::new(ga_config, problem);
341
342 #[cfg(not(target_arch = "wasm32"))]
344 {
345 let cancel_handle = runner.cancel_handle();
346 let cancelled_clone = cancelled.clone();
347 std::thread::spawn(move || {
348 while !cancelled_clone.load(Ordering::Relaxed) {
349 std::thread::sleep(std::time::Duration::from_millis(100));
350 }
351 cancel_handle.store(true, Ordering::Relaxed);
352 });
353 }
354
355 let ga_result = runner.run();
356
357 let problem = PackingProblem::new(
359 geometries.to_vec(),
360 boundary.clone(),
361 config.clone(),
362 Arc::new(AtomicBool::new(false)),
363 );
364
365 let (placements, utilization, _placed_count) = problem.decode(&ga_result.best);
366 let unplaced = build_unplaced_list(&placements, geometries);
367
368 let mut result = SolveResult::new();
369 result.placements = placements;
370 result.unplaced = unplaced;
371 result.boundaries_used = 1;
372 result.utilization = utilization;
373 result.computation_time_ms = ga_result.elapsed.as_millis() as u64;
374 result.generations = Some(ga_result.generations);
375 result.best_fitness = Some(ga_result.best.fitness());
376 result.fitness_history = Some(ga_result.history);
377 result.strategy = Some("GeneticAlgorithm".to_string());
378 result.cancelled = cancelled.load(Ordering::Relaxed);
379 result.target_reached = ga_result.target_reached;
380
381 result
382}
383
384#[cfg(test)]
385mod tests {
386 use super::*;
387
388 #[test]
389 fn test_packing_chromosome_crossover() {
390 let mut rng = rand::rng();
391 let orientation_counts = vec![6, 6, 6, 6, 6];
392 let parent1 = PackingChromosome::random_with_options(5, &orientation_counts, &mut rng);
393 let parent2 = PackingChromosome::random_with_options(5, &orientation_counts, &mut rng);
394
395 let child = parent1.order_crossover(&parent2, &orientation_counts, &mut rng);
396
397 assert_eq!(child.order.len(), 5);
399 let mut sorted = child.order.clone();
400 sorted.sort();
401 assert_eq!(sorted, (0..5).collect::<Vec<_>>());
402
403 for (i, &orient) in child.orientations.iter().enumerate() {
405 assert!(orient < orientation_counts[i]);
406 }
407 }
408
409 #[test]
410 fn test_packing_chromosome_mutation() {
411 let mut rng = rand::rng();
412 let orientation_counts = vec![6, 6, 6, 6, 6];
413 let mut chromosome =
414 PackingChromosome::random_with_options(5, &orientation_counts, &mut rng);
415
416 chromosome.swap_mutate(&mut rng);
417
418 let mut sorted = chromosome.order.clone();
420 sorted.sort();
421 assert_eq!(sorted, (0..5).collect::<Vec<_>>());
422 }
423
424 #[test]
425 fn test_ga_packing_basic() {
426 let geometries = vec![
427 Geometry3D::new("B1", 20.0, 20.0, 20.0).with_quantity(2),
428 Geometry3D::new("B2", 15.0, 15.0, 15.0).with_quantity(2),
429 ];
430
431 let boundary = Boundary3D::new(100.0, 80.0, 50.0);
432 let config = Config::default();
433 let ga_config = GaConfig::default()
434 .with_population_size(20)
435 .with_max_generations(10);
436
437 let result = run_ga_packing(
438 &geometries,
439 &boundary,
440 &config,
441 ga_config,
442 Arc::new(AtomicBool::new(false)),
443 );
444
445 assert!(result.utilization > 0.0);
446 assert!(!result.placements.is_empty());
447 }
448
449 #[test]
450 fn test_ga_packing_all_placed() {
451 let geometries = vec![Geometry3D::new("B1", 20.0, 20.0, 20.0).with_quantity(4)];
452
453 let boundary = Boundary3D::new(100.0, 100.0, 100.0);
454 let config = Config::default();
455 let ga_config = GaConfig::default()
456 .with_population_size(30)
457 .with_max_generations(20);
458
459 let result = run_ga_packing(
460 &geometries,
461 &boundary,
462 &config,
463 ga_config,
464 Arc::new(AtomicBool::new(false)),
465 );
466
467 assert_eq!(result.placements.len(), 4);
469 assert!(result.unplaced.is_empty());
470 }
471
472 #[test]
473 fn test_ga_packing_with_orientations() {
474 use crate::geometry::OrientationConstraint;
475 let geometries = vec![Geometry3D::new("B1", 50.0, 10.0, 10.0)
477 .with_quantity(3)
478 .with_orientation(OrientationConstraint::Any)];
479
480 let boundary = Boundary3D::new(60.0, 60.0, 60.0);
481 let config = Config::default();
482 let ga_config = GaConfig::default()
483 .with_population_size(30)
484 .with_max_generations(20);
485
486 let result = run_ga_packing(
487 &geometries,
488 &boundary,
489 &config,
490 ga_config,
491 Arc::new(AtomicBool::new(false)),
492 );
493
494 assert!(result.utilization > 0.0);
495 assert!(!result.placements.is_empty());
496 }
497
498 #[test]
499 fn test_packing_problem_decode() {
500 let geometries = vec![Geometry3D::new("B1", 20.0, 20.0, 20.0).with_quantity(2)];
501
502 let boundary = Boundary3D::new(100.0, 100.0, 100.0);
503 let config = Config::default();
504 let cancelled = Arc::new(AtomicBool::new(false));
505
506 let problem = PackingProblem::new(geometries, boundary, config, cancelled);
507
508 assert_eq!(problem.num_instances(), 2);
509
510 let chromosome = PackingChromosome::new(2);
512 let (placements, utilization, placed_count) = problem.decode(&chromosome);
513
514 assert_eq!(placed_count, 2);
515 assert_eq!(placements.len(), 2);
516 assert!(utilization > 0.0);
517 }
518
519 #[test]
520 fn test_ga_packing_mass_constraint() {
521 let geometries = vec![Geometry3D::new("B1", 20.0, 20.0, 20.0)
522 .with_quantity(10)
523 .with_mass(100.0)];
524
525 let boundary = Boundary3D::new(100.0, 100.0, 100.0).with_max_mass(350.0);
526 let config = Config::default();
527 let ga_config = GaConfig::default()
528 .with_population_size(20)
529 .with_max_generations(10);
530
531 let result = run_ga_packing(
532 &geometries,
533 &boundary,
534 &config,
535 ga_config,
536 Arc::new(AtomicBool::new(false)),
537 );
538
539 assert!(result.placements.len() <= 3);
541 }
542}