Source code for histopath_bim_des.distribution

"""Random distributions for the simulation model.

This module overrides the `salabim.Constant` and
`salabim.Triangular` classes to provide better
string representations, and adds a PERT distribution.

See: https://en.wikipedia.org/wiki/PERT_distribution
"""

from typing import Union, Self, Callable

import salabim as sim


[docs] class Constant(sim.Constant): """Constant distribution. Attributes: _value (float) """ def __repr__(self) -> str: return f'Constant({self._value}, time_unit={self.time_unit})'
[docs] class Tri(sim.Triangular): """Triangular distribution. Attributes: _low: Minimum of the distribution _mode: Mode of the distribution. If None, replaced with the mean of `_low` and `_high`. _high: Maximum of the distribution. If None, replaced with `_min`, thus forming a constant distribution. """ def __init__( self, low: float, mode: float | None = None, high: float | None = None, time_unit: str | None = None, randomstream=None, env: sim.Environment | None = None ) -> None: # Reorder low,high,mode parameters super().__init__(low, high, mode, time_unit, randomstream, env) def __repr__(self) -> str: return f"Triangular(low={self._low}, mode={self._mode}, high={self._high}, "\ f"time_unit={self.time_unit})"
[docs] class PERT(sim.Triangular): """PERT distribution. A three-point distribution with more probability mass around the mode than the triangular distribution. The mean of the distribution is `(_low + _shape * _mode + _high) / (_shape + 2)`. By default, `_shape = 4`. Attributes: _low: Minimum of the distribution. _mode: Mode of the distribution. If None, replaced with the mean of `_low` and `_high`. _high: Maximum of the distribution. If None, replaced with `_min`, thus forming a constant distribution. """ def __init__( self, low: float, mode: float | None = None, high: float | None = None, time_unit: str | None = None, randomstream=None, env: sim.Environment | None = None, ) -> None: super().__init__(low, high, mode, time_unit, randomstream, env) self._shape = 4 self._range = high - low self._alpha = 1 + self._shape * (mode - low) / self._range self._beta = 1 + self._shape * (high - mode) / self._range self._mean = (low + self._shape * mode + high) / (self._shape + 2) def __repr__(self) -> str: return f"PERT(low={self._low}, mode={self._mode}, high={self._high}, "\ f"shape={self._shape}, time_unit={self.time_unit})"
[docs] def print_info(self, as_str: bool = False, file: sim.TextIO | None = None) -> str: """ Print info about the distribution.""" result = [] result.append("PERT " + hex(id(self))) result.append(" low=" + str(self._low) + " " + self.time_unit) result.append(" high=" + str(self._high) + " " + self.time_unit) result.append(" mode=" + str(self._mode) + " " + self.time_unit) result.append(" shape=" + str(self._shape)) result.append(" randomstream=" + hex(id(self.randomstream))) return sim.return_or_print(result, as_str, file)
[docs] def sample(self) -> float: beta = self.randomstream.betavariate val = self._low + beta(self._alpha, self._beta) * self._range return val * self.time_unit_factor
[docs] def mean(self) -> float: return self._mean * self.time_unit_factor
Distribution = Union[Constant, Tri, PERT] """A continuous distribution (constant, triangular, or PERT)."""
[docs] class IntPERT: """Discretized PERT distribution.""" def __init__( self, low: int, mode: int, high: int, randomstream=None, env: sim.Environment | None = None ): self.low = low """Minimum of the distribution.""" self.mode = mode """Mode of the distribution.""" self.high = high """Maximum of the distribution.""" self.pert = PERT(low-mode-0.5, 0, high-mode+0.5, randomstream=randomstream, env=env) """Underlying continuous PERT distribution, i.e. `PERT(low-mode-0.5, 0, high-mode+0.5)`."""
[docs] def sample(self) -> int: """Sample the distribution.""" return self()
def __call__(self) -> int: # Round towards 0 and add the mode return int(self.pert.sample()) + self.mode def __repr__(self) -> str: return f'IntPERT({self.low}, {self.mode}, {self.high})'
[docs] class IntTri: """Discretized Triangular distribution.""" def __init__( self, low: int, mode: int, high: int, randomstream=None, env: sim.Environment | None = None ): self.low = low """Minimum of the distribution.""" self.mode = mode """Mode of the distribution.""" self.high = high """Maximum of the distribution.""" self.tri = Tri(low-mode-0.5, 0, high-mode+0.5, randomstream=randomstream, env=env) """Underlying continuous Tri distribution, i.e. `Tri(low-mode-0.5, 0, high-mode+0.5)`."""
[docs] def sample(self) -> int: """Sample the distribution.""" return self()
def __call__(self) -> int: # Round towards 0 and add the mode return int(self.tri.sample()) + self.mode def __repr__(self) -> str: return f'IntPERT({self.low}, {self.mode}, {self.high})'
[docs] class IntConstant(sim.Constant): """Alias of sim.Constant for integers.""" value: int def __init__( self, value: int, randomstream=None, env: sim.Environment = None ): super().__init__(value, None, randomstream, env) sample: Callable[[Self], int] __call__: Callable[[Self], int]
IntDistribution = Union[IntConstant, IntTri, IntPERT] """A continuous distribution (constant, triangular, or PERT)."""