Conceptual marine design
The problem
Parsons and Scott (2004) design a bulk carrier at the concept stage, with a parametric model after Sen and Yang (1998): six genes, the ship's length L, beam B, depth D and draft T, in m, its block coefficient C_B and its speed V_k, in knots, give its displacement, the power it needs, its steel, outfit and machinery weights, the cargo it carries a voyage, the round trips it makes a year, and what it costs to build and run. The three criteria are the transportation cost, the light ship weight, which stands for the cost of building it, and the annual cargo, which is maximized:
minimize f₁ = annual cost / annual cargo the transportation cost, in £/t
f₂ = W_s + W_o + W_m the light ship weight, in t
f₃ = −annual cargo in t a year
where Δ = 1.025 L B T C_B, V = 0.5144 V_k, F_n = V / √(9.8065 L)
P = Δ^(2/3) V_k³ / (a + b F_n), a = 4977.06 C_B² − 8105.61 C_B + 4456.51,
b = −10847.2 C_B² + 12817 C_B − 6960.32
W_s = 0.034 L^1.7 B^0.7 D^0.4 C_B^0.5, W_o = L^0.8 B^0.6 D^0.3 C_B^0.1,
W_m = 0.17 P^0.9, DWT = Δ − (W_s + W_o + W_m),
daily consumption C = 0.19 P · 24 / 1000 + 0.2
sea days = 5000 / (24 V_k), cargo deadweight = DWT − C (sea days + 5) − 2 DWT^0.5
port days = 2 (cargo deadweight / 8000 + 0.5), RTPA = 350 / (sea days + port days)
annual cost = 0.2 · 1.3 (2000 W_s^0.85 + 3500 W_o + 2400 P^0.8) + 40000 DWT^0.3
+ (1.05 C · sea days · 100 + 6.3 DWT^0.8) RTPA
annual cargo = cargo deadweight · RTPA
subject to L/B ≥ 6, L/D ≤ 15, L/T ≤ 19, T ≤ 0.45 DWT^0.31, T ≤ 0.7 D + 0.7,
25,000 ≤ DWT ≤ 500,000, F_n ≤ 0.32,
GM_T = 0.53 T + (0.085 C_B − 0.002) B² / (T C_B) − (1 + 0.52 D) ≥ 0.07 B
L in [150, 274.32], B in [20, 32.31], D in [10, 25], T in [8, 11.71], C_B in [0.63, 0.75],
V_k in [14, 18]
The model is the paper's appendix, and the limits its Panamax case 2: the Panama Canal's locks bound
L, B and T, and the least deadweight is 25,000 t. The lower bounds of L, B, D and T and the upper
bound of D are genoxide's, as the paper gives none, and hold every feasible design. The paper's
designs give its printed criteria with this model; Tanabe and Ishibuchi's restatement (2020, problem
RE4-6-2) differs, with a fourth objective, a least deadweight of 3000 t, narrower bounds and, in its
code, sea days of (5000/24) V_k. The problem is genoxide's MarineDesign in
multi::problems::engineering.
The front isn't known. Its ideal point, (8.376894, 5240.3356, −700,552.76), is the paper's three single-criterion designs (its table 4) computed again: the cheapest transport at 14 knots, the lightest ship at the least deadweight, and the most cargo at 18 knots. genoxide's reference front, the non-dominated designs of about 11,300 runs of SHADE, each minimizing an achievement scalarizing function along one of Das and Dennis's directions, and of eighty runs of SMS-EMOA and NSGA-III of 500 or 2,000 generations, each design then improved by SHADE looking for one that dominates it, has 15,461 points, worst values (11.0662, 12,435.8, −386,500), the estimated nadir point, and a hypervolume of 0.8624 in the scaled objectives below: a lower bound on the whole front's.
What makes it hard
The optima lie on the constraints and the bounds: 88% of the reference front's designs have the largest block coefficient, 68% the largest draft, and each single-criterion design meets two or more constraints and several bounds at once (the lightest ship has L/B = 6, a draft at both of its limits and a deadweight of exactly 25,000 t). The objectives span very different scales, from 8 £/t to 700,000 t a year. And the front's worst transportation cost, 11.07 £/t, isn't at any of the three single- criterion designs, whose worst is 10.29: it belongs to a fast ship of middling size (L ≈ 176 m at 18 knots), lighter than the largest and carrying more than the lightest, which the extremes alone don't show.
Representation
A Real genome of 6 genes, the design variables. The problem is genoxide's
multi::problems::engineering::MarineDesign (gx.problems.multi_engineering.MarineDesign in
Python), whose fitness is the three objectives and the total constraint violation. Solutions compare
by constrained dominance.
Algorithm
SMS-EMOA, which keeps the solutions that add the most hypervolume, with a population of 92 for 500 generations, simulated binary crossover (η = 15, at a rate of 0.9) and polynomial mutation (η = 20, at a rate of 1/6 per gene). With the same operators, NSGA-III with 91 directions ends with less hypervolume over seeds 1 to 20, 89.36% to 92.15% of the reference front's, SPEA2 89.67% to 91.83%, and NSGA-II 84.26% to 88.83%.
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 annual cargo as a positive number. 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 cheapest transport to the lightest ship and the most cargo. 92 points of the reference front, chosen one by one for the most hypervolume, give 96.19% of its hypervolume.
The run's front has 92 feasible solutions, with transportation costs from 8.3776 £/t, light ship weights from 5346.7 t and annual cargoes up to 700,544 t, close to the three minima, and 92.57% of the reference front's hypervolume, 96.2% of what 92 chosen points reach. Its transportation costs go up to 11.6209, beyond the reference front's worst: a solution there is dominated by the reference front, though not by the run's own. Over seeds 1 to 20, every run ends between 92.09% and 93.26%.
Known optimum: not known in closed form; ideal point (8.376894, 5240.3356, −700,552.76); genoxide's reference front has a hypervolume of 0.8624 in objectives scaled by the ideal point and its estimated nadir point (11.0662, 12,435.8, −386,500) (reference point (1.1, 1.1, 1.1))
Source: examples/marine_design
Interactive run: tachsin.gr/projects/genoxide/examples/marine-design
cargo run --release --example marine_design
//! Conceptual marine design: the Panamax bulk carrier that carries cargo the cheapest, with the
//! lightest ship and the most cargo a year, subject to nine constraints.
//!
//! SMS-EMOA with a population of 92, 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 marine_design
//! ```
mod trace;
use genoxide::Objective::Minimize;
use genoxide::multi::indicator::hypervolume;
use genoxide::multi::problems::MultiProblem;
use genoxide::multi::problems::engineering::MarineDesign;
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] = [11.0662, 12435.8, -386_500.0];
// 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.8624;
fn main() -> Result<()> {
let problem = MarineDesign;
let sms_emoa = SmsEmoa::builder(problem.representation(), [Minimize; 3])
.population_size(92)
.crossover(SimulatedBinaryCrossover::new(15.0)?)
.mutate(PolynomialMutation::per_gene(1.0 / 6.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(sms_emoa, 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 = MarineDesign.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!("SMS-EMOA, {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);
// the third objective is the annual cargo, negated
println!(
" transportation cost from {:.4} to {:.4} £/t, light ship weight from {:.1} to {:.1} t, \
annual cargo from {:.0} to {:.0} t",
low(0),
high(0),
low(1),
high(1),
-high(2),
-low(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/marine_design/main.py
"""Conceptual marine design: the Panamax bulk carrier that carries cargo the cheapest, with the
lightest ship and the most cargo a year, subject to nine constraints.
SMS-EMOA with a population of 92, 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/marine_design/main.py
"""
import genoxide as gx
from trace import REFERENCE, Trace, scaled
# the run's length
GENERATIONS = 500
problem = gx.problems.multi_engineering.MarineDesign()
sms_emoa = gx.SmsEmoa(
problem.genome,
objectives=problem.objectives,
population_size=92,
crossover=gx.SimulatedBinaryCrossover(15),
mutation=gx.PolynomialMutation(20, rate=1 / 6),
seed=1,
)
# with GENOXIDE_TRACE=<file>, a trace of the run for the plot on the example's page
trace = Trace(problem)
result = sms_emoa.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"SMS-EMOA, {GENERATIONS} generations: {size} solutions on the front, {count}")
low, high = front.min(axis=0), front.max(axis=0)
# the third objective is the annual cargo, negated
print(
f" transportation cost from {low[0]:.4f} to {high[0]:.4f} £/t, "
f"light ship weight from {low[1]:.1f} to {high[1]:.1f} t, "
f"annual cargo from {-high[2]:.0f} to {-low[2]:.0f} t"
)
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:
SMS-EMOA, 500 generations: 92 solutions on the front, all feasible
transportation cost from 8.3776 to 11.6209 £/t, light ship weight from 5346.7 to 12435.3 t, annual cargo from 387515 to 700544 t
hypervolume 0.7983, 92.57% of the reference front's 0.8624