Skill Tracer
Skills › Scientific Agent Skills › pymoo › constraints_mcdm.md

constraints_mcdm.md

186 lines · skills/pymoo/references/constraints_mcdm.md at 5f2bc03

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# Constraints and decision making (pymoo 0.6.2)
2

Constraints and decision making (pymoo 0.6.2)

Raw lines 1 to 2
3## Encode and test the original constraints
4
5All objectives are minimized. Negate a maximization objective and keep that sign
6conversion with the result. Inequalities use `G <= 0`; equalities use `H = 0`.
7`n_ieq_constr` and `n_eq_constr` declare the separate counts.
8
9```python
10import numpy as np
11from pymoo.core.problem import ElementwiseProblem
13class ConstrainedProblem(ElementwiseProblem):
14 def __init__(self):
15 super().__init__(n_var=2, n_obj=2, n_ieq_constr=2, n_eq_constr=1,
16 xl=np.zeros(2), xu=np.full(2, 5.0))
18 def _evaluate(self, x, out, *args, **kwargs):
19 out["F"] = [np.sum(x**2), np.sum((x - 1)**2)]
20 # x0 + x1 >= 5; x0**2 + x1**2 <= 25
21 out["G"] = [5 - x[0] - x[1], np.sum(x**2) - 25]
22 out["H"] = [x[0] - 2*x[1]]
24problem = ConstrainedProblem()
25F, G, H = problem.evaluate(np.array([[4.0, 2.0], [0.0, 0.0]]),
26 return_values_of=["F", "G", "H"])
27assert np.all(G[0] <= 0) and H[0, 0] == 0
28assert G[1, 0] > 0
29```
31For vectorized `Problem`, return arrays of shape `(n_candidates, n_obj)`,
32`(n_candidates, n_ieq_constr)`, and `(n_candidates, n_eq_constr)`. For
33`ElementwiseProblem`, return one vector of each kind per candidate.
35The default individual configuration has inequality tolerance 0, equality
36residual tolerance `1e-4`, and `cv_eps=0`. Thus exact floating-point equality is
37not the solver's feasibility test. Normalize constraint residuals by documented
38positive physical scales before aggregating them: otherwise a large-unit
39constraint can dominate violation rankings. Scaling also changes the physical
40meaning of an equality tolerance. Re-evaluate original units at the final design.

Encode and test the original constraints

All objectives are minimized. Negate a maximization objective and keep that sign conversion with the result. Inequalities use G <= 0; equalities use H = 0. n_ieq_constr and n_eq_constr declare the separate counts.

import numpy as np
from pymoo.core.problem import ElementwiseProblem

class ConstrainedProblem(ElementwiseProblem):
    def __init__(self):
        super().__init__(n_var=2, n_obj=2, n_ieq_constr=2, n_eq_constr=1,
                         xl=np.zeros(2), xu=np.full(2, 5.0))

    def _evaluate(self, x, out, *args, **kwargs):
        out["F"] = [np.sum(x**2), np.sum((x - 1)**2)]
        # x0 + x1 >= 5; x0**2 + x1**2 <= 25
        out["G"] = [5 - x[0] - x[1], np.sum(x**2) - 25]
        out["H"] = [x[0] - 2*x[1]]

problem = ConstrainedProblem()
F, G, H = problem.evaluate(np.array([[4.0, 2.0], [0.0, 0.0]]),
                           return_values_of=["F", "G", "H"])
assert np.all(G[0] <= 0) and H[0, 0] == 0
assert G[1, 0] > 0

For vectorized Problem, return arrays of shape (n_candidates, n_obj), (n_candidates, n_ieq_constr), and (n_candidates, n_eq_constr). For ElementwiseProblem, return one vector of each kind per candidate.

The default individual configuration has inequality tolerance 0, equality residual tolerance 1e-4, and cv_eps=0. Thus exact floating-point equality is not the solver's feasibility test. Normalize constraint residuals by documented positive physical scales before aggregating them: otherwise a large-unit constraint can dominate violation rankings. Scaling also changes the physical meaning of an equality tolerance. Re-evaluate original units at the final design.

