Source code for suppy.projections._projections

"""Base classes for all projection objects."""
from abc import ABC, abstractmethod
import numpy as np
import numpy.typing as npt

try:
    import cupy as cp

    NO_GPU = False
except ImportError:
    NO_GPU = True
    cp = np


[docs] class Projection(ABC): """ Abstract base class for projections used in feasibility algorithms. Parameters ---------- relaxation : float, optional Relaxation parameter for the projection, by default 1. proximity_flag : bool Flag to indicate whether to take this object into account when calculating proximity, by default True. Attributes ---------- relaxation : float Relaxation parameter for the projection. proximity_flag : bool Flag to indicate whether to take this object into account when calculating proximity. """ def __init__(self, relaxation=1, proximity_flag=True, _use_gpu=False): self.relaxation = relaxation self.proximity_flag = proximity_flag self._use_gpu = _use_gpu # @ensure_float_array # removed decorator since it leads to unwanted behavior
[docs] def step(self, x: npt.NDArray) -> np.ndarray: """ Perform the (possibly relaxed) projection of input array 'x' onto the constraint. Parameters ---------- x : npt.NDArray The input array to be projected. Returns ------- npt.NDArray The (possibly relaxed) projection of 'x' onto the constraint. """ return self.project(x)
[docs] def project(self, x: npt.NDArray) -> np.ndarray: """ Perform the (possibly relaxed) projection of input array 'x' onto the constraint. Parameters ---------- x : npt.NDArray The input array to be projected. Returns ------- npt.NDArray The (possibly relaxed) projection of 'x' onto the constraint. """ if self.relaxation == 1: return self._project(x) return x.copy() * (1 - self.relaxation) + self.relaxation * (self._project(x))
@abstractmethod def _project(self, x: npt.NDArray) -> np.ndarray: """Internal method to project the point x onto the set."""
[docs] def proximity(self, x: npt.NDArray, proximity_measures: list) -> float: """ Calculate proximity measures of point `x` to the set. Parameters ---------- x : npt.NDArray Input array for which the proximity measure is to be calculated. Returns ------- list[float] The proximity measures of the input array `x`. """ xp = cp if isinstance(x, cp.ndarray) else np if self.proximity_flag: return xp.array(self._proximity(x, proximity_measures)) return xp.zeros(len(proximity_measures))
@abstractmethod def _proximity(self, x: npt.NDArray, proximity_measures: list) -> list[float]: """ Calculate proximity measures of point `x` to set. Parameters ---------- x : npt.NDArray Input array for which the proximity measures are to be calculated. proximity_measures : list list of proximity measures to calculate. Returns ------- list[float] The proximity measures of the input array `x`. """
[docs] class BasicProjection(Projection, ABC): """ BasicProjection is an abstract base class that extends the Projection class. It allows for projecting onto a subset of the input array based on provided indices. Parameters ---------- relaxation : float, optional Relaxation parameter for the projection, by default 1. idx : npt.NDArray or None, optional Indices to apply the projection, by default None. proximity_flag : bool Flag to indicate whether to take this object into account when calculating proximity, by default True. Attributes ---------- relaxation : float Relaxation parameter for the projection. proximity_flag : bool Flag to indicate whether to take this object into account when calculating proximity. idx : npt.NDArray Subset of the input vector to apply the projection on. """ def __init__( self, relaxation=1, idx: npt.NDArray | None = None, proximity_flag=True, _use_gpu=False ): super().__init__(relaxation, proximity_flag, _use_gpu) self.idx = idx if idx is not None else np.s_[:] # NOTE: This method should not be required since the base class implementation is sufficient # def project(self, x: npt.NDArray) -> np.ndarray: # """ # Perform the (possibly relaxed) projection of input array 'x' onto the constraint. # Parameters # ---------- # x : npt.NDArray # The input array to be projected. # Returns # ------- # npt.NDArray # The (possibly relaxed) projection of 'x' onto the constraint. # """ # if self.relaxation == 1: # return self._project(x) # else: # x[self.idx] = x[self.idx] * (1 - self.relaxation) + self.relaxation * ( # self._project(x)[self.idx] # ) # return x def _proximity(self, x: npt.NDArray, proximity_measures: list) -> list[float]: # probably should have some option to choose the distance res = x[self.idx] - self._project(x.copy())[self.idx] dist = (res**2).sum() ** (1 / 2) measures = [] for measure in proximity_measures: if isinstance(measure, tuple): if measure[0] == "p_norm": measures.append(dist ** measure[1]) else: raise ValueError("Invalid proximity measure") elif isinstance(measure, str) and measure == "max_norm": measures.append(dist) else: raise ValueError("Invalid proximity measure") return measures