Car side impact, three objectives
The problem
The car side impact problem of Gu et al. (2001) designs a car body for the European side-impact test: its seven genes are the thicknesses of the B-pillar inner and reinforcement, the floor side inner, the cross members, the door beam, the door beltline reinforcement and the roof rail, and response surfaces fitted to crash simulations give the dummy's loads, velocities and rib deflections. Jain and Deb (2014, section V-F and appendix) made it a problem of three objectives:
minimize f₁ = 1.98 + 4.9x₁ + 6.67x₂ + 6.98x₃ + 4.01x₄ + 1.78x₅ + 0.00001x₆ + 2.73x₇ the weight
f₂ = F = 4.72 − 0.5x₄ − 0.19x₂x₃ the pubic force, in kN
f₃ = (V_MBP + V_FD) / 2 the mean velocity, in mm/ms
V_MBP = 10.58 − 0.674x₁x₂ − 0.67275x₂, V_FD = 16.45 − 0.489x₃x₇ − 0.843x₅x₆
subject to the ten constraints of the single-objective problem: the abdomen load ≤ 1 kN, the
upper, middle and lower chest velocities ≤ 0.32 m/s, the upper, middle and lower rib
deflections ≤ 32 mm, F ≤ 4 kN, V_MBP ≤ 9.9 mm/ms and V_FD ≤ 15.7 mm/ms
x₁, x₃, x₄ in [0.5, 1.5], x₂ in [0.45, 1.35], x₅ in [0.875, 2.625], x₆, x₇ in [0.4, 1.2]
The definition was checked in Jain and Deb's accepted manuscript; Gu et al.'s paper couldn't be
read. genoxide's CarSideImpact in multi::problems::engineering shares the single-objective
CarSideImpact's code, whose docs note two coefficients that the eleven-variable form would give
differently.
The front isn't known. Its ideal point, from genoxide's SHADE, is (23.585658, 3.58525, 10.610644): the single-objective problem's least weight, and the least pubic force and mean velocity, both at bounds. genoxide's reference front, the non-dominated designs of about 12,000 runs of SHADE, each minimizing an achievement scalarizing function along one of Das and Dennis's directions, and of sixteen runs of NSGA-III and SMS-EMOA of 2,000 generations, has worst values (42.768, 4.0, 12.5212), the estimated nadir point, and a hypervolume of 0.8687 in the scaled objectives below: a lower bound on the whole front's.
What makes it hard
Ten constraints, and a front whose shape is far from the plane of the reference directions: Jain and Deb found solutions for only 95 of their 153 directions, the others pointing where the front isn't (their figure 19). The pubic force's constraint, F ≤ 4, is also the front's edge.
Representation
A Real genome of 7 genes. The problem is genoxide's multi::problems::engineering::CarSideImpact
(gx.problems.multi_engineering.CarSideImpact in Python), whose fitness is the three objectives and
the total constraint violation. Solutions compare by constrained dominance.
Algorithm
NSGA-III with Jain and Deb's settings: the 153 reference directions of Das and Dennis's method with 16 divisions, a population of 156, for 500 generations, simulated binary crossover with η = 30 (at a rate of 1) and polynomial mutation with η = 20 at a rate of 1/7 per gene.
Output
The first line gives the size of the final front and how many of its solutions are feasible, the
second the range of each objective on it. The third gives its hypervolume up to the reference point
(1.1, 1.1, 1.1), in objectives scaled to [0, 1] by the ideal point and the estimated nadir point, as
a share of the reference front's. In Python, run evaluates the problem in Rust, so both versions
print the same.
The project page plays this run back.
Good results
A good front is feasible and spread over the whole surface, from the lightest car to the least pubic force and mean velocity. 156 points of the reference front, chosen one by one for the most hypervolume, give 96.22% of its hypervolume: no set of 156 does much better.
The run's front has 156 feasible solutions, with weights from 23.665 to 42.766, pubic forces from 3.5853 to 4 and mean velocities from 10.6108 to 12.5129, and 93.94% of the reference front's hypervolume, 97.6% of what 156 chosen points reach. Over seeds 1 to 20, every run ends between 93.74% and 94.19%.
Known optimum: not known in closed form; ideal point (23.585658, 3.58525, 10.610644); genoxide's reference front has a hypervolume of 0.8687 in objectives scaled by the ideal point and its estimated nadir point (42.768, 4.0, 12.5212) (reference point (1.1, 1.1, 1.1))
Source: examples/car_side_impact_3obj
Interactive run: tachsin.gr/projects/genoxide/examples/car-side-impact-3obj
cargo run --release --example car_side_impact_3obj
//! Car side impact, three objectives: minimize a car's weight, the pubic force on a passenger and
//! the mean velocity of the B-pillar and the front door in a side impact, subject to ten
//! constraints.
//!
//! NSGA-III with Jain and Deb's settings: the 153 reference directions of Das and Dennis's method
//! with 16 divisions, a population of 156, for 500 generations. Prints how many solutions of the
//! final front are feasible, the range of each objective on it, and its hypervolume, as a share of
//! that of genoxide's reference front.
//!
//! With `GENOXIDE_TRACE=<file>`, it also writes a trace of its run for the plot on the example's
//! page, with `trace.rs`.
//!
//! ```text
//! cargo run --release --example car_side_impact_3obj
//! ```
mod trace;
use genoxide::Objective::Minimize;
use genoxide::multi::indicator::hypervolume;
use genoxide::multi::problems::MultiProblem;
use genoxide::multi::problems::engineering::CarSideImpact;
use genoxide::prelude::*;
// the run's length
const GENERATIONS: u64 = 500;
// the front's nadir point, estimated from genoxide's reference front (see the README): with the
// ideal point, what scales the objectives to [0, 1]
pub const NADIR: [f64; 3] = [42.768, 4.0, 12.5212];
// the hypervolume of genoxide's reference front, in scaled objectives with the reference point
// (1.1, 1.1, 1.1): a lower bound on the whole front's
pub const REFERENCE: f64 = 0.8687;
fn main() -> Result<()> {
let problem = CarSideImpact;
// 16 divisions: the reference directions of Das and Dennis's method
let directions = multi::das_dennis::<3>(16);
let nsga3 = Nsga3::builder(problem.representation(), [Minimize; 3], directions)
.population_size(156)
.crossover(SimulatedBinaryCrossover::new(30.0)?)
.mutate(PolynomialMutation::per_gene(1.0 / 7.0, 20.0)?)
.seed(1)
.build()?;
// with GENOXIDE_TRACE=<file>, a trace of the run for the plot on the example's page
let mut trace = trace::Trace::from_env();
let outcome = MultiEngine::new(nsga3, problem)
.stop_when(Stop::generations(GENERATIONS))
.on_generation(|snapshot| trace.record(snapshot))
.run()?;
let (front, size) = feasible(outcome.front());
report(&front, size);
trace.write();
Ok(())
}
// the objectives scaled to [0, 1] by the front's ideal point and its estimated nadir point
pub fn scaled(points: &[[f64; 3]]) -> Vec<[f64; 3]> {
let ideal = CarSideImpact.ideal_point().expect("known");
let scale = |p: &[f64; 3]| [0, 1, 2].map(|j| (p[j] - ideal[j]) / (NADIR[j] - ideal[j]));
points.iter().map(scale).collect()
}
// the feasible solutions of the run's front: how many of them, the range of each objective, and
// their hypervolume, as a share of the reference front's
fn report(front: &[[f64; 3]], size: usize) {
let feasible = if front.len() == size {
"all feasible".to_string()
} else {
format!("{} feasible", front.len())
};
println!("NSGA-III, {GENERATIONS} generations: {size} solutions on the front, {feasible}");
let low = |j: usize| front.iter().map(|p| p[j]).fold(f64::INFINITY, f64::min);
let high = |j: usize| front.iter().map(|p| p[j]).fold(f64::NEG_INFINITY, f64::max);
println!(
" weight from {:.3} to {:.3}, pubic force from {:.4} to {:.4}, \
mean velocity from {:.4} to {:.4}",
low(0),
high(0),
low(1),
high(1),
low(2),
high(2)
);
let volume = hypervolume(&scaled(front), &[1.1; 3], &[Minimize; 3]);
println!(
" hypervolume {volume:.4}, {:.2}% of the reference front's {REFERENCE}",
100.0 * volume / REFERENCE
);
}
// the objective values of the feasible solutions of a front, and the front's size
fn feasible<G: Genome>(front: &[Individual<G, multi::Scores<3>>]) -> (Vec<[f64; 3]>, usize) {
let values = front
.iter()
.filter_map(|x| {
x.fitness()
.filter(|s| s.is_feasible())
.and_then(|s| s.values())
})
.collect();
(values, front.len())
}
python examples/car_side_impact_3obj/main.py
"""Car side impact, three objectives: minimize a car's weight, the pubic force on a passenger and
the mean velocity of the B-pillar and the front door in a side impact, subject to ten constraints.
NSGA-III with Jain and Deb's settings: the 153 reference directions of Das and Dennis's method with
16 divisions, a population of 156, for 500 generations. Prints how many solutions of the final
front are feasible, the range of each objective on it, and its hypervolume, as a share of that of
genoxide's reference front.
With ``GENOXIDE_TRACE=<file>``, it also writes a trace of its run for the plot on the example's
page, with trace.py.
python examples/car_side_impact_3obj/main.py
"""
import genoxide as gx
from trace import REFERENCE, Trace, scaled
# the run's length
GENERATIONS = 500
problem = gx.problems.multi_engineering.CarSideImpact()
# 16 divisions: the reference directions of Das and Dennis's method
nsga3 = gx.Nsga3(
problem.genome,
objectives=problem.objectives,
reference_directions=gx.das_dennis(3, 16),
population_size=156,
crossover=gx.SimulatedBinaryCrossover(30),
mutation=gx.PolynomialMutation(20, rate=1 / 7),
seed=1,
)
# with GENOXIDE_TRACE=<file>, a trace of the run for the plot on the example's page
trace = Trace(problem)
result = nsga3.run(problem, generations=GENERATIONS, on_generation=trace.on_generation)
feasible = result.front_violations == 0
front = result.front_objectives[feasible]
size = len(result.front_objectives)
count = "all feasible" if feasible.all() else f"{int(feasible.sum())} feasible"
print(f"NSGA-III, {GENERATIONS} generations: {size} solutions on the front, {count}")
low, high = front.min(axis=0), front.max(axis=0)
print(
f" weight from {low[0]:.3f} to {high[0]:.3f}, "
f"pubic force from {low[1]:.4f} to {high[1]:.4f}, "
f"mean velocity from {low[2]:.4f} to {high[2]:.4f}"
)
volume = gx.indicators.hypervolume(scaled(problem, front), [1.1, 1.1, 1.1])
print(
f" hypervolume {volume:.4f}, {100 * volume / REFERENCE:.2f}% of the reference front's "
f"{REFERENCE}"
)
trace.write()
What it prints, from a seeded run:
NSGA-III, 500 generations: 156 solutions on the front, all feasible
weight from 23.665 to 42.766, pubic force from 3.5853 to 4.0000, mean velocity from 10.6108 to 12.5129
hypervolume 0.8160, 93.94% of the reference front's 0.8687