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:
|
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 |