library.networks
Additional network types: household, cluster, spatial, and theoretical.
HouseholdNet builds households from DHS-style survey data; ClusterNet divides agents into fully connected clusters, and HybridNet is Covasim’s population of households, schools, workplaces, and community; DiskNet connects agents that are spatially close, GeoNet connects them across real places, and GridNet puts them on a lattice; NullNet is useful for debugging, SmallWorldNet and ScaleFreeNet are the classic graphs, and StaticNet turns a NetworkX graph into a network (by default, an Erdős–Rényi graph). Core network classes (ss.RandomNet, ss.DegreeNet, ss.SexualNet, etc.) live in starsim.networks.
Most of these networks run as agents only. Those a compartmental run can approximate by a mean number of contacts are marked in their files.
Used with: agents and compartments; declared and coded modules
Classes
| Name | Description |
|---|---|
| ClusterNet | Fully connected clusters of agents, e.g. households, schools, or workplaces |
| DiskNet | Disk graph in which edges are made between agents located within a user-defined radius. |
| GeoNet | Random contacts between real places |
| GridNet | Agents on a square grid, in contact with their neighbors |
| HouseholdNet | A household contact network built from DHS-style survey data. |
| HybridNet | Covasim v3’s “hybrid” population: households, schools, workplaces, and community |
| NullNet | A convenience class for a network of size n that only has self-connections with a weight of 0. |
| ScaleFreeNet | A scale-free (Barabási-Albert) network: a few agents have many contacts, and most have few |
| SmallWorldNet | A small-world (Watts-Strogatz) network: mostly local contacts, with a few long-range ones |
| StaticNet | A network class of static partnerships converted from a networkx graph. There’s no formation of new partnerships |
ClusterNet
library.networks.ClusterNet(
pars=None,
cluster_size=_,
who=_,
dynamic=_,
**kwargs,
)Fully connected clusters of agents, e.g. households, schools, or workplaces
Each agent (optionally, only those within an age range) is assigned to one cluster, with cluster sizes drawn from cluster_size, and every pair of agents within a cluster is connected. Agents are assigned to clusters in a random order (from a CRN-safe random number per agent), so clusters are not correlated with other networks. Based on make_microstructured_contacts() from Covasim v3: as there, clusters of size 0 are skipped, and the last cluster is truncated to fit.
Note: CRN safe, unless dynamic=True and people are born or die.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| cluster_size | int/ss.Dist |
the size of each cluster; a number is the mean of a Poisson distribution (a size, like a target number of partners in ss.DegreeNet; for clusters all of one size, give ss.constant(n)) |
_ |
| who | Cond / str / func |
if given, who takes part: a condition over people (e.g. (ss.person.age >= 6) & (ss.person.age < 22)), as for ss.RandomNet |
_ |
| dynamic | bool |
if True, the clusters are recreated on each timestep; if False (default), they are created once and never change | _ |
| pars | dict |
the same parameters, as a dict | None |
Example
households = ssl.ClusterNet(name='households', cluster_size=ss.poisson(3))
sim = ss.Sim(diseases='sis', networks=households)
sim.run()Methods
| Name | Description |
|---|---|
| add_pairs | Assign agents to clusters, and connect every pair of agents within each cluster |
| step | If dynamic, recreate the clusters; otherwise, do nothing |
add_pairs
library.networks.ClusterNet.add_pairs()Assign agents to clusters, and connect every pair of agents within each cluster
step
library.networks.ClusterNet.step()If dynamic, recreate the clusters; otherwise, do nothing
DiskNet
library.networks.DiskNet(pars=None, **kwargs)Disk graph in which edges are made between agents located within a user-defined radius.
Interactions take place within a square with edge length of 1. Agents are initialized to have a random position and orientation within this square. On each time step, agents advance v*dt in the direction they are pointed. When encountering a wall, agents are reflected.
Edges are formed between two agents if they are within r distance of each other.
Note: CRN safe.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| r | float |
radius within which edges are formed | |
| v | rate |
speed at which agents move, a distance per unit time (e.g. ss.freqperday(0.05)) |
|
| pars | dict |
the same parameters, as a dict | None |
Attributes
| Name | Type | Description |
|---|---|---|
| x | FloatArr | x position, in [0, 1] |
| y | FloatArr | y position, in [0, 1] |
| theta | FloatArr | direction of travel, in radians |
Examples
import starsim as ss
import starsim.library as ssl
sim = ss.Sim(diseases='sis', networks=ssl.DiskNet(r=0.05))
sim.run()Methods
| Name | Description |
|---|---|
| add_pairs | Generate contacts |
add_pairs
library.networks.DiskNet.add_pairs()Generate contacts
GeoNet
library.networks.GeoNet(
places='Kenya',
level=1,
local=0.9,
c=2.0,
pars=None,
**kwargs,
)Random contacts between real places
Each agent lives in a place, in proportion to its population. Most of an agent’s contacts are in their own place, and the rest are in other places, more of them in places that are large and near, as in a gravity model: the fraction going from place i to place j is in proportion to pop_j/distance_ij**c. Within each place, contacts are paired at random. A country’s places need no download (see ss.geo.geography).
The place of each agent is the state place, an index into geo.names. Like ss.RandomNet, this is not CRN safe when people are born or die.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| places | str/ss.geo.geography |
a country by name, or a geography with populations and distances | 'Kenya' |
| level | int |
for a country, the administrative level (see ss.geo.geography) |
1 |
| local | float |
the fraction of contacts in an agent’s own place | 0.9 |
| c | float |
how quickly contacts fall off with distance | 2.0 |
| kwargs | dict |
passed to ss.RandomNet, e.g. n_contacts and dur |
|
| pars | dict |
the same parameters, as a dict | None |
Examples
import starsim as ss
import starsim.library as ssl
sim = ss.Sim(n_agents=10_000, diseases='sis', networks=ssl.GeoNet('Kenya', n_contacts=10))
sim.run()Methods
| Name | Description |
|---|---|
| contact_fracs | The fraction of the contacts of the people of each place (rows) that are in each place (columns), as a sparse array |
| pair | Find edges (see ss.RandomNet.pair()): each contact is in a place drawn from the fractions of the agent’s own place |
contact_fracs
library.networks.GeoNet.contact_fracs()The fraction of the contacts of the people of each place (rows) that are in each place (columns), as a sparse array
pair
library.networks.GeoNet.pair(inds, n_contacts)Find edges (see ss.RandomNet.pair()): each contact is in a place drawn from the fractions of the agent’s own place
GridNet
library.networks.GridNet(diagonal=False, periodic=False, pars=None, **kwargs)Agents on a square grid, in contact with their neighbors
Agents are placed row by row on the smallest square that holds them, as in lattice models of spatial spread. The network is fixed, so agents born later have no contacts. Note: CRN safe.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| diagonal | bool |
if True, agents have 8 neighbors rather than 4 | False |
| periodic | bool |
if True, the edges of the grid wrap around, so that everyone has the same number of neighbors | False |
| pars | dict |
the same parameters, as a dict | None |
Examples
import starsim as ss
import starsim.library as ssl
sim = ss.Sim(n_agents=2500, diseases='sir', networks=ssl.GridNet(periodic=True))
sim.run()HouseholdNet
library.networks.HouseholdNet(
pars=None,
dhs_data=None,
dynamic=None,
prob_move_out=_,
**kwargs,
)A household contact network built from DHS-style survey data.
When initialized, this network overrides the age (and optionally sex) of all agents in the sim and assigns each agent a household ID. Use with caution if other modules depend upon or alter age and sex.
Households are created by selecting a random household from the provided data and setting the age and sex of agents to match, repeating until all agents have been assigned to a household. Ages in the data are typically in integer years; a random fractional year is added so agents don’t share exact ages.
This network assumes only one mother per household. Births are automatically added to their mother’s household network.
Note: not CRN safe unless dynamic=False (the partners who move out are drawn together).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| dhs_data | DataFrame / str |
A pandas or Sciris dataframe with columns hh_id and ages. Optionally also sexes. The ages column should contain comma-separated age strings (e.g. "72, 17, 30"). If sexes is included, it should contain comma-separated values using DHS convention (1 = male, 2 = female) with the same number of entries as ages. Pass 'default' to use synthetic data (see make_default_data()), e.g. for demos and testing. |
None |
| dynamic | bool |
If True, households evolve over time: one female is assigned as head of each household, pregnant non-head females may move out to form new households, and births are added to the mother’s household, which needs ss.Pregnancy. If False, the network is static and step() is a no-op. By default, households evolve if the sim has ss.Pregnancy, and are static otherwise. |
None |
| prob_move_out | float |
Probability a non-head female moves out to start her own household, evaluated once at the start of each pregnancy. Default 0.7. Only used when households evolve. | _ |
| pars | dict |
the same parameters, as a dict | None |
To update the households less often than every step of the sim, give the network its own step, e.g. dt=ss.years(1).
The expected dataframe format is::
hh_id ages sexes
0 0 72, 17, 30 1, 1, 2
1 1 37 2
2 2 13, 55, 36 2, 1, 2
3 3 52, 13, 12, 64, 53 1, 2, 1, 2
4 4 30, 66 1, 1
Data in this format can be obtained from the DHS Program. To prepare a DHS household dataset:
Register and request access at https://dhsprogram.com
Download a Household Recode (HR) dataset in Stata format (e.g.
XXHR7xDT.zip)Use
HouseholdNet.load_dhs()to extract the data::import starsim as ss; import starsim.library as ssl dhs_data = ssl.networks.HouseholdNet.load_dhs(‘XXHR7xDT/XXHR7xFL.DTA’) sim = ss.Sim(networks=ssl.networks.HouseholdNet(dhs_data=dhs_data, dynamic=False)) sim.run()
If real data are not available, synthetic data can be constructed::
import numpy as np
import sciris as sc
import starsim as ss; import starsim.library as ssl
n = 1000
age_strings = []
for i in range(n):
household_size = np.random.randint(1, 6)
ages = np.random.randint(0, 80, household_size)
age_strings.append(sc.strjoin(ages))
dhs_data = sc.dataframe(hh_id=np.arange(n), ages=age_strings)
household = ssl.networks.HouseholdNet(dhs_data=dhs_data, dynamic=False)
sim = ss.Sim(diseases='sis', networks=household)
sim.run()
sim.plot()
Methods
| Name | Description |
|---|---|
| add_pairs | Generate contacts by assigning agents to households sampled from the data. |
| create_new_households | Find females that are pregnant and not a head of household. |
| init_post | Add the first edges, and add a random fraction of a year to each age |
| init_pre | Initialize the network: households evolve if the sim has ss.Pregnancy (unless dynamic says otherwise), which they need |
| load_dhs | Load a DHS Household Recode (HR) Stata file and return a dataframe |
| make_default_data | Generate synthetic household data, used when dhs_data='default'. |
add_pairs
library.networks.HouseholdNet.add_pairs()Generate contacts by assigning agents to households sampled from the data.
Households are drawn uniformly at random (with replacement) until they cover the whole population. Sampling, age/sex assignment, edge creation, and head-of-household selection are all vectorized rather than looped per household.
create_new_households
library.networks.HouseholdNet.create_new_households()Find females that are pregnant and not a head of household. Move them and a randomly sampled male partner to a new household.
init_post
library.networks.HouseholdNet.init_post(add_pairs=True)Add the first edges, and add a random fraction of a year to each age
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| add_pairs | bool |
whether to add edges | True |
init_pre
library.networks.HouseholdNet.init_pre(sim)Initialize the network: households evolve if the sim has ss.Pregnancy (unless dynamic says otherwise), which they need
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| sim | Sim | the sim being initialized | required |
load_dhs
library.networks.HouseholdNet.load_dhs(path)Load a DHS Household Recode (HR) Stata file and return a dataframe suitable for use with HouseholdNet.
Reads the wide-format HR file, extracts per-member age (HV105) and sex (HV104) columns, filters to valid entries (age <= 95 and sex in [1, 2]), and returns a dataframe with columns hh_id, ages, and sexes.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| path | str / Path |
Path to a DHS Household Recode Stata file (e.g. XXHR7xFL.DTA). |
required |
Returns
| Name | Type | Description |
|---|---|---|
sc.dataframe: A dataframe with columns hh_id, ages, and sexes ready for use with HouseholdNet(dhs_data=...). |
Examples
import starsim as ss; import starsim.library as ssl
dhs_data = ssl.networks.HouseholdNet.load_dhs('ZZHR62FL.DTA')
sim = ss.Sim(networks=ssl.networks.HouseholdNet(dhs_data=dhs_data, dynamic=False))
sim.run()make_default_data
library.networks.HouseholdNet.make_default_data(n=1000, seed=1)Generate synthetic household data, used when dhs_data='default'.
Creates n households of 1-5 members with ages uniformly distributed between 0 and 80. Intended for demos and testing when real DHS data are not available; see load_dhs() for loading actual survey data.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| n | int |
number of synthetic households to generate | 1000 |
| seed | int |
random seed for reproducibility | 1 |
Returns
| Name | Type | Description |
|---|---|---|
sc.dataframe: A dataframe with columns hh_id and ages ready for use with HouseholdNet(dhs_data=...). |
HybridNet
library.networks.HybridNet(
pars=None,
household_size=_,
n_contacts=_,
school_ages=_,
work_ages=_,
dynamic=_,
method=_,
clustered=_,
**kwargs,
)Covasim v3’s “hybrid” population: households, schools, workplaces, and community
When added to a sim, this expands into four separate networks: households (h), schools (s), and workplaces (w), each an ssl.ClusterNet, and community (c), an ss.RandomNet with Poisson-distributed contacts. Schools and workplaces are restricted by age. As in Covasim v3, the networks are static; unlike it, schools and workplaces are clusters by default, so that a person’s contacts also know each other (use clustered=False for Covasim v3’s random networks). With the default Poisson sizes, clusters give the same mean number of contacts as a random network. Each network can then be modified separately, e.g. sim.networks.s.
Transmissibility on each network is the disease’s, by network. Covasim v3’s relative transmissibility of the layers is ssl.HybridNet.rel_betas (3 for households, 0.6 for schools and workplaces, and 0.3 for the community), and ssl.HybridNet.betas() gives a disease’s beta for each layer from its beta per contact (by default Covasim’s, 0.016 a day), e.g. ss.SIS(beta=ssl.HybridNet.betas()), which is beta=dict(h=ss.perday(0.048), s=ss.perday(0.0096), w=ss.perday(0.0096), c=ss.perday(0.0048)).
Note: CRN safe, unless dynamic=True and people are born or die.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| household_size | int/ss.Dist |
the size of each household; a number is the mean of a Poisson distribution, as for ssl.ClusterNet |
_ |
| n_contacts | dict |
the number of contacts per agent in schools, workplaces, and community: a number, as for ss.RandomNet, is the same for everyone (rounded at random; with clustered=True, a cluster of one more than that), and a distribution gives its spread (default: Covasim’s, ss.poisson(20), ss.poisson(16), and ss.poisson(20)) |
_ |
| school_ages | list |
the age range of agents in schools | _ |
| work_ages | list |
the age range of agents in workplaces | _ |
| dynamic | bool |
whether the networks are recreated on each timestep | _ |
| method | str |
how the targets of edges in random networks are chosen (see ss.RandomNet); ‘uniform’ as in Covasim v3 |
_ |
| clustered | bool |
if True (default), schools and workplaces are ssl.ClusterNets, sized to give n_contacts for numbers and Poisson and constant distributions (other distributions are used directly as cluster sizes, which gives a different mean); if False, they are ss.RandomNets, as in Covasim v3 |
_ |
| pars | dict |
the same parameters, as a dict | None |
Examples
sim = ss.Sim(diseases='sis', networks=ssl.HybridNet())
sim.run()
print(sim.networks.keys()) # ['h', 's', 'w', 'c']
# Larger households and smaller schools; n_contacts can be partial
hybrid = ssl.HybridNet(household_size=ss.poisson(3), n_contacts=dict(s=ss.poisson(10)))
# Covasim's transmissibility on each layer, for a disease with twice its beta per contact
sir = ss.SIR(beta=ssl.HybridNet.betas(ss.perday(0.032)))
sim = ss.Sim(diseases=sir, networks=ssl.HybridNet(), unit='day', stop=60).run()Methods
| Name | Description |
|---|---|
| betas | The beta of a disease on each layer, by name: its beta per contact times the layer’s relative transmissibility (rel_betas, Covasim v3’s) |
| expand | Create the four networks; called automatically when added to a sim |
| step | Not used, since the networks returned by expand() are used instead |
betas
library.networks.HybridNet.betas(beta=None)The beta of a disease on each layer, by name: its beta per contact times the layer’s relative transmissibility (rel_betas, Covasim v3’s)
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| beta | float / rate |
the beta per contact (default Covasim’s, ss.perday(0.016)); a number is in the model’s unit |
None |
Example:
ss.SIS(beta=ssl.HybridNet.betas()) # dict(h=ss.perday(0.048), s=ss.perday(0.0096), w=ss.perday(0.0096), c=ss.perday(0.0048))expand
library.networks.HybridNet.expand()Create the four networks; called automatically when added to a sim
step
library.networks.HybridNet.step()Not used, since the networks returned by expand() are used instead
NullNet
library.networks.NullNet(n_people=None, **kwargs)A convenience class for a network of size n that only has self-connections with a weight of 0. This network can be useful for debugging purposes or as a placeholder network during development for conditions that require more complex network mechanisms.
Guarantees there’s one (1) contact per agent (themselves), and that their connection weight is zero.
For an empty network (i.e., no edges), use ss.RandomNet(n_contacts=0).
Note: CRN safe.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| n_people | int |
number of agents in the network; defaults to the sim’s n_agents |
None |
Methods
| Name | Description |
|---|---|
| init_pre | Initialize the network, with the number of people from the sim if not given |
| step | Not used for NullNet |
init_pre
library.networks.NullNet.init_pre(sim)Initialize the network, with the number of people from the sim if not given
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| sim | Sim | the sim being initialized | required |
step
library.networks.NullNet.step()Not used for NullNet
ScaleFreeNet
library.networks.ScaleFreeNet(n_contacts=4, pars=None, **kwargs)A scale-free (Barabási-Albert) network: a few agents have many contacts, and most have few
Each agent in turn joins half of n_contacts agents, with those who already have many contacts being more likely to be chosen, so the mean number of contacts is n_contacts. The network is fixed. Note: CRN safe.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| n_contacts | int |
the mean number of contacts of each agent (a whole even number) | 4 |
| pars | dict |
the same parameters, as a dict | None |
SmallWorldNet
library.networks.SmallWorldNet(n_contacts=4, rewire=0.1, pars=None, **kwargs)A small-world (Watts-Strogatz) network: mostly local contacts, with a few long-range ones
Agents are on a ring, in contact with their n_contacts nearest neighbors, and each contact is rewired to a random agent with probability rewire. The network is fixed. Note: CRN safe.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| n_contacts | int |
the number of neighbors of each agent (even; an odd number is rounded down) | 4 |
| rewire | float |
the probability of rewiring each contact | 0.1 |
| pars | dict |
the same parameters, as a dict | None |
StaticNet
library.networks.StaticNet(graph=None, pars=None, **kwargs)A network class of static partnerships converted from a networkx graph. There’s no formation of new partnerships and initialized partnerships only end when one of the partners dies. The networkx graph can be created outside Starsim if population size is known. Or the graph can be created by passing a networkx generator function to Starsim.
If “seed=True” is passed as a keyword argument or a parameter in pars, it is replaced with the built-in RNG. The parameter “n” is supplied automatically to be equal to n_agents. With no graph, it is a random (Erdős–Rényi) graph: the one nx.fast_gnp_random_graph() would make, edge for edge, made without NetworkX (see random_graph()).
Note: CRN safe.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| graph | nx.Graph / func |
a NetworkX graph, or a function that makes one from the number of agents n (and p and seed, if it takes them); with none, a random (Erdős–Rényi) graph |
None |
| n_contacts | float |
for a random graph, the mean number of contacts of each agent (used unless p is given) |
|
| p | float |
for a random graph, the probability that each pair of agents is in contact | |
| seed | bool / int |
the seed of a graph function; True for the network’s own random numbers | |
| kwargs | dict |
other arguments, passed to the graph function |
Examples
# Generate a networkx graph and pass to Starsim
import networkx as nx
import starsim as ss
import starsim.library as ssl
g = nx.scale_free_graph(n=10000)
ssl.StaticNet(graph=g)
# Pass a networkx graph generator to Starsim
ssl.StaticNet(graph=nx.erdos_renyi_graph, p=0.0001, seed=True)Methods
| Name | Description |
|---|---|
| graph_pars | Its parameters, and their defaults: a subclass that makes its own graph has its own |
| init_pre | Initialize the network, with a random (Erdős–Rényi) graph if none is given |
| mean_contacts | The mean degree of its graph (see ss.Network.mean_contacts()), or for a random graph not yet made, its number of contacts or its chance of each pair |
| validate_pop | Check that the graph fits the number of agents and has edges |
graph_pars
library.networks.StaticNet.graph_pars()Its parameters, and their defaults: a subclass that makes its own graph has its own
init_pre
library.networks.StaticNet.init_pre(sim)Initialize the network, with a random (Erdős–Rényi) graph if none is given
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| sim | Sim | the sim being initialized | required |
mean_contacts
library.networks.StaticNet.mean_contacts(sim)The mean degree of its graph (see ss.Network.mean_contacts()), or for a random graph not yet made, its number of contacts or its chance of each pair
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| sim | Sim | the sim being initialized | required |
validate_pop
library.networks.StaticNet.validate_pop(n_agents)Check that the graph fits the number of agents and has edges
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| n_agents | int |
the number of agents | required |