test(pareto,metrics,selection): pin shared-utility comparisons and arithmetic

Phase 1, tier 3 of the mutation-testing campaign — the shared Pareto /
metric / selection utilities used by every multi-objective algorithm.
A scoped cargo-mutants run found 75 survivors across these files; the
tests below target them.

- metrics/hypervolume.rs: dominates() boundary cases, non_dominated_
  projection retained-set pins, hso_recursive 1-D/2-D base cases,
  hypervolume_nd_from_evaluations empty/non-dominating skips.
- selection/tournament.rs: challenger_wins across the full feasibility
  cross-product + equal-objective tie; better_by_objective and
  better_by_feasibility branch pins; stochastic_ranking_select pf=0
  feasibility ordering and count-wraps-modulo-population.
- pareto/crowding.rs: exact interior crowding distance on symmetric
  and asymmetric fronts (pins the (next-prev)/span arithmetic).
- pareto/sort.rs: three-non-dominated-then-one-dominated and a strict
  3-chain producing three singleton fronts.
- pareto/dominance.rs: trade-off → NonDominated, better-on-one-equal-
  on-other → Dominates, identical → Equal.
- pareto/archive.rs: truncate boundary, trade-off kept alongside,
  equal candidate rejected, smaller-violation infeasible eviction.
- pareto/front.rs: best_candidate keeps the first of tied minima.
- metrics/spacing.rs: exact spacing for a varying-NN-distance front.

src/core/problem.rs's lone survivor (decision_schema default body
'replace with vec![]') is an equivalent mutant — Vec::new() and vec![]
are identical — and is left in the residue.
This commit is contained in:
2026-05-13 22:58:17 -06:00
parent 7b8b7170f4
commit 4569244a68
8 changed files with 403 additions and 0 deletions
+14
View File
@@ -180,4 +180,18 @@ mod tests {
];
assert!(best_candidate(&pop, &s).is_none());
}
/// `best_candidate` keeps the *first* minimum on a tie — pins the strict
/// `v < best_min` (a `<=` mutant would keep the last tied candidate).
#[test]
fn best_candidate_keeps_first_on_tie() {
use crate::core::objective::Objective;
let s = ObjectiveSpace::new(vec![Objective::minimize("f")]);
let pop = [
Candidate::new(1u32, Evaluation::new(vec![1.0])),
Candidate::new(2u32, Evaluation::new(vec![1.0])),
];
let best = best_candidate(&pop, &s).unwrap();
assert_eq!(best.decision, 1, "should keep the first of two tied minima");
}
}