Skip to main content

internals.dists

internals.dists

The machinery of random-number-safe distributions: ss.Dist, and how the distributions of a sim are found, seeded, and drawn from

It exists so that each decision in a sim has its own random stream, which keeps two runs comparable (common random numbers). As compartments, a distribution mostly describes a dwell time or a parameter: the engine draws from its own generator.

The distributions themselves are in ss.distributions.

Used with: agents and compartments; declared and coded modules

Classes

Name Description
Dist Base class for tracking one random number generator associated with one distribution,
Dists Class for managing a collection of Dist objects
scipy_dist A distribution of scipy.stats, by name, and its parameters (kwds), as a frozen one holds them

Dist

internals.dists.Dist(
    dist=None,
    distname=None,
    name=None,
    unit=None,
    round=False,
    seed=None,
    offset=None,
    strict=True,
    sim=None,
    module=None,
    mock=False,
    **kwargs,
)

Base class for tracking one random number generator associated with one distribution, i.e. one decision per timestep.

See ss.distributions.dist_list for a full list of supported distributions. Their parameters are named by their moments where they have them (ss.normal(mean, std), ss.expon(mean), ss.poisson(mean)), and otherwise by their usual names (ss.gamma(shape, scale)); each is translated to SciPy’s or NumPy’s names inside (sync_pars()), and a keyword a distribution doesn’t take is a TypeError when it is made.

Distributions scale in different ways, such as converting between time units, given by scaling. Some distributions can’t be scaled at all (e.g. ss.beta_dist(), whose values are fractions). For distributions that can be scaled, some distributions can only be (linearly) scaled before the random numbers are generated (called “predraw”), some can only be scaled after (called “postdraw”), and some can be scaled in either way (both).

For example, a normal distribution is both since 2Normal(a, b) = Normal(2a, 2b). A Poisson distribution is “predraw” since 2Poisson(λ) ≠ Poisson(2λ), and there is no way to get the correct shape of a different Poisson distribution once the numbers have been drawn. Finally, distributions with unitless shape parameters as well as parameter that can have units (e.g. a gamma distribution with shape and scale parameters) are referred to as “postdraw” since scaling all input parameters is invalid (i.e. 2Gamma(shape, scale) ≠ Gamma(2shape, 2scale)), but they can still be scaled (i.e. 2Gamma(shape, scale) = Gamma(1shape, 2*scale)).

So scaling is one of:

- 'postdraw' (after the random numbers are drawn, e.g. `ss.weibull()`)
- 'predraw' (before the draw, e.g. `ss.poisson()`)
- ['predraw', 'postdraw'] (either, e.g. `ss.normal()`)
- None (not at all, e.g. `ss.beta_dist()`)

Note: by default, ss.Dist is initialized with an ss.Sim object to ensure random number reproducibility. You can override this with either ss.Dist(strict=False) on creation, or dist.init(force=True) after creation. A distribution with a unit (e.g. ss.erlang(k=4, mean=ss.days(10))) draws in that unit outside a sim, with neither.

Although it’s possible in theory to define a custom distribution (i.e., not one from NumPy or SciPy), in practice this is difficult. The distribution needs to have both a way to return random variates (easy), as well as the probability point function (inverse CDF). In addition, the distribution must be able to take a NumPy RNG as its bit generator. It’s easier to just use a default Dist (e.g., ss.random()), and then take its output as input (i.e., quantiles) for whatever custom distribution you want to create.

Parameters

Name Type Description Default
dist rv_generic optional; a scipy.stats distribution (frozen or not) to get the ppf from None
distname str the name for this class of distribution (e.g. “uniform”) None
name str the name for this particular distribution (e.g. “age_at_death”) None
unit str/ss.TimePar if provided, convert the output of the distribution to a timepar (e.g. rate or duration); can also be inferred from distribution parameters (see examples below) None
round bool / str if True (or ‘stochastic’), randomly round each value up or down to an integer; if ‘nearest’, round to the nearest integer (see dist.rvs()) False
seed int the user-chosen random seed (e.g. 3) None
offset int / str the seed offset, in place of the one from its path in the sim (e.g. offset='hiv_art'), so that its draws stay the same if it moves (e.g. to another module); it must be unique within the sim. If None (the default), the path in the sim is hashed for it None
strict bool if True, require initialization True
sim Sim usually determined on initialization; the sim to use as input to callable parameters None
module Module usually determined on initialization; the module to use as input to callable parameters None
mock int if provided, then initialize with a mock Sim object (of size mock) for debugging purposes False
kwargs dict parameters of the distribution

