Source code for scalarization.ASF

import abc
import numpy as np

from abc import abstractmethod
from typing import List, Union


[docs]class ASFError(Exception): """Raised when an error related to the ASF classes is encountered."""
[docs]class ASFBase(abc.ABC): """A base class for representing achievement scalarizing functions. Instances of the implementations of this class should function as function. """ @abstractmethod
[docs] def __call__(self, objective_vector: np.ndarray, reference_point: np.ndarray) -> Union[float, np.ndarray]: """Evaluate the ASF. Args: objective_vectors (np.ndarray): The objective vectors to calculate the values. reference_point (np.ndarray): The reference point to calculate the values. Returns: Union[float, np.ndarray]: Either a single ASF value or a vector of values if objective is a 2D array. Note: The reference point may not always necessarily be feasible, but it's dimensions should match that of the objective vector. """ pass
[docs]class SimpleASF(ASFBase): """Implements a simple order-representing ASF. Args: weights (np.ndarray): A weight vector that holds weights. It's length should match the number of objectives in the underlying MOO problem the achievement problem aims to solve. Attributes: weights (np.ndarray): A weight vector that holds weights. It's length should match the number of objectives in the underlying MOO problem the achievement problem aims to solve. """ def __init__(self, weights: np.ndarray): self.weights = weights
[docs] def __call__(self, objective_vector: np.ndarray, reference_point: np.ndarray) -> Union[float, np.ndarray]: """Evaluate the simple order-representing ASF. Args: objective_vector (np.ndarray): A vector representing a solution in the solution space. reference_point (np.ndarray): A vector representing a reference point in the solution space. Note: The shaped of objective_vector and reference_point must match. """ return np.max( np.where(np.isnan(reference_point), -np.inf, self.weights * (objective_vector - reference_point)), axis=-1 )
[docs]class ReferencePointASF(ASFBase): """Uses a reference point q and preferential factors to scalarize a MOO problem. Args: preferential_factors (np.ndarray): The preferential factors. nadir (np.ndarray): The nadir point of the MOO problem to be scalarized. utopian_point (np.ndarray): The utopian point of the MOO problem to be scalarized. rho (float): A small number to be used to scale the sm factor in the ASF. Defaults to 0.1. Attributes: preferential_factors (np.ndarray): The preferential factors. nadir (np.ndarray): The nadir point of the MOO problem to be scalarized. utopian_point (np.ndarray): The utopian point of the MOO problem to be scalarized. rho (float): A small number to be used to scale the sm factor in the ASF. Defaults to 0.1. References: Miettinen, K.; Eskelinen, P.; Ruiz, F. & Luque, M. NAUTILUS method: An interactive technique in multiobjective optimization based on the nadir point Europen Journal of Operational Research, 2010, 206, 426-434 """ def __init__( self, preferential_factors: np.ndarray, nadir: np.ndarray, utopian_point: np.ndarray, rho: float = 1e-6 ): self.preferential_factors = preferential_factors self.nadir = nadir self.utopian_point = utopian_point self.rho = rho
[docs] def __call__(self, objective_vector: np.ndarray, reference_point: np.ndarray) -> Union[float, np.ndarray]: mu = self.preferential_factors f = objective_vector q = reference_point rho = self.rho z_nad = self.nadir z_uto = self.utopian_point max_term = np.max(mu * (f - q), axis=-1) sum_term = rho * np.sum((f - q) / (z_nad - z_uto), axis=-1) return max_term + sum_term
[docs]class MaxOfTwoASF(ASFBase): """Implements the ASF used in NIMBUS, which takes the maximum of two terms. Args: nadir (np.ndarray): The nadir point. ideal (np.ndarray): The ideal point. lt_inds (List[int]): Indices of the objectives categorized to be decreased. lte_inds (List[int]): Indices of the objectives categorized to be reduced until some value is reached. rho (float): A small number to form the utopian point. rho_sum (float): A small number to be used as a weight for the sum term. Attributes: nadir (np.ndarray): The nadir point. ideal (np.ndarray): The ideal point. lt_inds (List[int]): Indices of the objectives categorized to be decreased. lte_inds (List[int]): Indices of the objectives categorized to be reduced until some value is reached. rho (float): A small number to form the utopian point. rho_sum (float): A small number to be used as a weight for the sum term. References: Miettinen, K. & Mäkelä, Marko M. Synchronous approach in interactive multiobjective optimization European Journal of Operational Research, 2006, 170, 909-922 """ def __init__( self, nadir: np.ndarray, ideal: np.ndarray, lt_inds: List[int], lte_inds: List[int], rho: float = 1e-6, rho_sum: float = 1e-6, ): self.nadir = nadir self.ideal = ideal self.lt_inds = lt_inds self.lte_inds = lte_inds self.rho = rho self.rho_sum = rho_sum
[docs] def __call__(self, objective_vector: np.ndarray, reference_point: np.ndarray) -> Union[float, np.ndarray]: # assure this function works with single objective vectors if objective_vector.ndim == 1: f = objective_vector.reshape((1, -1)) else: f = objective_vector ii = self.lt_inds jj = self.lte_inds z = reference_point nad = self.nadir ide = self.ideal uto = self.ideal - self.rho lt_term = (f[:, ii] - ide[ii]) / (nad[ii] - uto[ii]) lte_term = (f[:, jj] - z[jj]) / (nad[jj] - uto[jj]) max_term = np.max(np.hstack((lt_term, lte_term)), axis=1) sum_term = self.rho_sum * np.sum(f / (nad - uto), axis=1) return max_term + sum_term
[docs]class StomASF(ASFBase): """Implementation of the satisfying trade-off method (STOM). Args: ideal (np.ndarray): The ideal point. rho (float): A small number to form the utopian point. rho_sum (float): A small number to be used as a weight for the sum term. Attributes: ideal (np.ndarray): The ideal point. rho (float): A small number to form the utopian point. rho_sum (float): A small number to be used as a weight for the sum term. References: Miettinen, K. & Mäkelä, Marko M. Synchronous approach in interactive multiobjective optimization European Journal of Operational Research, 2006, 170, 909-922 """ def __init__(self, ideal: np.ndarray, rho: float = 1e-6, rho_sum: float = 1e-6): self.ideal = ideal self.rho = rho self.rho_sum = rho_sum
[docs] def __call__(self, objective_vectors: np.ndarray, reference_point: np.ndarray) -> Union[float, np.ndarray]: # assure this function works with single objective vectors if objective_vectors.ndim == 1: f = objective_vectors.reshape((1, -1)) else: f = objective_vectors z = reference_point uto = self.ideal - self.rho max_term = np.max((f - uto) / (z - uto), axis=1) sum_term = self.rho_sum * np.sum((f) / (z - uto), axis=1) return max_term + sum_term
[docs]class PointMethodASF(ASFBase): """Implementation of the reference point based ASF. Args: nadir (np.ndarray): The nadir point. ideal (np.ndarray): The ideal point. rho (float): A small number to form the utopian point. rho_sum (float): A small number to be used as a weight for the sum term. References: Miettinen, K. & Mäkelä, Marko M. Synchronous approach in interactive multiobjective optimization European Journal of Operational Research, 2006, 170, 909-922 """ def __init__(self, nadir: np.ndarray, ideal: np.ndarray, rho: float = 1e-6, rho_sum: float = 1e-6): self.nadir = nadir self.ideal = ideal self.rho = rho self.rho_sum = rho_sum
[docs] def __call__(self, objective_vectors: np.ndarray, reference_point: np.ndarray): # assure this function works with single objective vectors if objective_vectors.ndim == 1: f = objective_vectors.reshape((1, -1)) else: f = objective_vectors z = reference_point nad = self.nadir uto = self.ideal - self.rho max_term = np.max((f - z) / (nad - uto), axis=1) sum_term = self.rho_sum * np.sum((f) / (nad - uto), axis=1) return max_term + sum_term
[docs]class AugmentedGuessASF(ASFBase): """Implementation of the augmented GUESS related ASF. Args: nadir (np.ndarray): The nadir point. ideal (np.ndarray): The ideal point. index_to_exclude (List[int]): The indices of the objective functions to be excluded in calculating the first term of the ASF. rho (float): A small number to form the utopian point. rho_sum (float): A small number to be used as a weight for the sum term. References: Miettinen, K. & Mäkelä, Marko M. Synchronous approach in interactive multiobjective optimization European Journal of Operational Research, 2006, 170, 909-922 """ def __init__( self, nadir: np.ndarray, ideal: np.ndarray, index_to_exclude: List[int], rho: float = 1e-6, rho_sum: float = 1e-6, ): self.nadir = nadir self.ideal = ideal self.index_to_exclude = index_to_exclude self.rho = rho self.rho_sum = rho_sum
[docs] def __call__(self, objective_vectors: np.ndarray, reference_point: np.ndarray): # assure this function works with single objective vectors if objective_vectors.ndim == 1: f = objective_vectors.reshape((1, -1)) else: f = objective_vectors if reference_point.ndim == 1: z = reference_point elif reference_point.ndim == 2 and reference_point.shape[0] == 1: z = reference_point[0] else: msg = "Error interpreting reference point" raise ASFError(msg) nad = self.nadir uto = self.ideal - self.rho ex_mask = np.full((f.shape[1]), True, dtype=bool) ex_mask[self.index_to_exclude] = False max_term = np.max((f[:, ex_mask] - nad[ex_mask]) / (nad[ex_mask] - z[ex_mask]), axis=1) sum_term_1 = self.rho_sum * np.sum((f[:, ex_mask]) / (nad[ex_mask] - z[ex_mask]), axis=1) # avoid division by zeros sum_term_2 = self.rho_sum * np.sum((f[:, ~ex_mask]) / (nad[~ex_mask] - uto[~ex_mask]), axis=1) return max_term + sum_term_1 + sum_term_2
[docs]class GuessASF(ASFBase): """Implementation of the naive or GUESS ASF. Args: nadir (np.ndarray): The nadir point of the problem being scalarized. References: Miettinen, K., Mäkelä, M. On scalarizing functions in multiobjective optimization OR Spectrum 24, 193–213 (2002) """ def __init__(self, nadir: np.ndarray): self.nadir = nadir
[docs] def __call__(self, objective_vectors: np.ndarray, reference_point: np.ndarray): nad = self.nadir f = np.atleast_2d(objective_vectors) z = reference_point max_term = np.max((f - nad) / (nad - z), axis=1) return max_term