Raw lines 3 to 41
42## Choose a supported handling method
44- `NSGA2` uses feasibility-first comparisons and constrained survival. This is
45 common, not universal: pymoo's `MOEAD` rejects constrained problems.
46- `SRES` and `ISRES` provide stochastic ranking for single-objective constraints.
47- `ConstraintsAsObjective(problem)` removes solver constraints and **prepends**
48 CV to `F`: `[CV, f1, ...]`. Its default `append=True` adds an objective;
49 `append=False` optimizes only CV. Infeasible trade-offs may remain in the result.
50- `ConstraintsAsPenalty(problem, penalty=...)` is demonstrated here only for
51 single-objective problems. In 0.6.2 its implementation reshapes CV to `F.shape`
52 and fails for ordinary multi-objective arrays. Penalties require unit/scale
53 choices and do not guarantee feasibility.
54- A `Repair._do(problem, X, **kwargs)` returns repaired decision vectors. Clipping
55 to box bounds repairs only those bounds; it does not repair arbitrary `G/H`.
56- `AdaptiveEpsilonConstraintHandling(algorithm, perc_eps_until=0.5)` is the
57 epsilon wrapper in `pymoo.constraints.eps`; temporary tolerance relaxation is
58 not a license to report a final infeasible design as feasible.
60When calling a wrapper directly, use `return_values_of=["F", "G", "H"]` and
61`return_as_dictionary=True`, then retrieve `["F"]`. Its implementation deletes both
62constraint keys: omitted keys or requesting a returned G/H tuple can raise
63`KeyError`. The normal optimizer evaluator uses the dictionary path. These snippets
64assume an existing problem of the indicated objective count:
66```python
67from pymoo.constraints.as_obj import ConstraintsAsObjective
68problem_with_cv = ConstraintsAsObjective(problem)
69transformed = problem_with_cv.evaluate(np.array([[4.0, 2.0]]),
70 return_values_of=["F", "G", "H"], return_as_dictionary=True)["F"]
71# Recompute problem.evaluate(X, return_values_of=["F", "G", "H"])
72# on any candidate selected from problem_with_cv before reporting feasibility.
73```
75A run without a feasible optimum normally returns `result.F is None` and
76`result.X is None`. `return_least_infeasible=True` returns a diagnostic candidate,
77not a feasible solution. The transformed wrappers remove constraints, so their
78reported CV cannot certify the original problem. For ordinary unwrapped MOO
79results use `result.opt.get("FEAS").ravel()` after the None check; single-objective
80`X/F/CV` can be one-dimensional.

Choose a supported handling method

  • NSGA2 uses feasibility-first comparisons and constrained survival. This is common, not universal: pymoo's MOEAD rejects constrained problems.
  • SRES and ISRES provide stochastic ranking for single-objective constraints.
  • ConstraintsAsObjective(problem) removes solver constraints and prepends CV to F: [CV, f1, ...]. Its default append=True adds an objective; append=False optimizes only CV. Infeasible trade-offs may remain in the result.
  • ConstraintsAsPenalty(problem, penalty=...) is demonstrated here only for single-objective problems. In 0.6.2 its implementation reshapes CV to F.shape and fails for ordinary multi-objective arrays. Penalties require unit/scale choices and do not guarantee feasibility.
  • A Repair._do(problem, X, **kwargs) returns repaired decision vectors. Clipping to box bounds repairs only those bounds; it does not repair arbitrary G/H.
  • AdaptiveEpsilonConstraintHandling(algorithm, perc_eps_until=0.5) is the epsilon wrapper in pymoo.constraints.eps; temporary tolerance relaxation is not a license to report a final infeasible design as feasible.

When calling a wrapper directly, use return_values_of=["F", "G", "H"] and return_as_dictionary=True, then retrieve ["F"]. Its implementation deletes both constraint keys: omitted keys or requesting a returned G/H tuple can raise KeyError. The normal optimizer evaluator uses the dictionary path. These snippets assume an existing problem of the indicated objective count:

from pymoo.constraints.as_obj import ConstraintsAsObjective
problem_with_cv = ConstraintsAsObjective(problem)
transformed = problem_with_cv.evaluate(np.array([[4.0, 2.0]]),
    return_values_of=["F", "G", "H"], return_as_dictionary=True)["F"]
# Recompute problem.evaluate(X, return_values_of=["F", "G", "H"])
# on any candidate selected from problem_with_cv before reporting feasibility.

A run without a feasible optimum normally returns result.F is None and result.X is None. return_least_infeasible=True returns a diagnostic candidate, not a feasible solution. The transformed wrappers remove constraints, so their reported CV cannot certify the original problem. For ordinary unwrapped MOO results use result.opt.get("FEAS").ravel() after the None check; single-objective X/F/CV can be one-dimensional.

