test(tlbo,umda,simulated_annealing,tabu_search,tpe,snes,rvea,spea2): pin comparison and geometry helpers
Phase 1 tests for eight more algorithms — the feasibility-first comparison helpers (better / better_than / compare_so / worse_than), plus algorithm-specific pure functions: - tlbo: best_index min/max/tie. - tpe: oriented_target sign-flip + penalty; split_good_bad partition and clamp-to-at-least-one-each. - snes: nes_utilities sum-to-zero + descending + positive-best. - rvea: unit_normalize (3-4-5 → 0.6/0.8), zero-vector passthrough; closest_reference smallest-angle; smallest_neighbor_angle = π/2 for orthogonal refs. - spea2: euclidean distance basics; binary_tournament prefers lower fitness.
This commit is contained in:
@@ -556,4 +556,41 @@ mod tests {
|
||||
);
|
||||
let _ = opt.run(&SchafferN1);
|
||||
}
|
||||
|
||||
// ---- Mutation-test pinned helpers --------------------------------------
|
||||
|
||||
#[test]
|
||||
fn unit_normalize_produces_unit_vector() {
|
||||
let v = unit_normalize(vec![3.0, 4.0]);
|
||||
let norm: f64 = v.iter().map(|x| x * x).sum::<f64>().sqrt();
|
||||
assert!((norm - 1.0).abs() < 1e-12);
|
||||
assert!((v[0] - 0.6).abs() < 1e-12);
|
||||
assert!((v[1] - 0.8).abs() < 1e-12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_normalize_zero_vector_unchanged() {
|
||||
// A (near-)zero vector is left as-is (no division by ~0).
|
||||
let v = unit_normalize(vec![0.0, 0.0]);
|
||||
assert_eq!(v, vec![0.0, 0.0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closest_reference_picks_smallest_angle() {
|
||||
// References along the two axes; a point near the x-axis associates
|
||||
// with reference 0 at a small angle.
|
||||
let refs = vec![vec![1.0, 0.0], vec![0.0, 1.0]];
|
||||
let (idx, angle) = closest_reference(&[1.0, 0.0], &refs);
|
||||
assert_eq!(idx, 0);
|
||||
assert!(angle.abs() < 1e-9, "angle = {angle}");
|
||||
let (idx2, _) = closest_reference(&[0.1, 1.0], &refs);
|
||||
assert_eq!(idx2, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smallest_neighbor_angle_of_orthogonal_refs_is_pi_over_2() {
|
||||
let refs = vec![vec![1.0, 0.0], vec![0.0, 1.0]];
|
||||
let a = smallest_neighbor_angle(&refs);
|
||||
assert!((a - std::f64::consts::FRAC_PI_2).abs() < 1e-9, "angle = {a}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,4 +407,26 @@ mod tests {
|
||||
);
|
||||
let _ = opt.run(&Sphere1D);
|
||||
}
|
||||
|
||||
// ---- Mutation-test pinned helpers --------------------------------------
|
||||
|
||||
use crate::core::evaluation::Evaluation;
|
||||
use crate::core::objective::Direction;
|
||||
|
||||
#[test]
|
||||
fn better_than_feasibility_first_and_direction() {
|
||||
let feasible = Evaluation::new(vec![100.0]);
|
||||
let infeasible = Evaluation::constrained(vec![0.0], 1.0);
|
||||
assert!(better_than(&feasible, &infeasible, Direction::Minimize));
|
||||
assert!(!better_than(&infeasible, &feasible, Direction::Minimize));
|
||||
let lo = Evaluation::new(vec![1.0]);
|
||||
let hi = Evaluation::new(vec![2.0]);
|
||||
assert!(better_than(&lo, &hi, Direction::Minimize));
|
||||
assert!(better_than(&hi, &lo, Direction::Maximize));
|
||||
let eq = Evaluation::new(vec![1.0]);
|
||||
assert!(!better_than(&lo, &eq, Direction::Minimize));
|
||||
let v_lo = Evaluation::constrained(vec![0.0], 0.2);
|
||||
let v_hi = Evaluation::constrained(vec![0.0], 0.8);
|
||||
assert!(better_than(&v_lo, &v_hi, Direction::Minimize));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,4 +457,46 @@ mod tests {
|
||||
let mut opt = make_optimizer(0);
|
||||
let _ = opt.run(&SchafferN1);
|
||||
}
|
||||
|
||||
// ---- Mutation-test pinned helpers --------------------------------------
|
||||
|
||||
use crate::core::evaluation::Evaluation;
|
||||
use crate::core::objective::Direction;
|
||||
|
||||
#[test]
|
||||
fn nes_utilities_sum_to_zero_and_are_descending() {
|
||||
// The NES utility weights are a shifted log-rank scheme; they sum
|
||||
// to (approximately) zero and the first (best-ranked) is largest.
|
||||
let u = nes_utilities(10);
|
||||
assert_eq!(u.len(), 10);
|
||||
let sum: f64 = u.iter().sum();
|
||||
assert!(sum.abs() < 1e-9, "utilities sum = {sum}");
|
||||
// Descending: best rank gets the most weight.
|
||||
for w in u.windows(2) {
|
||||
assert!(w[0] >= w[1] - 1e-12, "not descending: {:?}", u);
|
||||
}
|
||||
// The first utility is positive (it gets above-average weight).
|
||||
assert!(u[0] > 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compare_feasibility_first_and_direction() {
|
||||
let feasible = Evaluation::new(vec![100.0]);
|
||||
let infeasible = Evaluation::constrained(vec![0.0], 1.0);
|
||||
assert_eq!(compare(&feasible, &infeasible, Direction::Minimize), std::cmp::Ordering::Less);
|
||||
let lo = Evaluation::new(vec![1.0]);
|
||||
let hi = Evaluation::new(vec![2.0]);
|
||||
assert_eq!(compare(&lo, &hi, Direction::Minimize), std::cmp::Ordering::Less);
|
||||
assert_eq!(compare(&lo, &hi, Direction::Maximize), std::cmp::Ordering::Greater);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn better_is_strict_less() {
|
||||
let lo = Evaluation::new(vec![1.0]);
|
||||
let hi = Evaluation::new(vec![2.0]);
|
||||
assert!(better(&lo, &hi, Direction::Minimize));
|
||||
assert!(!better(&hi, &lo, Direction::Minimize));
|
||||
let eq = Evaluation::new(vec![1.0]);
|
||||
assert!(!better(&lo, &eq, Direction::Minimize));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -601,4 +601,30 @@ mod tests {
|
||||
);
|
||||
let _ = opt.run(&SchafferN1);
|
||||
}
|
||||
|
||||
// ---- Mutation-test pinned helpers --------------------------------------
|
||||
|
||||
#[test]
|
||||
fn euclidean_distance_basics() {
|
||||
// (0,0) to (3,4) = 5.
|
||||
assert!((euclidean(&[0.0, 0.0], &[3.0, 4.0]) - 5.0).abs() < 1e-12);
|
||||
// symmetric and zero-to-self.
|
||||
assert!((euclidean(&[3.0, 4.0], &[0.0, 0.0]) - 5.0).abs() < 1e-12);
|
||||
assert_eq!(euclidean(&[1.0, 2.0, 3.0], &[1.0, 2.0, 3.0]), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn binary_tournament_prefers_lower_fitness() {
|
||||
// SPEA2 fitness is "lower is better" — index 1 here is the best.
|
||||
use crate::core::rng::rng_from_seed;
|
||||
let fitness = vec![5.0_f64, 0.5];
|
||||
let mut wins1 = 0;
|
||||
for seed in 0..200 {
|
||||
let mut rng = rng_from_seed(seed);
|
||||
if binary_tournament(&fitness, &mut rng) == 1 {
|
||||
wins1 += 1;
|
||||
}
|
||||
}
|
||||
assert!(wins1 > 130, "lower-fitness index won only {wins1}/200");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,4 +421,24 @@ mod tests {
|
||||
rb.best.unwrap().evaluation.objectives,
|
||||
);
|
||||
}
|
||||
|
||||
// ---- Mutation-test pinned helpers --------------------------------------
|
||||
|
||||
#[test]
|
||||
fn better_than_feasibility_first_and_direction() {
|
||||
use crate::core::objective::Direction;
|
||||
let feasible = Evaluation::new(vec![100.0]);
|
||||
let infeasible = Evaluation::constrained(vec![0.0], 1.0);
|
||||
assert!(better_than(&feasible, &infeasible, Direction::Minimize));
|
||||
assert!(!better_than(&infeasible, &feasible, Direction::Minimize));
|
||||
let lo = Evaluation::new(vec![1.0]);
|
||||
let hi = Evaluation::new(vec![2.0]);
|
||||
assert!(better_than(&lo, &hi, Direction::Minimize));
|
||||
assert!(better_than(&hi, &lo, Direction::Maximize));
|
||||
let eq = Evaluation::new(vec![1.0]);
|
||||
assert!(!better_than(&lo, &eq, Direction::Minimize));
|
||||
let v_lo = Evaluation::constrained(vec![0.0], 0.2);
|
||||
let v_hi = Evaluation::constrained(vec![0.0], 0.8);
|
||||
assert!(better_than(&v_lo, &v_hi, Direction::Minimize));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,4 +383,40 @@ mod tests {
|
||||
let mut opt = make_optimizer(0);
|
||||
let _ = opt.run(&SchafferN1);
|
||||
}
|
||||
|
||||
// ---- Mutation-test pinned helpers --------------------------------------
|
||||
|
||||
use crate::core::evaluation::Evaluation;
|
||||
use crate::core::objective::Direction;
|
||||
|
||||
#[test]
|
||||
fn better_feasibility_first_and_direction() {
|
||||
let feasible = Evaluation::new(vec![100.0]);
|
||||
let infeasible = Evaluation::constrained(vec![0.0], 1.0);
|
||||
assert!(better(&feasible, &infeasible, Direction::Minimize));
|
||||
assert!(!better(&infeasible, &feasible, Direction::Minimize));
|
||||
let lo = Evaluation::new(vec![1.0]);
|
||||
let hi = Evaluation::new(vec![2.0]);
|
||||
assert!(better(&lo, &hi, Direction::Minimize));
|
||||
assert!(better(&hi, &lo, Direction::Maximize));
|
||||
let eq = Evaluation::new(vec![1.0]);
|
||||
assert!(!better(&lo, &eq, Direction::Minimize));
|
||||
let v_lo = Evaluation::constrained(vec![0.0], 0.2);
|
||||
let v_hi = Evaluation::constrained(vec![0.0], 0.8);
|
||||
assert!(better(&v_lo, &v_hi, Direction::Minimize));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn best_index_finds_min_and_max() {
|
||||
let evals = [
|
||||
Evaluation::new(vec![3.0]),
|
||||
Evaluation::new(vec![1.0]),
|
||||
Evaluation::new(vec![4.0]),
|
||||
];
|
||||
assert_eq!(best_index(&evals, Direction::Minimize), 1);
|
||||
assert_eq!(best_index(&evals, Direction::Maximize), 2);
|
||||
// tie keeps the first.
|
||||
let flat = [Evaluation::new(vec![1.0]), Evaluation::new(vec![1.0])];
|
||||
assert_eq!(best_index(&flat, Direction::Minimize), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,4 +552,56 @@ mod tests {
|
||||
let mut opt = make_optimizer(0);
|
||||
let _ = opt.run(&SchafferN1);
|
||||
}
|
||||
|
||||
// ---- Mutation-test pinned helpers --------------------------------------
|
||||
|
||||
use crate::core::evaluation::Evaluation;
|
||||
use crate::core::objective::Direction;
|
||||
|
||||
#[test]
|
||||
fn oriented_target_flips_sign_and_penalizes() {
|
||||
let e = Evaluation::new(vec![3.0]);
|
||||
assert!((oriented_target(&e, Direction::Minimize) - 3.0).abs() < 1e-12);
|
||||
assert!((oriented_target(&e, Direction::Maximize) + 3.0).abs() < 1e-12);
|
||||
let mut bad = Evaluation::new(vec![1.0]);
|
||||
bad.constraint_violation = 0.5;
|
||||
assert!((oriented_target(&bad, Direction::Minimize) - 500_001.0).abs() < 1e-9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn better_feasibility_first_and_direction() {
|
||||
let feasible = Evaluation::new(vec![100.0]);
|
||||
let infeasible = Evaluation::constrained(vec![0.0], 1.0);
|
||||
assert!(better(&feasible, &infeasible, Direction::Minimize));
|
||||
let lo = Evaluation::new(vec![1.0]);
|
||||
let hi = Evaluation::new(vec![2.0]);
|
||||
assert!(better(&lo, &hi, Direction::Minimize));
|
||||
assert!(better(&hi, &lo, Direction::Maximize));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn split_good_bad_partitions_by_target_rank() {
|
||||
// targets 5, 1, 3, 9, 7 → ranked 1<3<5<7<9 → indices 1,2,0,4,3.
|
||||
let targets = [5.0, 1.0, 3.0, 9.0, 7.0];
|
||||
let (good, bad) = split_good_bad(&targets, 0.4);
|
||||
// 40% of 5 = 2 good.
|
||||
assert_eq!(good.len(), 2);
|
||||
assert_eq!(bad.len(), 3);
|
||||
// The two smallest targets (1.0 at idx 1, 3.0 at idx 2) are "good".
|
||||
assert!(good.contains(&1));
|
||||
assert!(good.contains(&2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn split_good_bad_clamps_to_at_least_one_each() {
|
||||
let targets = [5.0, 1.0, 3.0];
|
||||
// good_fraction 0.0 would round to 0 — must clamp to >= 1.
|
||||
let (good, bad) = split_good_bad(&targets, 0.0);
|
||||
assert!(!good.is_empty());
|
||||
assert!(!bad.is_empty());
|
||||
// good_fraction 1.0 would take everything — must leave >= 1 bad.
|
||||
let (good2, bad2) = split_good_bad(&targets, 1.0);
|
||||
assert!(!good2.is_empty());
|
||||
assert!(!bad2.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,4 +449,30 @@ mod tests {
|
||||
});
|
||||
let _ = opt.run(&DummyMo);
|
||||
}
|
||||
|
||||
// ---- Mutation-test pinned helpers --------------------------------------
|
||||
|
||||
#[test]
|
||||
fn compare_so_feasibility_first_and_direction() {
|
||||
let feasible = Evaluation::new(vec![100.0]);
|
||||
let infeasible = Evaluation::constrained(vec![0.0], 1.0);
|
||||
assert_eq!(compare_so(&feasible, &infeasible, Direction::Minimize), std::cmp::Ordering::Less);
|
||||
let lo = Evaluation::new(vec![1.0]);
|
||||
let hi = Evaluation::new(vec![2.0]);
|
||||
assert_eq!(compare_so(&lo, &hi, Direction::Minimize), std::cmp::Ordering::Less);
|
||||
assert_eq!(compare_so(&lo, &hi, Direction::Maximize), std::cmp::Ordering::Greater);
|
||||
let v_lo = Evaluation::constrained(vec![0.0], 0.2);
|
||||
let v_hi = Evaluation::constrained(vec![0.0], 0.8);
|
||||
assert_eq!(compare_so(&v_lo, &v_hi, Direction::Minimize), std::cmp::Ordering::Less);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn better_than_so_is_strict_less() {
|
||||
let lo = Evaluation::new(vec![1.0]);
|
||||
let hi = Evaluation::new(vec![2.0]);
|
||||
assert!(better_than_so(&lo, &hi, Direction::Minimize));
|
||||
assert!(!better_than_so(&hi, &lo, Direction::Minimize));
|
||||
let eq = Evaluation::new(vec![1.0]);
|
||||
assert!(!better_than_so(&lo, &eq, Direction::Minimize));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user