Watts-May SEIR with lasting partnerships
The SEIR epidemic of Watts and May, where partnerships that last longer spread the infection faster.
- Name:
watts_may_concurrency - Field:
biology/epidemiology - Formalism: ordinary differential equations
- References: Watts and May 1992; Masuda, Miller, and Holme 2021
Select this system
from skcomplex.systems import create_simulation, create_system, describe
system = create_system('watts_may_concurrency')
entry = describe('watts_may_concurrency')
trajectory = create_simulation('watts_may_concurrency').prepare().run().trajectory_
Factory
@register_system(
field='biology/epidemiology',
tags=('concurrency', 'partnerships', 'seir', 'delay', 'hiv'),
references=('Watts and May 1992', 'Masuda, Miller, and Holme 2021'),
defaults={'until': 40.0, 'dt': 0.25},
)
def watts_may_concurrency(
infected: float = 0.01,
*,
c: float = 2.0,
beta: float = 0.2,
tau: float = 1.0,
T: float = 0.5,
nu: float = 0.1,
) -> System:
"""Return the SEIR epidemic of Watts and May, where partnerships that last longer spread the infection faster.
A susceptible person is infected at the rate `pi(t) = c beta [I / (S + E + I) + int exp(-(t - t') / tau) pi(t' - T)
S(t' - T) / (S + E + I)(t') dt']`, by a new partner who is infectious or by a current partner who turned infectious
during the partnership. The mean number of current partners is `c tau`, so a longer partnership raises the mean
degree of the momentary network, and the infection rate grows with `tau` at fixed `c` and `beta`.
Args:
infected:
The initial fraction of infectious people.
c:
The rate of acquiring new partners.
beta:
The probability of infection by an infectious partner over a partnership.
tau:
The mean duration of a partnership.
T:
The duration of the exposed period.
nu:
The rate at which the infectious develop the disease.
Returns:
system:
The system, with the fractions `population.S`, `population.E`, `population.I`, `population.R`, and the
infection memory `population.J`.
Examples:
>>> from skcomplex.systems.biology.epidemiology import watts_may_concurrency
>>> watts_may_concurrency(tau=3.0).elements[0].tau
3.0
"""
population = PartnershipEpidemicElement(
'population',
S=1 - infected,
I=infected,
c=c,
beta=beta,
tau=tau,
T=T,
nu=nu,
)
return System(elements=[population])
Components
Elements:
PartnershipEpidemicElement, fromElement
class PartnershipEpidemicElement(Element):
"""Population of Watts and May whose infections pass along partnerships that last, with a fixed exposed period.
Susceptible people `S` acquire new partners at the rate `c`, each infecting with the probability `beta` over the
partnership, and are infected at the rate `pi = c beta (I / A + J)` with the active population `A = S + E + I`. The
first term is the infection by a new partner who is infectious, and the memory `J` is the infection by a current
partner who turned infectious after the partnership formed, `dJ/dt = pi(t - T) S(t - T) / A - J / tau`, where
partnerships last a mean time `tau`. The exposed `E` turn infectious after the fixed time `T`, and the infectious
`I` develop the disease, which ends their sexual activity, at the rate `nu`, into `R`. The infection starts at time
zero, so the infectious and the memory are zero before it.
Args:
label:
The label of the population.
visual:
How the element is drawn, or `None` for `default_visual_`.
S:
The initial fraction of susceptible people.
E:
The initial fraction of exposed people.
I:
The initial fraction of infectious people.
R:
The initial fraction of people who developed the disease.
J:
The initial infection memory of current partnerships.
c:
The rate of acquiring new partners.
beta:
The probability of infection by an infectious partner over a partnership.
tau:
The mean duration of a partnership.
T:
The duration of the exposed period.
nu:
The rate at which the infectious develop the disease.
Examples:
>>> from skcomplex.systems.biology.epidemiology import PartnershipEpidemicElement
>>> PartnershipEpidemicElement('population').tau
1.0
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
S: Annotated[float, StateSpec(domain=FRACTION)] = 0.99,
E: Annotated[float, StateSpec(domain=FRACTION)] = 0.0,
I: Annotated[float, StateSpec(domain=FRACTION, history=0.0)] = 0.01,
R: Annotated[float, StateSpec(domain=FRACTION)] = 0.0,
J: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo), history=0.0)] = 0.0,
c: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 2.0,
beta: Annotated[float, ParameterSpec(domain=FRACTION)] = 0.2,
tau: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
T: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 0.5,
nu: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.1,
) -> None:
super().__init__(label, visual=visual)
self.S = S
self.E = E
self.I = I
self.R = R
self.J = J
self.c = c
self.beta = beta
self.tau = tau
self.T = T
self.nu = nu
def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
"""Return the infection by new and current partners, the exposed period, and the progression.
Args:
s:
The namespace of the symbols the laws can use.
Returns:
laws:
The laws of the component.
"""
active = s.S + s.E + s.I
infection = s.c * s.beta * (s.I / active + s.J) * s.S
past = [delayed(state, s.T) for state in (s.S, s.E, s.I, s.J)]
past_active = past[0] + past[1] + past[2]
matured = s.c * s.beta * (past[2] / past_active + past[3]) * past[0]
return [
RateLaw(s.S, -infection),
RateLaw(s.E, infection - matured),
RateLaw(s.I, matured - s.nu * s.I),
RateLaw(s.R, s.nu * s.I),
RateLaw(s.J, matured / active - s.J / s.tau),
]
Equations
population.S: diff(S_population(t), t) == -beta_population*c_population*(I_population(t)/(E_population(t) + I_population(t) + S_population(t)) + J_population(t))*S_population(t)
population.E: diff(E_population(t), t) == -beta_population*c_population*(I_population(-T_population + t)/(E_population(-T_population + t) + I_population(-T_population + t) + S_population(-T_population + t)) + J_population(-T_population + t))*S_population(-T_population + t) + beta_population*c_population*(I_population(t)/(E_population(t) + I_population(t) + S_population(t)) + J_population(t))*S_population(t)
population.I: diff(I_population(t), t) == beta_population*c_population*(I_population(-T_population + t)/(E_population(-T_population + t) + I_population(-T_population + t) + S_population(-T_population + t)) + J_population(-T_population + t))*S_population(-T_population + t) - nu_population*I_population(t)
population.R: diff(R_population(t), t) == nu_population*I_population(t)
population.J: diff(J_population(t), t) == beta_population*c_population*(I_population(-T_population + t)/(E_population(-T_population + t) + I_population(-T_population + t) + S_population(-T_population + t)) + J_population(-T_population + t))*S_population(-T_population + t)/(E_population(t) + I_population(t) + S_population(t)) - J_population(t)/tau_population