Skip to main content

solvers.choose

solvers.choose

The solvers to choose from (ss.solvers.ode(), binomial(), gillespie(), tau(), and abm(), each with its options), and, not for the user, choosing the solver: which engine runs a declared model, and why (choose_solver()), and the steps that build a declared model before it is run (parameters by name, births and deaths of the population, a named population, the unit, life tables)

It exists so that a model need not say how it runs: the choice is made from what it holds, and the reason is kept. Modules written as code are taken into account, and get an engine too.

Used with: agents and compartments; declared and coded modules

Classes

Name Description
Solver A solver, for ss.Sim(solver=...), with only the options of its own: ss.solvers.ode(), binomial(), gillespie(), tau(), or abm()
Undeclared The engine of a disease that declares no states or transitions (see ss.Disease): there is nothing for it to run, so the disease’s own
abm Agents, with their options: ss.Sim(solver=ss.solvers.abm(step='events')); solver='abm' is the same with the defaults
binomial The chain-binomial solver (stochastic compartments: counts of people, with chance, and much faster than agents for a large population), with its options: ss.Sim(solver=ss.solvers.binomial(substeps=1)); solver='binomial' is the same with the defaults
gillespie The exact stochastic solver for compartments (Gillespie’s algorithm: one event at a time, with no error from the timestep, but slow for a large population), which has no options: ss.Sim(solver='gillespie')
ode The ODE solver (deterministic compartments: the average epidemic, with no randomness), with its options: ss.Sim(solver=ss.solvers.ode(integrator='euler', substeps=1)); solver='ode' is the same with the defaults
tau Tau-leaping (stochastic compartments, in leaps short enough that the rates change little over each: close to Gillespie’s exact answer, and much faster for a large population), which has no options: ss.Sim(solver='tau')

Solver

solvers.choose.Solver(**kwargs)

A solver, for ss.Sim(solver=...), with only the options of its own: ss.solvers.ode(), binomial(), gillespie(), tau(), or abm()

The common case is its name, solver='ode', which is the same as solver=ss.solvers.ode(). Once the sim is initialized, sim.solver is the one that runs (its defaults, if it was asked for by name or chosen by 'auto'), so which one it is can be tested as a class, e.g. isinstance(sim.solver, ss.solvers.abm); str(sim.solver) is its name.

Parameters

Name Type Description Default
kwargs dict the solver’s options

Methods

Name Description
given The options given, other than the defaults
given
solvers.choose.Solver.given()

The options given, other than the defaults

Undeclared

solvers.choose.Undeclared(module, sim)

The engine of a disease that declares no states or transitions (see ss.Disease): there is nothing for it to run, so the disease’s own code is all of it. It gives that code what a declared disease’s engine gives its own, as agents: the modifiers and products on its rel_sus, rel_trans, and parameters (dis.current_value(), and self.pars while it runs), transmission over the networks (ss.Disease.transmit(rel_trans=..., rel_sus=...); see Transmission), and, for a delivery, the people its conditions select and a cure (force())

It exists so that code written for a disease can act on any disease in the same way.

Methods

Name Description
clear_flows Nothing: a disease written as code counts its own flows
define Nothing declared, so no distributions of its own (the disease’s code makes its own); with strains, those of who infects whom and of protection (see Transmission.strain_cases()), by the names a declared disease’s transmission called infection (e.g. ss.SIR’s) gives them, so the same model draws the same numbers both ways
eval An expression of parameters (e.g. a modifier’s amount, ss.par(2, bounds=[1, 4])), at their values
force A cure by a delivery (see ss.Event.cure()): the disease’s own clear_infection()
nothing Nothing to run: each step of the lifecycle is the disease’s own code
record_events Nothing: a disease written as code records its own flows
record_states The count of each ss.BoolState, as for any module
start_step Values held for a step (e.g. the modifiers of a network’s contacts) are worked out again: at the step’s start, and once the interventions have run (the disease’s step())
unapplied Why the disease would not apply the modifiers (mods) on a parameter (name) or on its rel_sus or rel_trans (kind), or None if it would: its class says its own code reads them (allow_modifiers; see ss.connectors.check_applied())
value A parameter given as ss.par (e.g. with bounds, to be fitted) is its value, which the disease’s code reads
clear_flows
solvers.choose.Undeclared.clear_flows()

Nothing: a disease written as code counts its own flows

define
solvers.choose.Undeclared.define()

