Quickstart: NSGA-II on DTLZ2#

This example minimizes the three objectives of DTLZ2 in a 12-dimensional decision space, with all variables bounded by [0, 1]. It chooses CUDA when available and otherwise uses CPU, sets a random seed, initializes the workflow, and performs 50 iterations. It then extracts non-dominated solutions from the final population and computes IGD.

Complete example#

"""Optimize DTLZ2 with NSGA-II and report the current population's IGD."""

import torch

from evomo.algorithms import NSGA2
from evomo.metrics import igd
from evomo.operators.selection import non_dominate_rank
from evomo.problems.numerical import DTLZ2
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 = DTLZ2(d=12, m=3)
    algorithm = NSGA2(
        pop_size=100,
        n_objs=problem.m,
        lb=torch.zeros(problem.d, device=device),
        ub=torch.ones(problem.d, device=device),
        device=device,
    )
    workflow = UnifiedWorkflow(algorithm, problem, device=device)
    workflow.init_step()
    for _ in range(50):
        workflow.step()

    fitness = workflow.algorithm.fit
    finite = torch.isfinite(fitness).all(dim=1)
    population = workflow.algorithm.pop[finite]
    fitness = fitness[finite]
    if fitness.shape[0] == 0:
        raise RuntimeError("No finite objective values; check the problem evaluation")
    front = non_dominate_rank(fitness) == 0
    pareto_solutions = population[front]
    pareto_fitness = fitness[front]
    reference_front = problem.pf().to(device=fitness.device, dtype=fitness.dtype)

    print(f"Device: {device}")
    print(f"Population: {tuple(workflow.algorithm.pop.shape)}")
    print(f"Non-dominated solutions: {pareto_solutions.shape[0]}")
    print(f"IGD: {igd(pareto_fitness, reference_front).item():.6f}")


if __name__ == "__main__":
    main()

After installing the source checkout, run this command from the repository root:

python docs/examples/quickstart.py

Understanding the result#

workflow.algorithm.pop stores decision variables and workflow.algorithm.fit stores the corresponding objective values. Rows of these tensors refer to the same candidate solutions. Algorithms use minimization internally. Since this example does not change objective directions, these values are also DTLZ2’s original objectives.

non_dominate_rank(fitness) == 0 extracts the first non-dominated front of the current set. Filtering happens after optimization and is kept outside the compiled step. problem.pf() supplies reference front samples. Lower IGD means the reference points are closer on average to the resulting solution set. Hardware, versions, and random operations may change the numerical result, so the example does not prescribe a fixed score.

The 50 iterations illustrate the interface; they do not establish convergence or algorithm performance.

Next steps#