Skip to content

distribution

This module defines abstract classes for statistical distributions.

We define 2 types of distributions. The first is UnivariateDistribution. Most named statistical distributions we learn about in a statistics course (Normal, Beta, Uniform, etc.) can be thought of as instances of UnivariateDistribution.

Second, we define a statistical distribution whose parameter might not be known. Such a distribution is tracked by keeping around a really large sample. We name this a VectorizedDistribution. In many cases, working with vectorized distributions is easier since it allows us to apply algebraic operations to them.

UnivariateDistribution

Bases: Algebra, Generic[T_in]

A univariate probability distribution.

All named distributions defined in this library are subclasses of UnivariateDistribution. This class can also be used by a user for defining new distributions (as subclasses) but beware that the class mandates lots of properties and methods be defined for each concrete subclass.

Parameters:

Name Type Description Default
is_continuous

used to track if the distribution is continuous or discrete.

required
Implementation details
  • If is_continuous is set to True, then T_in should be a "continuous" type like float or numpy.float64. If it is set to False, then T_in should be a discrete type like int or numpy.int_.
  • We set frozen=True to enforce immutability of each subclass and each instance of this class.
  • We set unsafe_hash=True to make this class hashable (it is immutable after all).
Source code in distribution_algebra/distribution.py
@attr.frozen(kw_only=True)
class UnivariateDistribution(Algebra, Generic[T_in]):
    """A univariate probability distribution.

    All named distributions defined in this library are subclasses of
    `UnivariateDistribution`. This class can also be used by a user for defining new
    distributions (as subclasses) but beware that the class mandates
    lots of properties and methods be defined for each concrete subclass.

    Params:
       is_continuous: used to track if the distribution is continuous or discrete.

    Other Params: Implementation details
       * If `is_continuous` is set to True, then `T_in` should be a "continuous" type like
         `float` or `numpy.float64`. If it is set to False, then `T_in` should be a discrete
         type like `int` or `numpy.int_`.
       * We set `frozen=True` to enforce immutability of each subclass and each instance of
         this class.
       * We set `unsafe_hash=True` to make this class hashable (it is immutable
         after all).
    """
    @property
    def is_continuous(self) -> bool:
        """Return True if the distribution is continuous, else return False.

        Continuity (or distcreteness) of the distribution is the
        continuity (or discreteness) of its support space. This is
        captured by the Generic type variable `T_in`. If the
        distribution is continuous (like `Normal <:
        UnivariateDistribution[numpy.float64]`, the this property returns
        True. If the distribution is discrete (like `Binomial <:
        UnivariateDistribution[numpy.int_]`, then this property is False.

        """
        assert hasattr(self, "__orig_bases__")
        T_in_at_runtime: type = get_args(self.__orig_bases__[0])[0]  # pyright: ignore
        match T_in_at_runtime:
            case np.int_ | np.int_:
                return False
            case np.float64:
                return True
            case _:
                raise TypeError(f"Found unsupported type {T_in_at_runtime}")

    @property
    @abstractmethod
    def median(self) -> float:
        """Return the median value of the distribution if it is defined."""

    @property
    @abstractmethod
    def mode(self) -> float:
        """Return the mode (most-liekely) value of the distribution if it is defined."""

    @property
    @abstractmethod
    def support(self) -> tuple[T_in, T_in]:
        """Return the (min, max) range of the distribution's support interval.

        In some cases, if one of the support ends is positive or
        negative infinity, we use the closest value to infinity
        expressible while using a constrained `T_in` type.

        """

    @abstractmethod
    def draw(self, size: int) -> NDArray[T_in]:
        """Draw a random sample of given `size` from the distribution.

        For efficiently drawing many random numbers from a
        distribution with fixed parameters, it is recommended that
        this method be used with the appropriate `size` instead of
        drawing `size=1` samples inside a separate loop.

        Note:
           Not to be confused with `distribution_algebra.plotter.plot`
           method which is used for plotting a distribution's graph.

        Implementation detail:
           This method should always use numpy's `default_rng` and its
           associated methods for drawing random samples from various
           distributions.

        """

    def to_vectorized(self) -> VectorizedDistribution[T_in]:
        """Convert to a `VectorizedDistribution`.

        This method is called internally when we perform algebraic
        operations on distributions whose closed-form expressions are
        not known.

        Note:
           In most cases, if using `(x:
           UnivariateDistribution).to_vectorized()` to create a
           `VectorizedDistribution` instance, the instance's
           `is_continuous` parameter is equal to `x.is_continuous`. In
           other words, continuous (discrete) univariate distributions
           give rise to continuous (discrete) vectorized
           distributions.

        """
        return VectorizedDistribution(  # type: ignore
            sample=self.draw(size=SAMPLE_SIZE),
            is_continuous=self.is_continuous)

    @abstractmethod
    def pdf(self, linspace: NDArray[T_in]) -> NDArray[np.float64]:
        """Return the probability density (or mass) function's values at the given domain points.

        Implementation detail:
           This method usually uses scipy's statistical distribution's
           probability density/mass functions.

        In the case of continuous (or discrete) distributions, this
        function accepts a parameter vector of points in
        `numpy.float64` (or `numpy.int_`) space. While the function's
        implementation does not put any constraints on the input
        vector, the user can usually generate an appropriate input by
        using `numpy.linspace` or `numpy.arange`.

        """

    def __add__(self, other: Any) -> Any:
        """Return the left-sum of a UnivatiateDistribution with any other type.

        Types:
           - `UnivariateDistribution + UnivariateDistribution`: the result is the sum of
             the vectorized versions of the two distributions.
           - `UnivariateDistribution + Any`: the left-parameter is vectorized and then
             added to the right-parameter.

        """
        match other:
            case UnivariateDistribution():
                return self.to_vectorized() + other.to_vectorized()
            case _:
                return self.to_vectorized() + other

    def __mul__(self, other: Any) -> Any:
        """Return the left-product of a UnivariateDistribution with any other type.

        Types:
           - `UnivariateDistribution * UnivariateDistribution`: the result is the product of
             the vectorized versions of the two distributions.
           - `UnivariateDistribution * Any`: the left-parameter is vectorized and then
             multiplied by the right-parameter.
        """
        match other:
            case UnivariateDistribution():
                return self.to_vectorized() * other.to_vectorized()
            case _:
                return self.to_vectorized() * other

    def __sub__(self, other: Any) -> Any:
        """Return the left-difference of a UnivariateDistribution with any other type.

        Types:
           - `UnivariateDistribution - UnivariateDistribution`: the result is the
             difference of the vectorized versions of the two distributions.
           - `UnivariateDistribution - Any`: the left-parameter is vectorized and then
             the right-parameter is subtracted from it.
        """
        match other:
            case UnivariateDistribution():
                return self.to_vectorized() - other.to_vectorized()
            case _:
                return self.to_vectorized() - other

    def __truediv__(self, other: Any) -> Any:
        """Return the left-division of a UnivariateDistribution with any other type.

        Types:
           - `UnivariateDistribution / UnivariateDistribution`: the result is the
             ratio of the vectorized versions of the two distributions.
           - `UnivariateDistribution / Any`: the left-parameter is vectorized and then
             divided by the right-parameter.

        """
        match other:
            case UnivariateDistribution():
                return self.to_vectorized() / other.to_vectorized()
            case _:
                return self.to_vectorized() / other

    def __pow__(self, other: Any) -> Any:
        """Return the left-power of a VectorDistribution with any other type.

        Types:
           - `UnivariateDistribution ** UnivariateDistribution`: the result is the
              vectorized version of the first distribution raised to the power  of the two distributions.
           - `UnivariateDistribution ** Any`: the left-parameter is vectorized and then
             raised to the right-parameter.

        """
        match other:
            case UnivariateDistribution():
                return self.to_vectorized() ** other.to_vectorized()
            case _:
                return self.to_vectorized() ** other

    def __neg__(self) -> Any:
        return -self.to_vectorized()


    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({vars(self)})"

    def __eq__(self, other: Any) -> bool:
        match other:
            case UnivariateDistribution():
                if not isinstance(other, type(self)):
                    return False
                if not vars(self).keys() == vars(other).keys():
                    return False
                return all(isclose(value, vars(other)[field], abs_tol=ABS_TOL)
                                   for field, value in vars(self).items())
            case _:
                return NotImplemented

