Skip to main content

networks

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 network
get_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 p1
remove_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 exactly n_contacts, while the target side is Poisson-distributed with mean n_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 exactly n_contacts contacts, 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 own n_contacts, as in Covasim v3’s random networks. This gives less variable numbers of contacts (e.g. with n_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 client

Methods

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')), from starsim_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 (an ss.MixingPool of 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 (or beta*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).n or beta*I.n_infectious: their number, each counted by its weight, β·Σ_j C_ij·w_j·I_j (for n_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