from __future__ import annotations
from typing import Any
import numpy as np
from smac.multi_objective.abstract_multi_objective_algorithm import (
    AbstractMultiObjectiveAlgorithm,
)
from smac.scenario import Scenario
[docs]
class ParEGO(AbstractMultiObjectiveAlgorithm):
    """ParEGO implementation based on https://www.cs.bham.ac.uk/~jdk/UKCI-2015.pdf.
    Parameters
    ----------
    scenario : Scenario
    rho : float, defaults to 0.05
        A small positive value.
    seed : int | None, defaults to None
    """
    def __init__(
        self,
        scenario: Scenario,
        rho: float = 0.05,
        seed: int | None = None,
    ):
        super(ParEGO, self).__init__()
        if seed is None:
            seed = scenario.seed
        self._n_objectives = scenario.count_objectives()
        self._seed = seed
        self._rng = np.random.RandomState(seed)
        self._rho = rho
        # Will be set on starting an SMBO iteration
        self._theta: np.ndarray | None = None
    @property
    def meta(self) -> dict[str, Any]:  # noqa: D102
        meta = super().meta
        meta.update(
            {
                "name": self.__class__.__name__,
                "rho": self._rho,
                "seed": self._seed,
            }
        )
        return meta
[docs]
    def update_on_iteration_start(self) -> None:  # noqa: D102
        self._theta = self._rng.rand(self._n_objectives)
        # Normalize so that all theta values sum up to 1
        self._theta = self._theta / (np.sum(self._theta) + 1e-10) 
[docs]
    def __call__(self, values: list[float]) -> float:  # noqa: D102
        # Weight the values
        if self._theta is None:
            raise ValueError("Iteration not yet initalized; Call `update_on_iteration_start()` first")
        theta_f = self._theta * values
        return float(np.max(theta_f, axis=0) + self._rho * np.sum(theta_f, axis=0))