Nothing declared, so no distributions of its own (the disease’s code makes its own); with strains, those of who infects whom and of protection (see Transmission.strain_cases()), by the names a declared disease’s transmission called infection (e.g. ss.SIR’s) gives them, so the same model draws the same numbers both ways

eval
solvers.choose.Undeclared.eval(expr)

An expression of parameters (e.g. a modifier’s amount, ss.par(2, bounds=[1, 4])), at their values

force
solvers.choose.Undeclared.force(uids, tr, dst, srcs=None)

A cure by a delivery (see ss.Event.cure()): the disease’s own clear_infection()

nothing
solvers.choose.Undeclared.nothing()

Nothing to run: each step of the lifecycle is the disease’s own code

record_events
solvers.choose.Undeclared.record_events()

Nothing: a disease written as code records its own flows

record_states
solvers.choose.Undeclared.record_states()

The count of each ss.BoolState, as for any module

start_step
solvers.choose.Undeclared.start_step()

Values held for a step (e.g. the modifiers of a network’s contacts) are worked out again: at the step’s start, and once the interventions have run (the disease’s step())

unapplied
solvers.choose.Undeclared.unapplied(kind, name=None, mods=())

Why the disease would not apply the modifiers (mods) on a parameter (name) or on its rel_sus or rel_trans (kind), or None if it would: its class says its own code reads them (allow_modifiers; see ss.connectors.check_applied())

value
solvers.choose.Undeclared.value(p)

A parameter given as ss.par (e.g. with bounds, to be fitted) is its value, which the disease’s code reads

abm

solvers.choose.abm(step=None)

Agents, with their options: ss.Sim(solver=ss.solvers.abm(step='events')); solver='abm' is the same with the defaults

Also a declared module’s own way of running as agents, which the sim’s does not override: ss.Disease(..., solver=ss.solvers.abm(step='wholestep')); or what a module’s class suggests, suggested_solver = ss.solvers.abm(step='wholestep') in the class body, which solver='abm' (or 'auto') follows, and a sim’s solver that names a step (e.g. ss.solvers.abm(step='midstep')) overrides (see ss.solvers.agent_solver()).

Parameters

Name Type Description Default
step str

how the agents of a declared module are stepped:

  • ‘midstep’ (the default): by steps, with those who arrive in a state during a step facing its rates for the rest of it, which is the more exact at a coarse step
  • ‘wholestep’: by steps, with rates over whole steps only, for those in the state at the start of the step, as code with ss.bernoulli() takes them: faster, and a little less exact at a coarse step; use it to match a model written as code
  • ‘events’: event by event in continuous time within each step (a prototype)
None

binomial

solvers.choose.binomial(substeps=None)

The chain-binomial solver (stochastic compartments: counts of people, with chance, and much faster than agents for a large population), with its options: ss.Sim(solver=ss.solvers.binomial(substeps=1)); solver='binomial' is the same with the defaults

Parameters

Name Type Description Default
substeps int the number of substeps per timestep (default: enough that no more than about a tenth of the people in a compartment leave it in one). More is more accurate and slower; 1 takes each step whole, as discrete-time chain-binomial models (and most other frameworks’) do, e.g. to reproduce their numbers None

gillespie

solvers.choose.gillespie(**kwargs)

The exact stochastic solver for compartments (Gillespie’s algorithm: one event at a time, with no error from the timestep, but slow for a large population), which has no options: ss.Sim(solver='gillespie')

ode

solvers.choose.ode(integrator='rk4', substeps=None, dwells='stages')

The ODE solver (deterministic compartments: the average epidemic, with no randomness), with its options: ss.Sim(solver=ss.solvers.ode(integrator='euler', substeps=1)); solver='ode' is the same with the defaults

Parameters

Name Type Description Default
integrator str the scheme: ‘rk4’ (fourth-order Runge–Kutta, the default: accurate at a coarse step) or ‘euler’ (simpler and less accurate; use it to match a model written with Euler steps) 'rk4'
substeps int the number of substeps per timestep (default: enough that no compartment turns over, or grows through its inflows, by more than a quarter in one). More is more accurate and slower. With integrator='euler', substeps=1 takes each step whole, as a model written in discrete time does, e.g. to reproduce another model’s numbers None
dwells str how the time spent in a state is modeled: ‘stages’ (the default: as Erlang stages, or exponential, which is fast) or ‘exact’ (the duration’s own distribution, e.g. a fixed 5 days, as a conveyor of whole steps at its own chance of leaving: an integro-differential model, which under branching=True is the renewal equation; see the user guide on branching processes). Use ‘exact’ when the shape of the duration matters, e.g. a fixed incubation period 'stages'