Examples

# Create a Bernoulli distribution
p_death = ss.bernoulli(p=0.1).init(force=True)
p_death.rvs(50) # Create 50 draws

# Create a normal distribution that's also a timepar
dur_infection = ss.normal(mean=12, std=2, unit='years')
dur_infection = ss.normal(mean=ss.years(12), std=2) # Same as above
dur_infection = ss.normal(mean=ss.years(12), std=ss.months(24)) # Same as above, perform time unit conversion internally
dur_infection.init(force=True).plot_hist() # Show results

# Create a duration that is rounded to the nearest whole number of days
dur_latent = ss.lognormal(mean=ss.days(4.5), std=ss.days(1.5), round='nearest')

# Create a distribution manually
dist = ss.Dist(dist=sps.norm, loc=3).init(force=True)
dist.rvs(10) # Return 10 normally distributed random numbers

Attributes

Name Description
rng The random number generator
state Get the current state

Methods

Name Description
call_par Check if this parameter needs to be called to be turned into an array; not for the user
call_pars Check if any parameters need to be called to be turned into arrays; not for the user
check_kwargs Raise a TypeError for a keyword the distribution does not take, naming those it does; not for the user
check_unitless_pars Check that parameters that must be dimensionless have not been given time units
clear_run_cache Clear transient callable/draw caches without unlinking the distribution
convert_callable Method to handle how callable parameters are processed; not for the user
convert_timepars Convert time parameters (durations and rates) to scalars
crn_key For a compiled loop that draws the uniforms itself (e.g. ss.solvers.backends.passes.infect()), in place of rvs(uids): the seed and
disp Return full display of object
get_state Return a copy of the state
harmonize_par_units Ensure that parameters that share dimensions also share time units
init Calculate the starting seed and create the RNG
jump Advance the RNG, e.g. to timestep “to”, by jumping
jump_dt Automatically jump on the next value of dt
link_module Shortcut for linking the module
link_sim Shortcut for linking the sim, only overwriting an existing one if overwrite=True; not for the user
make_history Store the current state in history
make_rvs Return default random numbers for scalar parameters; not for the user
mock Create a distribution using a mock sim for testing purposes
own_dt One of the distribution’s own time unit (e.g. ss.days(1)), or None if it has none: what it draws in outside a sim (durations divided by it, rates multiplied by it), rather than in steps of its module
own_unit The distribution’s own time unit, as the class of its time parameters (e.g. ss.days for ss.erlang(k=4, mean=ss.days(10)), or ss.peryear), or None if it has none
par_names The parameters a distribution takes, from its signature (e.g. [‘mean’, ‘std’] for ss.normal); not for the user
plot_hist Plot the current state of the RNG as a histogram (from a copy initialized on its own, if this one has no sim yet); outside a sim, a distribution with a unit is drawn and labeled in it (e.g. days)
ppf Return default random numbers for array parameters; not for the user
preserve_timepars Preserve time units when a timepar parameter is updated with a plain number
process_dist Ensure the distribution works; not for the user
process_pars Ensure the supplied dist and parameters are valid, and initialize them; not for the user
process_seed Obtain the seed offset by hashing the path to this distribution; not for the user
process_size Handle an input of either size or UIDs and calculate size, UIDs, and slots; not for the user
rand Simple way to get simple random numbers
randround Round the values up or down to an integer stochastically; usually called via dist.rvs(round=True)
reset Restore state, allowing the same numbers to be resampled
resolved Whether a draw for some agents is just ppf() of their uniforms: the parameters are plain values (processed once, and again only if
round_rvs Round the values (in timesteps, for durations); usually called via dist.rvs(round=True)
rvs Get random variates – use this!
set Set (change) the distribution type, or one or more parameters of the distribution
shrink Shrink the size of the distribution for saving to disk; NB, also clears per-agent parameter values
signature Each parameter and what it is scaled by, if the parameters are all plain values (otherwise None); not for the user
step_dt The time step that durations and rates are converted to: its module’s, else its sim’s, else (outside a sim) one of its own unit; not for the user
streams A stream of uniforms for each agent, for compiled code that needs a varying number of draws per agent (e.g. one per act, or in a queue)
sync Move the generator to an index: its initial state, jumped that many times
sync_pars Perform any necessary synchronizations or transformations on distribution parameters; not for the user
to_json Return a dictionary representation of the Dist
update_dist_pars Update SciPy distribution parameters; not for the user
validate_pars Check if parameters are valid; only used for non-SciPy distributions
validate_round Check that the rounding option is valid; not for the user
call_par
internals.dists.Dist.call_par(key, val, size, uids)

Check if this parameter needs to be called to be turned into an array; not for the user

call_pars
internals.dists.Dist.call_pars()

Check if any parameters need to be called to be turned into arrays; not for the user

check_kwargs
internals.dists.Dist.check_kwargs(kwargs)

Raise a TypeError for a keyword the distribution does not take, naming those it does; not for the user

check_unitless_pars
internals.dists.Dist.check_unitless_pars()

Check that parameters that must be dimensionless have not been given time units

Some distribution parameters are inherently unitless, such as the shape parameter of ss.weibull() or ss.gamma(); these are listed in the distribution’s unitless_pars attribute. Supplying a timepar for these is always an error, since scaling them by dt would change the shape of the distribution rather than its location or spread. Distributions with unitless_pars = None are not checked.

clear_run_cache
internals.dists.Dist.clear_run_cache()

Clear transient callable/draw caches without unlinking the distribution

convert_callable
internals.dists.Dist.convert_callable(parkey, func, size, uids)

Method to handle how callable parameters are processed; not for the user

convert_timepars
internals.dists.Dist.convert_timepars()

Convert time parameters (durations and rates) to scalars

This function converts time parameters into bare numbers that will be returned by rvs() depending on the timestep of the parent module for this Dist. The conversion for these types is

  • Durations are divided by dt (so the result will be a number of timesteps)
  • Rates are multiplied by dt: the number of events in the step, or, for a parameter that is a probability (prob_pars, e.g. the p of ss.bernoulli()) or an ss.prob, the chance in the step
crn_key
internals.dists.Dist.crn_key()

For a compiled loop that draws the uniforms itself (e.g. ss.solvers.backends.passes.infect()), in place of rvs(uids): the seed and index of this call’s draws, which are hash_uniforms(seed, ind, slots) at dtype, with the bookkeeping of one call to rvs(); not for the user. None if they cannot be drawn that way (without common random numbers, or while the draws are being recorded), in which case rvs() is called instead.

disp
internals.dists.Dist.disp()

Return full display of object

get_state
internals.dists.Dist.get_state()

Return a copy of the state

harmonize_par_units
internals.dists.Dist.harmonize_par_units()

Ensure that parameters that share dimensions also share time units

Some distributions have several parameters with the same dimensions (e.g. the mean and standard deviation of ss.lognormal()); these are listed in the distribution’s unit_pars attribute. Supplying units for only some of them is the expected usage, since the units of the others follow from it: for example, ss.lognormal(mean=ss.years(10)) means the same thing as ss.lognormal(mean=ss.years(10), std=ss.years(1)). Any plain numbers are therefore converted to the same timepar as their siblings. (Without this, they would be interpreted as numbers of timesteps, and would give different answers for different values of dt.) Mixing durations and rates cannot be resolved this way, so it raises an exception. Distributions with unit_pars = None are not checked.

init
internals.dists.Dist.init(
    trace=None,
    seed=None,
    module=None,
    sim=None,
    slots=None,
    force=False,
)

Calculate the starting seed and create the RNG

Typically this is not invoked by the user, although the user can call it with force=True to initialize a distribution manually independently of a ss.Sim object (which is equivalent to setting strict=False when creating the dist).

Parameters
Name Type Description Default
trace str the distribution’s location within the sim None
seed int the base random number seed that other random number seeds will be generated from None
module ss.Module the parent module None
sim ss.Sim the parent sim None
slots array the agent slots of the parent sim None
force bool whether to skip validation (if the dist has already been initialized, and if any inputs are None) False
jump
internals.dists.Dist.jump(to=None, delta=1, force=False)

Advance the RNG, e.g. to timestep “to”, by jumping

Parameters
Name Type Description Default
to int the index to jump to; if None, jump by delta None
delta int the number of jumps from the current index 1
force bool whether to jump even if already past to, or with ss.options.crn=False False
jump_dt
internals.dists.Dist.jump_dt(ti=None, force=False)

Automatically jump on the next value of dt

Parameters
Name Type Description Default
ti int if specified, jump to this timestep (default: the module’s current one) None
make_history
internals.dists.Dist.make_history(reset=False)

Store the current state in history

make_rvs
internals.dists.Dist.make_rvs()

Return default random numbers for scalar parameters; not for the user

mock
internals.dists.Dist.mock(trace='mock', **kwargs)

Create a distribution using a mock sim for testing purposes

Parameters
Name Type Description Default
trace str the “trace” of the distribution (normally, where it would be located in the sim) 'mock'
**kwargs dict passed to ss.debugtools.mock_sim() as well as ss.debugtools.mock_module() (typically time args, e.g. dt)
Examples
dist = ss.normal(3, 2, unit='years').mock(dt=ss.days(1))
dist.rvs(10)
own_dt
internals.dists.Dist.own_dt()

One of the distribution’s own time unit (e.g. ss.days(1)), or None if it has none: what it draws in outside a sim (durations divided by it, rates multiplied by it), rather than in steps of its module

own_unit
internals.dists.Dist.own_unit()

The distribution’s own time unit, as the class of its time parameters (e.g. ss.days for ss.erlang(k=4, mean=ss.days(10)), or ss.peryear), or None if it has none

par_names
internals.dists.Dist.par_names()

The parameters a distribution takes, from its signature (e.g. [‘mean’, ‘std’] for ss.normal); not for the user

plot_hist
internals.dists.Dist.plot_hist(n=1000, bins=None, fig_kw=None, hist_kw=None)

Plot the current state of the RNG as a histogram (from a copy initialized on its own, if this one has no sim yet); outside a sim, a distribution with a unit is drawn and labeled in it (e.g. days)

Parameters
Name Type Description Default
n int the number of values to draw 1000
bins int / array the bins of the histogram None
fig_kw dict passed to plt.figure() None
hist_kw dict passed to plt.hist() None
ppf
internals.dists.Dist.ppf(rands)

Return default random numbers for array parameters; not for the user

preserve_timepars
internals.dists.Dist.preserve_timepars(kwargs)

Preserve time units when a timepar parameter is updated with a plain number

For example, if a distribution was created with mean=ss.years(5) and is then updated with mean=6, the new value should be ss.years(6) rather than a bare 6 (which would be interpreted as 6 timesteps). Modifies kwargs in place.

process_dist
internals.dists.Dist.process_dist()

Ensure the distribution works; not for the user

process_pars
internals.dists.Dist.process_pars(call=True)

Ensure the supplied dist and parameters are valid, and initialize them; not for the user

process_seed
internals.dists.Dist.process_seed(trace=None, seed=None)

Obtain the seed offset by hashing the path to this distribution; not for the user

process_size
internals.dists.Dist.process_size(n=1)

Handle an input of either size or UIDs and calculate size, UIDs, and slots; not for the user

rand
internals.dists.Dist.rand(size)

Simple way to get simple random numbers

Parameters
Name Type Description Default
size int / tuple the number or shape of values required
randround
internals.dists.Dist.randround(rvs, slots=None)

Round the values up or down to an integer stochastically; usually called via dist.rvs(round=True)

Parameters
Name Type Description Default
rvs array the values to round required
slots array if provided, use one CRN uniform per slot (distinct from the draw itself) rather than the next numbers from the RNG None
reset
internals.dists.Dist.reset(state=0)

Restore state, allowing the same numbers to be resampled

Use 0 for original state, -1 for most recent state. Note: with ss.options.crn=False, the state is only stored for rvs(reset=True), so -1 may refer to an earlier state.

Parameters
Name Type Description Default
state int / dict the position in the history to restore, or a state of the generator 0
Examples
dist = ss.random(seed=5, strict=False)
r1 = dist(5)
r2 = dist(5)
dist.reset(-1)
r3 = dist(5)
dist.reset(0)
r4 = dist(5)
assert all(r1 != r2)
assert all(r2 == r3)
assert all(r4 == r1)
resolved
internals.dists.Dist.resolved()

Whether a draw for some agents is just ppf() of their uniforms: the parameters are plain values (processed once, and again only if one is replaced; see process_pars()), and the draw has no unit or rounding to apply after it; not for the user. For a caller that makes the uniforms itself (e.g. the agent engine, keyed by its rounds of moves: see ss.solvers.Agents.draw())

round_rvs
internals.dists.Dist.round_rvs(rvs, round)

Round the values (in timesteps, for durations); usually called via dist.rvs(round=True)

rvs
internals.dists.Dist.rvs(n=1, round=None, reset=False, repeats=False)

Get random variates – use this!

If the distribution is a duration, the values are in timesteps (e.g. in days if dt=ss.days(1)), and are only whole numbers if round is used.

With common random numbers (the default), each agent’s value comes from their slot and the call, so every call gives new values, but an agent who appears twice in the UIDs of one call gets the same value both times (e.g. two events for one person in a batch); give repeats=True for each to be drawn independently. It isn’t checked by default, since UIDs from Starsim (e.g. arr.uids) never repeat, and checking every call would make draws slower.

Parameters
Name Type Description Default
n int / tuple / arr if an int or tuple, return this many random variates; if an array, treat as UIDs 1
round bool / str if True (or ‘stochastic’), randomly round up or down based on how close the value is; if ‘nearest’, round to the nearest integer; if None, use the value set when the distribution was created None
reset bool whether to automatically reset the random number distribution state after being called False
repeats bool whether the UIDs may repeat, so that each repeat gets a value of its own (still common random numbers, except for ss.choice and ss.histogram, whose repeats are drawn after every slot) False
set
internals.dists.Dist.set(*args, dist=None, **kwargs)

Set (change) the distribution type, or one or more parameters of the distribution

Parameters
Name Type Description Default
*args list new values of the parameters, in order
dist scipy.stats.rv_continuous a new distribution, to change the type None
**kwargs dict new values of the parameters, by name
shrink
internals.dists.Dist.shrink(max_arr_size=100)

Shrink the size of the distribution for saving to disk; NB, also clears per-agent parameter values

signature
internals.dists.Dist.signature()

Each parameter and what it is scaled by, if the parameters are all plain values (otherwise None); not for the user

step_dt
internals.dists.Dist.step_dt()

The time step that durations and rates are converted to: its module’s, else its sim’s, else (outside a sim) one of its own unit; not for the user

streams
internals.dists.Dist.streams(uids)

A stream of uniforms for each agent, for compiled code that needs a varying number of draws per agent (e.g. one per act, or in a queue)

This counts as one call of the distribution (as rvs(uids) does), and gives each agent the state of a stream, which ss.draws.next_u(state) turns into the next uniform in (0, 1) and the new state, in code compiled with ss.jit (or Numba) or in Python. With common random numbers (ss.options.crn), an agent’s stream depends only on the distribution’s seed, the call (the step, and how many times it has been called in the step), and the agent: the same agent in the same step and call gives the same sequence, whoever else is drawn for, and however many uniforms are taken from other streams. The streams of different agents, calls, and distributions are independent. Without common random numbers, the streams are still independent and seeded by the sim, but not paired between scenarios. The distribution’s own parameters are not used.

Parameters
Name Type Description Default
uids array the agents required
Returns
Name Type Description
An array of uint64 states, one for each agent
Examples
states = self.dist.streams(uids)
u, state = ss.draws.next_u(states[0]) # The first uniform of the first agent's stream
sync
internals.dists.Dist.sync(ind)

Move the generator to an index: its initial state, jumped that many times

sync_pars
internals.dists.Dist.sync_pars()

Perform any necessary synchronizations or transformations on distribution parameters; not for the user

to_json
internals.dists.Dist.to_json()

Return a dictionary representation of the Dist

update_dist_pars
internals.dists.Dist.update_dist_pars(pars=None)

Update SciPy distribution parameters; not for the user

validate_pars
internals.dists.Dist.validate_pars()

Check if parameters are valid; only used for non-SciPy distributions

validate_round
internals.dists.Dist.validate_round(round)

Check that the rounding option is valid; not for the user

Dists

internals.dists.Dists(obj=None, *args, base_seed=None, sim=None)

Class for managing a collection of Dist objects

It exists so that the sim can seed, jump, and reset every distribution in one place.

Methods

Name Description
check_seeds Check that no two distributions share the same seed
clear_run_caches Clear rebuildable per-run caches from each managed distribution
copy_to_module Copy the Sim’s Dists object to the specified module
init Set the base seed, find and initialize all distributions in an object
jump Advance all RNGs, e.g. to call “to”, by jumping
jump_dt Advance all RNGs to the next timestep
keys Return the keys of the Dist objects
reset Reset each RNG
check_seeds
internals.dists.Dists.check_seeds()

Check that no two distributions share the same seed

clear_run_caches
internals.dists.Dists.clear_run_caches()

Clear rebuildable per-run caches from each managed distribution

copy_to_module
internals.dists.Dists.copy_to_module(module)

Copy the Sim’s Dists object to the specified module

init
internals.dists.Dists.init(obj=None, base_seed=None, sim=None, force=False)

Set the base seed, find and initialize all distributions in an object

In practice, the object is usually a Sim, but can be anything.

jump
internals.dists.Dists.jump(to=None, delta=1, force=False)

Advance all RNGs, e.g. to call “to”, by jumping

jump_dt
internals.dists.Dists.jump_dt(ti=None, force=False)

Advance all RNGs to the next timestep

Parameters
Name Type Description Default
ti int if specified, jump to this timestep (default: current sim timestep) None
keys
internals.dists.Dists.keys()

Return the keys of the Dist objects

reset
internals.dists.Dists.reset()

Reset each RNG

scipy_dist

internals.dists.scipy_dist(name, **kwds)

A distribution of scipy.stats, by name, and its parameters (kwds), as a frozen one holds them

Most draws use a distribution’s own inverse CDF or NumPy’s, and only read kwds, so SciPy is only imported and the distribution only frozen if something else is asked of it (e.g. ppf() for a shape with no inverse of its own).

Functions

Name Description
even_out Evenly spaced uniforms in the same order as rands, for ss.Sim(even_draws=True); not for the user
every np.all(x), for a parameter’s check: a number (or the bool of one) is tested in Python, since np.all() of one costs more than a small draw
hash_uniforms Generate one uniform random number in (0, 1) per slot via a counter-based (splitmix64)
inverse The inverse CDF of a distribution at the uniforms rands, compiled (by ss.solvers.backends.draws, exact); not for the user
link_dists Link distributions to the sim and the module; used in module.init() and people.init()
make_dist Make a distribution from a dictionary
no_dists The classes of objects that never hold a distribution (or a time parameter), so are not searched for them
occurrences The occurrence of each slot in an array (0 for its first, 1 for its second, …), or None if no slot is repeated; not for the user
positive Whether a parameter is above 0 everywhere: a number (the usual case) tested in Python, anything else as an array
same_objects Whether two lists hold the same objects, or plain values that are equal and of the same type
some np.any(x), likewise
str2int Convert a string to an int to use as a random seed; not for the user
timepar_of The time parameter that a distribution’s parameter is, or holds in an array of them (which is slow, but allowed); else None
to_array A list or tuple of numbers as an array, so a distribution’s parameters can be given as lists (e.g. ss.normal(mean=[3.5, 4.5], std=[0.9, 0.8])); anything else as it is

even_out

internals.dists.even_out(rands, seed, ind)

Evenly spaced uniforms in the same order as rands, for ss.Sim(even_draws=True); not for the user

The smallest of n uniforms becomes (0 + v)/n, the next (1 + v)/n, and so on, with one offset v for the call. Each number is still exactly uniform, so every distribution keeps its shape, but together they have the mean and spread they should. Each stays close to its original value, so CRN keeps working, though less well (see ss.Sim).

Parameters

Name Type Description Default
rands array the uniforms required
seed int the seed of the call, as hash_uniforms() required
ind int the index of the call, as hash_uniforms() required

every

internals.dists.every(x)

np.all(x), for a parameter’s check: a number (or the bool of one) is tested in Python, since np.all() of one costs more than a small draw

hash_uniforms

internals.dists.hash_uniforms(seed, ind, slots, dtype=None, even=False)

Generate one uniform random number in (0, 1) per slot via a counter-based (splitmix64) hash keyed by (seed, ind); not for the user.

This is the engine behind Starsim’s common random numbers (CRN). Because each output depends only on its slot (plus the per-draw key), the same slot always yields the same value regardless of how many other agents are drawn – so structural differences between scenarios don’t ripple into unrelated agents. It generates exactly len(slots) numbers.

The result is returned at dtype (default ss.dtypes.float) so the CRN path matches the dtype of the native draw path (Dist.rand). The mantissa width is matched to the float width (24-bit for float32, 53-bit for float64) so every value stays strictly < 1 – a plain float64->float32 cast would round the largest values up to exactly 1.0 and break the [0, 1) contract (e.g. expon.ppf(1.0) is inf). With even, the numbers of the call are evenly spaced (see even_out()).

inverse

internals.dists.inverse(name, rands, *pars, big=None, fallback=None)

The inverse CDF of a distribution at the uniforms rands, compiled (by ss.solvers.backends.draws, exact); not for the user

Parameters

Name Type Description Default
name str the distribution in ss.solvers.backends.draws.many required
rands array the uniforms required
pars list its parameters, each a number or an array (one value per uniform)
big array where the draws are not by inversion in ss.solvers.backends.draws (means above its INV_MAX), so are taken from fallback instead, to stay monotone in the uniform None
fallback func the inverse CDF to use there, as a function of the uniforms and the indices of the draws None

make_dist

internals.dists.make_dist(pars=None, **kwargs)

Make a distribution from a dictionary

Parameters

Name Type Description Default
pars dict the distribution, with its name under type, e.g. dict(type='normal', mean=1, std=0.5) None
**kwargs dict merged into pars

no_dists

internals.dists.no_dists()

The classes of objects that never hold a distribution (or a time parameter), so are not searched for them

occurrences

internals.dists.occurrences(slots)

The occurrence of each slot in an array (0 for its first, 1 for its second, …), or None if no slot is repeated; not for the user

positive

internals.dists.positive(x)

Whether a parameter is above 0 everywhere: a number (the usual case) tested in Python, anything else as an array

same_objects

internals.dists.same_objects(a, b)

Whether two lists hold the same objects, or plain values that are equal and of the same type

some

internals.dists.some(x)

np.any(x), likewise

str2int

internals.dists.str2int(string, modulo=1000000000)

Convert a string to an int to use as a random seed; not for the user

Cannot use Python’s built-in hash() since it’s randomized for strings. Hashlib (sc.sha) is 5x slower than int.from_bytes(string.encode(), byteorder=‘big’), but should only add a couple milliseconds to a typical sim.

timepar_of

internals.dists.timepar_of(val)

The time parameter that a distribution’s parameter is, or holds in an array of them (which is slow, but allowed); else None

to_array

internals.dists.to_array(val)

A list or tuple of numbers as an array, so a distribution’s parameters can be given as lists (e.g. ss.normal(mean=[3.5, 4.5], std=[0.9, 0.8])); anything else as it is