Raw lines 42 to 81
82## Prepare candidates for decision making
84Use a finite, feasible, nondominated candidate set and keep row alignment with
85`X`. An optimizer's nondominated set is an approximation, not proof of the true
86Pareto front. Do not silently turn failed evaluations into valid objective values.
88Choose ideal/nadir bounds from scientific requirements or a common reference set.
89Bounds estimated separately for each run can conceal scale and convergence
90changes. For an individual candidate set:
92```python
93from pymoo.problems import get_problem
94F = get_problem("zdt1").pareto_front()
95ideal, nadir = F.min(axis=0), F.max(axis=0)
96span = nadir - ideal
97if not np.isfinite(F).all() or np.any(span <= 0):
98 raise ValueError("Resolve nonfinite or constant objectives before normalization")
99F_norm = (F - ideal) / span
100```
102A single feasible candidate needs no preference ranking. For constant objectives,
103explicitly remove them and revise weights, or select a scientifically justified
104fixed scale. Do not divide by zero or mask NaNs to obtain an arbitrary index.

Prepare candidates for decision making

Use a finite, feasible, nondominated candidate set and keep row alignment with X. An optimizer's nondominated set is an approximation, not proof of the true Pareto front. Do not silently turn failed evaluations into valid objective values.

Choose ideal/nadir bounds from scientific requirements or a common reference set. Bounds estimated separately for each run can conceal scale and convergence changes. For an individual candidate set:

from pymoo.problems import get_problem
F = get_problem("zdt1").pareto_front()
ideal, nadir = F.min(axis=0), F.max(axis=0)
span = nadir - ideal
if not np.isfinite(F).all() or np.any(span <= 0):
    raise ValueError("Resolve nonfinite or constant objectives before normalization")
F_norm = (F - ideal) / span

A single feasible candidate needs no preference ranking. For constant objectives, explicitly remove them and revise weights, or select a scientifically justified fixed scale. Do not divide by zero or mask NaNs to obtain an arbitrary index.

Raw lines 82 to 105
106## Pseudo-weights
108`PseudoWeights` compares the desired weight vector with normalized distances from
109the worst values. It minimizes L1 distance between pseudo-weight vectors; it does
110**not** minimize a weighted sum. Pseudo-weights need not reproduce weighted-sum
111solutions on nonconvex fronts, and increasing one weight does not guarantee a
112monotone response on every finite candidate set.
114```python
115from pymoo.mcdm.pseudo_weights import PseudoWeights
116weights = np.array([0.3, 0.7]) # nonnegative, finite, sum to one
117selected, achieved = PseudoWeights(weights).do(F, return_pseudo_weights=True)
118assert np.isclose(achieved[selected].sum(), 1.0)
119selected_objectives = F[selected]
120```
122The bundled `scripts/decision_making_example.py` validates candidate/weight shapes,
123feasibility, nondominance, and constant columns before selection. Its conservative
124CV guard assumes the default final feasibility configuration; for custom
125`cv_eps`, explicitly validate the original physical tolerances first.

Pseudo-weights

PseudoWeights compares the desired weight vector with normalized distances from the worst values. It minimizes L1 distance between pseudo-weight vectors; it does not minimize a weighted sum. Pseudo-weights need not reproduce weighted-sum solutions on nonconvex fronts, and increasing one weight does not guarantee a monotone response on every finite candidate set.

from pymoo.mcdm.pseudo_weights import PseudoWeights
weights = np.array([0.3, 0.7])  # nonnegative, finite, sum to one
selected, achieved = PseudoWeights(weights).do(F, return_pseudo_weights=True)
assert np.isclose(achieved[selected].sum(), 1.0)
selected_objectives = F[selected]

The bundled scripts/decision_making_example.py validates candidate/weight shapes, feasibility, nondominance, and constant columns before selection. Its conservative CV guard assumes the default final feasibility configuration; for custom cv_eps, explicitly validate the original physical tolerances first.

Raw lines 106 to 126
Runs code scripts/decision_making_example.py :122
127## Scalarization and high trade-off points
129For compromise programming, use a working decomposition such as `ASF`. The
1300.6.2 `CompromiseProgramming` class is unfinished and does not return a usable
131selection. ASF divides by its supplied weights: to make larger preference weights
132penalize an objective more strongly, pass reciprocal preferences after normalization.
134```python
135from pymoo.decomposition.asf import ASF
136preferences = np.array([0.3, 0.7]) # strictly positive for this recipe
137selected_asf = int(ASF().do(F_norm, 1.0 / preferences).argmin())
138```
140The supported trade-off method is `pymoo.mcdm.high_tradeoff.HighTradeoffPoints`,
141not `pymoo.mcdm.knee.KneePoint`. It can return no selected indices and can be
142sensitive to sampling density, duplicates, and normalization. Its existence does
143not establish a unique scientific knee. The 0.6.2 source currently hardcodes the
144neighborhood epsilon inside `_do`; do not claim that its constructor's `epsilon`
145changes the calculation. This method is source-reviewed, not part of the executed
146selection recipe.

Scalarization and high trade-off points

