Skip to content

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)