connectors
Connectors, which link modules as the sim runs: e.g. one disease making another more likely
Most links between diseases need no connector, since diseases can read each other’s states (e.g. in a rate). A connector is for the rest, e.g. seasonal forcing or the effect of a coinfection. The one most used is ss.modify(), which changes a named quantity while a condition holds, e.g. a seasonal ss.modify(sir.rel_trans, multiply=ss.seasonal(amplitude=0.2, peak='January')).
Used with: agents and compartments; declared and coded modules
Classes
| Name | Description |
|---|---|
| Connector | Base class for connectors, which link diseases (or other modules) to each other |
| Due | A modifier’s when= as a function of the timeline: true on the steps its schedule is due (see parse_when()), as ss.schedule() itself works them out, once for the sim |
| Target | A reference to a quantity of a module that is not a parameter, e.g. sir.rel_sus; used as the target of ss.modify() |
| modify | Change a named quantity while a condition holds: scale it, shift it, or set it |
Connector
connectors.Connector(name=None, label=None, **kwargs)Base class for connectors, which link diseases (or other modules) to each other
A connector can do anything, so it has no fixed structure: its step() says what it does.
Due
connectors.Due(schedule, sim)A modifier’s when= as a function of the timeline: true on the steps its schedule is due (see parse_when()), as ss.schedule() itself works them out, once for the sim
Target
connectors.Target(module, kind, name=None)A reference to a quantity of a module that is not a parameter, e.g. sir.rel_sus; used as the target of ss.modify()
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| module | Module / str |
the module the quantity belongs to, or its name | required |
| kind | str |
the kind of quantity, e.g. 'rel_sus', 'transition', or 'branch' |
required |
| name | str |
the name of the quantity, if the kind has several | None |
modify
connectors.modify(
target,
multiply=None,
add=None,
set=None,
efficacy=None,
frac=None,
network=None,
who=None,
when=None,
until=None,
start=None,
stop=None,
name=None,
**kwargs,
)Change a named quantity while a condition holds: scale it, shift it, or set it
A modifier never overwrites the quantity it changes: the base value is kept, and the value used is (set value if any, else base) × multipliers + addends over the modifiers that are currently active. It can be passed to a sim in connectors (interactions that are part of the model) or interventions (policy), or be a member of a declared disease, as part of its natural history.
The target can be any of these (for a modifier that is a member of a disease, its own, e.g. beta, recovery, or 'rel_trans'):
- a parameter, e.g.
sir.pars.beta, including a parameter of any module written as code - a transition, e.g.
sir.recovery, which scales its rate (or its probability, if it is a branch withp=) sir.rel_susorsir.rel_trans, each person’s susceptibility or infectiousness- the contacts of a network, as the network itself (e.g.
ss.contacts('Kenya', layer='school'), matched by its name) or as a path ('school.pars.n_contacts') - the path of any of these as a string, e.g.
'sir.pars.beta'
For a transition with several destinations (e.g. ss.Transition(Tx, {R: 0.8, I: None}, rate=...)), the transition scales its rate (or its dwell time), and a destination ('tb.treatment.R') scales the probability of that destination. With an outcome for each strain carried (per_strain='each', with the probability of the first destination), that is the probability for the whole set of strains a person carries (e.g. that all are cured: Π c_k), not each strain’s.
Note: CRN safe.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| target | par / str |
the quantity to change (see above) | required |
| multiply | float |
multiply the quantity by this; like add and set, it can also be ss.by(...), a function of the sim, or an ss.timeseries, for changes at given times |
None |
| add | float |
add this to the quantity: a parameter, or an inflow (a transition from ss.outside, e.g. CFUs shed into a reservoir), to which it adds an amount per unit time from outside the model |
None |
| set | float |
replace the quantity with this (a parameter only) | None |
| efficacy | float |
for protection such as a condom or a needle program, the fraction of the risk it removes: the same as multiply=1 - efficacy (give one of them) |
None |
| frac | float |
with multiply= or efficacy=, the fraction of acts (or, on a route without acts, of contacts) it acts on: the hazard is multiplied by 1 − frac·(1 − multiply), exactly, since hazards add; a number, an ss.timeseries, an ss.by, or (with network=, as agents) a function of the network returning one value per edge (e.g. lambda net: net.edges.condoms) |
None |
| network | str / list |
for a modifier of a transmission, the network or networks (by name, as in the sim’s networks=, including mixing pools) it applies on; by default, every one. On a parameter (e.g. tb.pars.beta), it acts on every transmission the parameter scales (e.g. an infection and its reinfections). A disease written as code has no transmission to name, so on a network the target is its rel_sus (of the people who would be infected over it) |
None |
| who | Cond / str / func |
who it applies to: a condition (e.g. sir.vaccinated, or a state by its path, who='sir.I', as ss.ref('sir.I')), a condition on a count (e.g. sir.I.n > 1000, worked out at each step; in compartments, as part of the rates where it can be, so the run stays in the kernels), or a function of the sim (default: everyone); with network=, the people who would be infected |
None |
| start | date / float / dur |
when it starts to apply (default: the start of the sim): a date, a bare number (a calendar year if the sim has a calendar, else a time in its unit from 0), or a duration, that long after the start of the sim; the same as when=ss.schedule(start=...) |
None |
| stop | date / float / dur |
when it stops applying, likewise (default: never; a stop after the end of the sim never stops it) | None |
| when | schedule / func |
when it applies, on time alone, which can be read before the run: ss.schedule(start=..., stop=...) for a window (a stop after the end of the sim never stops it; a bare number is a calendar year if the sim has a calendar, else a time in its unit from 0), ss.schedule() with times or how often for those steps only, or a function of the date of each step, true on the steps it applies (e.g. lambda now: now.month in [6, 7, 8], in a sim with a calendar); a condition on anything else goes in who= |
None |
| until | Cond / func / dur |
how long it stays on once who= (or when=) selects someone: until a condition holds, as for a lockdown that starts at one level of cases and ends at a lower one (with who=; while both hold, who= wins); or a duration, at least this long, as a rule that is lifted only after a while (a bare number is in the sim’s unit) |
None |
| name | str |
the name of the modifier | None |
Examples
S, I, R = ss.states(3)
sir = ss.Disease(infection=S >> I @ 0.3*I, recovery=ss.Transition(I, R, dur=10), init_prev=0.01)
masks = ss.modify(sir.pars.beta, multiply=0.6, start=30)
sim = ss.Sim(sir, interventions=masks, stop=100).run()
# Schools closed from day 200 to day 260 (in a sim with a calendar, that long after its start)
closure = ss.modify(sir.pars.beta, multiply=0.7, start=ss.days(200), stop=ss.days(260))
# Changes at given times, each holding until the next; before the first, the quantity has its own value
lockdown = ss.modify(sir.pars.beta, multiply=ss.timeseries({30: 0.5, 60: 1.0}), start=30)
# Each place on its own: contacts cut by 40% for at least 14 days once a week's cases pass 200 per 100,000
npi = ss.modify(sir.pars.beta, multiply=0.6, who=sir.infection.incidence(over=ss.days(7), by='patch') > 200e-5, until=ss.days(14))
# A lockdown that starts when 2% of people are infectious and ends when 1% are
lockdown = ss.modify(sir.pars.beta, multiply=0.5, who=sir.I.frac >= 0.02, until=sir.I.frac <= 0.01)
# Condoms, 90% effective against gonorrhea, used in a fraction of acts that grows over time, on one network
use = ss.timeseries({2000: 0.05, 2020: 0.3})
condoms = ss.modify(ng.infection, efficacy=0.9, frac=use, network='sexualnet')
# A member of a disease, with the rest of its natural history
class Measles(ss.Disease):
S, I, R = ss.states('S I R')
infection = S >> I @ 0.5*I
recovery = I >> R @ ss.days(10)
fading = ss.modify('rel_trans', multiply=0.5**(I.time_in/ss.days(3)), who=I)
# Summer only, every year
summer = ss.modify(sir.pars.beta, multiply=1.2, when=lambda now: now.month in [6, 7, 8])Attributes
| Name | Description |
|---|---|
| active | Whether the modifier applies on the current step |
| constant_amount | Whether the amount and frac are the same at every step: numbers, or expressions of parameters with plain values |
| fixed_amount | Whether the amount depends at most on time: not a function of the sim, nor an expression that reads states (e.g. 1 - 0.7*b.R.frac), which is worked out at each step |
| holding | Whether it stays on for people after who= selects them: for a while, or until a condition holds (until=) |
| per_edge | Whether its fraction is a function of the network, one value per edge |
| tabulated | Whether the engines can work out the modifier for every step before a run (see ss.solvers.Compartmental.tables()): one that depends only on time, and on who it applies to only through states and stratifications |
| target | The target, as a path |
| timed | Whether the modifier depends only on time (not on who, or on a function of the sim), so can be worked out for every step before a run |
Methods
| Name | Description |
|---|---|
| apply | Apply the modifier to a value, for the people it applies to |
| current | The amount on this step: a scalar, or one value per agent or group; with frac=, the multiplier on the hazard, 1 − frac·(1 − multiply); given a mask, a clock is read only for the agents in it |
| describe | One line saying what the modifier does |
| finalize_results | Parameters are back to their base values after the run |
| frac_of | The fraction of acts (or contacts) it acts on this step: a scalar, or one value per agent or group; or, given a network, a function of it gives one per edge |
| held_mask | With until=: who the modifier applies to, including those it still holds for |
| init_post | A parameter of a module written as code with no engine of its own (an ss.Module or ss.Connector), whose code reads it from self.pars: written there at the start of each step, as the engine of a disease written as code does |
| init_pre | Find the quantity this modifies, by name if it was given as a string, and register the modifier with the sim |
| marks | When the modifier acts, for marking on plots (see sim.plot()): its start and stop on the results’ time axis, if it has either and no steps within them (when=); else none |
| nan_sources | What in the amount can have no value for some people, and how to give it one |
| own | Become a member of a declared module, whose states, parameters, and transitions have their names now |
| start_step | Write the modified parameters of a module written as code, if any |
| step | Nothing: a modifier acts when the quantity it changes is read (see combine()) |
apply
connectors.modify.apply(value, engine)Apply the modifier to a value, for the people it applies to
current
connectors.modify.current(engine, frac=True, mask=None)The amount on this step: a scalar, or one value per agent or group; with frac=, the multiplier on the hazard, 1 − frac·(1 − multiply); given a mask, a clock is read only for the agents in it
describe
connectors.modify.describe()One line saying what the modifier does
finalize_results
connectors.modify.finalize_results()Parameters are back to their base values after the run
frac_of
connectors.modify.frac_of(engine, net=None)The fraction of acts (or contacts) it acts on this step: a scalar, or one value per agent or group; or, given a network, a function of it gives one per edge
held_mask
connectors.modify.held_mask(mask, engine)With until=: who the modifier applies to, including those it still holds for
init_post
connectors.modify.init_post()A parameter of a module written as code with no engine of its own (an ss.Module or ss.Connector), whose code reads it from self.pars: written there at the start of each step, as the engine of a disease written as code does
init_pre
connectors.modify.init_pre(sim)Find the quantity this modifies, by name if it was given as a string, and register the modifier with the sim
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| sim | Sim | the sim being initialized | required |
marks
connectors.modify.marks()When the modifier acts, for marking on plots (see sim.plot()): its start and stop on the results’ time axis, if it has either and no steps within them (when=); else none
nan_sources
connectors.modify.nan_sources()What in the amount can have no value for some people, and how to give it one
own
connectors.modify.own(module, name)Become a member of a declared module, whose states, parameters, and transitions have their names now
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| module | Module | the module | required |
| name | str |
the name of the modifier in it | required |
start_step
connectors.modify.start_step()Write the modified parameters of a module written as code, if any
step
connectors.modify.step()Nothing: a modifier acts when the quantity it changes is read (see combine())
Functions
| Name | Description |
|---|---|
| check_applied | Raise a ValueError for any modifier or efficacy of a product that nothing in the sim would apply, naming it and why |
| combine | Apply the modifiers that are active on a quantity: set, then multiply, then add |
| contact_mult | The multiplier on the contacts of a network from the modifiers of its n_contacts, for an engine (see Engine.contact_mult()) |
| describe_mod | What a modifier is, for a message: ss.modify(), a product’s efficacy, or another’s (e.g. a strain’s immunity) |
| parse_target | Work out what a modifier changes: returns the module (or its name), the kind of quantity, and its name |
| parse_when | The when= of a modifier, as (start, stop, schedule): a window, and an ss.schedule() of the steps within it it applies on, if not all; |
| reached | Whether a time on the timeline is at or after another, allowing for rounding: the timeline keeps its times to 9 decimals, so e.g. ss.months(7) is a hair after step 7 of monthly steps |
| route_mult | The multiplier on a transmission over one route to the people who would be infected (trg), 1 if there are none: from the modifiers |
| scaled_transmissions | The transmissions (by name) whose rates a parameter scales, for a modifier of it on a route (ss.modify(sir.pars.beta, network=...)): those |
| to_steps | The number of steps in a duration (a bare number is in the sim’s unit), at least one |
| to_ti | The first step that starts at or after a time, which must be within the sim |
| to_time | Convert a time (a date, a number, or a duration) to the same kind of time as the sim’s timeline |
| unapplied | Why a declared module (its graph and name) would not apply the modifiers (mods) on one of its quantities (kind and name, as keyed in sim.modifiers), or None if it would |
check_applied
connectors.check_applied(sim)Raise a ValueError for any modifier or efficacy of a product that nothing in the sim would apply, naming it and why
Called once by sim.init(), after every module has registered its modifiers in sim.modifiers (by module, kind of quantity, and name). For each quantity modified, the module it belongs to says whether it reads it: a declared module from its graph (unapplied()), and a module written as code from what its own code reads (ss.solvers.Effects.unapplied_par(), and for a disease ss.solvers.Undeclared.unapplied()). A network’s contacts are read by every transmission over it. It costs one pass over the quantities modified.
combine
connectors.combine(mods, value, engine)Apply the modifiers that are active on a quantity: set, then multiply, then add
contact_mult
connectors.contact_mult(engine, name, mods)The multiplier on the contacts of a network from the modifiers of its n_contacts, for an engine (see Engine.contact_mult())
describe_mod
connectors.describe_mod(m)What a modifier is, for a message: ss.modify(), a product’s efficacy, or another’s (e.g. a strain’s immunity)
parse_target
connectors.parse_target(target)Work out what a modifier changes: returns the module (or its name), the kind of quantity, and its name
parse_when
connectors.parse_when(when, start=None, stop=None)The when= of a modifier, as (start, stop, schedule): a window, and an ss.schedule() of the steps within it it applies on, if not all; a function of the date is the schedule of the steps it is true on. start= and stop= of ss.modify() are the window, as in ss.schedule()
reached
connectors.reached(now, time)Whether a time on the timeline is at or after another, allowing for rounding: the timeline keeps its times to 9 decimals, so e.g. ss.months(7) is a hair after step 7 of monthly steps
route_mult
connectors.route_mult(engine, name, route, inds, trg)The multiplier on a transmission over one route to the people who would be infected (trg), 1 if there are none: from the modifiers of the route’s contacts (n_contacts), and from those given the network (ss.modify(..., network=)), each 1 − frac·(1 − multiply) for the people it applies to (who=, of the people who would be infected)
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| engine | ss.solvers.Transmission |
the engine of the disease, as agents | required |
| name | str |
the name the modifiers on the route are under: a declared disease’s transmission, or rel_sus for a disease written as code |
required |
| route | Route |
the route | required |
| inds | array |
the edges of a network (Ellipsis for all), for a fraction given per edge; None for a route with no edges | required |
| trg | array |
the people who would be infected, by UID (or a slice of every agent) | required |
scaled_transmissions
connectors.scaled_transmissions(graph, par, what)The transmissions (by name) whose rates a parameter scales, for a modifier of it on a route (ss.modify(sir.pars.beta, network=...)): those in which it is a factor of the rate from every source of infection (e.g. beta in beta*(k*A + S)), so that scaling the transmission on the route is scaling the parameter
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| graph | ss.graph.Graph |
the graph of the disease | required |
| par | ss.par |
the parameter | required |
| what | str |
the modifier, for the errors | required |
to_steps
connectors.to_steps(sim, dur)The number of steps in a duration (a bare number is in the sim’s unit), at least one
to_ti
connectors.to_ti(sim, val, name='time', after=False)The first step that starts at or after a time, which must be within the sim
Step ti runs from time point ti to the next (see ss.loop.Loop), so a time at the start of the sim is step 0.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| sim | Sim | the sim | required |
| val | date / dur / float |
the time; see to_time() |
required |
| name | str |
what the time is, for the error message (e.g. ‘the start of “masks”’) | 'time' |
| after | bool |
whether a time after the end is allowed (e.g. for a stop, meaning it never stops) | False |
to_time
connectors.to_time(sim, val)Convert a time (a date, a number, or a duration) to the same kind of time as the sim’s timeline
How a bare number is read is decided once, from the sim’s start (see ss.timeline.calendar_of()): if the sim has a calendar (its start is a date or a year), a number is a calendar year (2005 is the start of 2005, 2005.5 the middle of it); otherwise, it is a time in the sim’s unit from 0. A duration is that long after the sim’s start if the sim has a calendar (ss.days(30) is 30 days in), and with none a point on the sim’s own axis, counted from 0 as sim.t.tvec is (ss.days(30) is day 30, the same when the start is 0); see ss.Timeline.to_time().
unapplied
connectors.unapplied(graph, modname, kind, name, mods)Why a declared module (its graph and name) would not apply the modifiers (mods) on one of its quantities (kind and name, as keyed in sim.modifiers), or None if it would