Struct MoProblem
pub struct MoProblem { /* private fields */ }Expand description
A multi-objective benchmark problem: a boxed continuous domain, a vector objective, optional constraints, and a known Pareto front.
Every objective is minimized (the convention the whole crate drives), and every problem’s true front is analytic — which is what makes the convergence axis measurable at all rather than a comparison of two searchers against each other.
Build one with a named constructor and hand it to run.
Implementations§
§impl MoProblem
impl MoProblem
pub const fn schema(&self) -> &SpaceSchema
pub const fn schema(&self) -> &SpaceSchema
The declared search space.
pub const fn n_objectives(&self) -> usize
pub const fn n_objectives(&self) -> usize
How many objectives the problem reports.
pub fn directions(&self) -> Vec<Direction>
pub fn directions(&self) -> Vec<Direction>
The study directions: minimize, one per objective.
pub const fn is_constrained(&self) -> bool
pub const fn is_constrained(&self) -> bool
true if the problem declares inequality constraints.
pub fn reference(&self) -> &[f64]
pub fn reference(&self) -> &[f64]
The fixed hypervolume reference point this oracle measures against.
Fixed, and a property of the problem rather than of a run, because a
reference recomputed from each run’s own nadir makes two runs’
hypervolumes incomparable — exactly the trap
nadir_point documents. Each one is
the nadir of the true front plus a 10 % margin, which is the
convention the ZDT/DTLZ literature uses: a solution only earns
hypervolume by getting inside the box the true front spans, so a
scattered cloud of far-from-front points scores nothing rather than being
credited for its spread.
pub fn wide_reference(&self) -> Vec<f64>
pub fn wide_reference(&self) -> Vec<f64>
A generous hypervolume reference: far enough out that even a completely unconverged cloud of points encloses volume.
Kept because a reference point is a choice, and a claim that only holds
under one choice is not a claim. Under reference a searcher that never
reaches the front scores exactly zero, which can make a margin look total
when it merely means “the loser scored nothing”; under this one every
evaluated point encloses volume, so the comparison is a ratio of two
positive numbers. Measured at the oracle’s budget the ranking is the same
either way — NSGA-II beats random search on 0.969 (ZDT1) and 1.000
(ZDT2) of 64 seeds under this generous reference, against 0.953 /
0.969 under the tight one — so the headline does not depend on the
choice. It is the less converged regimes where a generous reference
flatters a scattered sample (docs/design/STATUS.md, the NSGA-II notes), which
is why the choice is documented rather than incidental.
pub fn front_span(&self) -> Vec<f64>
pub fn front_span(&self) -> Vec<f64>
The span of the true front in each objective — the denominator
MoReport::spread normalizes by.
“The front is spread” only means something against the range there is to spread over, and that range is a property of the problem, not of the run.
pub fn true_front(&self) -> &[Vec<f64>]
pub fn true_front(&self) -> &[Vec<f64>]
A discretization of the true Pareto front, in objective space.
pub fn reference_set(&self) -> &[Vec<f64>]
pub fn reference_set(&self) -> &[Vec<f64>]
An evenly-distributed subsample of the true front, about 128 points — the
target set
MoReport::inverted_generational_distance measures coverage against.
Built at construction rather than strided out of
true_front, because striding a lattice
(DTLZ2’s front is a surface, sampled row-major) would silently collapse the
set onto a couple of meridians and make IGD blind to a whole axis.
pub fn eval(&self, point: &Assignment) -> Vec<f64>
pub fn eval(&self, point: &Assignment) -> Vec<f64>
The objective vector at point.
A parameter that is missing or not a float yields NaN for that
coordinate rather than panicking — a NaN objective is never admitted to
an NSGA-II population and can never win a Pareto comparison, so a
malformed assignment degrades instead of aborting a run.
pub fn constraints(&self, point: &Assignment) -> Vec<f64>
pub fn constraints(&self, point: &Assignment) -> Vec<f64>
The constraint values at point — ≤ 0 is satisfied, atune’s (and
Optuna’s) sign convention.
Empty for an unconstrained problem, which is a declaration of nothing
rather than a declaration of feasibility
(Point::new).
pub fn distance_to_front(&self, values: &[f64]) -> f64
pub fn distance_to_front(&self, values: &[f64]) -> f64
The Euclidean distance from an objective vector to the true Pareto front.
The convergence axis, and the reason every problem here has an analytic
front. For dtlz2 the answer is closed-form
(|‖f‖ − 1|, since the front is the unit sphere and the nearest point on
a sphere lies along the ray); for the others it is a scan of
1024 curve points followed by 40 ternary-search steps between the
winner’s neighbours, so the answer is a property of the curve rather than
of the sampling density.
A malformed vector (wrong arity, non-finite) yields
f64::INFINITY — unmeasurably far, never accidentally good.
pub fn zdt1(dim: usize) -> Result<Self>
pub fn zdt1(dim: usize) -> Result<Self>
ZDT1 in dim dimensions over [0, 1]^dim.
f₁ = x₀, g = 1 + 9·mean(x₁..x_{d−1}), f₂ = g·(1 − √(f₁/g)). Every
Pareto-optimal solution has x₁.. = 0, i.e. g = 1, so the true front is
the convex curve f₂ = 1 − √f₁ for f₁ ∈ [0, 1].
The standard multi-objective baseline: unimodal, separable, and with a knowable answer, so “did the front converge” is a measurement rather than an opinion.
§Errors
Error::InvalidSpace if the schema is
malformed — never for dim ≥ 2.
pub fn zdt2(dim: usize) -> Result<Self>
pub fn zdt2(dim: usize) -> Result<Self>
ZDT2 in dim dimensions over [0, 1]^dim.
As zdt1 but f₂ = g·(1 − (f₁/g)²), so the true front
is the concave curve f₂ = 1 − f₁².
Concavity is the point: a weighted-sum scalarization can only ever return points on the convex hull of the front, so on ZDT2 it reaches the two extremes and nothing between them. Only a dominance-based ranking — which is what NSGA-II is — covers the interior, and this is the problem that shows it.
§Errors
Error::InvalidSpace if the schema is
malformed — never for dim ≥ 2.
pub fn dtlz2(dim: usize) -> Result<Self>
pub fn dtlz2(dim: usize) -> Result<Self>
DTLZ2 with three objectives in dim dimensions over [0, 1]^dim.
g = Σ_{i≥2} (xᵢ − ½)², and with r = 1 + g
f₁ = r·cos(x₀π/2)·cos(x₁π/2)
f₂ = r·cos(x₀π/2)·sin(x₁π/2)
f₃ = r·sin(x₀π/2)The true front is g = 0 — the positive octant of the unit sphere,
Σ fᵢ² = 1 — which is why the distance to it is closed-form here.
Three objectives are not two: the crowding distance sums over one more
axis, the hypervolume switches from the O(N log N) staircase sweep to
the O(N² log N) dimension sweep, and a front is a surface rather than a
curve. A two-objective-only oracle would leave all of that unmeasured.
§Errors
Error::InvalidSpace if the schema is
malformed — never for dim ≥ 3.
pub fn constr_ex() -> Result<Self>
pub fn constr_ex() -> Result<Self>
Deb’s CONSTR: the constrained two-objective problem whose unconstrained optimum is illegal.
x₀ ∈ [0.1, 1], x₁ ∈ [0, 5], minimize f₁ = x₀ and f₂ = (1 + x₁)/x₀
subject to
g₁: x₁ + 9x₀ ≥ 6 → 6 − x₁ − 9x₀ ≤ 0
g₂: −x₁ + 9x₀ ≥ 1 → 1 + x₁ − 9x₀ ≤ 0The unconstrained front is x₁ = 0, f₂ = 1/f₁ over the whole
f₁ ∈ [0.1, 1] — and its left half violates g₁. The constrained front
is therefore f₂ = (7 − 9f₁)/f₁ on the g₁ boundary for
f₁ ∈ [7/18, 2/3] and f₂ = 1/f₁ for f₁ ∈ [2/3, 1].
That geometry is exactly what makes the problem a feasibility test with
teeth: an infeasible point such as (x₀, x₁) = (0.2, 0) has objectives
(0.2, 5), which dominates most of the legal front. A sampler that
ranks by objectives alone will fill its population with it; a
feasibility-first one will not
(constrained_dominates).
§Errors
Error::InvalidSpace if the schema is
malformed — never, for these fixed bounds.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for MoProblem
impl RefUnwindSafe for MoProblem
impl Send for MoProblem
impl Sync for MoProblem
impl Unpin for MoProblem
impl UnsafeUnpin for MoProblem
impl UnwindSafe for MoProblem
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T, U> Imply<T> for U
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more