is_continuous property

is_continuous: bool

Return True if the distribution is continuous, else return False.

Continuity (or distcreteness) of the distribution is the continuity (or discreteness) of its support space. This is captured by the Generic type variable T_in. If the distribution is continuous (like Normal <: UnivariateDistribution[numpy.float64], the this property returns True. If the distribution is discrete (like Binomial <: UnivariateDistribution[numpy.int_], then this property is False.

median abstractmethod property

median: float

Return the median value of the distribution if it is defined.

mode abstractmethod property

mode: float

Return the mode (most-liekely) value of the distribution if it is defined.

support abstractmethod property

support: tuple[T_in, T_in]

Return the (min, max) range of the distribution's support interval.

In some cases, if one of the support ends is positive or negative infinity, we use the closest value to infinity expressible while using a constrained T_in type.

__add__

__add__(other: Any) -> Any

Return the left-sum of a UnivatiateDistribution with any other type.

Types
  • UnivariateDistribution + UnivariateDistribution: the result is the sum of the vectorized versions of the two distributions.
  • UnivariateDistribution + Any: the left-parameter is vectorized and then added to the right-parameter.
Source code in distribution_algebra/distribution.py
def __add__(self, other: Any) -> Any:
    """Return the left-sum of a UnivatiateDistribution with any other type.

    Types:
       - `UnivariateDistribution + UnivariateDistribution`: the result is the sum of
         the vectorized versions of the two distributions.
       - `UnivariateDistribution + Any`: the left-parameter is vectorized and then
         added to the right-parameter.

    """
    match other:
        case UnivariateDistribution():
            return self.to_vectorized() + other.to_vectorized()
        case _:
            return self.to_vectorized() + other

__mul__

__mul__(other: Any) -> Any

Return the left-product of a UnivariateDistribution with any other type.

Types
  • UnivariateDistribution * UnivariateDistribution: the result is the product of the vectorized versions of the two distributions.
  • UnivariateDistribution * Any: the left-parameter is vectorized and then multiplied by the right-parameter.
Source code in distribution_algebra/distribution.py
def __mul__(self, other: Any) -> Any:
    """Return the left-product of a UnivariateDistribution with any other type.

    Types:
       - `UnivariateDistribution * UnivariateDistribution`: the result is the product of
         the vectorized versions of the two distributions.
       - `UnivariateDistribution * Any`: the left-parameter is vectorized and then
         multiplied by the right-parameter.
    """
    match other:
        case UnivariateDistribution():
            return self.to_vectorized() * other.to_vectorized()
        case _:
            return self.to_vectorized() * other

__pow__

__pow__(other: Any) -> Any

Return the left-power of a VectorDistribution with any other type.

Types
  • UnivariateDistribution ** UnivariateDistribution: the result is the vectorized version of the first distribution raised to the power of the two distributions.
  • UnivariateDistribution ** Any: the left-parameter is vectorized and then raised to the right-parameter.
Source code in distribution_algebra/distribution.py
def __pow__(self, other: Any) -> Any:
    """Return the left-power of a VectorDistribution with any other type.

    Types:
       - `UnivariateDistribution ** UnivariateDistribution`: the result is the
          vectorized version of the first distribution raised to the power  of the two distributions.
       - `UnivariateDistribution ** Any`: the left-parameter is vectorized and then
         raised to the right-parameter.

    """
    match other:
        case UnivariateDistribution():
            return self.to_vectorized() ** other.to_vectorized()
        case _:
            return self.to_vectorized() ** other

__sub__

__sub__(other: Any) -> Any

Return the left-difference of a UnivariateDistribution with any other type.

Types
  • UnivariateDistribution - UnivariateDistribution: the result is the difference of the vectorized versions of the two distributions.
  • UnivariateDistribution - Any: the left-parameter is vectorized and then the right-parameter is subtracted from it.
Source code in distribution_algebra/distribution.py
def __sub__(self, other: Any) -> Any:
    """Return the left-difference of a UnivariateDistribution with any other type.

    Types:
       - `UnivariateDistribution - UnivariateDistribution`: the result is the
         difference of the vectorized versions of the two distributions.
       - `UnivariateDistribution - Any`: the left-parameter is vectorized and then
         the right-parameter is subtracted from it.
    """
    match other:
        case UnivariateDistribution():
            return self.to_vectorized() - other.to_vectorized()
        case _:
            return self.to_vectorized() - other

__truediv__

__truediv__(other: Any) -> Any

Return the left-division of a UnivariateDistribution with any other type.

Types
  • UnivariateDistribution / UnivariateDistribution: the result is the ratio of the vectorized versions of the two distributions.
  • UnivariateDistribution / Any: the left-parameter is vectorized and then divided by the right-parameter.
Source code in distribution_algebra/distribution.py
def __truediv__(self, other: Any) -> Any:
    """Return the left-division of a UnivariateDistribution with any other type.

    Types:
       - `UnivariateDistribution / UnivariateDistribution`: the result is the
         ratio of the vectorized versions of the two distributions.
       - `UnivariateDistribution / Any`: the left-parameter is vectorized and then
         divided by the right-parameter.

    """
    match other:
        case UnivariateDistribution():
            return self.to_vectorized() / other.to_vectorized()
        case _:
            return self.to_vectorized() / other

draw abstractmethod

draw(size: int) -> NDArray[T_in]

Draw a random sample of given size from the distribution.

For efficiently drawing many random numbers from a distribution with fixed parameters, it is recommended that this method be used with the appropriate size instead of drawing size=1 samples inside a separate loop.

Note

Not to be confused with distribution_algebra.plotter.plot method which is used for plotting a distribution's graph.

Implementation detail

This method should always use numpy's default_rng and its associated methods for drawing random samples from various distributions.

Source code in distribution_algebra/distribution.py
@abstractmethod
def draw(self, size: int) -> NDArray[T_in]:
    """Draw a random sample of given `size` from the distribution.

    For efficiently drawing many random numbers from a
    distribution with fixed parameters, it is recommended that
    this method be used with the appropriate `size` instead of
    drawing `size=1` samples inside a separate loop.

    Note:
       Not to be confused with `distribution_algebra.plotter.plot`
       method which is used for plotting a distribution's graph.

    Implementation detail:
       This method should always use numpy's `default_rng` and its
       associated methods for drawing random samples from various
       distributions.

    """

pdf abstractmethod

pdf(linspace: NDArray[T_in]) -> NDArray[np.float64]

Return the probability density (or mass) function's values at the given domain points.

Implementation detail

This method usually uses scipy's statistical distribution's probability density/mass functions.

In the case of continuous (or discrete) distributions, this function accepts a parameter vector of points in numpy.float64 (or numpy.int_) space. While the function's implementation does not put any constraints on the input vector, the user can usually generate an appropriate input by using numpy.linspace or numpy.arange.

Source code in distribution_algebra/distribution.py
@abstractmethod
def pdf(self, linspace: NDArray[T_in]) -> NDArray[np.float64]:
    """Return the probability density (or mass) function's values at the given domain points.

    Implementation detail:
       This method usually uses scipy's statistical distribution's
       probability density/mass functions.

    In the case of continuous (or discrete) distributions, this
    function accepts a parameter vector of points in
    `numpy.float64` (or `numpy.int_`) space. While the function's
    implementation does not put any constraints on the input
    vector, the user can usually generate an appropriate input by
    using `numpy.linspace` or `numpy.arange`.

    """

to_vectorized

to_vectorized() -> VectorizedDistribution[T_in]

Convert to a VectorizedDistribution.

This method is called internally when we perform algebraic operations on distributions whose closed-form expressions are not known.

Note

In most cases, if using (x: UnivariateDistribution).to_vectorized() to create a VectorizedDistribution instance, the instance's is_continuous parameter is equal to x.is_continuous. In other words, continuous (discrete) univariate distributions give rise to continuous (discrete) vectorized distributions.

Source code in distribution_algebra/distribution.py
def to_vectorized(self) -> VectorizedDistribution[T_in]:
    """Convert to a `VectorizedDistribution`.

    This method is called internally when we perform algebraic
    operations on distributions whose closed-form expressions are
    not known.

    Note:
       In most cases, if using `(x:
       UnivariateDistribution).to_vectorized()` to create a
       `VectorizedDistribution` instance, the instance's
       `is_continuous` parameter is equal to `x.is_continuous`. In
       other words, continuous (discrete) univariate distributions
       give rise to continuous (discrete) vectorized
       distributions.

    """
    return VectorizedDistribution(  # type: ignore
        sample=self.draw(size=SAMPLE_SIZE),
        is_continuous=self.is_continuous)

VectorizedDistribution

Bases: Algebra, Generic[T_in]

A Vectorized form of a probability distribution.

Parameters:

Name Type Description Default
sample

the sample-vector used in lieu of a closed-form expression for the distribution.

required
is_continuous

used to track if the distribution is continuous or discrete.

required
Implementation details
  • We use attr.frozen in this definition because it supports validation and immutability.
  • We use attr.frozen to enforce immutability of each instance of this class. To modify the class or its sample-attribute, the user must define a new instance (or use attr.evolve) instead of modifying an existing one.
  • sample is chosen to be a numpy array to leverage the fast, vectorized operations enabled by numpy's C API.
Source code in distribution_algebra/distribution.py
@attr.frozen(kw_only=True)
class VectorizedDistribution(Algebra, Generic[T_in]):
    """A Vectorized form of a probability distribution.

    Params:
       sample: the sample-vector used in lieu of a closed-form expression for
          the distribution.
       is_continuous: used to track if the distribution is continuous or discrete.

    Other Params: Implementation details
       * We use `attr.frozen` in this definition because it supports validation and
         immutability.
       * We use `attr.frozen` to enforce immutability of each instance of this class.
         To modify the class or its sample-attribute, the user must define a new
         instance (or use `attr.evolve`) instead of modifying an existing one.
       * `sample` is chosen to be a numpy array to leverage the fast, vectorized
         operations enabled by numpy's C API.

    """
    sample: NDArray[T_in] = attr.field(eq=attr.cmp_using(eq=np.array_equal))  # type: ignore
    is_continuous: bool = attr.field(repr=False)

    @is_continuous.validator  # type: ignore
    def check_is_continuous(self, _: Literal["is_continuous"], is_continuous_value: bool) -> None:
        match self.sample.dtype:
            case np.int_:
                assert not is_continuous_value
            case np.float64:
                assert is_continuous_value
            case _:
                raise TypeError(f"Encountered unknown type {self.sample.dtype} in VectorizedDistribution.")


    @property
    def mean(self) -> np.float64:
        """Mean of the distribution, defined as the mean of the sample.

        Returns:
           mean of the distribution, defined as the mean of the sample.
        """
        return np.float64(self.sample.mean())

    @property
    def var(self) -> np.float64:
        """Variance of the distribution, defined as the variance of the sample.

        Returns:
           variance of the distribution, defined as the variance of the sample.
        """
        return np.float64(self.sample.var())


    def __add__(self, other: Any) -> Any:
        """Return the left-sum of a VectorDistribution with any other type.

        Types:
           - `VectorizedDistribution + VectorizedDistribution`: the result is also a
             vectorized-distrubution whose samples parameter is the element-wise sum
             of the sample parameters of the summands.
           - `VectorizedDistribution + Number`: the result is a vectorized-distribution
             whose sample values have been shifted.
        """
        match other:
            case VectorizedDistribution():
                return VectorizedDistribution(  # type: ignore
                    sample=self.sample + other.sample,
                    is_continuous=self.is_continuous or other.is_continuous)
            case float() | int():
                return VectorizedDistribution(
                    sample=self.sample + other,  # type: ignore
                    is_continuous=self.is_continuous or isinstance(other, float))
            case _:
                return NotImplemented

    def __mul__(self, other: Any) -> Any:
        """Return the left-product of a VectorDistribution with any other type.

        Types:
           - `VectorizedDistribution * VectorizedDistribution`: the result is also a
             vectorized-distrubution whose samples parameter is the element-wise product
             of the sample parameters of the multiplicands.
           - `VectorizedDistribution * Number`: the result is a vectorized-distribution
             whose sample values have been scaled.
        """
        match other:
            case VectorizedDistribution():
                return VectorizedDistribution(  # type: ignore
                    sample=self.sample * other.sample,
                    is_continuous=self.is_continuous or other.is_continuous)
            case float() | int():
                return VectorizedDistribution(  # type: ignore
                    sample=self.sample * other,
                    is_continuous=self.is_continuous or isinstance(other, float))
            case _:
                return NotImplemented

    def __sub__(self, other: Any) -> Any:
        """Return the left-difference of a VectorDistribution with any other type.

        Types:
           - `VectorizedDistribution - VectorizedDistribution`: the result is also a
             vectorized-distrubution whose samples parameter is the element-wise difference
             of the sample parameters of the arguments.
           - `VectorizedDistribution - Number`: the result is a vectorized-distribution
             whose sample values have been shifted.
        """
        match other:
            case VectorizedDistribution():
                return VectorizedDistribution(  # type: ignore
                    sample=self.sample - other.sample,
                    is_continuous=self.is_continuous or other.is_continuous)
            case float() | int():
                return VectorizedDistribution(  # type: ignore
                    sample=self.sample - other,
                    is_continuous=self.is_continuous or isinstance(other, float))
            case _:
                return NotImplemented

    def __pow__(self, other: Any) -> Any:
        """Return the left-power of a VectorDistribution with any other type.

        Types:
           - `VectorizedDistribution ** VectorizedDistribution`: the result is also a
             vectorized-distrubution. Result's i'th sample entry is
             `self.sample[i] ** other.sample[i]`.
           - `VectorizedDistribution - Number`: the result is a vectorized-distribution.
             Result's i'th sample entry is `self.sample[i] ** other`.

        Warns:
           - UserWarning: if computing `VectorizedDistribution ** (VectorizedDistrion | Number)`
             would result in raising a negative number to any power.
           - UserWarning: if computing `VectorizedDistribution ** (VectorizedDistribution | Number)`
             would result in a `0 ** 0` computation.
        """
        match other:
            case VectorizedDistribution():
                if (self.sample < 0).any():
                    warnings.warn("All entries in base array should be positive.")
                if (other.sample == 0).any() and (self.sample == 0).any():
                    warnings.warn("Attempting to compute 0**0 during (base array)**(other array) computation.")
                return VectorizedDistribution(  # type: ignore
                    sample=self.sample ** other.sample,
                    is_continuous=self.is_continuous or other.is_continuous)
            case float() | int():
                if (self.sample < 0).any():
                    warnings.warn("All entries in base array should be positive.")
                if not other and (self.sample == 0).any():
                    warnings.warn("Attempting to compute 0**0 during (base array)**other computation.")
                return VectorizedDistribution(  # type: ignore
                    sample=self.sample ** other,
                    is_continuous=self.is_continuous or isinstance(other, float))
            case _:
                return NotImplemented

    def __truediv__(self, other: Any) -> Any:
        """Return the left-division of a VectorDistribution with any other type.

        Types:
           - `VectorizedDistribution / VectorizedDistribution`: the result is also a
             vectorized-distrubution whose sample parameter is the element-wise ratio
             of the sample parameters of the arguments.
           - `VectorizedDistribution / Number`: the result is a vectorized-distribution
             whose sample parameter has been scaled down.

        Warns:
           - UserWarning: if computing `VectorizedDistribution / (VectorizedDistrion | Number)`
             would result in a `x / 0` calculation, for any x.
        """
        match other:
            case VectorizedDistribution():
                if (other.sample == 0).any():
                    warnings.warn("All entries in denominator array should be non-zero.")
                return VectorizedDistribution(  # type: ignore
                    sample=self.sample / other.sample,
                    is_continuous=True)
            case float() | int():
                if other == 0:
                    warnings.warn("Attempting to divide a vectorized distribution by zero.")
                return VectorizedDistribution(  # type: ignore
                    sample=self.sample / other,
                    is_continuous=True)
            case _:
                return NotImplemented

    def __neg__(self) -> Any:
        """Return the negative of a VectorizedDistribution.

        Returns:
           (VectorizedDistribution): A VectorizedDistribution whose sample entries are the
              negative of the original's.
        """
        return VectorizedDistribution(sample=-self.sample, is_continuous=self.is_continuous)  # type: ignore

mean property

mean: np.float64

Mean of the distribution, defined as the mean of the sample.

Returns:

Type Description
np.float64

mean of the distribution, defined as the mean of the sample.

var property

var: np.float64

Variance of the distribution, defined as the variance of the sample.

Returns:

Type Description
np.float64

variance of the distribution, defined as the variance of the sample.

__add__

__add__(other: Any) -> Any

Return the left-sum of a VectorDistribution with any other type.

Types
  • VectorizedDistribution + VectorizedDistribution: the result is also a vectorized-distrubution whose samples parameter is the element-wise sum of the sample parameters of the summands.
  • VectorizedDistribution + Number: the result is a vectorized-distribution whose sample values have been shifted.
Source code in distribution_algebra/distribution.py
def __add__(self, other: Any) -> Any:
    """Return the left-sum of a VectorDistribution with any other type.

    Types:
       - `VectorizedDistribution + VectorizedDistribution`: the result is also a
         vectorized-distrubution whose samples parameter is the element-wise sum
         of the sample parameters of the summands.
       - `VectorizedDistribution + Number`: the result is a vectorized-distribution
         whose sample values have been shifted.
    """
    match other:
        case VectorizedDistribution():
            return VectorizedDistribution(  # type: ignore
                sample=self.sample + other.sample,
                is_continuous=self.is_continuous or other.is_continuous)
        case float() | int():
            return VectorizedDistribution(
                sample=self.sample + other,  # type: ignore
                is_continuous=self.is_continuous or isinstance(other, float))
        case _:
            return NotImplemented

__mul__

__mul__(other: Any) -> Any

Return the left-product of a VectorDistribution with any other type.

Types
  • VectorizedDistribution * VectorizedDistribution: the result is also a vectorized-distrubution whose samples parameter is the element-wise product of the sample parameters of the multiplicands.
  • VectorizedDistribution * Number: the result is a vectorized-distribution whose sample values have been scaled.
Source code in distribution_algebra/distribution.py
def __mul__(self, other: Any) -> Any:
    """Return the left-product of a VectorDistribution with any other type.

    Types:
       - `VectorizedDistribution * VectorizedDistribution`: the result is also a
         vectorized-distrubution whose samples parameter is the element-wise product
         of the sample parameters of the multiplicands.
       - `VectorizedDistribution * Number`: the result is a vectorized-distribution
         whose sample values have been scaled.
    """
    match other:
        case VectorizedDistribution():
            return VectorizedDistribution(  # type: ignore
                sample=self.sample * other.sample,
                is_continuous=self.is_continuous or other.is_continuous)
        case float() | int():
            return VectorizedDistribution(  # type: ignore
                sample=self.sample * other,
                is_continuous=self.is_continuous or isinstance(other, float))
        case _:
            return NotImplemented

__neg__

__neg__() -> Any

Return the negative of a VectorizedDistribution.

Returns:

Type Description
VectorizedDistribution

A VectorizedDistribution whose sample entries are the negative of the original's.

Source code in distribution_algebra/distribution.py
def __neg__(self) -> Any:
    """Return the negative of a VectorizedDistribution.

    Returns:
       (VectorizedDistribution): A VectorizedDistribution whose sample entries are the
          negative of the original's.
    """
    return VectorizedDistribution(sample=-self.sample, is_continuous=self.is_continuous)  # type: ignore

__pow__

__pow__(other: Any) -> Any

Return the left-power of a VectorDistribution with any other type.

Types
  • VectorizedDistribution ** VectorizedDistribution: the result is also a vectorized-distrubution. Result's i'th sample entry is self.sample[i] ** other.sample[i].
  • VectorizedDistribution - Number: the result is a vectorized-distribution. Result's i'th sample entry is self.sample[i] ** other.
Warns
  • UserWarning: if computing VectorizedDistribution ** (VectorizedDistrion | Number) would result in raising a negative number to any power.
  • UserWarning: if computing VectorizedDistribution ** (VectorizedDistribution | Number) would result in a 0 ** 0 computation.
Source code in distribution_algebra/distribution.py
def __pow__(self, other: Any) -> Any:
    """Return the left-power of a VectorDistribution with any other type.

    Types:
       - `VectorizedDistribution ** VectorizedDistribution`: the result is also a
         vectorized-distrubution. Result's i'th sample entry is
         `self.sample[i] ** other.sample[i]`.
       - `VectorizedDistribution - Number`: the result is a vectorized-distribution.
         Result's i'th sample entry is `self.sample[i] ** other`.

    Warns:
       - UserWarning: if computing `VectorizedDistribution ** (VectorizedDistrion | Number)`
         would result in raising a negative number to any power.
       - UserWarning: if computing `VectorizedDistribution ** (VectorizedDistribution | Number)`
         would result in a `0 ** 0` computation.
    """
    match other:
        case VectorizedDistribution():
            if (self.sample < 0).any():
                warnings.warn("All entries in base array should be positive.")
            if (other.sample == 0).any() and (self.sample == 0).any():
                warnings.warn("Attempting to compute 0**0 during (base array)**(other array) computation.")
            return VectorizedDistribution(  # type: ignore
                sample=self.sample ** other.sample,
                is_continuous=self.is_continuous or other.is_continuous)
        case float() | int():
            if (self.sample < 0).any():
                warnings.warn("All entries in base array should be positive.")
            if not other and (self.sample == 0).any():
                warnings.warn("Attempting to compute 0**0 during (base array)**other computation.")
            return VectorizedDistribution(  # type: ignore
                sample=self.sample ** other,
                is_continuous=self.is_continuous or isinstance(other, float))
        case _:
            return NotImplemented

__sub__

__sub__(other: Any) -> Any

Return the left-difference of a VectorDistribution with any other type.

Types
  • VectorizedDistribution - VectorizedDistribution: the result is also a vectorized-distrubution whose samples parameter is the element-wise difference of the sample parameters of the arguments.
  • VectorizedDistribution - Number: the result is a vectorized-distribution whose sample values have been shifted.
Source code in distribution_algebra/distribution.py
def __sub__(self, other: Any) -> Any:
    """Return the left-difference of a VectorDistribution with any other type.

    Types:
       - `VectorizedDistribution - VectorizedDistribution`: the result is also a
         vectorized-distrubution whose samples parameter is the element-wise difference
         of the sample parameters of the arguments.
       - `VectorizedDistribution - Number`: the result is a vectorized-distribution
         whose sample values have been shifted.
    """
    match other:
        case VectorizedDistribution():
            return VectorizedDistribution(  # type: ignore
                sample=self.sample - other.sample,
                is_continuous=self.is_continuous or other.is_continuous)
        case float() | int():
            return VectorizedDistribution(  # type: ignore
                sample=self.sample - other,
                is_continuous=self.is_continuous or isinstance(other, float))
        case _:
            return NotImplemented

__truediv__

__truediv__(other: Any) -> Any

Return the left-division of a VectorDistribution with any other type.

Types
  • VectorizedDistribution / VectorizedDistribution: the result is also a vectorized-distrubution whose sample parameter is the element-wise ratio of the sample parameters of the arguments.
  • VectorizedDistribution / Number: the result is a vectorized-distribution whose sample parameter has been scaled down.
Warns
  • UserWarning: if computing VectorizedDistribution / (VectorizedDistrion | Number) would result in a x / 0 calculation, for any x.
Source code in distribution_algebra/distribution.py
def __truediv__(self, other: Any) -> Any:
    """Return the left-division of a VectorDistribution with any other type.

    Types:
       - `VectorizedDistribution / VectorizedDistribution`: the result is also a
         vectorized-distrubution whose sample parameter is the element-wise ratio
         of the sample parameters of the arguments.
       - `VectorizedDistribution / Number`: the result is a vectorized-distribution
         whose sample parameter has been scaled down.

    Warns:
       - UserWarning: if computing `VectorizedDistribution / (VectorizedDistrion | Number)`
         would result in a `x / 0` calculation, for any x.
    """
    match other:
        case VectorizedDistribution():
            if (other.sample == 0).any():
                warnings.warn("All entries in denominator array should be non-zero.")
            return VectorizedDistribution(  # type: ignore
                sample=self.sample / other.sample,
                is_continuous=True)
        case float() | int():
            if other == 0:
                warnings.warn("Attempting to divide a vectorized distribution by zero.")
            return VectorizedDistribution(  # type: ignore
                sample=self.sample / other,
                is_continuous=True)
        case _:
            return NotImplemented

Last update: February 6, 2023