A Rust implementation of the NSGA‑II multi‑objective evolutionary algorithm.
The crate provides the core components required to build deterministic or stochastic evolutionary workflows.
- non‑dominated sorting (parallel dominance matrix, tri‑state dominance)
- crowding distance (NaN‑safe, stable ordering)
- simulated binary crossover (SBX)
- polynomial mutation
- tournament selection
- constraint handling
- strict and auto hypervolume
- IGD (Inverted Generational Distance)
- per‑generation Pareto snapshots
- early stopping
- reproducible runs via optional RNG seeding
Objective evaluation and dominance checks run in parallel through Rayon.
Algorithm parameters use a builder‑style API.
The problem module is now split into a directory:
src/problem/
problem_trait.rs — core Problem trait
schaffer.rs — Schaffer N.1
zdt.rs — ZDT1, ZDT2, ZDT3
dtlz.rs — DTLZ1, DTLZ2, DTLZ3
kursawe.rs — Kursawe
All built‑in problems implement the same Problem trait and are re‑exported through problem::.
Other modules:
evolve— NSGA‑II engine andRunResultsort— non‑dominated sorting and crowding distancedata— individuals, dominance logic, feasibility rulesmetrics— strict HV, auto HV, IGD
All tests for Schaffer, ZDT, DTLZ, and Kursawe pass.
use rs_nsga2::evolve::Evolution;use rs_nsga2::problem::Schaffer;fnmain(){let result = Evolution::new(Schaffer::default(),100,500).evolve();for ind in&result.pareto_front{println!("{:?} -> {:?}", ind.features, ind.objectives);}}use rs_nsga2::evolve::Evolution;use rs_nsga2::problem::ZDT1;fnmain(){let result = Evolution::new(ZDT1::new(30),200,400).evolve();println!("Front size: {}", result.pareto_front.len());}ZDT2 and ZDT3 follow the same pattern.
use rs_nsga2::evolve::Evolution;use rs_nsga2::problem::DTLZ2;fnmain(){let result = Evolution::new(DTLZ2::new(3,12),300,600).evolve();println!("Front size: {}", result.pareto_front.len());}DTLZ1 and DTLZ3 follow the same pattern.
use rs_nsga2::evolve::Evolution;use rs_nsga2::problem::Kursawe;fnmain(){let result = Evolution::new(Kursawe::default(),100,200).evolve();println!("Front size: {}", result.pareto_front.len());}use rs_nsga2::evolve::Evolution;use rs_nsga2::problem::Problem;structMyProblem{ranges:[(f64,f64);2],}implMyProblem{fnnew() -> Self{Self{ranges:[(0.0,1.0),(0.0,1.0)]}}}implProblemforMyProblem{fnnum_variables(&self) -> usize{2}fnnum_objectives(&self) -> usize{2}fnvariable_ranges(&self) -> &[(f64,f64)]{&self.ranges}fncalculate_objectives(&self,x:&[f64]) -> Vec<f64>{vec![x[0] + x[1],(x[0] - 1.0).powi(2) + (x[1] - 1.0).powi(2)]}}fnmain(){let result = Evolution::new(MyProblem::new(),200,300).evolve();println!("Front size: {}", result.pareto_front.len());}use rs_nsga2::evolve::Evolution;use rs_nsga2::problem::Problem;structConstrainedProblem{ranges:[(f64,f64);2],}implConstrainedProblem{fnnew() -> Self{Self{ranges:[(0.0,5.0),(0.0,5.0)]}}}implProblemforConstrainedProblem{fnnum_variables(&self) -> usize{2}fnnum_objectives(&self) -> usize{2}fnvariable_ranges(&self) -> &[(f64,f64)]{&self.ranges}fncalculate_objectives(&self,x:&[f64]) -> Vec<f64>{vec![x[0], x[1]]}fnconstraint_violations(&self,x:&[f64]) -> Vec<f64>{vec![2.0 - x[0] - x[1]]// x0 + x1 >= 2}}fnmain(){let result = Evolution::new(ConstrainedProblem::new(),100,200).evolve();println!("Front size: {}", result.pareto_front.len());}use rs_nsga2::evolve::Evolution;use rs_nsga2::problem::Schaffer;fnmain(){let result = Evolution::new(Schaffer::default(),100,500).with_reference_point(vec![10.0,10.0]).with_convergence_threshold(10,0.001).evolve();println!("Generations completed: {}", result.generations_completed);}- tri‑state dominance (
IDominatesJ,JDominatesI,None) - parallel dominance matrix
- sequential front extraction
- feasibility rules:
- feasible dominates infeasible
- among infeasible: lower total violation dominates
- stable, NaN‑safe sorting per objective
- infinite distance for boundary individuals
- finite‑difference distance for interior individuals
use rs_nsga2::metrics::hypervolume_2d_strict;Reference point must dominate the front.
use rs_nsga2::metrics::hypervolume_2d_auto;Reference point is expanded when needed.
use rs_nsga2::metrics::igd;Computes the average distance from the true front to the obtained front.
Each generation:
- tournament selection
- SBX crossover
- polynomial mutation
- parallel objective evaluation
- merge parents and offspring
- non‑dominated sorting
- crowding‑distance truncation
Feasible solutions dominate infeasible ones.
Among infeasible solutions, lower total violation dominates.
| Field | Description |
|---|---|
pareto_front | final Pareto front |
history | per‑generation Pareto snapshots |
hypervolume_history | HV per generation (NaN if no reference point) |
igd_history | IGD per generation (NaN if no true front) |
generations_completed | number of executed generations |
Included benchmarks:
- full NSGA‑II loop
- sorting only
- strict vs auto hypervolume
- IGD microbench
Run all:
cargo bench
Run a specific benchmark:
cargo bench --bench igd
cargo bench --bench sorting
cargo bench --bench evolution
Reports are written to target/criterion/.
- Pham Ngo Gia Bao
- Tram Loi Quan
- Quan Thanh Tho
- Akhil Garg
- Giorgio