Problems

Problems#

The moospread package provides a PyTorch class PymooProblemTorch inspired by pymoo, which can be used to define custom problems for the SPREAD solver. As an example, consider the ZDT2 problem, a bi-objective benchmark (typically defined with \(n=30\) decision variables) with a non-convex Pareto-optimal front.

Decision variables

\[x = (x_1, \ldots, x_n) \in [0,1]^n.\]

Objectives (to be minimized)

\[\begin{split}\begin{aligned} f_1(x) &= x_1,\\ f_2(x) &= g(x)\left(1 - \left(\frac{f_1(x)}{g(x)}\right)^2\right),\qquad \text{with}\ g(x) = 1 + \frac{9}{n-1}\sum_{i=2}^{n} x_i. \end{aligned}\end{split}\]

Equivalently, using the helper function \(h(f_1,g) = 1 - (f_1/g)^2\), one can write \(f_2(x) = g(x)\,h(f_1(x), g(x))\).

Pareto-optimal set and Pareto front

The Pareto-optimal set is given by:

\[0 \le x_1^\star \le 1 \quad\text{and}\quad x_i^\star = 0 \;\; \text{for } i=2,\ldots,n.\]

The corresponding Pareto front (in objective space) is:

\[f_2 = 1 - f_1^2, \qquad 0 \le f_1 \le 1.\]

Implementation

This problem can be implemented as follows:

import torch
from moospread import PymooProblemTorch

class ZDT2(PymooProblemTorch):
    def __init__(
        self,
        n_var: int = 30,          # number of decision variables
        ref_point=None,           # reference point for hypervolume calculation
        **kwargs
    ):
        super().__init__(
            n_var=n_var,
            n_obj=2,  # number of objectives
            xl=torch.zeros(n_var, dtype=torch.float) + 1e-6,  # lower bounds
            xu=torch.ones(n_var, dtype=torch.float) - 1e-6,   # upper bounds
            vtype=float,
            **kwargs
        )
        self.ref_point = ref_point

    # Provide the true Pareto front (if known; otherwise return None).
    def _calc_pareto_front(self, n_pareto_points: int = 100) -> torch.Tensor:
        x = torch.linspace(0.0, 1.0, n_pareto_points, device=self.device)
        return torch.stack([x, 1.0 - x.pow(2)], dim=1)

    # Implement the objective function evaluation.
    # Use PyTorch operations to preserve differentiability.
    def _evaluate(self, x: torch.Tensor, out: dict, *args, **kwargs) -> None:
        f1 = x[:, 0]
        c = torch.sum(x[:, 1:], dim=1)
        g = 1.0 + 9.0 * c / (self.n_var - 1)
        term = torch.clamp(f1 / g, min=0.0)
        f2 = g * (1.0 - term.pow(2))
        out["F"] = torch.stack([f1, f2], dim=1)

In the Offline Setting, the _evaluate method is not required, since the optimization process relies exclusively on a provided dataset and surrogate proxies rather than on evaluations of the true objective functions. In that case, features that depend on true evaluations (e.g., hypervolume computation or certain plotting utilities) may be unavailable. moospread also provides several Test Problems that can be accessed via moospread.tasks.