tau

solvers.choose.tau(**kwargs)

Tau-leaping (stochastic compartments, in leaps short enough that the rates change little over each: close to Gillespie’s exact answer, and much faster for a large population), which has no options: ss.Sim(solver='tau')

Functions

Name Description
as_ode A new sim of the same model as an ODE, for what is worked out from compartments (R0(), Rt(), an export): its number of agents is dropped, and the people they stood for are its population; the settings of agents only (rescale=, even_draws=) are dropped too, since sim.new(solver='ode') would refuse them
as_solver The solver asked for, as a Solver (e.g. ‘ode’ as ss.solvers.ode()), checked; 'auto' stays as it is, since it is chosen when the sim is initialized. Not for the user
auto_reasons The reasons, from the capability record, that solver='auto' runs the model as agents (none if it runs it as compartments)
choose_engine Whether JAX takes the sim’s whole run of the ODE, as ss.gradients runs it, rather than the kernels, and why
choose_solver Work out the sim’s unit and solver; see ss.Disease for declaring a model that can be run with different solvers
flows_of The transitions of a declared disease that are births, arrivals, or deaths of everyone, so are the population’s
group_people In a compartmental run, one row per group: the properties that are not dimensions have no values to give, so are removed, as are the active agents (auids), and counting who is alive counts the people, not the groups
in_proportions Whether a sim’s numbers are fractions of a population of 1 (total_pop=1): as an ODE, or as agents each a fraction of it, but not as a stochastic compartmental model, which then has one person
init_capabilities One record for each ingredient that changes which solvers can run the model, read by solver='auto', sim.describe(), and the CapabilityError of a compartmental solver that was asked for
init_group_values The values of the dimensions in the people: as agents, each person’s level of each; in a compartmental run, with one row per group, the age of each group (the middle of its age group) and its sex
init_joint_states In a compartmental run: track jointly the states of diseases that refer to each other, or whose people are born or die (see joint_groups())
make_engine Create the engine that runs a module’s graph under the sim’s solver: in a compartmental run, the one laid out to choose the solver (see lay_out()), unless the groups changed since; for a disease that declares nothing, one that runs nothing
set_par_values Apply parameter values given by name (pars=dict(beta=0.1), or ‘sir.beta’ if the name is used by more than one module; for a parameter with a value per level, ‘beta[a]’ for one level)
settle_start Once every module has its people: as agents, those placed in a state nobody waits in move on at once; and the diseases tracked jointly take their initial conditions
solver_table What decides which engine runs a model, as a dataframe: a row for each rule, in the order they are read, with a column for each
why_not Why an engine ('kernels', a whole run in one call; or 'jax', the ODE in JAX, as for gradients) cannot run the sim, in a few words, or None if it can

as_ode

solvers.choose.as_ode(sim, **kwargs)

A new sim of the same model as an ODE, for what is worked out from compartments (R0(), Rt(), an export): its number of agents is dropped, and the people they stood for are its population; the settings of agents only (rescale=, even_draws=) are dropped too, since sim.new(solver='ode') would refuse them

as_solver

solvers.choose.as_solver(solver)

The solver asked for, as a Solver (e.g. ‘ode’ as ss.solvers.ode()), checked; 'auto' stays as it is, since it is chosen when the sim is initialized. Not for the user

auto_reasons

solvers.choose.auto_reasons(sim)

The reasons, from the capability record, that solver='auto' runs the model as agents (none if it runs it as compartments)

choose_engine

solvers.choose.choose_engine(sim)

Whether JAX takes the sim’s whole run of the ODE, as ss.gradients runs it, rather than the kernels, and why

By sim.jax: True for JAX, and otherwise (False, or None, the default) the kernels. JAX is never chosen for a run by itself: it is chosen where it is needed, for gradients and the fits and log-densities that use them (sim.gradient(), sim.log_density(), sim.fit()), and is worth asking for only for a long run over many groups (it takes about half a second to start in a session, and compiles each model; an ODE run in JAX agrees with the kernels to about 1e-14). The other solvers are run by the kernels, as is a model JAX does not cover (see why_not()), with a note.

Returns whether to use JAX, and the reason, also kept as sim.engine_reason.

choose_solver

solvers.choose.choose_solver(sim)

