Source code for suppy.utils._bounds

from typing import List

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 Bounds: """ A class to help with hyperslab calculations. Parameters ---------- lb : None or array_like, optional Lower bounds. If None, defaults to negative infinity if `ub` is provided. ub : None or array_like, optional Upper bounds. If None, defaults to positive infinity if `lb` is provided. Attributes ---------- l : array_like Lower bounds. u : array_like Upper bounds. half_distance : array_like Half the distance between lower and upper bounds. center : array_like Center point between lower and upper bounds. Raises ------ ValueError If the sizes of the lower and upper bounds do not match. If any lower bound is greater than the corresponding upper bound. """ def __init__(self, lb: None | npt.NDArray = None, ub: None | npt.NDArray = None): # TODO: Rework validity check? Should be possible to just pass a scaler # TODO: default values for lower and upper bounds and check if lb is None and ub is not None: lb = -np.inf elif ub is None and lb is not None: ub = np.inf elif lb is None and ub is None: raise ValueError("At least one of the bounds must be provided") self.l = lb self.u = ub self.half_distance = self._half_distance() self.center = self._center()
[docs] def residual(self, x: npt.NDArray) -> tuple[npt.NDArray, npt.NDArray]: """ Calculate the residuals between the input vector `x` and the bounds `l` and `u`. Parameters ---------- x : npt.NDArray Input vector for which the residuals are to be calculated. Returns ------- tuple of npt.NDArray A tuple containing two arrays: - The residuals between `x` and the lower bound `l`. - The residuals between the upper bound `u` and `x`. """ return x - self.l, self.u - x
[docs] def single_residual(self, x: float, i: int) -> tuple[float, float]: """ Calculate the residuals for a given value for a specific constraint with respect to the lower and upper bounds. Parameters ---------- x : float The value for which the residuals are calculated. i : int The index of the bounds to use. Returns ------- tuple of float A tuple containing the residuals (x - lower_bound, upper_bound - x). """ return x - self.l[i], self.u[i] - x
[docs] def indexed_residual( self, x: npt.NDArray, i: List[int] | npt.NDArray ) -> tuple[npt.NDArray, npt.NDArray]: """ Compute the residuals for the given indices. Parameters ---------- x : npt.NDArray The input array. i : List[int] or npt.NDArray The indices for which to compute the residuals. Returns ------- tuple of npt.NDArray A tuple containing two arrays: - The residuals of `x` with respect to the lower bounds. - The residuals of `x` with respect to the upper bounds. """ return x - self.l[i], self.u[i] - x
def _center(self) -> npt.NDArray: """ Calculate the center point between the lower bound (self.l) and the upper bound (self.u). Returns ------- npt.NDArray The midpoint value between self.l and self.u. """ return (self.l + self.u) / 2 def _half_distance(self) -> npt.NDArray: """ Calculate half the distance between the upper and lower bounds. Returns ------- npt.NDArray Half the distance between the upper bound (self.u) and the lower bound (self.l). """ return (self.u - self.l) / 2
[docs] def project(self, x: npt.NDArray) -> npt.NDArray: """ Project the input array `x` onto the bounds defined by `self.l` and `self.u`. Parameters ---------- x : npt.NDArray Input array to be projected. Returns ------- npt.NDArray The projected array where each element is clipped to be within the bounds defined by `self.l` and `self.u`. """ xp = cp if isinstance(x, cp.ndarray) else np return xp.minimum(self.u, xp.maximum(self.l, x))