Quenched mean-field SIS
The SIS epidemic on a fixed network in quenched mean field, one infection probability per node.
- Name:
quenched_mean_field_sis - Field:
biology/epidemiology - Formalism: ordinary differential equations
- References: Pastor-Satorras et al. 2015; Wang et al. 2003
Factory
@register_model(
field='biology/epidemiology',
tags=('network', 'quenched mean field', 'ode', 'epidemic threshold', 'spectral radius'),
references=('Pastor-Satorras et al. 2015', 'Wang et al. 2003'),
defaults={'until': 40.0, 'dt': 0.2},
)
def quenched_mean_field_sis(
graph: nx.Graph | None = None,
*,
n: int = 300,
m: int = 3,
lam: float = 0.2,
p: float = 0.05,
seed: int | None = 1,
) -> System:
"""Return the SIS epidemic on a fixed network in quenched mean field, one infection probability per node.
The infection persists above the threshold `lam_c = 1 / Lambda`, the inverse of the largest eigenvalue of the
adjacency matrix, and dies out below it. On scale-free networks the hubs keep it alive at small `lam`.
Args:
graph:
The network, or `None` for a Barabási-Albert network of `n` nodes with `m` edges per new node.
n:
The number of nodes of the default network.
m:
The number of edges of every new node of the default network.
lam:
The spreading rate, the ratio of the infection rate per edge and the recovery rate.
p:
The initial probability that every node is infected.
seed:
The seed of the default network.
Returns:
system:
The model, with the probabilities `nodes.p` of the group `nodes` on the nodes of the network.
Examples:
>>> import networkx as nx
>>> from skcomplex.models.biology.epidemiology import quenched_mean_field_sis
>>> quenched_mean_field_sis(nx.cycle_graph(10)).elements[0].check().prepare().size_
10
"""
space = NetworkSpace(graph) if graph is not None else NetworkSpace(nx.barabasi_albert_graph(n, m, seed=seed))
return System(
elements=[LatticeElement('nodes', element=CarrierElement, space=space, values={'p': p, 'lam': lam})],
)
Components
class CarrierElement(Element):
"""Node of a network whose state is the probability that it is infected, in quenched mean field.
The node recovers at rate 1 and every infected neighbour infects it at rate `lam`, with the probabilities of the
neighbours taken as independent, so `dp_i / dt = -p_i + lam (1 - p_i) sum_j A_ij p_j` over the neighbours `j`.
Args:
label:
The label of the node or of the group of nodes.
visual:
How the element is drawn, or `None` for `default_visual_`.
p:
The initial probability of being infected.
lam:
The spreading rate, the ratio of the infection rate per edge and the recovery rate.
Examples:
>>> from skcomplex.models.biology.epidemiology import CarrierElement
>>> CarrierElement('node').lam
0.2
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
p: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.05,
lam: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.2,
) -> None:
super().__init__(label, visual=visual)
self.p = p
self.lam = lam
def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
"""Return the recovery and the infection by the neighbours.
Args:
s:
The namespace of the symbols the laws can use.
Returns:
laws:
The laws of the component.
"""
return [RateLaw(s.p, -s.p + s.lam * (1 - s.p) * neighbors(s.other.p))]
Equations
nodes.p[i]: diff(p_nodes(t), t) == -_neighbours_0*lam_nodes*(p_nodes(t) - 1) - p_nodes(t)