feat(examples): add MOEA/D to ZDT1 and DTLZ2 comparison sections

Two new runners — `zdt1_moead` and `dtlz2_moead` — using the same
SBX + PolyMut variation as the other Pareto-based methods. Reference
divisions chosen so the implied population size is comparable to the
other algorithms in each section (99 → 100 weights for ZDT1; 12 → 91
weights for DTLZ2).
This commit is contained in:
2026-05-04 20:02:54 -06:00
parent 16032bf28b
commit ac0274f76f
+49
View File
@@ -234,6 +234,30 @@ fn zdt1_nsga2(seed: u64) -> MoRun {
MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() }
} }
fn zdt1_moead(seed: u64) -> MoRun {
let problem = Zdt1 { dim: ZDT1_DIM };
let bounds = vec![(0.0, 1.0); ZDT1_DIM];
let initializer = RealBounds::new(bounds.clone());
let variation = CompositeVariation {
crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5),
mutation: PolynomialMutation::new(bounds, 20.0, 1.0 / ZDT1_DIM as f64),
};
// 99 divisions → 100 weights for 2 obj. Each generation evaluates one
// child per weight (so `n_weights` evals/gen).
let pop = 100;
let gens = (ZDT1_BUDGET - pop) / pop;
let config = MoeadConfig {
generations: gens,
reference_divisions: 99,
neighborhood_size: 20,
seed,
};
let mut opt = Moead::new(config, initializer, variation);
let t0 = Instant::now();
let result = opt.run(&problem);
MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() }
}
fn zdt1_nsga3(seed: u64) -> MoRun { fn zdt1_nsga3(seed: u64) -> MoRun {
let problem = Zdt1 { dim: ZDT1_DIM }; let problem = Zdt1 { dim: ZDT1_DIM };
let bounds = vec![(0.0, 1.0); ZDT1_DIM]; let bounds = vec![(0.0, 1.0); ZDT1_DIM];
@@ -319,6 +343,29 @@ fn dtlz2_spea2(seed: u64) -> MoRun {
MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() }
} }
fn dtlz2_moead(seed: u64) -> MoRun {
let problem = dtlz2_problem();
let bounds = vec![(0.0, 1.0); DTLZ2_DIM];
let initializer = RealBounds::new(bounds.clone());
let variation = CompositeVariation {
crossover: SimulatedBinaryCrossover::new(bounds.clone(), 30.0, 1.0),
mutation: PolynomialMutation::new(bounds, 20.0, 1.0 / DTLZ2_DIM as f64),
};
// 12 divisions for 3 objectives = 91 weights — same density as NSGA-III.
let pop = 91;
let gens = (DTLZ2_BUDGET - pop) / pop;
let config = MoeadConfig {
generations: gens,
reference_divisions: 12,
neighborhood_size: 20,
seed,
};
let mut opt = Moead::new(config, initializer, variation);
let t0 = Instant::now();
let result = opt.run(&problem);
MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() }
}
fn dtlz2_nsga3(seed: u64) -> MoRun { fn dtlz2_nsga3(seed: u64) -> MoRun {
let problem = dtlz2_problem(); let problem = dtlz2_problem();
let bounds = vec![(0.0, 1.0); DTLZ2_DIM]; let bounds = vec![(0.0, 1.0); DTLZ2_DIM];
@@ -466,6 +513,7 @@ fn run_zdt1_comparison() {
("SPEA2", zdt1_spea2), ("SPEA2", zdt1_spea2),
("NSGA-II", zdt1_nsga2), ("NSGA-II", zdt1_nsga2),
("NSGA-III", zdt1_nsga3), ("NSGA-III", zdt1_nsga3),
("MOEA/D", zdt1_moead),
]; ];
for (name, runner) in runners { for (name, runner) in runners {
@@ -521,6 +569,7 @@ fn run_dtlz2_comparison() {
("NSGA-II", dtlz2_nsga2), ("NSGA-II", dtlz2_nsga2),
("SPEA2", dtlz2_spea2), ("SPEA2", dtlz2_spea2),
("NSGA-III", dtlz2_nsga3), ("NSGA-III", dtlz2_nsga3),
("MOEA/D", dtlz2_moead),
]; ];
for (name, runner) in runners { for (name, runner) in runners {