test(fuzz): add cargo-fuzz harness for Pareto and operator hot paths
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
#![no_main]
|
||||
//! Fuzz `ParetoArchive` for the non-domination invariant under arbitrary
|
||||
//! insertion/truncation sequences.
|
||||
|
||||
use arbitrary::Arbitrary;
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
use heuropt::core::candidate::Candidate;
|
||||
use heuropt::core::evaluation::Evaluation;
|
||||
use heuropt::core::objective::{Objective, ObjectiveSpace};
|
||||
use heuropt::pareto::archive::ParetoArchive;
|
||||
use heuropt::pareto::dominance::{Dominance, pareto_compare};
|
||||
|
||||
#[derive(Arbitrary, Debug)]
|
||||
enum Op {
|
||||
Insert(f64, f64),
|
||||
Truncate(u8),
|
||||
}
|
||||
|
||||
#[derive(Arbitrary, Debug)]
|
||||
struct Input {
|
||||
ops: Vec<Op>,
|
||||
}
|
||||
|
||||
fuzz_target!(|input: Input| {
|
||||
if input.ops.len() > 64 {
|
||||
return;
|
||||
}
|
||||
let space = ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")]);
|
||||
let mut archive: ParetoArchive<()> = ParetoArchive::new(space.clone());
|
||||
|
||||
for op in input.ops {
|
||||
match op {
|
||||
Op::Insert(a, b) => {
|
||||
let cand = Candidate::new((), Evaluation::new(vec![a, b]));
|
||||
archive.insert(cand);
|
||||
}
|
||||
Op::Truncate(n) => archive.truncate(n as usize),
|
||||
}
|
||||
}
|
||||
|
||||
// Members must be pairwise non-dominated.
|
||||
let m = archive.members();
|
||||
for i in 0..m.len() {
|
||||
for j in 0..m.len() {
|
||||
if i == j {
|
||||
continue;
|
||||
}
|
||||
let r = pareto_compare(&m[i].evaluation, &m[j].evaluation, &space);
|
||||
assert!(
|
||||
!matches!(r, Dominance::DominatedBy),
|
||||
"archive member {i} dominated by {j}: {:?} vs {:?}",
|
||||
m[i].evaluation.objectives,
|
||||
m[j].evaluation.objectives,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user