Source code for rework_pysatl_mpest.distributions.uniform

"""Module providing uniform distribution class"""

__author__ = "Maksim Pastukhov"
__copyright__ = "Copyright (c) 2025 PySATL project"
__license__ = "SPDX-License-Identifier: MIT"

import numpy as np
from numpy import float64
from scipy.stats import uniform

from ..core import Parameter
from .continuous_dist import ContinuousDistribution


[docs] class Uniform(ContinuousDistribution): """ The Uniform continuous probability distribution. The uniform distribution describes an experiment where there is an arbitrary outcome that lies between certain bounds. The probability is constant between these bounds and zero elsewhere. Parameters ---------- left_border : float Left border of section [a, b]. Can be any real number. right_border : float Right border of section [a, b]. Can be any real number. Attributes ---------- left_border : float Left border of section [a, b]. right_border : float Right border of section [a, b]. Raises ------ ValueError If left_border is greater than or equal to right_border, or if either parameter is not finite. Methods ------- .. autosummary:: :toctree: generated/ ppf pdf lpdf log_gradients generate """ LEFT_BORDER = "left_border" RIGHT_BORDER = "right_border" left_border = Parameter() right_border = Parameter() def __init__(self, left_border: float, right_border: float): super().__init__() if left_border >= right_border: raise ValueError("right_border parameter must be strictly greater than left_border") if not (np.isfinite(left_border) and np.isfinite(right_border)): raise ValueError("Both borders should be finite values") self.left_border = left_border self.right_border = right_border @property def name(self) -> str: return "Uniform" @property def params(self) -> set[str]: return {self.LEFT_BORDER, self.RIGHT_BORDER}
[docs] def pdf(self, X): """Probability density function (PDF). The PDF for the unifrom distribution is: .. math:: f(x | \\alpha, \\beta) = frac{1}{\\beta - \\alpha} where :math:`\\alpha` is the left_border parameter and :math:`\\beta` is the right_border parameter. The function is zero for :math:`x < \\alpha` or :math:`x > \\beta`. Parameters ---------- X : ArrayLike The input data points at which to evaluate the PDF. Returns ------- NDArray[np.float64] The PDF values corresponding to each point in :attr:`X`. """ X = np.asarray(X, dtype=float64) return np.where( (self.left_border <= X) & (self.right_border >= X), 1.0 / (self.right_border - self.left_border), 0.0 )
[docs] def ppf(self, P): """Percent Point Function (PPF) or quantile function. The PPF for the uniform distribution is: .. math:: Q(p | \\alpha, \\beta) = \\alpha + p \\cdot (\\beta - \\alpha) where :math:`\\alpha` is the left_border parameter and :math:`\\beta` is the right_border parameter. Parameters ---------- P : ArrayLike The probability values (between 0 and 1) at which to evaluate the PPF. Returns ------- NDArray[np.float64] The PPF values corresponding to each probability in :attr:`P`. """ P = np.asarray(P, dtype=float64) return np.where((P >= 0) & (P <= 1), self.left_border + P * (self.right_border - self.left_border), np.nan)
[docs] def lpdf(self, X): """Log of the Probability Density Function (LPDF). The log-PDF for the uniform distribution is: .. math:: \\ln f(x | \\alpha, \\beta) = -\\ln(\\beta - \\alpha) where :math:`\\alpha` is the left_border parameter and :math:`\\beta` is the right_border parameter. The function is -inf for :math:`\\alpha >= \\beta` or when x not in range. Parameters ---------- X : ArrayLike The input data points at which to evaluate the LPDF. Returns ------- NDArray[np.float64] The log-PDF values corresponding to each point in :attr:`X`. """ X = np.asarray(X, dtype=float64) in_range = (self.left_border <= X) & (self.right_border >= X) valid_dist = self.right_border > self.left_border return np.where(in_range & valid_dist, -np.log(self.right_border - self.left_border), -np.inf)
def _dlog_left_border(self, X): """Partial derivative of the lpdf w.r.t. the :attr:`left_border` parameter. .. math:: \\frac{\\partial \\ln f(x | \\alpha, \\beta)}{\\partial \\alpha} = frac{1.0}{(\\beta - \\alpha)} where :math:`\\alpha` is the left_border parameter and :math:`\\beta` is the right_border parameter. The derivative is non-zero only for `left_border <= X <= right_border`. """ X = np.asarray(X, dtype=float64) in_range = (self.left_border <= X) & (self.right_border >= X) return np.where(in_range, 1.0 / (self.right_border - self.left_border), 0.0) def _dlog_right_border(self, X): """Partial derivative of the lpdf w.r.t. the :attr:`right_border` parameter. .. math:: \\frac{\\partial \\ln f(x | \\alpha, \\beta)}{\\partial \\beta} = frac{-1.0}{(\\beta - \\alpha)} where :math:`\\alpha` is the left_border parameter and :math:`\\beta` is the right_border parameter. The derivative is non-zero only for `left_border <= X <= right_border`. """ X = np.asarray(X, dtype=float64) in_range = (self.left_border <= X) & (self.right_border >= X) return np.where(in_range, -1.0 / (self.right_border - self.left_border), 0.0)
[docs] def log_gradients(self, X): """Calculates the gradients of the log-PDF w.r.t. its parameters. The gradients are computed for the parameters that are not fixed. Parameters ---------- X : ArrayLike The input data points at which to calculate the gradients. Returns ------- NDArray[np.float64] An array where each row corresponds to a data point in :attr:`X` and each column corresponds to the gradient with respect to a specific optimizable parameter. The order of columns corresponds to the sorted order of :attr:`self.params_to_optimize`. """ X = np.asarray(X, dtype=float64) gradient_calculators = { self.LEFT_BORDER: self._dlog_left_border, self.RIGHT_BORDER: self._dlog_right_border, } optimizable_params = sorted(list(self.params_to_optimize)) if not optimizable_params: return np.empty((len(X), 0)) gradients = [gradient_calculators[param](X) for param in optimizable_params] return np.stack(gradients, axis=1)
[docs] def generate(self, size: int): """Generates random samples from the distribution. Parameters ---------- size : int The number of random samples to generate. Returns ------- NDArray[np.float64] A NumPy array containing the generated samples. """ return np.asarray( uniform.rvs(loc=self.left_border, scale=self.right_border - self.left_border, size=size), dtype=float64 )
def __repr__(self) -> str: """Returns a string representation of the object. Returns ------- str A string that can be used to recreate the object, e.g., "Uniform(left_border=0.0, right_border=2.0)". """ return f"{self.__class__.__name__}(left_border={self.left_border}, right_border={self.right_border})"