Work out the sim’s unit and solver; see ss.Disease for declaring a model that can be run with different solvers

Agents are made only when something needs them, whether the model is declared or written as code: a sim of modules written as code with nothing per person (e.g. an ODE of its own in NumPy) has no agents, and one with per-person arrays (e.g. a disease written as code) runs as agents. Which engine then takes a whole compartmental run, the kernels or (with sim.jax = True) JAX, is chosen when the sim is run, by choose_engine().

The steps: the modules are gathered and the declared model is built (init_model()); what matters to the choice is recorded (init_capabilities()), with what the layout of the compartments refuses, found by making it (init_layouts()); the solver is chosen; and the population is made ready for it (init_agents() or init_compartments()), and checked. The layouts are made once: a compartmental run uses them, unless its groups changed since.

flows_of

solvers.choose.flows_of(mod)

The transitions of a declared disease that are births, arrivals, or deaths of everyone, so are the population’s

group_people

solvers.choose.group_people(sim)

In a compartmental run, one row per group: the properties that are not dimensions have no values to give, so are removed, as are the active agents (auids), and counting who is alive counts the people, not the groups

in_proportions

solvers.choose.in_proportions(sim)

Whether a sim’s numbers are fractions of a population of 1 (total_pop=1): as an ODE, or as agents each a fraction of it, but not as a stochastic compartmental model, which then has one person

init_capabilities

solvers.choose.init_capabilities(sim)

One record for each ingredient that changes which solvers can run the model, read by solver='auto', sim.describe(), and the CapabilityError of a compartmental solver that was asked for

Each record has the ingredient; the reason it makes auto run the model as agents (reason); whether it stops a compartmental solver that was asked for (fatal); and whether auto runs as agents for it (switch): see capabilities. Code is never run to find out: setting compartmental = True or False on a module (or its class) says whether it works on groups, and agent_reasons() is what a module says for itself. Any module written as code that says nothing, and the code and functions of a declared module, run beside the compartments (and raise a CapabilityError if they ask for agents). What the layout of the compartments refuses is not predicted here: init_layouts() makes the layout, which runs none of the model’s code, and adds its refusal as a record.

init_group_values

solvers.choose.init_group_values(sim)

The values of the dimensions in the people: as agents, each person’s level of each; in a compartmental run, with one row per group, the age of each group (the middle of its age group) and its sex

init_joint_states

solvers.choose.init_joint_states(sim, layouts=None)

In a compartmental run: track jointly the states of diseases that refer to each other, or whose people are born or die (see joint_groups())

For example, ss.modify(hiv.rel_trans, multiply=3, who=syph.I) needs the number of people with HIV who also have syphilis. Each disease in joint states becomes a dimension of the sim, whose levels are its compartments and which applies to the people of the others, so the model grows to the product of their compartments, which is counted before anything is made, and kept within ss.options.max_joint. See ss.solvers.JointStates.

Parameters

Name Type Description Default
sim ss.Sim the sim required
layouts list the layouts already made for the sim’s groups (see joint_layout()), if any None

make_engine

solvers.choose.make_engine(module, sim)

Create the engine that runs a module’s graph under the sim’s solver: in a compartmental run, the one laid out to choose the solver (see lay_out()), unless the groups changed since; for a disease that declares nothing, one that runs nothing

set_par_values

solvers.choose.set_par_values(sim)

Apply parameter values given by name (pars=dict(beta=0.1), or ‘sir.beta’ if the name is used by more than one module; for a parameter with a value per level, ‘beta[a]’ for one level)

settle_start

solvers.choose.settle_start(sim)

Once every module has its people: as agents, those placed in a state nobody waits in move on at once; and the diseases tracked jointly take their initial conditions

solver_table

solvers.choose.solver_table()

What decides which engine runs a model, as a dataframe: a row for each rule, in the order they are read, with a column for each engine: agents, compartments (one step at a time), kernels (the whole run in one call), and jax (the ODE in JAX). In compartments, “if asked” is what solver='auto' runs as agents, but a compartmental solver that was asked for runs, approximating it.

Example

ss.solvers.solver_table()

why_not

solvers.choose.why_not(sim, engine, mods=None)

Why an engine ('kernels', a whole run in one call; or 'jax', the ODE in JAX, as for gradients) cannot run the sim, in a few words, or None if it can

Parameters

Name Type Description Default
sim ss.Sim the sim required
engine str the engine required
mods list for the kernels, only these declared modules’ rules None