The complete vocabulary
Every public name the specification uses, in one place.
Justified by best-practices/principles.md: a vocabulary nobody can hold in their head is a vocabulary nobody uses correctly.
The base is Starsim, so most of the names are Starsim’s own and are marked as such; names this specification adds are in bold.
Capitalization
The rule is Starsim’s existing one (ss.Dist and ss.normal, ss.Rate and ss.perday):
Modules, containers, and base classes are CamelCase. The concrete pieces you put inside a module are lowercase.
So ss.Sim, ss.MultiSim, ss.Disease, ss.RandomNet, ss.MixingPool, and ss.Intervention are CamelCase, along with the two graph classes ss.State and ss.Transition and the products ss.Vaccine, ss.Diagnostic, ss.Treatment. Time values, distributions, ss.par, ss.states, the places outside the model (ss.unborn, ss.dead, ss.outside), the transition constructors, ss.modify, ss.outcome, the parameter forms, the deliveries, and functions such as ss.parallel are lowercase. So are the loaders, which look up named data and return an ordinary object: ss.population('Kenya') returns an ss.People, and ss.contacts('Kenya') a set of ss.MixingPools. The tables they read are in ss.data (ss.data.age('Kenya'), ss.data.population(), ss.data.rates(), ss.data.contacts(), ss.data.geography()), which is not listed here, as ss.geo’s other names are not.
The names
ss. also exports names that are not listed here: the classes and helpers for writing modules as code, ss.note, ss.CapabilityError, and ss.AlreadyRunError. The other errors and warnings that are classes are in ss.errors (ss.errors.AtBound), the testing and profiling tools in ss.debugtools, and everything else in its own submodule (ss.solvers.Compartmental, ss.expressions.Expr).
Sim and modules
| Name | What it is |
|---|---|
ss.Sim |
Modules, a population, mixing, a timeline, and a solver. One sim is one run |
ss.People |
The population: size, demography, per-agent attributes |
ss.population |
A named population: ss.population('Kenya', year=2024) returns an ss.People |
ss.person |
A property of each person, before there are people: ss.person.age < 15 is a condition, and expit(b0 + b1*ss.person.urban) a rate, with each person’s value (each group’s, as compartments) |
ss.Module |
The one module abstraction; everything below is one |
ss.Disease |
Any disease: declared states and the transitions between them, code (define_states(), step()), or both. Most states hold people; a reservoir or a vector population is a state that holds a count |
ss.Strains |
The strains of a disease, ss.Disease(..., strains=ss.Strains(['DS', 'RIF'], ...)), giving dis.strains; unpacked into strain objects (DS, RIF = strains) for conditions and move=DS >> (DS, RIF). States are shared (one course, sets carried up to max_strains) or each strain’s own (shared=False, or shared=['Ca']); strains can also be diseases of their own sharing a natural history (ss.Strains([hpv16, hpv18])) |
ss.AgentDisease |
An ss.Disease written as code that runs only as agents, transmitted over the sim’s networks (beta, rel_sus, rel_trans, set_prognoses()), with hooks overridable one at a time (transmit(), transmissibility(), susceptibility(), route_betas(), set_outcomes(), set_congenital()), and strains (strains=ss.Strains(...), one at a time per person); Starsim 3’s ss.Infection |
ss.Demographics |
Births, deaths, pregnancy, migration |
ss.Intervention |
Base class for interventions with custom step() code |
ss.Connector |
Base class for cross-module step() code |
ss.Analyzer |
Base class for extra results |
Starsim’s library modules (ss.SIR, ss.SIS, ss.SEIR, ss.Births, ss.Deaths, ss.LegacyPregnancy, the ss.infection_log analyzer, and the rest) remain available and are not listed here; they are models written in the language, not part of it. Nor are the error and warning classes (ss.CapabilityError, ss.errors.ImportIncomplete, and the rest), which are listed in 15-errors.md: they are what the library says, not what a user writes.
States, transitions, parameters, results
| Name | What it is |
|---|---|
ss.State |
A state, with people, init, rel_sus, rel_trans, infected (counted in the results infected and prevalence), label |
ss.states |
Several states at once, named from the variables they are assigned to: S, I, R = ss.states(3). ss.states('S I R') when the names are wanted up front |
ss.unborn, ss.dead, ss.outside |
Outside the model, the three instances of ss.transitions.Outside: where births come from (ss.unborn >> S), where deaths go (I >> ss.dead @ 0.1), and where people arrive from and leave to alive (ss.outside >> S, S >> ss.outside); for a count, the same three (mosquitoes emerge from ss.unborn, bacteria are shed from ss.outside) |
ss.stay |
Staying where one is, as a destination of a split: ss.Transition(N, {L: p, ss.stay: None}, when=...) |
ss.Transition |
The one transition class; also built by a >> b @ x |
ss.on |
A trigger: a condition that starts to hold or an event that happens, with delay= for a time after an event. S >> V @ ss.on(ss.person.female & (ss.person.age >= 9)) is the shorthand for a transition’s when=, and when=ss.on(screening.positive, delay=ss.months(3)) a delivery three months after an event |
ss.birth |
Shorthand: an inflow of newborns, at a rate per person, or at an event (ss.birth(when=preg.delivery, children=2)) |
ss.death |
Shorthand: an outflow; lifetable= is a death rate for each age band of a life table, by each person’s age, under every solver |
ss.immigration |
Shorthand: an inflow of a number of people per unit time (n=ss.peryear(100)), at ages like the population’s |
ss.emigration |
Shorthand: an outflow, recorded as people who left rather than died |
ss.migration |
Net migration of everyone at a rate per person of either sign, e.g. ss.migration(rate='Albania') for the UN’s rates |
ss.move |
A move made now, from code: ss.move(I >> R, p=0.5) (or ss.move(I, R, p=0.5)), with n= people or p= each, among who=; not checked against the declared transitions |
ss.lifetable |
A life table (ages and deaths) for ss.death(lifetable=...), or by year and sex for ss.Deaths(lifetable=...) |
ss.transfer |
Movement between levels of a stratification: ss.transfer(dict(risk=('low', 'high')), rate=) |
ss.par |
A named parameter with bounds, prior, source, doc |
ss.pars |
Several parameters at once, from defaults and the values given, for a disease whose transitions are made in __init__(): p = ss.pars(defaults, **kwargs) |
ss.attr |
A value for each person: drawn or recorded at an event (when=), or a value for each event (a dict by event); drawn once (a trait); or derived from others. init= is the value before any event: a number, or a distribution or expression drawn for each person when they are made |
ss.outcome |
A reported quantity: a weighted sum of flows, delayed (delay=), and given a duration (dur=) for the number currently in it |
ss.SimData |
A data file or data frame with its columns matched to result names, which ss.Sim(data=) makes: ss.SimData(df, aliases=..., ignore_extra=True) if it has to be made directly |
ss.ref |
A state or transition named by its module and name, before the module is made: ss.ref('hiv.I') |
ss.mother |
A condition or value read on each unborn agent’s mother (include_born=True: also on the mothers of those already born), e.g. ss.mother(syph.I) (agents) |
The operators >>, @, |, and arithmetic on states are part of this group and add no names. For whole states | and + are the same set of people ((E | I) >> R, infected = E | I, (E | I).n); + is also arithmetic (weights, rates, N = S + E + I + R), and | also joins conditions. A dict of destinations is a split (I >> {ss.dead: p, R: None} @ dur). A custom per-step function needs no name of its own: a plain function is given as a keyword, or written in step().
Time
All Starsim’s, and ss.peract, a probability per act, which is not a time value.
| Kind | Type | Constructors |
|---|---|---|
| Duration | ss.Duration |
ss.days ss.weeks ss.months ss.years (whole months and years added to a date follow the calendar), and one unit as ss.day ss.week ss.month ss.year |
| Rate (hazard) | ss.per |
ss.perday ss.perweek ss.permonth ss.peryear |
| Probability per period | ss.prob |
ss.probperday ss.probperweek ss.probpermonth ss.probperyear (or ss.prob(0.1, 'day')) |
| Frequency (an expected count) | ss.freq |
ss.freqperday ss.freqperweek ss.freqpermonth ss.freqperyear |
| Calendar date | ss.date (ss.years(2000) is a duration of 2,000 years from 0, not ss.date(2000)) |
|
| Probability per act, on a network with acts | ss.peract |
ss.peract(0.01), or one value for each of the network’s directions, ss.peract(m2f=0.3, f2m=0.15) (also {('m', 'f'): 0.3}); no unit and no conversion |
That is four types, each with a constructor per unit, and ss.date: two choices (which kind, which unit), and a bare number works in every slot for anyone who does not want to use them. A frequency is distinct from a rate: a frequency times a duration is a number of events, where a rate times a duration is a probability, and adding one to the other is an error (02-types.md). A network’s acts= takes a frequency (or a rate).
Distributions
Starsim’s, with Starsim’s names, each given by its moments where it has them (ss.normal(mean, std), ss.expon(mean), ss.poisson(mean), ss.gamma(mean=, std=)), with a shift loc= as SciPy’s (so ss.gamma(mean=, std=, loc=)’s mean is of the whole distribution, shift included), and a TypeError at construction for a keyword a distribution does not take: ss.Dist (the base class, which also wraps any SciPy distribution) and ss.normal, ss.lognormal (mean=, std= of the values, or log_mean=, log_std= of their logarithms), ss.expon, ss.gamma, ss.weibull, ss.poisson, ss.nbinom, ss.uniform, ss.bernoulli, ss.constant (value=), ss.beta_dist (mean=, std=), ss.choice; and three new ones, ss.erlang (a number of exponential stages with a mean, which a compartmental run takes as stages), ss.truncnorm, and ss.binom (mostly as a likelihood). The rest of Starsim’s set is available and not listed.
Parameter forms
ss.by (one value per stratum), ss.timeseries (varies in time), ss.seasonal (a yearly cycle). The other forms a parameter can take (a number, a typed quantity, a distribution, a function) need no names of their own.
Mixing and space
Everything spatial is in the submodule ss.geo.
| Name | What it is |
|---|---|
ss.Network |
Base class for networks |
ss.RandomNet ss.SexualNet |
Individual-level networks (Starsim’s; who takes part is who=, a condition; each edge has a weight, and transmissibility is the disease’s, by route, not the network’s; its other networks are available too, and the library’s, e.g. ssl.StaticNet for a NetworkX graph). ss.SexualNet is partnerships between two groups (between=): men and women ('mf', the default), men ('mm'), or e.g. sex workers and clients (('fsw', 'client')), with acts per unit time in each, as in Starsim 3 (people with no partner are paired), or with a target number of partners at a time (n_contacts, concurrency, max_contacts). Partnerships between anyone (needle sharing), and EpiModel’s targets (match=), are the library’s ss.DegreeNet; more structured sexual networks are STIsim’s |
ss.MixingPool |
Group-level mixing; ss.MixingPool(n_contacts=10) with no groups is homogeneous (several together are ss.networks.MixingPools, which ss.contacts returns) |
ss.contacts |
A contact matrix as mixing pools: a named one, ss.contacts('Kenya'), one by a dimension (ss.contacts(risk=M), ss.contacts(age=A, area=B)), one matrix over several dimensions together (dims=[...]), or homogeneous mixing (ss.contacts(10)). A matrix is read as a contact survey, row i the contacts of a person in group i |
ss.geo.geography |
A geography: names, populations, centroids, distances; named (ss.geo.geography('Kenya', level=1)) or given directly |
ss.geo.Mobility |
Movement between places from a matrix (kind=, away=) |
ss.geo.gravity |
Movement model, with parameters to fit or guess, and kind= and away= |
ss.geo.radiation |
Movement model with no parameters but its scale (Stouffer and competing destinations are deferred; a matrix covers them) |
ss.geo.register |
A register of moves from data (ss.geo.register('moves.csv')) |
Interactions and interventions
| Name | What it is |
|---|---|
ss.modify |
While a condition holds (who=), and when (when=, e.g. ss.schedule(start=, stop=)), multiply, add to, or set a named quantity |
ss.immunity |
Protection between strains, combined by a rule: what protects is due_to= (a state, or an attribute), the quantity it lowers is the keyword and the protection its value, a matrix (rows from, columns against) or a number (ss.immunity(due_to=R, protection=m, combine='sum'), where 1 is full protection); for ss.Strains(immunity=) |
ss.Vaccine ss.Diagnostic ss.Treatment |
Products: nouns for things (Starsim 3’s ss.Vx, ss.Dx, ss.Tx remain only as base classes for products written as code, not as aliases; ss.Diagnostic was ss.Test, renamed so that no product and delivery differ only by case) |
ss.Event |
Something that happens to people (a move, a counter, a visit), to whom (who=), when (when=), and to how many (n= at most each time, p= each) |
ss.Delivery |
An event that delivers a product (or a move), with coverage= and capacity= for how many it reaches, and what only interventions need (trace=, skip_reached_within=, disease=) |
ss.vaccinate ss.screen ss.treat |
Deliveries of a product: verbs, subclasses of ss.Delivery |
ss.schedule |
When, in full: ss.schedule(at=None, every=None, start=None, stop=None), with the times (or a function of the date) first, by position or as at= (a lone duration by position raises, asking for every= or at=); with no arguments, due at every step; for when= |
ss.capacity |
A capacity with a priority and a queue: capacity=ss.capacity(ss.peryear(50_000), priority=-hiv.cd4, queue=True) |
ss.at_age |
Who reaches an age, as a condition for who=: who=ss.at_age(ss.months(9)) & ss.person.female |
ss.boxcar |
A waning: full protection for a time, then an exponential decline (an e-folding time) or none (waning=ss.boxcar(plateau=ss.days(180), decay=ss.days(730))), under every solver |
Running, comparing, saving
ss.MultiSim (several runs of a sim, or several sims, whose results have a run dimension; one sim given to it is one run), ss.parallel (the one way to run many: ss.parallel(sim, n_runs=100) for replicates, ss.parallel({'Baseline': s1, 'Vaccine': s2}) for scenarios, labeled by a dict, with workers= the number of processes), ss.options (Starsim’s global options; ss.options.crn = False turns common random numbers off, ss.options.threads (STARSIM_THREADS) is the threads of Starsim’s own passes and particle filters, and ss.options.blas_threads BLAS’s), ss.load (a sim saved by ss.save, or a model in another framework’s format), ss.save (a sim with its results), ss.compile (compile the compartmental solvers’ kernels ahead of time, e.g. in a Docker image or before a calibration; until then, small runs are taken uncompiled, with the same results).
Types you get back or subclass
ss.Result, ss.Timeline, ss.Rate, ss.TimePar, all Starsim’s. Users rarely construct these. The base classes and machinery behind them are in their submodules (ss.modules.Base, ss.loop.Loop, ss.sim.SimPars, ss.parameters.Pars).
Fitting is the submodule ss.fitting, which import starsim does not import (its first use does), so its names are not listed here: ss.fitting.Fit, what sim.fit() returns (called fit below), ss.fitting.compare_models (fit and compare by out-of-sample prediction), ss.fitting.method (a fitting method with its settings, for sim.fit(method=)), and ss.fitting.maximize and ss.fitting.minimize (the goal of sim.optimize(), which reallocates spending between programs: sim.optimize(ss.fitting.maximize('all_vs', at=2030))). (Starsim 3’s ss.Calibration is removed.)
Methods and attributes
| On | Names |
|---|---|
A model (ss.Disease, any module) |
describe diagram equations transitions states strats add_callback stratify copy pars R0 |
| A state or a sum of states | n n_infectious frac total time_in progress enter exit |
A product that people hold (ss.Vaccine, ss.Treatment) |
held_frac |
| A transition | to incidence uids ever n_times since |
A modifiable quantity (sir.rel_sus, a parameter) |
current_value() |
ss.Sim |
init run (until=, verbose=) plot summarize to_df loop results describe stratify to_json fit optimize sensitivity check_dt what_modifies new restart gradient to_synthetic_data |
sim.loop |
insert to_df plot |
| A result | select by cum resample sum |
A result of an ss.MultiSim, which has a run dimension |
quantiles values |
An ss.MultiSim, which is what ss.parallel returns |
run plot summarize to_df results diff |
fit |
sim pars plot summarize to_df project samples |
What ss.fitting.compare_models returns |
fits plot summarize to_df |
The split is deliberate: describe, diagram, equations are about the model (each returns text that shows itself at a prompt or in a notebook, and is printed with print() in a script); plot, summarize, to_df are about results, as in Starsim. values is Starsim’s name for the array behind a result; on a MultiSim result it is the full runs-by-time array. .n is the number in a state in each place ((w*I).n with each person counted by w, and .n_infectious by their infectiousness; a condition has .n too, (hiv.L | hiv.A).n), and .total in the whole population; .frac is the share; .time_in is the time each person has spent in it (in p= and rates out of it). On a transition with several destinations, .X is the flow to its destination X (cin.X, read res.cin.X), not a new name. Reading sir.rel_sus gives the base value; sir.current_value('rel_sus') gives it with the active modifiers applied (agents only). msim.check_runs() is deferred and not listed.
A simple SIR uses four of these names: ss.states, ss.Disease, ss.Sim, and run (five with plot, and ss.days if the duration is typed). A stratified, calibrated national model with interventions uses perhaps forty.
Keywords
Keywords are not listed above, but they are vocabulary too, so the main sets are listed here.
| On | Keywords |
|---|---|
ss.Sim |
the model (a module or a list of modules) as the first argument; networks people total_pop n_agents start stop dur (the length of the run; any two of the three give the third, and all three may be given if they agree) dt unit solver (a name, or ss.solvers.ode(integrator=, substeps=, dwells=), ss.solvers.binomial(substeps=), ss.solvers.abm(step=)) approximations ('note', 'allow', 'error') rescale (True, or a dict of its options) stratify (one dict of dimensions) rand_seed verbose interventions connectors analyzers data pars (the model’s parameters, and the sim’s settings and modules, told apart by name) group_results record_people aging require_kernels step_order label; plus diseases, demographics, modules as in Starsim. sim.pop_scale is read-only, total_pop / n_agents |
ss.MultiSim, ss.parallel |
the sims (or a dict of them by label); n_runs pars priors workers until verbose shrink |
ss.Disease |
the transitions and outcomes by position, or as transitions outcomes results (each positional form has a keyword alternative); name dt init_prev (a share, up to 1) init init_n (a number of people) default_state strains stratify solver (its own ss.solvers.abm(step=)), and one keyword per member. An instance of a declared disease takes extra states, transitions, and outcomes beside its class’s |
ss.State |
name people init init_n rel_sus rel_trans infected keep_time label |
ss.Transition |
source dest rate dur p stages who (who it applies to) when (an event, ss.on(event, delay=), or a condition that triggers it; @ ss.on(...) is its shorthand) acquisition per_strain ('each' or 'first') move increment min_stay name label |
ss.birth |
rate (per person) or when (an event), children p_female |
ss.immigration |
n (a number of people per unit time) |
ss.migration |
rate (per person, of either sign, or a place) |
ss.death, ss.emigration |
rate dur; lifetable (ss.death) |
ss.transfer |
a dict of the dimension and its pair of levels (or increment, for a counter), rate dur p states, and the delivery keywords |
ss.par |
bounds prior source doc |
stratify (on a model or a sim) |
dimensions only, any number, each its levels (a dict of shares) or ss.levels(levels, states=, who=, kind=, mobility=, fractions=); inplace |
ss.Strains |
the strains (names, a dict of values by strain, or diseases), shared immunity max_strains transmit fractions name label, and values by parameter |
ss.immunity |
due_to protection combine |
ss.modify |
multiply add set efficacy frac network who when (ss.schedule(start=, stop=), or a function of the date) until (a condition, or a duration to hold) name |
ss.Event |
what happens (a move, a counter, or nothing, a visit) first; who when n p strain increment name label |
Deliveries (ss.Delivery, ss.vaccinate, ss.screen, ss.treat) |
the product (or a move) first; who (who is eligible: a condition on people or on the population, a function of the sim, ss.at_age(), joined with &) when (nothing for routine; a time or a list of times; an event; ss.on(event, delay=); a function of the date; or ss.schedule()) coverage capacity (a number, a rate, or ss.capacity(n, priority=, queue=)) strain increment trace skip_reached_within disease |
ss.schedule |
at (times, or a function of the date; also by position, except a lone duration) every start stop; none for every step |
ss.on |
the event or condition; delay |
| Products | efficacy waning cost (all); leaky (ss.Vaccine); sensitivity specificity result_delay detects outcomes (ss.Diagnostic); cure (a probability, a dict by state, or a dict by what it moves people along) and moves (moves that are not cures) (ss.Treatment) |
ss.outcome |
delay dur likelihood |
ss.SimData |
aliases ignore_extra |
ss.MixingPool |
n_contacts source target |
ss.SexualNet |
between ('mf', 'mm', or a pair of groups) dur acts participation debut n_contacts concurrency max_contacts match |
ss.geo.Mobility |
matrix kind away |
ss.geo.gravity, ss.geo.radiation |
kind away (and source_power dest_power distance_power scale for gravity, scale for radiation) |
ss.contacts |
a matrix, a country, a number, or a matrix by dimension as a keyword (risk=M); ages dims name study layer unit |
ss.RandomNet |
n_contacts (the same for everyone; a distribution gives the shape) dur (in the sim’s unit when a bare number; one step by default) who memory |
| Networks | sides (and net.directions, which follow from them; between on ss.SexualNet); who for who takes part |
sim.fit |
pars data weights likelihood (a distribution for every series, a dict by series, or False) loss penalty method (a name, ss.fitting.method(name, **settings), or a list) rand_seed |
ss.fitting.method |
the name, and the method’s own settings (e.g. n_trials, draws, chains, particles, samples, n_runs, reseed, require_jax, workers) |
sim.optimize |
goal (ss.fitting.maximize(result, at=) or ss.fitting.minimize(...)) total start spend bounds target |
ss.fitting.compare_models |
data holdout folds |
Slots and their types
A bare number in any of these slots takes the slot’s type and the sim’s unit (02-types.md). In a sim where nothing has a unit, “per unit time” below is per timestep and a duration is a number of steps. Every slot also accepts the rest of the parameter ladder (04-parameters.md), including a function.
| Slot | A bare number is | Appears on |
|---|---|---|
beta= |
a rate per unit time with no network; a probability per contact on a network without acts; a probability per act on a network with acts (ss.SexualNet, ss.DegreeNet(acts=...)), as ss.peract. A dict gives one value per network; directions are named with ss.peract(m2f=..., f2m=...) (a pair is an error) |
ss.SIR, ss.SIS, ss.SEIR and the library’s diseases |
rate=, and a bare number after @ |
a rate (hazard) per unit time | ss.Transition and every shorthand constructor |
dur= |
a duration in the module’s unit; a scalar means an exponential dwell time with that mean (on a network, the mean length of an edge); on the sim (and ss.Timeline), the length of the run, any two of start=, stop= and dur= giving the third (a bare number is in the sim’s unit) |
ss.Transition, ss.transfer, ss.outcome, networks, ss.Sim |
p= |
a branch probability, or the chance for each person eligible | ss.Transition, ss.transfer, ss.move, ss.Event |
n= |
a number of people (at most this many each time); per unit time on ss.immigration |
ss.Event, ss.move, ss.immigration |
stages= |
a whole number of sub-stages | ss.Transition |
init= |
a share of people, up to 1 (a whole number too, with a warning to write a float); on the disease, a dict of these by state name | ss.State, ss.Disease |
init_n= |
a number of people; on the disease, a dict of these by state name | ss.State, ss.Disease |
init_prev= |
a share of people, up to 1 (above 1 raises, pointing to init_n=) |
ss.Disease |
n_contacts= |
a mean: a count of contacts, the same for everyone (ss.RandomNet, ss.MixingPool, a matrix of ss.networks.MixingPools, ssl.HybridNet’s layers), or a target or a size that is a Poisson mean (ss.DegreeNet, ss.SexualNet, ssl.ClusterNet(cluster_size=)); a distribution gives the shape |
networks, mixing pools |
acts= |
on lasting partnerships, an error (it would be acts per step): give a frequency, e.g. ss.freqperweek(2) (or a rate) |
ss.SexualNet, ss.DegreeNet |
frac= |
the fraction of acts (or contacts) a modifier acts on | ss.modify |
coverage= |
a fraction of the eligible people | deliveries, ss.transfer |
capacity= |
a number per round (or step), or a rate per unit time (required for routine delivery); ss.capacity(n, priority=, queue=) for a priority or a queue |
deliveries |
efficacy=, sensitivity=, specificity= |
a probability | products; efficacy= also on ss.modify (multiply=1 - efficacy) |
waning= |
a duration: the product is lost at an exponentially distributed time with that mean; ss.boxcar(plateau=, decay=), under every solver; a function of the time since delivery, fn(t) or fn(t, sim, uids) (or any of t, self, sim, uids by name) with t a duration, fades the efficacy instead, as agents only (a new dose restarts either); a dict gives each effect its own; other shapes are written as states (08) |
products |
during= |
the product is held, and acts, only while people are in these states | products |
delay=, result_delay= |
a duration | ss.outcome and ss.on; ss.Diagnostic |
every= |
a duration | ss.schedule |
away= |
a fraction of time | ss.geo.Mobility, ss.geo.gravity, ss.geo.radiation |
multiply=, add=, set= |
a multiplier, or a value in the target’s own type | ss.modify |
when= (a time), at=, start=, stop= |
a calendar year (a bare number from 1800 on; one between 1000 and 1800 warns that it is ambiguous), or a date; or a duration, that long after the sim’s start (ss.days(10), in a list or not; in a sim with no calendar, a time from 0), except the sim’s own stop=, where a duration in a sim with a calendar is an error suggesting dur=; a duration of 1800 years or more is that calendar year. A time outside the sim raises a ValueError |
ss.schedule, deliveries and events, ss.modify, ss.timeseries, a module’s start=, sim.run(until=) |
bounds= |
a pair, in the unit of the parameter’s value | ss.par |
cost= |
a plain number | products |
Removed since the first draft
| Removed | Why | Use instead |
|---|---|---|
ss.Effect |
Renamed and split: scaling a quantity is one thing, complex logic another | ss.modify, or Python in step() |
ss.Expr, and ss.age, ss.sex, ss.uid, ss.time, ss.year, ss.weekday, ss.has, ss.scheduled |
A second set of names for things the sim already has | Conditions on the real attributes: (ppl.age > 65) & ~sir.vaccinated, or a function of the sim |
ss.Continuous |
A special kind of state and route for reservoirs | An ordinary ss.State that is not people, with counts (W.n) in rates |
ss.stouffer/ss.competing_destinations, msim.check_runs() |
Deferred from v1: one use case each, and the vocabulary is smaller without them (19-open-questions.md) | A mobility matrix; check_dt |
ss.process |
A penalty-box name for custom code | A plain function as a keyword, or step() |
ss.Homogeneous |
Not needed | No network given, or ss.MixingPool(n_contacts=10) |
ss.Network(edges=) |
Starsim already has names | Starsim’s networks; ssl.StaticNet(graph=...), with a NetworkX graph or a function that makes one |
ss.Par, ss.Outcome |
Capitalization rule | ss.par, ss.outcome |
ss.cascade, observe(), Model.results() |
A separate observation step (ss.cascade came back as a group of steps, and was removed again on 2026-10-20: it ran as its steps would as a list) |
Results declared as keywords with the model; the steps as a list |
ss.visit, ss.Resource, ss.share |
Delivery extras, removed 2026-10-20 with stock targets (target=, above=), shared resources (uses=), and count_events= |
ss.Delivery(who=...) with no product; capacity= or who= as a function of the sim; an analyzer |
ss.N, ss.count |
Extra names for a sum and a keyword | N = S + I + R; capacity= |
ss.dist(...) |
Duplicate | ss.Dist(dist=...) |
ss.lognorm, ss.binomial, ss.neg_binomial, ss.rate |
Not Starsim’s names | ss.lognormal, Starsim’s set, ss.nbinom, ss.per |
ss.exp_decay, ss.linear_decay |
Covered by existing forms | waning= as a duration, a function, or ss.timeseries |
ss.act, queue(), phase=, sim.substep() |
Queued updates and named phases are gone | Starsim’s ordered loop, sim.loop.insert() |
ss.Stage, fit.apply() |
Simpler spellings | fit(method=[...]); fit.sim |
capabilities(), closure=, to_ode(), scale= |
The closure ladder and per-transition hybrid solvers are out of v1 | describe(), solver=, printed approximations |
jacobian(), fixed_points(), sim.equations() |
Symbolic analysis is not a v1 feature | sir.equations() |
summary() on a model, to_dict() |
summarize is about results in Starsim; the model is its code, and to_json is a sim’s results |
describe() |
The dict tuple shorthand dict(infection=('S -> I', 0.05)) |
Ambiguous: it guesses the kind of transition from a bare value | The operator form |
ss.transmission, ss.progression (arrow strings) |
A second spelling of a transition, with states made from their names and the source of infection guessed | S >> I @ beta * I, ss.Transition(I, R, dur=10) |
rate=ss.on(...), after= an event, undriven= |
Duplicates of a trigger, and a flag that only silenced a warning | when=ss.on(event, delay=); a transition with @ 0 |
Weighted destinations (S >> (p*A + (1-p)*B)), p= a function giving a row |
Duplicates of the list form of branches | S >> {A: p, B: None} @ rate, or ss.Transition(S, [A, B], p=[p, 1 - p]); a function for each destination |
ss.any |
A reduce of \| |
a \| b, or a list in who= |
ss.Route |
Starsim’s name; the base class is ss.networks.Route, not exported |
ss.Network, passed with networks= |
ss.stratify(sir, ...), ss.RandomExactNet, birth_rate=/death_rate= on ss.Sim, a delivery’s years=/dates=/eligible=, track_writes= |
Duplicates of other spellings (D34, D36, D38, D39) | sir.stratify(...), ss.RandomNet(method='exact'), demographics=[ss.birth(...), ss.death(...)], when=/who=, sim.what_modifies() |
ss.explain, ss.conformance, mixing=, n=, route=, ontology=, kind= on parameters, take=/doses=, min=/max= clamps, crn_strict, run(force=), run(reset=) |
Small things that did not earn a name | Documentation pages; networks=; total_pop=; inline provenance in ss.par(source=) |
An 'ide' solver |
A name of its own for an option of the ODE (built 2026-10-28) | ss.solvers.ode(dwells='exact') |
The second draft’s names that went in this revision:
| Removed | Why | Use instead |
|---|---|---|
ss.Compartments |
A second module class for states that are not people. The states use the same syntax as the disease’s, and the name was confusing, since S, I, and R are compartments too |
An ordinary ss.State in the disease; people=False is inferred, or given |
None as a source or sink (None >> W) |
Python evaluates None @ 0.1 before it sees the arrow |
ss.null (itself replaced on 2026-11-06 by ss.unborn, ss.dead, and ss.outside, below) |
method= on ss.Sim and sim.run, methods(), 'ctmc' as the name |
“Solver” is the word; method= stays on sim.fit for the fitting method |
solver=, 'gillespie'; ss.solvers.ode(integrator=) for the ODE scheme |
n_reps on ss.Sim, .trajectories, check_reps |
A sim is one run | ss.MultiSim(sim, n_runs=100) or ss.parallel(sim, n_runs=100); .values and .quantiles on its results; check_runs (deferred) |
crn_key |
No case found where keying common random numbers on agent attributes beats Starsim’s slots | Slots; ss.options.crn = False to turn CRN off |
ss.export |
A model is a Python script, which is already the thing to share | The script; ss.save of the sim |
ss.Needs, sim.needs() (later ss.Requires, sim.requires()) |
Metadata that was never enforced and that no model used (removed 2026-10-21) | A docstring; doc= and source= on ss.par |
ss.People('Kenya') |
A loader is lowercase, like ss.contacts |
ss.population('Kenya') |
.sel() |
Spelled out | .select(), and .by() for one series per group |
sim.transmissions |
The log is large and slows the run, so it is recorded on request | analyzers='infection_log', Starsim’s analyzer |
mean_degree |
Starsim’s name | n_contacts, which may be a distribution |
min_duration= |
A policy with memory is fragile as a keyword | ss.modify(until=); step() or an intervention class for anything that has to remember |
p= on ss.outcome |
A number times a flow already means that fraction of it | ss.outcome(0.5 * infection) |
summary() |
Starsim’s name | summarize() |
Since the review of October 2026, these names went, or were promised and never built:
| Removed | Why | Use instead |
|---|---|---|
ss.update |
A coded module does it: a connector’s step() keeps the value, read by a function in a rate |
A connector, or step() |
ss.clock() |
(A \| B).time_in is the same clock |
(A \| B).time_in |
X.dwell |
The same clock as X.time_in, half a step later |
X.time_in |
ss.spread |
Back-dating a module’s start is a longer run; only pregnancy’s burn-in keeps it, inside the module | Start the sim earlier |
offspring= |
A birth in the same disease does it | ss.birth(NB, when=farrowing, children=3) |
ss.func |
Never built; a plain function given as a keyword already does what it was for | A plain function, or step() |
sim.crn_report() |
Never built | ss.options.crn, and paired runs (11-random.md) |
dependence= and n_ref= on ss.MixingPool |
Never built; density dependence is a count in the rate | beta * I.n |
.prev on a state |
The code’s name is .frac |
.frac, and .n or .total for counts |
.base on a modifiable array |
Reading the array gives the base value | sir.rel_sus for the base, sir.current_value('rel_sus') for the value with modifiers applied |
ss.rate, ss.time_prob, ss.rate_prob |
Starsim 2 names, deprecated in Starsim 3 (Starsim’s other time names are kept) | ss.peryear(x), ss.probperyear(x) |
solver='sde', 'stochastic', 'ctmc' |
One solver per method | 'gillespie' (exact), 'tau' (tau-leaping), 'binomial' |
ss.Calibration as the result of a fit |
sim.fit() returns an ss.fitting.Fit |
ss.fitting.Fit (Starsim 3’s ss.Calibration is removed) |
sim.to_spec(), sim.to_code(), sim.hash(), ss.diff() (removed 2026-10-27) |
The model is its code: there is nothing to translate a sim back into, and two models are compared by their results or a text diff | The script, describe(), and ss.save('run.sim', sim); an import gives code (sim.imported.code) |
msim.reduce(), msim.mean(), msim.median(), ss.MultiResult, ss.multi_run() |
A MultiSim is its results, with a run dimension | msim.results.sir.I.quantiles() (and .median(), .mean()); ss.parallel(pars=...) |
ss.outcome(every=), annualize() |
Resampling belongs to a result | result.resample('year') |
ss.routine_vx, ss.campaign_vx, ss.simple_vx, ss.routine_screening, ss.campaign_screening, ss.routine_triage, ss.campaign_triage, ss.seasonality, ss.rate, ss.time_prob, ss.rate_prob |
Starsim 3 and 2 duplicates of the language | Products with deliveries, ss.seasonal, ss.per and ss.prob; each is mapped in skills/migrate-starsim3 |
The vocabulary review of November 2026 (starsim#1547) renamed names that meant different things in different places, or whose short form hid what they were:
| Removed | Why | Use instead |
|---|---|---|
ss.ppl, ss.people() |
An abbreviation no other name uses; a loader that differed from ss.People only by case |
ss.person, ss.population('Kenya') |
ss.state() |
Differed from ss.State only by case, and also names transitions |
ss.ref() |
ss.simdata |
Users give data= to the sim |
ss.SimData, if it has to be made directly |
ss.lognorm_ex, ss.lognorm_im, ss.beta_mean |
Suffixes that don’t say which moments are given | ss.lognormal(mean=, std=) or (log_mean=, log_std=); ss.beta_dist(mean=, std=) |
ss.box |
Signal-processing jargon for a shape whose keywords didn’t name its parts | ss.boxcar(plateau=, decay=) |
ss.dur (as the type) |
A type, not something a model writes | ss.Duration; ss.days() and the other units in models |
ss.MaternalNet, ss.AgeGroup |
A copy of ss.PrenatalNet; a filter that duplicates conditions on age |
ss.PrenatalNet; ss.person.age conditions |
ss.immunity(rel_sus=) |
1 meant full protection, the reverse of rel_sus everywhere else |
protection= |
ss.Sim(results=), approx=, nopython=, use_aging=, people_results=, custom= |
Same word as ss.Disease(results=); a three-way Boolean; Numba’s jargon; a prefix that added nothing |
group_results=, approximations=, require_kernels= (and require_jax= on sim.fit), aging=, record_people=, modules= |
ss.Transition(src=, dst=, first=, after=, per='strain'), where= as a trigger |
Abbreviations; first read as an ordering, after as an event; per is also the rate type; where= meant two things |
source=, dest=, ss.Disease(default_state=), min_stay=, per_strain=; when= (or @ ss.on(...)) |
X.time, X.n_trans, transition.count, disease.count() |
.time read as the sim’s time; “trans” as transitions; count meant two things |
X.time_in, X.n_infectious, transition.n_times, (A \| B).n |
ss.birth(per=), ss.birth(sex=), ss.Pregnancy(sex_ratio=) |
per is the rate type; three names for one share |
when=; p_female= everywhere |
Deliveries’ what=, deliver=, interval=; ss.Diagnostic(results=, delay=); ss.Treatment(to=, transition=); ss.modify(route=) |
Words that didn’t say what they hold; two meanings of delay; four keywords for one decision |
the first argument, who=, ss.schedule(every=); outcomes=, result_delay=; cure= and moves=; network= |
split= on ss.Strains and stratify(); ss.timeseries(mode='constant'); sim.fit(gof=); n_cpus=; sim.to_data() |
Clearer words | fractions=; interpolation='step'; loss=; workers=; sim.to_synthetic_data() |
The transitions review of November 6, 2026 replaced names that hid what they meant, and stopped guessing a result:
| Removed | Why | Use instead |
|---|---|---|
ss.null |
One name for births, deaths, and migration, so a flow out of the model did not say whether people died or left, nor a flow in whether they were born or arrived | ss.unborn (births), ss.dead (deaths), ss.outside (arrivals and departures alive), the instances of ss.transitions.Outside |
res.<transition>.null |
The flow of a branch out of the model | res.<transition>.dead |
A state D for deaths |
It kept the dead among the living (in N, on networks) |
I >> ss.dead |
ss.Infection |
A declared ss.SIR is an infection too, so the name misled |
ss.AgentDisease (written as code, agents only); a declared disease with its own transmit(transition) |
validate_beta() |
Said what it checked, not what it gives | route_betas(), the beta of each network |
Prevalence worked out from the transitions (Graph.resolve_prevalence()) |
A guess that missed states infected but not infectious (COVID’s severe stages) | Mark the states, ss.State(infected=True), for infected and prevalence; or declare prevalence |
(S \| I) >> D raising, and \| and + meaning different sets |
Two spellings that read alike for one set of people | Either, for whole states; brackets with \| as sources, (E \| I) >> R |
The API review of November 7, 2026 gave each idea one word across the API, and made every keyword either work or raise:
| Removed | Why | Use instead |
|---|---|---|
where= (on transitions, ss.modify, ss.move, networks, stratify()), eligibility= |
Two words for who something applies to; eligibility= was silently ignored |
who= everywhere (stratify() takes it in ss.levels()), including an intervention written as code, whose check_eligibility() stays (restored 2026-11-07) |
on=, at=, every=, delay=, at_age= on events and deliveries; on= on transitions, ss.attr, ss.birth; hold= on ss.modify; a transition’s dur= with an event |
Seven ways of saying when | when=: a time or list of times, an event, ss.on(event, delay=), a function of the date, or ss.schedule(at=, every=, start=, stop=); who=ss.at_age(...); ss.modify(until=). start= and stop= stay on events, deliveries and ss.modify (restored 2026-11-07), as shorthand for when=ss.schedule(start=, stop=), with any when= (a window on an event’s trigger) |
ss.schedule(when=), dates=, fn= |
Duplicates of what when= takes |
ss.schedule(at=...), by position or keyword (also a function of the date) |
priority=, queue= on deliveries |
Rare options on a crowded signature | capacity=ss.capacity(n, priority=, queue=) |
beta= on a route; edges.beta |
Honored as agents and dropped as compartments | The disease’s transmissibility by route; edges.weight |
Solver names 'ide', 'abm-midstep', 'abm-wholestep', 'abm-events'; integrator= and substeps= on the sim |
A solver’s options belong to the solver (a class’s suggested_solver stays, as ss.solvers.abm(step=...) or a step) |
ss.solvers.ode(dwells='exact'), ss.solvers.abm(step=...); ss.solvers.ode(integrator=, substeps=), ss.solvers.binomial(substeps=) |
rescale_threshold=, rescale_factor=, pop_scale= |
Did nothing alone; a third way to say the scale | rescale=dict(threshold=, factor=); total_pop= with n_agents= (sim.pop_scale is read-only) |
sim.run(n_runs=, parallel=, pars=, rand_seed=, solver=, profile=, shrink=), ss.parallel(label=sim), ss.MultiSim(base_sim=, initialize=) |
Several ways to run many sims | ss.parallel(sims, n_runs=, workers=) with labels from a dict; sim.restart(pars=, rand_seed=).run(); sim.new(solver=); sim.profile(); sim.shrink() |
rate= as people per unit time on ss.immigration; ss.birth(n=); ss.Pregnancy(embryos_per_pregnancy=); init_prev= above 1 as a number of people |
rate= is per person and n= a number of people everywhere else |
ss.immigration(n=); children=; init_n= |
ss.Pregnancy(preterm_threshold=, very_preterm_threshold=, rel_preterm=, rel_preterm_age=) |
Four keywords for one option | preterm=ss.preterm(threshold=, very_threshold=, rel=, by_age=). (maternal_death_rate=, the hazard while pregnant, and p_maternal_death=, Starsim 3’s chance at delivery, were merged into one maternal_death= and restored 2026-11-07, since they are two events; maternal_death= raises, naming them) |
stratify(fractions=, adjust=, where=) mixed with dimensions |
Options and dimensions in one call | Levels as a dict of shares; ss.levels(); ss.by() in the rate |
sim.fit()’s method settings (n_runs=, reseed=, require_jax=, n_trials=, …); ABC-SMC’s particles=; fit.mismatch; ss.fitting.optimize(); df=, seed= |
Settings of one method among another’s; a name with two meanings | ss.fitting.method(name, ...); samples=; fit.objective; sim.optimize(ss.fitting.maximize(...)); data=, rand_seed= |
output= on describe() and equations(); sim.summary, msim.compare(), msim.brief() |
Two ways to read each summary | Text that shows itself; sim.summarize(), msim.summarize(each=True), print(msim) |
ss.datedur at the top level; ss.date('2020.5') |
Identical to ss.months(); ambiguous |
ss.months(), ss.years(); ss.date(2020.5) or ss.date('2020-05') |
ss.options.num_threads (which meant BLAS’s in one release candidate and Starsim’s in another) |
The name said neither | ss.options.blas_threads (STARSIM_BLAS_THREADS) and ss.options.threads (STARSIM_THREADS); num_threads raises naming both |
ss.outcome(stay=); per_strain=True, single_strain=True; strains.move(); sim.people.person() |
One word for one thing | dur=; per_strain='each', 'first'; strains.change(); sim.people.get_person() |
Adding to the vocabulary
There is no fixed budget. Before adding a name, check:
- Does Starsim already have a name for it? Use that one.
- Can it be written with what exists? A bednet is an
ss.modify; a metapopulation is a stratification by patch; a junction is a branching transition; a reservoir is a state that is not people. - Would more than one kind of model use it? Something only one framework’s specialty needs probably belongs in a library built on top.
- Does it make some real model shorter than it is in its original framework? That is the test the whole design is held to.
- Is a keyword enough? Routine and campaign delivery are keywords on one function, not two classes.
Not in the language
Each of these exists in at least one reviewed framework. The reasons are in best-practices/principles.md.
| Not included | Instead |
|---|---|
| A standalone modeling language with its own syntax | Python objects |
| Models written in YAML, JSON, or spreadsheets | Python |
| Many named flow kinds | One transition class whose kind follows from its rate |
| A separate class per solver | solver= |
| A sim that is many runs | ss.MultiSim |
| A default time unit | Timesteps, when nothing in the model has a unit |
| A separate metapopulation model class | A stratification by patch |
| A special route, or a separate module class, for environmental or vector-borne transmission | Counts in rates, and ordinary states that are not people |
| A class per combination of delivery mode and product | Keywords |
| A rule language for triggers and targeting | Conditions and functions in Python |
| Overwrite adjustments in stratification | Multipliers |
| Positional or integer references to states | Names |
| Required declarations of what a module reads and writes | sim.what_modifies(), which lists the declared modifiers |
| Common random numbers keyed on agent attributes | Starsim’s slots |