For compromise programming, use a working decomposition such as ASF. The 0.6.2 CompromiseProgramming class is unfinished and does not return a usable selection. ASF divides by its supplied weights: to make larger preference weights penalize an objective more strongly, pass reciprocal preferences after normalization.

from pymoo.decomposition.asf import ASF
preferences = np.array([0.3, 0.7])  # strictly positive for this recipe
selected_asf = int(ASF().do(F_norm, 1.0 / preferences).argmin())

The supported trade-off method is pymoo.mcdm.high_tradeoff.HighTradeoffPoints, not pymoo.mcdm.knee.KneePoint. It can return no selected indices and can be sensitive to sampling density, duplicates, and normalization. Its existence does not establish a unique scientific knee. The 0.6.2 source currently hardcodes the neighborhood epsilon inside _do; do not claim that its constructor's epsilon changes the calculation. This method is source-reviewed, not part of the executed selection recipe.

Raw lines 127 to 147
148## Hypervolume and reference-front indicators
150All indicator inputs use the same minimization orientation, units, and fixed
151normalization across runs. For HV choose a fixed reference point worse (larger)
152than the relevant candidate objectives. Points beyond it contribute no useful
153volume. A large reference point can substantially change algorithm rankings.
155```python
156from pymoo.indicators.hv import HV
157from pymoo.indicators.igd import IGD
158from pymoo.indicators.igd_plus import IGDPlus
160small_front = np.array([[0.0, 1.0], [0.5, 0.5], [1.0, 0.0]])
161hv = HV(ref_point=np.array([2.0, 2.0]))
162assert np.isclose(hv(small_front), 3.25)
163# Exact leave-one-out contributions for a SMALL set; HV has no calc_contributions.
164contributions = np.array([
165 hv(small_front) - hv(np.delete(small_front, i, axis=0))
166 for i in range(len(small_front))
167])
168assert np.allclose(contributions, [0.5, 0.25, 0.5])
169assert IGD(small_front)(small_front) == 0
170assert IGDPlus(small_front)(small_front) == 0
171```
173Higher HV is better; lower GD/IGD/IGD+ is better. IGD depends on the coverage and
174density of a credible reference front; a pooled approximation is not a true front.
175Leave-one-out HV costs repeated indicator evaluations and grows expensive with
176many objectives. Selecting the top individual contributions is a heuristic, not
177an optimal size-k subset. Compare matched evaluation budgets over multiple seeds,
178report distributions and feasible-run rates, and verify promising designs with
179the original model. A stable indicator or termination flag alone is not convergence
180or a global optimality certificate.
182Sources reviewed 2026-10-01: [constraints](https://pymoo.org/constraints/index.html),
183[CV objective](https://pymoo.org/constraints/as_obj.html),
184[CV penalty](https://pymoo.org/constraints/as_penalty.html),
185[MCDM](https://pymoo.org/mcdm/index.html),
186[indicators](https://pymoo.org/misc/indicators.html), and the released 0.6.2 source.

Hypervolume and reference-front indicators

All indicator inputs use the same minimization orientation, units, and fixed normalization across runs. For HV choose a fixed reference point worse (larger) than the relevant candidate objectives. Points beyond it contribute no useful volume. A large reference point can substantially change algorithm rankings.

from pymoo.indicators.hv import HV
from pymoo.indicators.igd import IGD
from pymoo.indicators.igd_plus import IGDPlus

small_front = np.array([[0.0, 1.0], [0.5, 0.5], [1.0, 0.0]])
hv = HV(ref_point=np.array([2.0, 2.0]))
assert np.isclose(hv(small_front), 3.25)
# Exact leave-one-out contributions for a SMALL set; HV has no calc_contributions.
contributions = np.array([
    hv(small_front) - hv(np.delete(small_front, i, axis=0))
    for i in range(len(small_front))
])
assert np.allclose(contributions, [0.5, 0.25, 0.5])
assert IGD(small_front)(small_front) == 0
assert IGDPlus(small_front)(small_front) == 0

Higher HV is better; lower GD/IGD/IGD+ is better. IGD depends on the coverage and density of a credible reference front; a pooled approximation is not a true front. Leave-one-out HV costs repeated indicator evaluations and grows expensive with many objectives. Selecting the top individual contributions is a heuristic, not an optimal size-k subset. Compare matched evaluation budgets over multiple seeds, report distributions and feasible-run rates, and verify promising designs with the original model. A stable indicator or termination flag alone is not convergence or a global optimality certificate.

Sources reviewed 2026-10-01: constraints, CV objective, CV penalty, MCDM, indicators, and the released 0.6.2 source.

Raw lines 148 to 186