networks
Networks and mixing pools: who meets whom, and so who can infect whom
A network holds the contacts (edges) between people, over which a disease spreads. Mixing pools (ss.MixingPool, ss.networks.MixingPools) and contact matrices (ss.contacts()) spread it between groups instead, without edges. These exist so that a model’s contacts are separate from its diseases, and one network can carry several diseases. In a compartmental run, a network is replaced by what it implies: contact rates, a matrix, or a model of partner change.
Used with: agents and compartments; declared and coded modules
Classes
| Name | Description |
|---|---|
| BreastfeedingNet | A network from each mother to her infant while she breastfeeds, for transmission by breastfeeding |
| DegreeNet | Partnerships that form and dissolve, with a target number of partners at a time |
| DynamicNetwork | A network where partnerships update dynamically |
| MixingPool | Mixing between two groups of people (or everyone), without edges: used in place of a network |
| MixingPools | Mixing between groups of people, by a matrix of contacts between them: a mixing pool (ss.MixingPool) for each pair of groups, without edges |
| Network | A network of contacts (edges) between people, over which diseases spread, and the methods |
| PostnatalNet | A network from each mother to her infant after birth, for transmission after birth (e.g. by close contact) |
| PrenatalNet | A network from each pregnant woman to her unborn child, for transmission in pregnancy |
| RandomNet | Random connectivity between agents |
| Route | A way a disease spreads between people: the base class of networks and mixing pools |
| SexualNet | Sexual partnerships that form and dissolve, between two groups of people: by default, men and women |
BreastfeedingNet
networks.BreastfeedingNet(pars=None, **kwargs)A network from each mother to her infant while she breastfeeds, for transmission by breastfeeding
The sim needs an ss.Pregnancy, which adds an edge at birth; it ends when the mother stops breastfeeding (preg.lactating).
Note: CRN safe.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| pars | dict |
passed to ss.PostnatalNet |
None |
Methods
| Name | Description |
|---|---|
| end_pairs | Remove edges for mothers who have stopped breastfeeding (preg.lactating) |
end_pairs
networks.BreastfeedingNet.end_pairs()Remove edges for mothers who have stopped breastfeeding (preg.lactating)
DegreeNet
networks.DegreeNet(
pars=None,
n_contacts=_,
dur=_,
concurrency=_,
max_contacts=_,
match=_,
acts=_,
who=_,
between=_,
memory=_,
**kwargs,
)Partnerships that form and dissolve, with a target number of partners at a time
The number of partners people have at any moment (n_contacts) is a target for the whole population, not a fixed number for each person: partnerships end at random, and people look for new ones at a rate that depends on how many they already have, chosen so that the distribution of the number of partners stays at the target. Everyone moves through it over time, as in EpiModel’s networks fitted to target statistics: this covers the same ground as a dyad-independent exponential random graph model (ERGM), with the terms for edges, degree, and mixing, but not terms such as triangles. The network starts at the target. In an open population, partnerships that end at a constant rate (dur a mean) end less often by as many as are ended by a partner leaving (dying), as EpiModel’s d.rate correction does, so they last as long as given on average; with any other distribution of durations, people look for partners more (or less) often, by a factor that keeps the mean number of partners at the target. Partnerships are not only sexual: people who share needles have partners too. For sexual partnerships, with an age of debut, see ss.SexualNet.
Each partnership can be between one person of each of two groups (between), e.g. a man and a woman, so a disease’s beta on it can differ by direction; each group can then have its own number of partners (n_contacts as a dict). With no target (the default), people who have no partner are paired on every step, so nobody has more than one at a time.
A subclass that overrides step() forms its own partnerships: the base starts none for it, and has none of the parameters for forming them, only between.
Note: not CRN safe when people are born or die.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| n_contacts | float/ss.Dist/ss.by/dict |
if given, a target for the number of partners a person has at a time: a number, which is the mean of a Poisson (whose shape concurrency and max_contacts can change), or a distribution over whole numbers (e.g. ss.constant(1), for exactly one); or a number for each level of a property of people (ss.by(risk=[0.4, 1.6])), as EpiModel’s nodefactor; or one for each group of between, e.g. dict(fsw=3, client=0.5) (each partnership has one of each, so these should give the two groups about as many partners in all). By default, no target: at most one partner, pairing people who have none on every step |
_ |
| dur | float/ss.Duration/ss.Dist |
how long partnerships last: a mean (they end at a constant rate), or a distribution; a bare number is in the sim’s unit (default: 10) | _ |
| acts | rate/ss.Dist |
if given, the number of acts per unit time in each partnership: a frequency such as ss.freqperweek(2) (or a rate, ss.perweek(2)), or a distribution of them drawn once for each partnership; a disease’s beta is then per act (see ss.peract) |
_ |
| who | Cond / str / func |
if given, who takes part: a condition over people (e.g. ss.person.pwid), as for ss.Event(); nobody else is in the network |
_ |
| between | str / tuple |
if given, who partnerships are between: 'mf', a man and a woman, so its directions are m2f and f2m and a disease’s beta on it is ss.peract(m2f=..., f2m=...), or one value for both; 'mm', two men, with one beta; or a pair of groups, each the name of a boolean property of people or of a level of a stratification, e.g. ('fsw', 'client'), whose directions are fsw2client and client2fsw (nobody can be in both) |
_ |
| concurrency | float |
if given, the fraction of people with two or more partners at once; the distribution of n_contacts is adjusted to give it, keeping its mean |
_ |
| max_contacts | int |
if given, nobody has more partners than this at once | _ |
| match | dict |
if given, the fraction of partnerships between people at the same level of a property of people (a stratification, or an attribute such as 'female'), e.g. dict(risk=0.8), as EpiModel’s nodematch; or of each of several, e.g. dict(risk=0.8, region=0.6) |
_ |
| memory | dur |
how long it remembers partnerships that have ended, for contact tracing (see ss.DynamicNetwork) |
_ |
| pars | dict |
the same parameters, as a dict | None |
The realized network is recorded in the results (mean_degree, concurrency, and match, or with several properties match_<name> for each), and net.check() compares it with the targets.
Examples
# EpiModel's introductory network: 175 partnerships among 500 people, 110 people with two or more partners, nobody with four or more, and partnerships lasting 50 steps
net = ss.DegreeNet(n_contacts=0.7, concurrency=0.22, max_contacts=3, dur=50)
# Most partnerships within a risk group, and over half within a region
net = ss.DegreeNet(n_contacts=0.7, dur=50, match=dict(risk=0.8, region=0.6))
# And four times as many partners in the high-risk group (with sim.stratify(risk=['low', 'high']))
net = ss.DegreeNet(n_contacts=ss.by(risk=[0.4, 1.6]), dur=50, match=dict(risk=0.8))
# Needle sharing among people who inject drugs (a boolean property of people), with 20 injections a month in each partnership
needles = ss.DegreeNet(name='needles', who=ss.person.pwid, n_contacts=2, dur=ss.months(6), acts=ss.freqpermonth(20))
# Sex work, between female sex workers and their clients (boolean properties of people)
sw = ss.DegreeNet(name='sw', between=('fsw', 'client'), n_contacts=dict(fsw=5, client=0.5), dur=ss.months(1), acts=ss.freqperweek(1))Attributes
| Name | Description |
|---|---|
| forms_own | Whether a subclass forms its own partnerships (it overrides step()), so the base forms none for it |
| targeted | Whether the number of partners people have at a time is a target (n_contacts), rather than one at most |
Methods
| Name | Description |
|---|---|
| active | The agents who can have partners, as a boolean array: alive, and with who=, those it gives, and with between, those in one of its groups |
| add_pairs | Pair the people looking for a partner: those who look on this step, given how many partners they have and the target, or with no target, everyone who has none |
| adjust | With durations not at a constant rate, in an open population partners leave and newcomers start with none, so people look |
| agent_reasons | Lasting partnerships are approximate in compartments, so solver='auto' keeps agents |
| check | Run the sim again, and compare the network it makes with the network’s targets |
| degree_dist | The target fraction of people with 0, 1, 2, … partners at a time |
| degrees | The number of partners each person has |
| end_times | How many steps each new partnership lasts, or at the start, what is left of each partnership under way |
| init_post | Start at the target: give each person a number of partners, and pair them at random (with match=, some within their level) |
| init_pre | Check the parameters, and work out the targets: the distribution of the number of partners, and how often people look for one |
| init_results | With a target, the partnerships formed and the realized number of partners; with match=, the fraction at the same level |
| init_seek | The probability that someone in each row of the targets with each number of partners looks for another on a step, given how many steps partnerships last on average (for each row) |
| level_names | The name of each level of a property of people, for the rows of check(): a stratification’s labels (e.g. low, high), or else the levels’ numbers |
| levels | The level of every person in a property of people (by default, the one match= names), as an array indexed by UID |
| match_pairs | Pair people who are looking for partners, who are in a random order: in turn, one of each group of between |
| pair | Add partnerships between two lists of people, leaving out pairs who are already partners (with a target; without, only people with none look), with their durations and acts |
| record_events | The partnerships formed in the step, and the network of the step (made in its step, so recorded after it) |
| remove_uids | Remove the partnerships of people who leave, counting them (see end_times()) |
| rows | The row of each person in the targets: their level, with n_contacts by level, or their group, with n_contacts by group of between (else 0 for all) |
| seek_prob | The probability that each person looks for a partner on this step, given how many they have |
| seek_probs | The probability that someone with each number of partners looks for another on a step, for a target distribution, given how many steps partnerships last on average |
| set_between | The groups of between (or None), and from them the sides of the network |
| set_network_states | The states of people for the network, set on each step before pairing: none here (see ss.SexualNet) |
| side_raw | The group of between each person is in (-1 for neither), indexed by UID: by sex, men 0 and women 1 |
| step | End partnerships, set the states of people (e.g. debut), and form new partnerships |
active
networks.DegreeNet.active(people)The agents who can have partners, as a boolean array: alive, and with who=, those it gives, and with between, those in one of its groups
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| people | People | the people | required |
add_pairs
networks.DegreeNet.add_pairs()Pair the people looking for a partner: those who look on this step, given how many partners they have and the target, or with no target, everyone who has none
adjust
networks.DegreeNet.adjust(uids)With durations not at a constant rate, in an open population partners leave and newcomers start with none, so people look more (or less) often, by as much as keeps the mean number of partners at the target (a factor adjusted by a fifth of the gap each step, on a log scale), in each row of the targets
agent_reasons
networks.DegreeNet.agent_reasons(sim)Lasting partnerships are approximate in compartments, so solver='auto' keeps agents
check
networks.DegreeNet.check(n_runs=3, output=False)Run the sim again, and compare the network it makes with the network’s targets
Reports, for each target (the mean number of partners, the fraction with two or more, the maximum, and with match=, the fraction of partnerships at the same level), the target, the mean over the second half of each run, and the spread across runs; a target that is missed by more than the spread is a warning. With a number of partners for each level (n_contacts=ss.by(...)), each level has a row of its own (e.g. mean_degree[high]), and these and the rows for everyone are at the end of each run, with the targets for everyone those of the mix of levels then (which changes if, say, more die at one level).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| n_runs | int |
the number of runs | 3 |
| output | bool |
only return the table, without printing it | False |
Returns
| Name | Type | Description |
|---|---|---|
| The table, as a dataframe |
degree_dist
networks.DegreeNet.degree_dist(n_contacts, concurrency=None, max_contacts=None)The target fraction of people with 0, 1, 2, … partners at a time
degrees
networks.DegreeNet.degrees(uids)The number of partners each person has
end_times
networks.DegreeNet.end_times(p1, start=False)How many steps each new partnership lasts, or at the start, what is left of each partnership under way
init_post
networks.DegreeNet.init_post(add_pairs=True)Start at the target: give each person a number of partners, and pair them at random (with match=, some within their level)
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| add_pairs | bool |
whether to add partnerships | True |
init_pre
networks.DegreeNet.init_pre(sim)Check the parameters, and work out the targets: the distribution of the number of partners, and how often people look for one
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| sim | Sim | the sim being initialized | required |
init_results
networks.DegreeNet.init_results()With a target, the partnerships formed and the realized number of partners; with match=, the fraction at the same level
init_seek
networks.DegreeNet.init_seek(mean_steps)The probability that someone in each row of the targets with each number of partners looks for another on a step, given how many steps partnerships last on average (for each row)
level_names
networks.DegreeNet.level_names(name)The name of each level of a property of people, for the rows of check(): a stratification’s labels (e.g. low, high), or else the levels’ numbers
levels
networks.DegreeNet.levels(name=None)The level of every person in a property of people (by default, the one match= names), as an array indexed by UID
match_pairs
networks.DegreeNet.match_pairs(people, rvs=None)Pair people who are looking for partners, who are in a random order: in turn, one of each group of between (or two of one), or, with match=, some within their own level, so that the fraction of partnerships at the same level is the target
With several properties, each person looks within their own level of each with its own chance, as with one, and is paired with someone who looks within the same levels of the same properties. This gives each target when the properties are independent of each other; net.check() says how close it is otherwise.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| people | uids | the people, once for each partner they look for | required |
| rvs | array |
a random number for each, for each property (properties by people), which says whether they look within their level (by default, drawn for each person) | None |
Returns the two lists of partners, and anyone left over.
pair
networks.DegreeNet.pair(p1, p2, start=False)Add partnerships between two lists of people, leaving out pairs who are already partners (with a target; without, only people with none look), with their durations and acts
record_events
networks.DegreeNet.record_events()The partnerships formed in the step, and the network of the step (made in its step, so recorded after it)
remove_uids
networks.DegreeNet.remove_uids(uids)Remove the partnerships of people who leave, counting them (see end_times())
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| uids | array |
the agents who leave | required |
rows
networks.DegreeNet.rows(uids)The row of each person in the targets: their level, with n_contacts by level, or their group, with n_contacts by group of between (else 0 for all)
seek_prob
networks.DegreeNet.seek_prob(uids)The probability that each person looks for a partner on this step, given how many they have
seek_probs
networks.DegreeNet.seek_probs(dist, mean_dur)The probability that someone with each number of partners looks for another on a step, for a target distribution, given how many steps partnerships last on average
set_between
networks.DegreeNet.set_between()The groups of between (or None), and from them the sides of the network
set_network_states
networks.DegreeNet.set_network_states()The states of people for the network, set on each step before pairing: none here (see ss.SexualNet)
side_raw
networks.DegreeNet.side_raw()The group of between each person is in (-1 for neither), indexed by UID: by sex, men 0 and women 1
step
networks.DegreeNet.step()End partnerships, set the states of people (e.g. debut), and form new partnerships
DynamicNetwork
networks.DynamicNetwork(pars=None, edges=None, memory=None, **kwargs)A network where partnerships update dynamically
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| memory | dur |
how long it remembers partnerships that have ended (default: not at all); find_contacts(), and so contact tracing (trace= of a delivery), then also reaches the partners of the last memory, e.g. memory=ss.months(12) for partner notification of the past year’s partners |
None |
| pars | dict |
passed to ss.Network |
None |
| edges | dict / dataframe |
passed to ss.Network |
None |
Methods
| Name | Description |
|---|---|
| end_pairs | Remove partnerships whose duration has expired or whose members have died |
| find_contacts | Find all contacts of the specified people (see ss.Network.find_contacts()), including, with memory=, their partners of the last memory |
| remember | With memory=: keep the partnerships that end now, and forget those that ended longer ago than it |
| step | Remove expired partnerships and add new ones |
end_pairs
networks.DynamicNetwork.end_pairs()Remove partnerships whose duration has expired or whose members have died
find_contacts
networks.DynamicNetwork.find_contacts(inds, as_array=True)Find all contacts of the specified people (see ss.Network.find_contacts()), including, with memory=, their partners of the last memory
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| inds | array |
the people | required |
| as_array | bool |
if True, return a sorted array rather than a set | True |
remember
networks.DynamicNetwork.remember(p1, p2)With memory=: keep the partnerships that end now, and forget those that ended longer ago than it
step
networks.DynamicNetwork.step()Remove expired partnerships and add new ones
MixingPool
networks.MixingPool(pars=None, source=_, target=_, n_contacts=_, **kwargs)Mixing between two groups of people (or everyone), without edges: used in place of a network
Each person in the target group is exposed to the average infectiousness of the source group, times their number of contacts. With no edges, it suits mixing in which who meets whom does not matter person by person. For mixing between several groups by a contact matrix, see ss.contacts().
Every communicable disease transmits over it; a disease’s beta on it is the disease’s, by route (0 for one that does not spread here).
Note: CRN safe as agents.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| source | Cond / uids / func |
the infectious side: a condition over people (e.g. ss.person.age < 15), UIDs, or a function of the sim; None for everyone alive |
_ |
| target | Cond / uids / func |
the susceptible side; as above | _ |
| n_contacts | float/ss.Dist |
the number of contacts of each target agent per unit of the sim’s time (or a rate, e.g. ss.perday(10)): a number is the same for everyone, and a distribution is drawn for each agent (e.g. ss.poisson(4)) |
_ |
Examples
import starsim as ss
# Set the parameters
mp_pars = dict(
source = ss.person.male, # Only men are infectious
target = None, # Everyone is susceptible
n_contacts = ss.poisson(mean=4),
)
# Seed 5% of the male population
def p_init(sim, uids):
return 0.05*sim.people.male[uids]
# Create and run the sim
sis = ss.SIS(init_prev=p_init)
mp = ss.MixingPool(**mp_pars)
sim = ss.Sim(diseases=sis, networks=mp)
sim.run()
sim.plot()Methods
| Name | Description |
|---|---|
| agent_reasons | A pool of some people has no compartmental form yet, and a number of contacts that differs between people is approximated |
| compute_transmission | The people infected over the pool in a step |
| init_post | Find the diseases that transmit over it, and check its contacts |
| init_pre | A number of contacts given as a rate (e.g. ss.perday(10)) is a number per unit of the sim’s time, as a bare number is |
| remove_uids | Remove people who leave (e.g. die) from source and target, if these are given as UIDs |
| shrink | Remove the people in each group, to save memory (e.g. before saving the sim) |
| step | Update the UIDs of the source and target |
agent_reasons
networks.MixingPool.agent_reasons(sim)A pool of some people has no compartmental form yet, and a number of contacts that differs between people is approximated
compute_transmission
networks.MixingPool.compute_transmission(
rel_sus,
rel_trans,
disease_beta,
disease,
)The people infected over the pool in a step
This is called by transmission (ss.Disease.transmit(), declared, or as code from ss.AgentDisease) together with network transmission.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| rel_sus | float |
Relative susceptibility | required |
| rel_trans | float |
Relative infectiousness | required |
| disease_beta | float |
The beta value for the disease, as the probability in the step | required |
| disease | Disease | The disease | required |
Returns
| Name | Type | Description |
|---|---|---|
| UIDs of agents who acquired the disease at this step |
init_post
networks.MixingPool.init_post()Find the diseases that transmit over it, and check its contacts
init_pre
networks.MixingPool.init_pre(sim)A number of contacts given as a rate (e.g. ss.perday(10)) is a number per unit of the sim’s time, as a bare number is
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| sim | Sim | the sim being initialized | required |
remove_uids
networks.MixingPool.remove_uids(uids)Remove people who leave (e.g. die) from source and target, if these are given as UIDs
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| uids | array |
the agents to remove | required |
shrink
networks.MixingPool.shrink()Remove the people in each group, to save memory (e.g. before saving the sim)
step
networks.MixingPool.step()Update the UIDs of the source and target
MixingPools
networks.MixingPools(pars=None, source=_, target=_, n_contacts=_, **kwargs)Mixing between groups of people, by a matrix of contacts between them: a mixing pool (ss.MixingPool) for each pair of groups, without edges
Usually made by ss.contacts(), from a contact matrix. With no edges, mixing pools suit mixing in which who meets whom matters only by group (e.g. age group), not person by person.
Every communicable disease transmits over them; a disease’s beta on them is the disease’s, by route (0 for one that does not spread here).
Note: CRN safe as agents.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| source | dict |
the infectious groups, by name: each a condition over people (e.g. ss.person.age < 15), UIDs, or a function of the sim |
_ |
| target | dict |
the susceptible groups; as above | _ |
| n_contacts | array/ss.Dist |
n_contacts[i,j], the number of contacts that one person in target group i has with people in source group j (rows are the people who have the contacts, as in a contact survey and ss.contacts()). A matrix of numbers is the same for everyone in each group. One distribution whose parameters are matrices (e.g. ss.poisson(M), or ss.nbinom(mean=M, n=2) for more spread) is drawn for each person when they enter a target group, and kept while they are in it. A matrix of distributions (one for each entry) is also drawn for each person, but is slower (a mixing pool for each pair of groups) |
_ |
| pars | dict |
the same parameters, as a dict | None |
Examples
import starsim as ss
mps = ss.networks.MixingPools(
source = {'0-15': ss.person.age < 15, '15+': ss.person.age >= 15},
target = {'0-15': ss.person.age < 15, '15+': ss.person.age >= 15},
n_contacts = [[2.4, 0.91], [0.49, 0.16]], # Illustrative contact matrix: row i, the contacts of a person in target group i
)
sim = ss.Sim(diseases='sis', networks=mps).run()
sim.plot()
# Contacts that differ between people: a Poisson draw for each person, with the matrix as its mean
mps = ss.networks.MixingPools(source=mps.pars.source, target=mps.pars.target, n_contacts=ss.poisson([[2.4, 0.91], [0.49, 0.16]]))Transmission to a susceptible person s is 1 − (1 − p)^(rel_sus_s · Σ_j c_sj · trans_j), for the disease’s chance p over the step, s’s contacts c_sj with source group j, and that group’s mean infectiousness trans_j. This is exact for a force of infection proportional to the contacts and the mean infectiousness. For discrete contacts with random members of a group, it is a mean-field approximation: it uses the group’s mean infectiousness rather than that of the people met, which slightly overstates infection (by a relative p(1 − f)/2 when a fraction f of the group is infectious). It is exact when everyone in the group is equally infectious, and close when p is small.
Methods
| Name | Description |
|---|---|
| agent_reasons | A number of contacts that differs between people is approximated by its mean in a compartmental run |
| band_exposure | As agents, for a matrix with its own age bands (ss.contacts('Kenya')), with no pool for each pair of bands: the |
| check_full | For one matrix over several stratifications together (ss.contacts(matrix, dims=[...])): check its shape against the levels of each |
| compute_transmission | Calculate transmission, for a disease written as code: through each pool, or for a matrix, from the fraction of each group or band |
| draw_contacts | With one distribution for the contacts: draw them for each person who has entered a target group since the last step, from its row of the matrix (none outside every target group) |
| exposure | As agents, with no pool for each pair of groups: see band_exposure() and group_exposure() |
| group_exposure | As agents, for a matrix of numbers: the UIDs of the people alive, and the exposure of each, as the pools for each pair of |
| init_post | Initialize each mixing pool, and find the diseases that use them |
| init_pre | Create a mixing pool for each pair of groups, unless the contacts between groups can be used directly |
| mean_matrix | The mean number of contacts, [target, source], per unit of the sim’s time (unit), as a compartmental run uses them |
| plain_contacts | The contacts as an array of numbers, if each is the same for everyone in its group; else None |
| remove_uids | Remove UIDs from each mixing pool |
| shrink | Remove the people in each group, to save memory (e.g. before saving the sim) |
| step | Find the people in each group, once per step, and give them to each mixing pool (rather than each pool finding its own) |
| to_unit | Put the contacts in the sim’s unit, once: a matrix counted over a unit of its own (ss.contacts('Kenya'), contacts a day) is scaled to contacts per unit of the sim’s time |
| validate_pars | Check that source and target are dicts of groups, and n_contacts has a row for each target group (who has the contacts) and a column for each source group |
agent_reasons
networks.MixingPools.agent_reasons(sim)A number of contacts that differs between people is approximated by its mean in a compartmental run
band_exposure
networks.MixingPools.band_exposure(weights)As agents, for a matrix with its own age bands (ss.contacts('Kenya')), with no pool for each pair of bands: the UIDs of the people alive, and the exposure of each, from the infectiousness-weighted fraction of each band (weights, one for each agent) and the contacts between bands, as those pools would give, since everyone in a band has the same contacts. People younger than the first band (e.g. not yet born) have none
check_full
networks.MixingPools.check_full(sim)For one matrix over several stratifications together (ss.contacts(matrix, dims=[...])): check its shape against the levels of each
compute_transmission
networks.MixingPools.compute_transmission(
rel_sus,
rel_trans,
disease_beta,
disease,
)Calculate transmission, for a disease written as code: through each pool, or for a matrix, from the fraction of each group or band that is infectious, as a declared disease’s is (see ss.solvers.Agents.transmit()), since everyone in a group has the same contacts
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| rel_sus | Arr | Relative susceptibility | required |
| rel_trans | Arr | Relative infectiousness | required |
| disease_beta | float |
The beta value for the disease, as the probability in the step | required |
| disease | Disease | The disease | required |
draw_contacts
networks.MixingPools.draw_contacts()With one distribution for the contacts: draw them for each person who has entered a target group since the last step, from its row of the matrix (none outside every target group)
exposure
networks.MixingPools.exposure(weights)As agents, with no pool for each pair of groups: see band_exposure() and group_exposure()
group_exposure
networks.MixingPools.group_exposure(weights)As agents, for a matrix of numbers: the UIDs of the people alive, and the exposure of each, as the pools for each pair of groups would give, from the infectiousness-weighted fraction of each source group (weights, one for each agent)
init_post
networks.MixingPools.init_post()Initialize each mixing pool, and find the diseases that use them
init_pre
networks.MixingPools.init_pre(sim)Create a mixing pool for each pair of groups, unless the contacts between groups can be used directly
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| sim | Sim | the sim being initialized | required |
mean_matrix
networks.MixingPools.mean_matrix(unit)The mean number of contacts, [target, source], per unit of the sim’s time (unit), as a compartmental run uses them
plain_contacts
networks.MixingPools.plain_contacts()The contacts as an array of numbers, if each is the same for everyone in its group; else None
remove_uids
networks.MixingPools.remove_uids(uids)Remove UIDs from each mixing pool
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| uids | array |
the agents to remove | required |
shrink
networks.MixingPools.shrink()Remove the people in each group, to save memory (e.g. before saving the sim)
step
networks.MixingPools.step()Find the people in each group, once per step, and give them to each mixing pool (rather than each pool finding its own)
to_unit
networks.MixingPools.to_unit(sim)Put the contacts in the sim’s unit, once: a matrix counted over a unit of its own (ss.contacts('Kenya'), contacts a day) is scaled to contacts per unit of the sim’s time
validate_pars
networks.MixingPools.validate_pars()Check that source and target are dicts of groups, and n_contacts has a row for each target group (who has the contacts) and a column for each source group
Network
networks.Network(pars=None, edges=None, **kwargs)A network of contacts (edges) between people, over which diseases spread, and the methods for changing them. This base class has fixed edges, given by hand; the built-in networks, which make their own and change them over time, are its subclasses, e.g. ss.RandomNet, ss.DegreeNet, ss.SexualNet, and ss.PrenatalNet. See ss.Disease for how transmission uses the edges.
Each edge has the two people it joins (p1 and p2) and a weight; subclasses add other columns, e.g. how long it lasts (dur) or the acts in it (acts).
Note: CRN safe, with the edges given.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| pars | dict |
parameters, for subclasses that define them | None |
| edges | dict / dataframe |
the edges of the network, as an alternative to passing them as keyword arguments | None |
| p1 | array |
an array of length N, the number of connections in the network, with the indices of people on one side of the connection. | |
| p2 | array |
an array of length N, the number of connections in the network, with the indices of people on the other side of the connection. | |
| weight | array |
the weight of each edge, which scales the hazard of transmission over it (1 if not given; see net_beta()); a disease’s own beta on the network is on the disease |
|
| one_way | bool |
whether it transmits only from p1 to p2, as mother-to-child networks do (by default, both ways) | |
| kwargs | dict |
standard module arguments (e.g. name, label, dt) are passed to update_pars(); other keys are copied directly into the network as columns of the edges |
All the columns of the edges must be arrays of the same length when the sim is initialized, though not all need be given when the network is made.
Examples
# Generate an average of 10 contacts for 1000 people
n_contacts_pp = 10
n_people = 1000
n = n_contacts_pp * n_people
p1 = np.random.randint(n_people, size=n)
p2 = np.random.randint(n_people, size=n)
weight = np.ones(n)
network = ss.Network(p1=p1, p2=p2, weight=weight, label='rand')
network = ss.Network(edges=dict(p1=p1, p2=p2, weight=weight), label='rand') # Alternate method
# Convert one network to another with extra columns
index = np.arange(n)
self_conn = p1 == p2
network2 = ss.Network(edges=network.edges, index=index, self_conn=self_conn, label=network.label)Attributes
| Name | Description |
|---|---|
| directions | The directions it can transmit in, <side of the infectious partner>2<side of the susceptible one>, |
| has_acts | Whether the network has acts in each partnership (edges.acts), so that a disease’s beta on it is a probability per act (see ss.peract) |
| members | The UIDs of everyone in at least one edge, sorted |
| p1 | The first half of a network edge (person 1) |
| p2 | The second half of a network edge (person 2) |
| weight | The weight of each edge, which scales the hazard of transmission over it (see net_beta()) |
Methods
| Name | Description |
|---|---|
| add_pairs | Add new edges: nothing in this base class, whose edges are given; subclasses say how pairs are formed |
| agent_reasons | An individual-level network that has an approximation; one with none is refused by the layout of the mixing (see ss.solvers.Mixing.init()) |
| append | Add edges to the network |
| drop_edges | Remove the k edges at the indices gone (in order; all edges if k is the number of edges), as ss.solvers.backends.passes.ending() gives them; not for the user |
| edge_directions | The direction of each edge from src to trg, as a position in directions (0 for a network with no sides); raises a ValueError for an edge with no direction |
| edge_index | The edges of each agent (see ss.solvers.backends.passes.edge_index()), so that transmission from a few people, or contact tracing, |
| eligible | The agents who can form edges, as a boolean array: alive, born, and (optionally) those a condition holds for |
| ends | The sources and targets of every edge, in direction d: p1 to p2 (0) or p2 to p1 (1) |
| find_contacts | Find all contacts of the given people, e.g. for contact tracing |
| from_df | Set the edges from a dataframe or a dict of arrays |
| get_inds | The edges at the given indices, as a dict of arrays by column |
| init_post | Add the first edges |
| init_pre | Initialize with the sim, initialize the edges, and validate p1 and p2 |
| init_results | Store network length by default |
| mean_contacts | For a compartmental run: the mean number of contacts per person per unit of the sim’s time, which homogeneous mixing approximates |
| meta_keys | The names of the columns of the edges |
| net_beta | The probability that each edge transmits in a step, given the disease’s probability (in the step, or on a network with acts, per act), for people with rel_trans and rel_sus of 1 |
| net_hazard | The hazard of each edge in a step, −log(1 − net_beta()), which rel_trans, rel_sus, and the modifiers on the route then scale (see |
| plot | Draw the network (or some of its edges), using NetworkX |
| pop_inds | Remove the edges at the given indices, and return them as a dict, as network.append() takes them |
| record_events | Store the number of edges in the network of the step (made in its step, so recorded after it) |
| remove_edges | Remove the specified edges from the network |
| remove_uids | Remove the edges of the given people, e.g. when they die (called by People.remove_dead()) |
| set_network_states | Set the states of people that the network depends on (nothing by default) |
| shrink | Remove the edges, to save memory (e.g. before saving the sim) |
| side_raw | The side of each agent, as a position in sides (-1 for neither), indexed by UID |
| sides_str | The sides, for messages and describe(), e.g. ‘sides m and f (m: male, f: female)’ |
| to_df | The edges, as a dataframe |
| to_dict | The edges, as a dict of arrays by column |
| to_edgelist | The edges, as a list of (p1, p2) pairs |
| to_graph | The network as a NetworkX DiGraph, e.g. to draw it or to use NetworkX’s measures of networks |
| transmit | The edges that transmit in a step, in each direction the network transmits in (p1 to p2, then p2 to p1 unless it is one way) |
| validate | Check the columns of the edges: convert any of the wrong type, and raise an error for any of the wrong length |
| validate_uids | Make p1 and p2 UID arrays, if they are not |
add_pairs
networks.Network.add_pairs()Add new edges: nothing in this base class, whose edges are given; subclasses say how pairs are formed
agent_reasons
networks.Network.agent_reasons(sim)An individual-level network that has an approximation; one with none is refused by the layout of the mixing (see ss.solvers.Mixing.init())
append
networks.Network.append(edges=None, **kwargs)Add edges to the network
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| edges | dict |
a dictionary of arrays with keys p1, p2, and optionally weight (1 if not given), as returned from network.pop_inds() | None |
| kwargs | dict |
the arrays of edges, by key |
drop_edges
networks.Network.drop_edges(k, gone)Remove the k edges at the indices gone (in order; all edges if k is the number of edges), as ss.solvers.backends.passes.ending() gives them; not for the user
edge_directions
networks.Network.edge_directions(src, trg)The direction of each edge from src to trg, as a position in directions (0 for a network with no sides); raises a ValueError for an edge with no direction
edge_index
networks.Network.edge_index(now=False)The edges of each agent (see ss.solvers.backends.passes.edge_index()), so that transmission from a few people, or contact tracing, visits only their edges: None until the edges have stayed the same from one step to the next, so a network whose edges change every step never makes one, unless now (for agents taken by events, which visit only the edges of the people infectious however often the edges change). The edges change when they are assigned again (e.g. edges.p1 = ...), as every method that changes them does.
eligible
networks.Network.eligible(who=None)The agents who can form edges, as a boolean array: alive, born, and (optionally) those a condition holds for
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| who | Cond / str / func |
if given, only these people: a condition over people (e.g. (ss.person.age >= 18) & (ss.person.age < 65)), as for ss.DegreeNet |
None |
ends
networks.Network.ends(d)The sources and targets of every edge, in direction d: p1 to p2 (0) or p2 to p1 (1)
find_contacts
networks.Network.find_contacts(inds, as_array=True)Find all contacts of the given people, e.g. for contact tracing
Both ends of each edge are checked, and each contact is returned once, however many edges join them to the people given. So it is not for uses where several edges count for more than one, e.g. exposure risk.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| inds | array |
indices of people whose edges to return | required |
| as_array | bool |
if true, return as sorted array (otherwise, return as unsorted set) | True |
Returns
| Name | Type | Description |
|---|---|---|
| contact_inds | array / set |
the indices of the pairing partners |
Example: If there were a network with - p1 = [1,2,3,4] - p2 = [2,3,1,4] Then find_contacts([1,3]) would return the array [1,2,3] (or the set {1,2,3}, with as_array=False)
from_df
networks.Network.from_df(df, keys=None)Set the edges from a dataframe or a dict of arrays
Replaces the existing edges, and returns the network, so it can also be used to create a new network. If weight is not supplied, it defaults to 1.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| df | dataframe / dict |
the edges, with columns p1, p2, and (optionally) weight |
required |
| keys | list |
the columns to use (default: the network’s meta keys) | None |
Examples
net = ss.Network(name='mynet').from_df(dict(p1=[0,1,2], p2=[1,2,3])) # Create a new network
net.from_df(df) # Replace the edges of an existing networkget_inds
networks.Network.get_inds(inds, remove=False)The edges at the given indices, as a dict of arrays by column
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| inds | (int, array, slice) |
the indices of the edges | required |
| remove | bool |
whether to also remove them from the network | False |
init_post
networks.Network.init_post(add_pairs=True)Add the first edges
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| add_pairs | bool |
whether to add them | True |
init_pre
networks.Network.init_pre(sim)Initialize with the sim, initialize the edges, and validate p1 and p2
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| sim | Sim | the sim being initialized | required |
init_results
networks.Network.init_results()Store network length by default
mean_contacts
networks.Network.mean_contacts(sim)For a compartmental run: the mean number of contacts per person per unit of the sim’s time, which homogeneous mixing approximates this network by, and where it comes from; or None, and why, if it has no such approximation (the default). A network of partnerships has none: its contacts last, so their number per unit time is not its number of partners (it uses the partner-change model instead: see ss.solvers.Mixing.partnerships()).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| sim | Sim | the sim being initialized | required |
meta_keys
networks.Network.meta_keys()The names of the columns of the edges
net_beta
networks.Network.net_beta(disease_beta=None, inds=None, disease=None)The probability that each edge transmits in a step, given the disease’s probability (in the step, or on a network with acts, per act), for people with rel_trans and rel_sus of 1
The edge’s weight, and on a network with acts its number of acts in the step, scale the hazard: the probability is 1 − (1 − beta)^(weight·acts). The rel_trans and rel_sus of its two people, and the modifiers on the route, then scale the hazard this gives (see Network.transmit()). A network written as code can override this, for a disease written as code; a declared disease works out a network with acts from its hazard per act (see ss.modify() for what changes it on one route).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| disease_beta | float |
the disease’s probability | None |
| inds | array |
the edges (default: all of them) | None |
| disease | Disease | the disease | None |
net_hazard
networks.Network.net_hazard(disease_beta=None, inds=None, disease=None)The hazard of each edge in a step, −log(1 − net_beta()), which rel_trans, rel_sus, and the modifiers on the route then scale (see ss.Network.transmit()): worked out directly, with the same arguments, unless a subclass overrides net_beta()
plot
networks.Network.plot(max_edges=500, random=False, alpha=0.2, **kwargs)Draw the network (or some of its edges), using NetworkX
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| max_edges | int |
the maximum number of edges to show | 500 |
| random | bool |
if true, select edges randomly; otherwise, show the first N | False |
| alpha | float |
the alpha value of the edges | 0.2 |
| kwargs | dict |
figure and style arguments (e.g. figsize, font) are passed to ss.plot_args(), the rest to nx.draw_networkx() |
pop_inds
networks.Network.pop_inds(inds)Remove the edges at the given indices, and return them as a dict, as network.append() takes them
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| inds | (int, array, slice) |
the indices of the edges to remove | required |
record_events
networks.Network.record_events()Store the number of edges in the network of the step (made in its step, so recorded after it)
remove_edges
networks.Network.remove_edges(inds)Remove the specified edges from the network
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| inds | array |
the indices of the edges to remove, or a boolean mask the same length as the network | required |
Example
net = sim.networks.randomnet
net.remove_edges(net.edges.p1 == 0) # Remove all edges with agent 0 as p1remove_uids
networks.Network.remove_uids(uids)Remove the edges of the given people, e.g. when they die (called by People.remove_dead())
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| uids | array |
the agents to remove | required |
set_network_states
networks.Network.set_network_states(people)Set the states of people that the network depends on (nothing by default)
Some depend on other properties of people, e.g. the age of debut on a sexual network varies by sex and over time, so they are set again whenever people are added.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| people | People | the people | required |
shrink
networks.Network.shrink()Remove the edges, to save memory (e.g. before saving the sim)
side_raw
networks.Network.side_raw()The side of each agent, as a position in sides (-1 for neither), indexed by UID
sides_str
networks.Network.sides_str()The sides, for messages and describe(), e.g. ‘sides m and f (m: male, f: female)’
to_df
networks.Network.to_df()The edges, as a dataframe
to_dict
networks.Network.to_dict()The edges, as a dict of arrays by column
to_edgelist
networks.Network.to_edgelist()The edges, as a list of (p1, p2) pairs
to_graph
networks.Network.to_graph(max_edges=None, random=False)The network as a NetworkX DiGraph, e.g. to draw it or to use NetworkX’s measures of networks
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| max_edges | int |
the maximum number of edges to show | None |
| random | bool |
if true, select edges randomly; otherwise, show the first N | False |
Examples
import networkx as nx
sim = ss.Sim(n_agents=100, networks='random').init()
G = sim.networks.randomnet.to_graph()
nx.draw(G)transmit
networks.Network.transmit(rel_trans, rel_sus, edge_hazard, rng, prob=None)The edges that transmit in a step, in each direction the network transmits in (p1 to p2, then p2 to p1 unless it is one way)
An edge from source to target transmits with probability 1 − exp(−rel_trans[source]·rel_sus[target]·hazard), or prob() if it is given: each factor scales the hazard over the step, so a contact infects at most once in it, wherever a model puts an effect. Random numbers are drawn only for the edges that can transmit: with CRN, each edge’s number depends only on its source and target, so this gives the same result as drawing one for every edge. The beta is worked out only for the edges from someone infectious to someone susceptible, which are found for both directions in one pass over the edges, or, once the edges have stayed the same for a step (edge_index()), from the edges of the infectious (or the susceptible) alone, if they have few; either way they are in the order of the edges.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| rel_trans | array |
the transmissibility of each agent, by UID (0 if not infectious) | required |
| rel_sus | array |
the susceptibility of each agent, by UID (0 if not susceptible) | required |
| edge_hazard | func |
of the direction (0 or 1), and the indices, sources, and targets of the edges from someone with rel_trans not 0 to someone with rel_sus not 0, the hazard of each of them in the step (an array, or a number for all): with a function as prob, only where it is 0 matters (no transmission); None to skip the direction (no random numbers are drawn) |
required |
| rng | ss.multi_random |
the random numbers, by source and target | required |
| prob | func |
of the direction, and the indices, sources, and targets of the edges that can transmit, the probability of each, in place of the one above | None |
Returns
| Name | Type | Description |
|---|---|---|
| A list of the (source UIDs, target UIDs) that transmit, for each direction not skipped |
validate
networks.Network.validate()Check the columns of the edges: convert any of the wrong type, and raise an error for any of the wrong length
validate_uids
networks.Network.validate_uids()Make p1 and p2 UID arrays, if they are not
PostnatalNet
networks.PostnatalNet(pars=None, dur=None, **kwargs)A network from each mother to her infant after birth, for transmission after birth (e.g. by close contact)
ss.Pregnancy (which the sim needs) adds an edge from the mother to each infant at birth, and it lasts for dur, so several of these networks can have postnatal periods of their own, e.g. one for each route of transmission. A disease transmits on it if its beta on it is above 0.
The duration is drawn by the mother’s UID, so it is the same for all her infants (a subclass can change this). It transmits one way only, from the mother (p1) to the child (p2), so its one direction is mother2child.
Note: CRN safe.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| pars | dict |
passed to ss.DynamicNetwork |
None |
| dur | ss.Duration / ss.Dist |
edge duration (postpartum period); if None, edges persist until agents die | None |
| **kwargs | dict |
merged with pars |
Methods
| Name | Description |
|---|---|
| add_pairs | Add an edge from each mother to her infant |
| init_pre | Find the sim’s ss.Pregnancy, which makes the edges |
add_pairs
networks.PostnatalNet.add_pairs(mother_uids=None, infant_uids=None)Add an edge from each mother to her infant
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| mother_uids | array |
the mothers, with repeats if a mother has several infants | None |
| infant_uids | array |
the infants | None |
init_pre
networks.PostnatalNet.init_pre(sim)Find the sim’s ss.Pregnancy, which makes the edges
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| sim | Sim | the sim being initialized | required |
PrenatalNet
networks.PrenatalNet(pars=None, edges=None, **kwargs)A network from each pregnant woman to her unborn child, for transmission in pregnancy
Its edges change over time, but ss.Pregnancy adds and removes them as pregnancies start and end (with the unborn as agents), so it is not a DynamicNetwork: it does not update itself. It transmits one way only, from the mother (p1) to the child (p2), so its one direction is mother2child.
Note: CRN safe.
Methods
| Name | Description |
|---|---|
| add_pairs | Add an edge from each mother to her unborn child; called by ss.Pregnancy |
| init_pre | Check that the sim has the ss.Pregnancy that makes the edges |
| step | Nothing: ss.Pregnancy adds and removes the edges |
add_pairs
networks.PrenatalNet.add_pairs(mother_uids=None, unborn_uids=None)Add an edge from each mother to her unborn child; called by ss.Pregnancy
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| mother_uids | array |
the mothers | None |
| unborn_uids | array |
the unborn children | None |
init_pre
networks.PrenatalNet.init_pre(sim)Check that the sim has the ss.Pregnancy that makes the edges
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| sim | Sim | the sim being initialized | required |
step
networks.PrenatalNet.step()Nothing: ss.Pregnancy adds and removes the edges
RandomNet
networks.RandomNet(
pars=None,
n_contacts=_,
dur=_,
who=_,
dynamic=_,
method=_,
remove_self_loops=_,
memory=_,
**kwargs,
)Random connectivity between agents
Each agent has a number of contacts (n_contacts), and the other end of each is drawn from the other agents’ contacts (method='fast', the default), so an agent’s chance of being picked is proportional to its own number of contacts. This keeps the mixing structure of risk-stratified networks (a small high-risk core with many contacts forms most of its edges with itself).
method says how the other end of each contact is drawn:
'fast'(the default): by sampling the contacts with replacement. The source side of each edge is exactlyn_contacts, while the target side is Poisson-distributed with meann_contacts, so agents have somewhat more variable numbers of contacts than with'exact'; about 1.8x faster than'exact'.'exact': by shuffling the contacts (sampling without replacement), so each agent has exactlyn_contactscontacts, if it is even: each agent starts half of its contacts and is the other end of as many, so an odd number is rounded at random to the even numbers either side for each agent (5 gives 4 or 6, 5 on average).'uniform': uniformly from all eligible agents, regardless of their ownn_contacts, as in Covasim v3’s random networks. This gives less variable numbers of contacts (e.g. withn_contacts=ss.poisson(20), a variance of about 15, rather than 30 for'fast'or 20 for'exact'), but loses the mixing structure above.'safe': by giving each contact a random place on a line, from the agent’s own random numbers, and joining it to its nearest neighbor there. Adding an agent then changes only the contacts next to theirs, which suits scenario comparisons, but it is slower, and does not yet work within places.
Note: not CRN safe when people are born or die, unless dynamic=False: with any of these methods but 'safe', one death rewires almost every edge.
By default (dur=0), each contact lasts one step of the network: with dynamic=True (the default), the contacts are drawn again on every step, and with dynamic=False, once. With dur= above 0, each contact lasts that long, and the ones that end are replaced by new ones drawn at random. For partnerships formed toward a target number of partners, see ss.DegreeNet.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| n_contacts | float/ss.Dist |
the number of (bidirectional) contacts of each agent: a number is the same for everyone (rounded at random, if it is not whole, so it is the mean), and a distribution gives its spread (e.g. ss.poisson(10)) |
_ |
| dur | float/ss.Duration/ss.Dist |
how long each contact lasts: a duration (the same for every contact), or a distribution; a bare number is in the sim’s unit (default: 0, one step) | _ |
| who | Cond / str / func |
if given, who takes part: a condition over people (e.g. (ss.person.age >= 18) & (ss.person.age < 65)), as for ss.DegreeNet; nobody else is in the network |
_ |
| dynamic | bool |
if True (default), contacts are replaced once they end (with dur=0, every step); if False, they are drawn once and never change (so agents who join later, e.g. newborns, are not added) |
_ |
| memory | dur |
how long it remembers contacts that have ended, for contact tracing (see ss.DynamicNetwork) |
_ |
| method | str |
how the other end of each contact is drawn: ‘fast’ (the default), ‘exact’, ‘uniform’, or ‘safe’ (see above) | _ |
| remove_self_loops | bool |
if True, drop the few edges that join an agent to itself (as any of the methods can make; e.g. about 1 in 25 edges with 50 agents, and far fewer in larger populations), so agents have slightly fewer contacts; off by default since such edges transmit nothing anyway. The same pair of agents can still be joined by more than one edge. | _ |
| pars | dict |
the same parameters, as a dict | None |
Note: n_contacts = 10 will create 5 edges per agent. Since disease transmission usually occurs bidirectionally, this means that the effective number of contacts per agent is actually 10. Consider 3 agents with 3 edges between them (a triangle): each agent is connected to 2 other agents.
Example
# Static network with Poisson-distributed contacts among adults, as in Covasim v3
net = ss.RandomNet(n_contacts=ss.poisson(20), who=(ss.person.age >= 18) & (ss.person.age < 65), dynamic=False, method='uniform')Attributes
| Name | Description |
|---|---|
| on_sim_time | Whether the network steps with the sim (worked out once) |
| one_step | With dur=0 (the default), and stepping with the sim, every edge ends at the next step, before anything transmits over it, so the dead’s edges need not be removed before then (see ss.networks.Route.one_step) |
Methods
| Name | Description |
|---|---|
| add_pairs | Add the new edges needed to bring each agent up to their number of contacts |
| draw_places | The place of each contact, from the fraction of time spent in each place (a sparse array, by rows) by the home of each, and a uniform number for each: the first place where the cumulative fraction reaches it |
| edge_steps | How many steps each new edge lasts after this one, from dur=: drawn for each if a distribution; a bare number is in the sim’s unit |
| get_edges | Find new edges, by method= |
| mean_contacts | Its mean number of contacts (see ss.Network.mean_contacts()) |
| nearest_edges | Find edges by giving each contact a random place on a line, and joining it to its nearest neighbor there (method='safe') |
| pair | Find edges (see get_edges()); in a model with places, within each place, as a compartmental run takes it to be |
| pair_within | Find edges among the contacts (stubs) in each of n_places places, the place of each given by place |
| same_contacts | The number of new contacts for each of n agents when it is the same whole number for everyone, so that nothing need be drawn: when |
| shuffle_edges | Find edges by shuffling the contacts, so each agent is at each end of exactly as many as given (method='exact'), so has twice that many |
| step | If dynamic, replace expired edges; otherwise, do nothing |
add_pairs
networks.RandomNet.add_pairs()Add the new edges needed to bring each agent up to their number of contacts
draw_places
networks.RandomNet.draw_places(frac, home, u)The place of each contact, from the fraction of time spent in each place (a sparse array, by rows) by the home of each, and a uniform number for each: the first place where the cumulative fraction reaches it
edge_steps
networks.RandomNet.edge_steps(p1)How many steps each new edge lasts after this one, from dur=: drawn for each if a distribution; a bare number is in the sim’s unit
get_edges
networks.RandomNet.get_edges(inds, n_contacts)Find new edges, by method=
Each person is at both ends of edges: a person given 5 contacts appears 5 times in source and, on average, 5 times in target, so has 10 in all. So n_contacts here is HALF the contacts a person is expected to have, when both are used (as for social contacts).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| inds | list / array |
person indices | required |
| n_contacts | list / array |
the same length as inds with the number of bidirectional contacts to assign to each person |
required |
Returns
| Name | Type | Description |
|---|---|---|
| Two arrays, for source and target |
mean_contacts
networks.RandomNet.mean_contacts(sim)Its mean number of contacts (see ss.Network.mean_contacts())
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| sim | Sim | the sim being initialized | required |
nearest_edges
networks.RandomNet.nearest_edges(inds, n_contacts)Find edges by giving each contact a random place on a line, and joining it to its nearest neighbor there (method='safe')
pair
networks.RandomNet.pair(inds, n_contacts)Find edges (see get_edges()); in a model with places, within each place, as a compartmental run takes it to be
With commuting or visiting between places, each of a person’s contacts is in a place drawn from the fraction of their time they spend in each.
pair_within
networks.RandomNet.pair_within(stubs, place, n_places, inds, n_contacts)Find edges among the contacts (stubs) in each of n_places places, the place of each given by place
same_contacts
networks.RandomNet.same_contacts(n, current)The number of new contacts for each of n agents when it is the same whole number for everyone, so that nothing need be drawn: when n_contacts is a whole number, and the edges needed (with current edges) give each a whole number (as every step with dur=0); else None. Only with common random numbers, since without them a draw not made would change the draws after it.
shuffle_edges
networks.RandomNet.shuffle_edges(inds, n_contacts)Find edges by shuffling the contacts, so each agent is at each end of exactly as many as given (method='exact'), so has twice that many
step
networks.RandomNet.step()If dynamic, replace expired edges; otherwise, do nothing
Route
networks.Route(name=None, label=None, **kwargs)A way a disease spreads between people: the base class of networks and mixing pools
A disease’s beta can be given for each route, by its name (e.g. ss.SIS(beta=dict(randomnet=0.05))).
Methods
| Name | Description |
|---|---|
| agent_reasons | A route works on groups, unless it says otherwise |
| compute_transmission | The people infected over this route in a step, for a disease written as code; a route other than a network (e.g. a mixing pool) defines it |
agent_reasons
networks.Route.agent_reasons(sim)A route works on groups, unless it says otherwise
compute_transmission
networks.Route.compute_transmission(
rel_sus,
rel_trans,
disease_beta,
disease=None,
)The people infected over this route in a step, for a disease written as code; a route other than a network (e.g. a mixing pool) defines it
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| rel_sus | array |
the relative susceptibility of each agent | required |
| rel_trans | array |
the relative transmissibility of each agent | required |
| disease_beta | float |
the disease’s transmission probability | required |
| disease | Disease | the disease | None |
SexualNet
networks.SexualNet(
pars=None,
between=_,
dur=_,
debut=_,
acts=_,
participation=_,
n_contacts=_,
concurrency=_,
max_contacts=_,
match=_,
**kwargs,
)Sexual partnerships that form and dissolve, between two groups of people: by default, men and women
A ss.DegreeNet between men and women (between='mf'), in which people take part after their debut (debut), if they do (participation, drawn at their debut). By default, people who have no partner are paired on every step, so nobody has more than one at a time. With n_contacts, the number of partners people have at a time is a target instead (among people who take part, after their debut), as for ss.DegreeNet; with partnerships much longer than people’s time in the network, it takes a few decades to settle.
Each partnership is between one person of each of two groups (between): a man and a woman, two men, or e.g. a female sex worker and a client, and each group can have its own number of partners (n_contacts as a dict). With match=, a fraction of partnerships is between people at the same level of a property of people, such as a risk group.
Each partnership has a number of acts per unit time (acts), so a disease’s beta on it is a probability per act (see ss.peract), and the fraction of them in which a condom is used (condoms, 0 unless set, e.g. by a subclass in add_pairs()), which a modifier of the transmission on the network reads, e.g. ss.modify(hiv.infection, efficacy=0.8, frac=lambda net: net.edges.condoms, network='sexualnet').
A subclass that overrides step() forms its own partnerships: the base starts none for it, and has none of the parameters for forming them, only between.
For several types of partnership with their own rules, see STIsim; for partnerships that are not sexual (e.g. needle sharing), ss.DegreeNet.
Note: not CRN safe when people are born or die.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| between | str / tuple |
who partnerships are between: 'mf' (the default), a man and a woman, so its directions are m2f and f2m and a disease’s beta on it is ss.peract(m2f=..., f2m=...), or one value for both; 'mm', two men (men who have sex with men), with one beta; or a pair of groups, each the name of a boolean property of people or of a level of a stratification, e.g. ('fsw', 'client'), whose directions are fsw2client and client2fsw (nobody can be in both) |
_ |
| dur | ss.Dist/ss.Duration/float |
how long partnerships last, as for ss.DegreeNet: a single duration, e.g. ss.months(6), is a mean, and partnerships end at a constant rate (an exponential); a bare number is in the sim’s unit; give a distribution for any other spread, e.g. ss.lognormal(mean=ss.months(6), std=ss.months(3)), which can vary by age, year, and individual pair (default: ss.lognormal(mean=ss.years(15), std=ss.years(1))) |
_ |
| debut | ss.Dist |
the age at which people start taking part (default: normal, mean 16); its parameters can be functions, e.g. to vary it by sex or over time | _ |
| acts | ss.freq/ss.Dist |
the number of acts per unit time in each partnership: a frequency (e.g. ss.freqperweek(2), or ss.freqperyear(80); a rate, ss.peryear(80), is the same), or a distribution of them drawn once for each partnership (default: lognormal, mean 80 a year, standard deviation 30) |
_ |
| participation | ss.Dist |
whether each person takes part, drawn at their debut (default: 90% do); its parameters can be functions, e.g. to vary it by age or sex | _ |
| n_contacts | float/ss.Dist/dict |
if given, a target for the number of partners people have at a time: a number, which is the mean of a Poisson, or a distribution over whole numbers; or one for each group of between, e.g. dict(fsw=3, client=0.5) (each partnership has one of each, so these should give the two groups about as many partners in all) |
_ |
| concurrency | float |
with n_contacts, the fraction of people with two or more partners at once |
_ |
| max_contacts | int |
with n_contacts, the most partners anyone has at once |
_ |
| match | dict |
if given, the fraction of partnerships between people at the same level of a property of people (a stratification, or an attribute), e.g. dict(risk=0.8) |
_ |
| kwargs | dict |
the other parameters of ss.DegreeNet (e.g. who, memory) |
|
| pars | dict |
the same parameters, as a dict | None |
Examples
net = ss.SexualNet() # Men and women, at most one partner at a time
msm = ss.SexualNet(name='msm', between='mm', participation=0.1, dur=ss.lognormal(mean=ss.years(2), std=ss.years(1)))
net = ss.SexualNet(n_contacts=ss.poisson(0.7), dur=ss.lognormal(mean=ss.years(2), std=ss.years(1)), concurrency=0.1, match=dict(risk=0.8))
sw = ss.SexualNet(name='sw', between=('fsw', 'client'), n_contacts=dict(fsw=5, client=0.5), dur=ss.months(1)) # With boolean attributes fsw and clientMethods
| Name | Description |
|---|---|
| active | The agents who can have partners, as a boolean array: alive, taking part, past their debut, and in a group of between |
| available | The UIDs of the agents of the given sex who can have partners and have none now |
| set_network_states | Draw the age of debut of anyone who has none (newborns, and people who arrive at any age), and whether each person takes part, when they reach their debut |
active
networks.SexualNet.active(people)The agents who can have partners, as a boolean array: alive, taking part, past their debut, and in a group of between
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| people | People | the people | required |
available
networks.SexualNet.available(people, sex)The UIDs of the agents of the given sex who can have partners and have none now
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| people | People | the people | required |
| sex | str |
‘male’ or ‘female’ | required |
set_network_states
networks.SexualNet.set_network_states()Draw the age of debut of anyone who has none (newborns, and people who arrive at any age), and whether each person takes part, when they reach their debut
Functions
| Name | Description |
|---|---|
| age_band | The ages a group of a mixing pool is for, as (low, high), if it is a condition on age alone: e.g. (5, 15) for |
| age_groups | The groups of a contact matrix by age, from the lower edge of each, e.g. {‘0-15’: …, ‘15+’: …} for [0, 15] |
| check_acts | acts= of lasting partnerships is acts per unit time: a frequency (e.g. ss.freqperweek(2), or a rate, ss.perweek(2); a probability per unit time is an error), or a distribution of them drawn once for each |
| check_contacts | The entries of a contact matrix are numbers of contacts, so are finite and not negative |
| check_drawn | Raise an error for a negative number of contacts drawn from a distribution, which would be a negative chance of infection |
| check_edges | The weight of each edge is weight; a column beta (Starsim 3’s name for it) is an error, since a disease’s beta is the disease’s |
| cond_str | A condition over people, for messages: e.g. ‘people.female’ |
| contacts | Mixing between groups from a contact matrix; returns ss.networks.MixingPools |
| dist_contacts | ss.contacts() for a distribution whose parameters are matrices by age, e.g. ss.contacts(ss.poisson(C), ages=[0, 15]), or tables by age |
| find_contacts | Find the partners of the specified agents in an edge list; see also Network.find_contacts(). |
| find_pregnancy | The sim’s ss.Pregnancy, which makes the edges of a prenatal or postnatal network; raises an error if it does not have exactly one |
| fisher_yates_shuffle | In-place Fisher-Yates shuffle using precomputed uniform random values. |
| nb_transmit | Optimized transmission kernel: returns the (source, target) UIDs of transmitting edges. |
| people_mask | Who a condition over people holds for, as a boolean per agent (indexed by UID) |
| pool_diseases | The diseases that transmit over a mixing pool or pools: every communicable disease (one whose beta on it is 0 transmits nothing there) |
| pool_uids | The people in a group of a mixing pool |
| route_beta | The value of a beta for each route (a dict by route name) on one route, or None if it has none |
| standardize_netkey | The name of a network, standardized, so that a beta by route matches it however it is written |
| transmits | Whether a disease is communicable: declared with a contact-mediated transition, or written as code with a beta |
age_band
networks.age_band(group)The ages a group of a mixing pool is for, as (low, high), if it is a condition on age alone: e.g. (5, 15) for (ss.person.age >= 5) & (ss.person.age < 15), and (65, inf) for ss.person.age >= 65; else None. A compartmental run reads these to regroup the contacts to the model’s age groups (see ss.solvers.Mixing.age_matrix())
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| group | any |
the group | required |
age_groups
networks.age_groups(ages)The groups of a contact matrix by age, from the lower edge of each, e.g. {‘0-15’: …, ‘15+’: …} for [0, 15]
check_acts
networks.check_acts(net, acts)acts= of lasting partnerships is acts per unit time: a frequency (e.g. ss.freqperweek(2), or a rate, ss.perweek(2); a probability per unit time is an error), or a distribution of them drawn once for each partnership; a bare number, or a count per step drawn once, would depend on the step, so is an error
check_contacts
networks.check_contacts(matrix, dim)The entries of a contact matrix are numbers of contacts, so are finite and not negative
check_drawn
networks.check_drawn(draws, name, dist)Raise an error for a negative number of contacts drawn from a distribution, which would be a negative chance of infection
check_edges
networks.check_edges(edges)The weight of each edge is weight; a column beta (Starsim 3’s name for it) is an error, since a disease’s beta is the disease’s
cond_str
networks.cond_str(cond)A condition over people, for messages: e.g. ‘people.female’
contacts
networks.contacts(
matrix=None,
ages=None,
dims=None,
name=None,
study=None,
layer='all',
unit=None,
**by_dim,
)Mixing between groups from a contact matrix; returns ss.networks.MixingPools
A matrix, and what it is over:
- a country’s, by name (
ss.contacts('Kenya')), fromstarsim_data, by 5-year age group; - its own age groups, given by their lower edges (
ss.contacts(matrix, ages=[0, 15, 65])), which need not be the model’s (it is regrouped to them); - the model’s own stratifications: its age groups (
ss.contacts(matrix)), another one, by name (ss.contacts(risk=matrix)), or several together (ss.contacts(matrix, dims=['sex', 'risk'])), for mixing that is not a product, e.g. men mix with women, and by risk differently for each sex; - nobody in particular: a number (
ss.contacts(10)) is homogeneous mixing, with that many contacts per person per unit time (anss.MixingPoolof everyone).
Mixing that is a product over stratifications takes a matrix for each, by name (ss.contacts(age=..., area=...)): the contacts between two groups are the product of the entries for each. The matrix by age can have its own age groups here too (ss.contacts(age=..., area=..., ages=[0, 15, 65])).
What the matrix multiplies is said by the rate of the infection, with I_j and N_j the number infectious and the number of people in group j, and N everyone:
beta*I: the fraction of each group who are infectious, β·Σ_j C_ij·I_j/N_j, with C the contacts of a person and β per contact (as for contact surveys);beta*I.n: their number, β·Σ_j C_ij·I_j, density-dependent (as in Lloyd & Jansen’s metapopulation);beta*I.frac(orbeta*I.n/N): their number over everyone, β·Σ_j C_ij·I_j/N (as in Keeling & Rohani’s matrices, and WAIFW matrices).- a weighted count,
beta*(ss.by(w)*I).norbeta*I.n_infectious: their number, each counted by its weight, β·Σ_j C_ij·w_j·I_j (forn_infectious, w_j is each group’s infectiousness).
So a textbook’s matrix goes in as the textbook gives it, with no rescaling. See the user guide’s page on population data for more.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| matrix | array/str/df/float/ss.Dist |
matrix[i,j], the number of contacts per unit time that one person in group i has with people in group j (as in a contact survey); or a distribution whose parameters are matrices by age (with ages=) or tables by age group, for contacts that differ between people (e.g. ss.contacts(ss.poisson(ss.data.contacts('Kenya'))): see ss.networks.MixingPools); or a country, by name or code (‘Kenya’, ‘KE’), or a US state (‘United States:Massachusetts’); or a table by age group, as ss.data.contacts() gives (with labels such as ‘0-4’ and ‘75+’, or an index named “age”; a table labeled otherwise is a matrix as it is); or a number, for homogeneous mixing |
None |
| ages | list / array |
the lower edge of each age group of the matrix (or of its matrix by age), e.g. [0, 15, 65] for 0–14, 15–64, and 65+ |
None |
| dims | list |
for one matrix over several of the model’s stratifications together: their names, and matrix[i, k, j, l] is the number of contacts per unit time that one person at levels (i, k) has with people at levels (j, l), or the same as a square matrix over the combinations of levels, the first stratification slowest |
None |
| name | str |
the name of the mixing (default: ‘contacts’, or the layer, e.g. ‘home’) | None |
| study | str |
for a country, the study: ‘prem’ (the default: Prem 2021, bundled, for 177 countries, with Prem 2017 for 10 more, e.g. Japan and Australia), ‘prem2021’ (177), ‘prem2017’ (152), or ‘mistry2021’ (36, by single year of age); for a US state, ‘mistry2021_us’ (the default; by single year of age); all but Prem 2021 are downloaded on first use | None |
| layer | str / list |
for a country, the setting: ‘all’ (the default), ‘home’, ‘school’, ‘work’, or ‘community’; or a list of them, for a list of layers, each named by its setting | 'all' |
| unit | str |
the unit of time the contacts are counted over, e.g. ‘day’ for contacts a day; by default ‘day’ for a country’s matrix (as the studies give them), and otherwise the sim’s unit, as for any bare number | None |
| by_dim | dict |
a matrix over each of the model’s stratifications, by name, e.g. risk=[[2, 1], [4, 8]]; with several, the contacts are the product of their entries |
Examples
S, I, R = ss.states(3)
sir = ss.Disease(infection=S >> I @ 0.05*I, recovery=ss.Transition(I, R, dur=10), init_prev=0.01)
mixing = ss.contacts([[10, 3], [3, 5]], ages=[0, 18])
sim = ss.Sim(sir, networks=mixing, unit='day', stop=100).run()
sir.stratify(risk=['low', 'high'])
mixing = ss.contacts(risk=[[2, 1], [1, 8]])
home, school = ss.contacts('Kenya', layer=['home', 'school']) # Contacts a day, by 5-year age group
sim = ss.Sim(sir, networks=[home, school], stratify=dict(age=[0, 5, 18, 65]), unit='day', stop=100)
# A product: by age, then 90% of contacts within one's own area
mixing = ss.contacts(age=[[10, 3], [3, 5]], area=[[0.9, 0.1], [0.1, 0.9]], ages=[0, 18])
# Keeling & Rohani's Program 3.1: a matrix of rates per year on fractions of everyone, so the rate is I.frac
S, I = ss.states('S I')
sis = ss.Disease(S >> I @ I.frac, I >> S @ ss.peryear(1), init_prev=0.001).stratify(risk={'high': 0.2, 'low': 0.8})
sim = ss.Sim(sis, networks=ss.contacts(risk=[[10, 0.1], [0.1, 1]]), unit='year', stop=15).run()
# Men mix with women, and the high-risk with each other more among men than among women: a matrix over sex and risk together
matrix = np.zeros((2, 2, 2, 2)) # [sex, risk, sex, risk], with sex ['female', 'male'] and risk ['low', 'high']
matrix[0, :, 1, :] = [[0.8, 0.2], [0.5, 1.5]] # Women's contacts with men
matrix[1, :, 0, :] = [[0.8, 0.5], [0.2, 2.0]] # Men's contacts with women
mixing = ss.contacts(matrix, dims=['sex', 'risk'])
# Contacts that differ between people: lognormal, around a country's mean contacts a day
C = ss.data.contacts('Kenya')
mixing = ss.contacts(ss.lognormal(mean=C, std=C*0.5))dist_contacts
networks.dist_contacts(dist, ages, name, unit)ss.contacts() for a distribution whose parameters are matrices by age, e.g. ss.contacts(ss.poisson(C), ages=[0, 15]), or tables by age group, as ss.data.contacts() gives them (whose age groups and unit, contacts a day, are then read from the table, as for the table itself). Each person draws their own contacts with each age group: see ss.networks.MixingPools
find_contacts
networks.find_contacts(p1, p2, inds, as_array=False)Find the partners of the specified agents in an edge list; see also Network.find_contacts().
By default, a set is returned so that custom tracing interventions can efficiently add extra people; with as_array=True, a sorted array of unique UIDs is returned instead, which is faster.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| p1 | array |
the first agent of each edge | required |
| p2 | array |
the second agent of each edge | required |
| inds | array |
the agents whose partners to find | required |
| as_array | bool |
if True, return a sorted array rather than a set | False |
find_pregnancy
networks.find_pregnancy(net)The sim’s ss.Pregnancy, which makes the edges of a prenatal or postnatal network; raises an error if it does not have exactly one
fisher_yates_shuffle
networks.fisher_yates_shuffle(arr, randvals)In-place Fisher-Yates shuffle using precomputed uniform random values.
Equivalent to np.random.permutation (a uniform random shuffle), but ~1.3x faster for the large arrays used in network generation. randvals should be uniform on [0, 1) and the same length as arr.
Note: randvals is consumed in order (randvals[k] for the k-th swap), not indexed by position. This means that changing the array length (e.g. adding one agent) shifts every swap, so the shuffle is not random-number safe – matching ss.RandomNet(method='exact')’s intended behavior (see ss.RandomNet(method='safe') for the CRN-safe version).
nb_transmit
networks.nb_transmit(src, trg, rel_trans, rel_sus, hazard, randvals)Optimized transmission kernel: returns the (source, target) UIDs of transmitting edges.
Edge i transmits with probability 1 − exp(−rel_trans[src]·rel_sus[trg]·hazard[i]): every factor scales the hazard in the step, so one contact infects at most once, whichever factor a model puts its effect in (see Network.transmit()). Fuses the gather/multiply/compare and the UID extraction into a single branchless pass. This avoids a full-length boolean temporary and two separate gather passes (trg[mask], src[mask]) – ~1.4x faster at typical transmission rates, more when transmission is common. UIDs are emitted in edge order, preserving CRN behavior.
people_mask
networks.people_mask(sim, cond)Who a condition over people holds for, as a boolean per agent (indexed by UID)
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| cond | Cond / str / func |
a condition (ss.person.pwid), the name of a level of a stratification or of a boolean property of people ('fsw'), or a function of the sim |
required |
pool_diseases
networks.pool_diseases(route)The diseases that transmit over a mixing pool or pools: every communicable disease (one whose beta on it is 0 transmits nothing there)
pool_uids
networks.pool_uids(sim, group)The people in a group of a mixing pool
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| sim | Sim | the sim | required |
| group | Cond / func / uids |
the people, as a condition over people (e.g. ss.person.age < 15), a function of the sim, or UIDs (default: all active agents) |
required |
route_beta
networks.route_beta(betas, route)The value of a beta for each route (a dict by route name) on one route, or None if it has none
The dict may give default, the value of every route it does not name, e.g. dict(sexualnet=ss.peract(0.01), msm=ss.peract(0.1), default=0).
standardize_netkey
networks.standardize_netkey(key)The name of a network, standardized, so that a beta by route matches it however it is written
A network’s name can be upper or lowercase, end in ‘net’ or not, and have a number added to make it unique: ‘RandomNet’ and ‘randomnet’ both become ‘random’, and ‘randomnet_1’ becomes ‘random_1’.
transmits
networks.transmits(disease)Whether a disease is communicable: declared with a contact-mediated transition, or written as code with a beta