Constrained multiobjective optimization#

Violation conventions#

CMOP.evaluate(population) returns (fitness, cv). Objectives have shape (N, M) and violations have shape (N, n_iq + n_eq), with one column per constraint.

  • Inequalities use g(x) <= 0; the violation is max(g(x), 0).

  • Equality violations are max(abs(h(x)) - constr_eq_eps, 0); the default tolerance is 1e-4.

  • A solution is feasible when all violations are zero. Current selection compares total violations.

A negative raw inequality value means the constraint is satisfied; do not use it directly as a violation. Currently, CMOP.evaluate always returns a pair, regardless of the constructor’s return_cv parameter.

NSGA-II on DOC1#

"""Keep constraint violations when solving DOC1 with NSGA-II."""

import torch

from evomo.algorithms import NSGA2
from evomo.operators.selection import non_dominate_rank
from evomo.problems.constrained import DOC1
from evomo.workflows import UnifiedWorkflow


def main():
    device = "cuda" if torch.cuda.is_available() else "cpu"
    torch.set_default_device(device)
    torch.manual_seed(42)

    problem = DOC1()
    algorithm = NSGA2(pop_size=100, n_objs=problem.m, lb=problem.lb, ub=problem.ub, device=device)
    workflow = UnifiedWorkflow(algorithm, problem, device=device)
    workflow.init_step()
    for _ in range(50):
        workflow.step()

    fitness = workflow.algorithm.fit
    cv = workflow.algorithm.cv
    feasible = (cv.sum(dim=1) <= 0) & torch.isfinite(fitness).all(dim=1)
    feasible_fitness = fitness[feasible]
    print(f"Fitness: {tuple(fitness.shape)}, CV: {tuple(cv.shape)}")
    print(f"Finite feasible solutions: {feasible.sum().item()}/{feasible.numel()}")
    if feasible_fitness.shape[0] > 0:
        print(feasible_fitness[non_dominate_rank(feasible_fitness) == 0])
    else:
        print("No feasible solution in the current population; do not report a feasible-front metric.")


if __name__ == "__main__":
    main()
python docs/examples/constrained.py

NSGA-II preserves violations during initialization and selection in each generation. Feasible solutions take priority over infeasible solutions. Infeasible solutions are first compared by total violation. Objective dominance applies between feasible solutions or when total violations are equal.

Extracting a feasible front#

First filter by violations and finite objective values, then extract rank-zero points from the remaining objectives. A front computed from objectives alone is not a feasible front. A small value for one objective also does not establish feasibility.

If no feasible solutions remain, report feasibility rates or violation statistics. GD and IGD require nonempty inputs, so feasible-front distance metrics cannot be reported in that case.

Using another algorithm#

A constrained run requires all three conditions:

  1. The problem returns both objectives and violations.

  2. The workflow preserves both, for example through UnifiedWorkflow.

  3. The algorithm uses violations in comparison, selection, and state updates.

The first two conditions are insufficient on their own. Some algorithms treat evaluate as a pure objective tensor. Even implementations that unpack its return value need to be checked for actual constraint-aware selection. This guide uses evomo.algorithms.NSGA2; it does not establish constraint support for every algorithm.