use u_nesting_core::ga::{GaConfig, PermutationChromosome};
use u_nesting_core::geometry::{Orientation3D, RotationConstraint};
use u_nesting_core::placement::{Placement, PlacementStats};
use u_nesting_core::result::SolveResult;
use u_nesting_core::transform::{Transform2D, Transform3D, AABB2D, AABB3D};
mod transform_tests {
use super::*;
use std::f64::consts::PI;
#[test]
fn test_transform2d_composition() {
let t1 = Transform2D::translation(10.0, 0.0);
let t2 = Transform2D::rotation(PI / 2.0);
let composed = t1.then(&t2);
let (x, y) = composed.transform_point(0.0, 0.0);
assert!((x - 10.0).abs() < 1e-10, "x = {}", x);
assert!((y - 0.0).abs() < 1e-10, "y = {}", y);
}
#[test]
fn test_transform2d_inverse_composition() {
let t = Transform2D::new(5.0, 10.0, PI / 6.0);
let inv = t.inverse();
let identity = t.then(&inv);
assert!(identity.is_identity(1e-10));
let identity2 = inv.then(&t);
assert!(identity2.is_identity(1e-10));
}
#[test]
fn test_transform3d_translation() {
let t: Transform3D<f64> = Transform3D::translation(1.0, 2.0, 3.0);
let (x, y, z) = t.transform_point(0.0, 0.0, 0.0);
assert!((x - 1.0).abs() < 1e-10);
assert!((y - 2.0).abs() < 1e-10);
assert!((z - 3.0).abs() < 1e-10);
}
#[test]
fn test_transform3d_rotation_x() {
let t: Transform3D<f64> = Transform3D::rotation(PI / 2.0, 0.0, 0.0);
let (x, y, z) = t.transform_point(0.0, 1.0, 0.0);
assert!((x - 0.0).abs() < 1e-10, "x = {}", x);
assert!((y - 0.0).abs() < 1e-10, "y = {}", y);
assert!((z - 1.0).abs() < 1e-10, "z = {}", z);
}
}
mod aabb_tests {
use super::*;
#[test]
fn test_aabb2d_operations() {
let a: AABB2D<f64> = AABB2D::new(0.0, 0.0, 10.0, 10.0);
let b: AABB2D<f64> = AABB2D::new(5.0, 5.0, 15.0, 15.0);
assert!(a.intersects(&b));
let intersection = a.intersection(&b).unwrap();
assert!((intersection.min_x - 5.0).abs() < 1e-10);
assert!((intersection.max_x - 10.0).abs() < 1e-10);
let union = a.union(&b);
assert!((union.min_x - 0.0).abs() < 1e-10);
assert!((union.max_x - 15.0).abs() < 1e-10);
assert!((a.area() - 100.0).abs() < 1e-10);
}
#[test]
fn test_aabb2d_no_intersection() {
let a: AABB2D<f64> = AABB2D::new(0.0, 0.0, 5.0, 5.0);
let b: AABB2D<f64> = AABB2D::new(10.0, 10.0, 15.0, 15.0);
assert!(!a.intersects(&b));
assert!(a.intersection(&b).is_none());
}
#[test]
fn test_aabb3d_operations() {
let a: AABB3D<f64> = AABB3D::new(0.0, 0.0, 0.0, 10.0, 10.0, 10.0);
let b: AABB3D<f64> = AABB3D::new(5.0, 5.0, 5.0, 15.0, 15.0, 15.0);
assert!(a.intersects(&b));
assert!((a.volume() - 1000.0).abs() < 1e-10);
let union = a.union(&b);
assert!((union.volume() - 3375.0).abs() < 1e-10); }
#[test]
fn test_aabb_from_points() {
let points: Vec<(f64, f64)> = vec![(1.0, 2.0), (5.0, 3.0), (2.0, 8.0), (7.0, 1.0)];
let aabb = AABB2D::from_points(&points).unwrap();
assert!((aabb.min_x - 1.0).abs() < 1e-10);
assert!((aabb.min_y - 1.0).abs() < 1e-10);
assert!((aabb.max_x - 7.0).abs() < 1e-10);
assert!((aabb.max_y - 8.0).abs() < 1e-10);
}
#[test]
fn test_aabb_expand() {
let aabb: AABB2D<f64> = AABB2D::new(5.0, 5.0, 10.0, 10.0);
let expanded = aabb.expand(2.0);
assert!((expanded.min_x - 3.0).abs() < 1e-10);
assert!((expanded.min_y - 3.0).abs() < 1e-10);
assert!((expanded.max_x - 12.0).abs() < 1e-10);
assert!((expanded.max_y - 12.0).abs() < 1e-10);
}
}
mod placement_tests {
use super::*;
#[test]
fn test_placement_transform_roundtrip() {
let original = Placement::new_2d("test".to_string(), 0, 15.0_f64, 25.0, 0.785);
let transform = original.to_transform_2d();
let recovered = Placement::from_transform_2d("test".to_string(), 0, &transform);
assert!((original.x() - recovered.x()).abs() < 1e-10);
assert!((original.y() - recovered.y()).abs() < 1e-10);
assert!((original.angle() - recovered.angle()).abs() < 1e-10);
}
#[test]
fn test_placement_3d_transform_roundtrip() {
let original = Placement::new_3d("box".to_string(), 1, 10.0_f64, 20.0, 30.0, 0.1, 0.2, 0.3);
let transform = original.to_transform_3d();
let recovered = Placement::from_transform_3d("box".to_string(), 1, &transform);
assert!((original.x() - recovered.x()).abs() < 1e-10);
assert!((original.y() - recovered.y()).abs() < 1e-10);
assert!((original.z().unwrap() - recovered.z().unwrap()).abs() < 1e-10);
}
#[test]
fn test_placement_stats_computation() {
let placements = vec![
Placement::new_2d("a".to_string(), 0, 0.0, 0.0, 0.0)
.with_rotation_index(0)
.with_boundary(0),
Placement::new_2d("a".to_string(), 1, 10.0, 0.0, 0.0)
.with_rotation_index(1)
.with_boundary(0)
.with_mirrored(true),
Placement::new_2d("b".to_string(), 0, 0.0, 10.0, 0.0)
.with_rotation_index(0)
.with_boundary(1),
Placement::new_2d("c".to_string(), 0, 20.0, 0.0, 0.0)
.with_rotation_index(2)
.with_boundary(0)
.with_mirrored(true),
];
let stats = PlacementStats::from_placements(&placements);
assert_eq!(stats.count, 4);
assert_eq!(stats.mirrored_count, 2);
assert_eq!(stats.rotation_distribution.get(&0), Some(&2));
assert_eq!(stats.rotation_distribution.get(&1), Some(&1));
assert_eq!(stats.rotation_distribution.get(&2), Some(&1));
assert_eq!(stats.boundary_distribution.get(&0), Some(&3));
assert_eq!(stats.boundary_distribution.get(&1), Some(&1));
}
}
mod rotation_constraint_tests {
use super::*;
use std::f64::consts::PI;
#[test]
fn test_rotation_none() {
let constraint: RotationConstraint<f64> = RotationConstraint::None;
assert!(constraint.is_fixed());
let angles = constraint.angles();
assert_eq!(angles.len(), 1);
assert!((angles[0] - 0.0).abs() < 1e-10);
}
#[test]
fn test_rotation_free() {
let constraint: RotationConstraint<f64> = RotationConstraint::Free;
assert!(!constraint.is_fixed());
let angles = constraint.angles();
assert!(angles.is_empty()); }
#[test]
fn test_rotation_axis_aligned() {
let constraint: RotationConstraint<f64> = RotationConstraint::axis_aligned();
let angles = constraint.angles();
assert_eq!(angles.len(), 4);
assert!((angles[0] - 0.0).abs() < 1e-10);
assert!((angles[1] - PI / 2.0).abs() < 1e-10);
assert!((angles[2] - PI).abs() < 1e-10);
assert!((angles[3] - 3.0 * PI / 2.0).abs() < 1e-10);
}
#[test]
fn test_rotation_steps() {
let constraint: RotationConstraint<f64> = RotationConstraint::steps(6);
let angles = constraint.angles();
assert_eq!(angles.len(), 6);
let step = 2.0 * PI / 6.0;
for (i, angle) in angles.iter().enumerate() {
let expected = step * i as f64;
assert!(
(angle - expected).abs() < 1e-10,
"angle[{}] = {}, expected {}",
i,
angle,
expected
);
}
}
}
mod orientation_3d_tests {
use super::*;
#[test]
fn test_orientation_counts() {
assert_eq!(Orientation3D::Fixed.count(), 1);
assert_eq!(Orientation3D::AxisAligned.count(), 6);
assert_eq!(Orientation3D::Orthogonal.count(), 24);
assert_eq!(Orientation3D::Free.count(), usize::MAX);
}
#[test]
fn test_orientation_is_fixed() {
assert!(Orientation3D::Fixed.is_fixed());
assert!(!Orientation3D::AxisAligned.is_fixed());
assert!(!Orientation3D::Orthogonal.is_fixed());
assert!(!Orientation3D::Free.is_fixed());
}
}
mod ga_tests {
use super::*;
use rand::prelude::*;
#[test]
fn test_ga_config_builder() {
let config = GaConfig::new()
.with_population_size(200)
.with_max_generations(1000)
.with_crossover_rate(0.9)
.with_mutation_rate(0.1)
.with_elite_count(10);
assert_eq!(config.population_size, 200);
assert_eq!(config.max_generations, 1000);
assert!((config.crossover_rate - 0.9).abs() < 1e-10);
assert!((config.mutation_rate - 0.1).abs() < 1e-10);
assert_eq!(config.elite_count, 10);
}
#[test]
fn test_ga_config_clamping() {
let config = GaConfig::new()
.with_crossover_rate(1.5) .with_mutation_rate(-0.1);
assert!((config.crossover_rate - 1.0).abs() < 1e-10);
assert!((config.mutation_rate - 0.0).abs() < 1e-10);
}
#[test]
fn test_permutation_chromosome_validity() {
let mut rng = StdRng::seed_from_u64(42);
let chromosome = PermutationChromosome::random_with_options(20, 4, &mut rng);
assert_eq!(chromosome.genes.len(), 20);
let mut sorted = chromosome.genes.clone();
sorted.sort();
assert_eq!(sorted, (0..20).collect::<Vec<_>>());
assert_eq!(chromosome.rotations.len(), 20);
for r in &chromosome.rotations {
assert!(*r < 4);
}
}
#[test]
fn test_permutation_crossover_validity() {
let mut rng = StdRng::seed_from_u64(42);
let parent1 = PermutationChromosome::random_with_options(15, 6, &mut rng);
let parent2 = PermutationChromosome::random_with_options(15, 6, &mut rng);
for _ in 0..100 {
let child = parent1.order_crossover(&parent2, &mut rng);
let mut sorted = child.genes.clone();
sorted.sort();
assert_eq!(
sorted,
(0..15).collect::<Vec<_>>(),
"Crossover produced invalid permutation"
);
}
}
#[test]
fn test_permutation_mutation_validity() {
let mut rng = StdRng::seed_from_u64(42);
let mut chromosome = PermutationChromosome::random_with_options(25, 8, &mut rng);
for _ in 0..100 {
chromosome.swap_mutate(&mut rng);
let mut sorted = chromosome.genes.clone();
sorted.sort();
assert_eq!(
sorted,
(0..25).collect::<Vec<_>>(),
"Swap mutation produced invalid permutation"
);
}
}
#[test]
fn test_permutation_inversion_mutation() {
let mut rng = StdRng::seed_from_u64(42);
let mut chromosome = PermutationChromosome::random_with_options(10, 2, &mut rng);
for _ in 0..50 {
chromosome.inversion_mutate(&mut rng);
let mut sorted = chromosome.genes.clone();
sorted.sort();
assert_eq!(
sorted,
(0..10).collect::<Vec<_>>(),
"Inversion mutation produced invalid permutation"
);
}
}
}
mod solve_result_tests {
use super::*;
#[test]
fn test_solve_result_creation() {
let placements = vec![
Placement::new_2d("a".to_string(), 0, 0.0, 0.0, 0.0),
Placement::new_2d("b".to_string(), 0, 10.0, 0.0, 0.0),
];
let result: SolveResult<f64> = SolveResult {
placements,
boundaries_used: 1,
utilization: 0.75,
unplaced: vec![],
computation_time_ms: 150,
generations: Some(50),
iterations: None,
best_fitness: Some(0.95),
fitness_history: Some(vec![0.5, 0.7, 0.85, 0.95]),
strategy: Some("BLF+GA".to_string()),
cancelled: false,
target_reached: true,
strip_stats: vec![],
total_piece_area: 0.0,
total_material_used: 0.0,
};
assert_eq!(result.placements.len(), 2);
assert_eq!(result.boundaries_used, 1);
assert!((result.utilization - 0.75).abs() < 1e-10);
assert!(result.target_reached);
assert!(!result.cancelled);
}
}