Randomness¶
stream ¶
Seeded random number streams.
SimulSI never touches global random state. Every simulation owns a root
:class:RandomStream built from a seed, and named sub-streams are derived
from (seed, name) with NumPy's SeedSequence. A sub-stream therefore
produces the same numbers no matter how many other streams exist or in which
order they were created, which keeps common random numbers intact when a
model changes (e.g. adding a resource does not shift the arrival stream).
RandomStream ¶
RandomStream(seed: int | None = None, *, name: str = 'root', mode: SamplingMode = 'native')
A reproducible stream of random numbers (PCG64 under the hood).
mode selects how variates are produced: "native" (NumPy's fast
samplers, the default), "inverse" (one uniform per variate via the
inverse CDF) or "antithetic" (the same, using 1 - U). An
inverse run and an antithetic run with the same seed form an
antithetic pair. Sub-streams inherit the mode.
stream ¶
stream(name: str) -> RandomStream
Return the named sub-stream (created on first use, then cached).
exponential ¶
exponential(mean: float) -> float
Exponential variate with the given mean (= 1 / rate).
lognormal ¶
lognormal(mean: float = 0.0, sigma: float = 1.0) -> float
Log-normal variate; mean/sigma are those of the underlying normal.
derive_seed ¶
derive_seed(seed: int, *keys: int | str) -> int
Deterministically derive an independent 63-bit seed from seed and keys.
fresh_seed ¶
fresh_seed() -> int
A new seed from OS entropy, for runs where the user did not supply one.
distributions ¶
Probability distributions.
Distributions are small immutable objects that know how to draw from a
:class:~simulsi.randomness.stream.RandomStream. They validate their
parameters eagerly, expose analytic mean/variance where they exist,
and round-trip through plain dictionaries (to_spec / :func:from_spec)
so they can appear in YAML configuration without any code execution.
Distribution ¶
Exponential
dataclass
¶
Exponential(rate: float | None = None, *, mean: float | None = None)
LogNormal
dataclass
¶
LogNormal(mu: float = 0.0, sigma: float = 1.0)
Bases: Distribution[float]
Log-normal; mu/sigma parameterise the underlying normal distribution.
Use :meth:from_moments to specify the mean and standard deviation of the
log-normal itself, which is usually what data gives you.
Empirical
dataclass
¶
Empirical(values: Sequence[float], weights: Sequence[float] | None = None)
Categorical
dataclass
¶
Categorical(categories: Sequence[Any] | Mapping[Any, float], probabilities: Sequence[float] | None = None)
Custom
dataclass
¶
Custom(fn: Callable[[RandomStream], T], name: str = 'custom')
as_distribution ¶
as_distribution(value: DistributionLike | Mapping[str, Any]) -> Distribution[Any]
Coerce numbers to :class:Constant and spec dicts via :func:from_spec.
from_spec ¶
from_spec(spec: Mapping[str, Any]) -> Distribution[Any]
Build a distribution from {"distribution": "<kind>", **params}.
Only registered distribution classes can be created, so configuration files can never trigger arbitrary code.
fitting ¶
Fit input distributions to data.
:func:fit_distribution fits a set of candidate families by maximum
likelihood (closed form where one exists), ranks them by AIC, BIC or the
Kolmogorov-Smirnov statistic, and returns ready-to-use SimulSI
distributions:
import numpy as np data = np.random.default_rng(1).gamma(2.0, 3.0, size=500) report = fit_distribution(data) report.best.name 'gamma' service = report.best.distribution # a simulsi Gamma, use it in a model
Fitting is no substitute for looking at the data: check the histogram
(report.plot() needs matplotlib), and consider
:class:~simulsi.randomness.Empirical when no family fits well.
FitResult
dataclass
¶
FitResult(name: str, distribution: Distribution[Any], params: dict[str, float], loglik: float, aic: float, bic: float, ks_statistic: float, ks_pvalue: float)
One fitted family. Lower aic/bic/ks_statistic is better.
FitReport
dataclass
¶
FitReport(n: int, criterion: Criterion, results: list[FitResult], skipped: dict[str, str] = dict(), data: NDArray[float64] | None = None)
Ranked fits plus the families that were skipped and why.
plot ¶
plot(top: int = 3, ax: Any = None) -> Any
Histogram of the data with the top fitted densities (needs matplotlib).
fit_distribution ¶
fit_distribution(data: Iterable[float], candidates: Sequence[str] = CANDIDATES, *, criterion: Criterion = 'aic') -> FitReport
Fit each candidate family to data and rank them by criterion.
Families whose support does not match the data (e.g. gamma with zeros,
Poisson with fractional values) are listed in report.skipped.
load_values ¶
load_values(path: str | Path, column: str | None = None) -> list[float]
Read numbers from a CSV column (default: the first numeric one) or a plain text file.
Plain text files may separate values by whitespace or commas. Blank cells are skipped.