Nicholson-Bailey
Hosts and parasitoids in discrete generations, whose equilibrium is unstable to growing oscillations.
- Name:
nicholson_bailey - Field:
biology/ecology - Formalism: discrete map
- References: Nicholson and Bailey 1935; Argun et al. 2021
Factory
@register_model(
field='biology/ecology',
tags=('host-parasitoid', 'discrete map', 'oscillation'),
references=('Nicholson and Bailey 1935', 'Argun et al. 2021'),
defaults={'until': 30, 'dt': 1},
)
def nicholson_bailey(
hosts: float = 25.0,
parasitoids: float = 10.0,
*,
k: float = 2.0,
a: float = 0.05,
c: float = 1.0,
) -> System:
"""Return hosts and parasitoids in discrete generations, whose equilibrium is unstable to growing oscillations.
The equilibrium is `H = k ln k / ((k - 1) a c)` and `P = ln k / a`.
Args:
hosts:
The initial number of hosts.
parasitoids:
The initial number of parasitoids.
k:
The reproductive rate of the hosts.
a:
The searching efficiency of the parasitoids.
c:
The number of viable eggs a parasitoid lays in a host.
Returns:
system:
The model, with the populations `hosts` and `parasitoids`.
Examples:
>>> from skcomplex.models.biology.ecology import nicholson_bailey
>>> nicholson_bailey(k=3.0).interactions[0].k
3.0
"""
return System(
elements=[PopulationElement('hosts', n=hosts), PopulationElement('parasitoids', n=parasitoids)],
interactions=[ParasitismInteraction('parasitism', host='hosts', parasitoid='parasitoids', k=k, a=a, c=c)],
)
Components
class PopulationElement(CompartmentElement):
"""Population of one species, a compartment whose state `n` holds its size.
Args:
label:
The label of the population.
visual:
How the element is drawn, or `None` for `default_visual_`.
n:
The initial size.
Examples:
>>> from skcomplex.models.biology.common.populations import PopulationElement
>>> PopulationElement('rabbits', n=40).n
40
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
n: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
) -> None:
super().__init__(label, visual=visual, n=n)
class ParasitismInteraction(Interaction):
"""Nicholson-Bailey parasitism of hosts by parasitoids in discrete generations.
Parasitoids search at random, so a host escapes with probability `exp(-a P)`, giving `H' = k H exp(-a P)` and
`P' = c H (1 - exp(-a P))`.
Args:
label:
The label of the interaction.
host:
The path of the host population `H`.
parasitoid:
The path of the parasitoid population `P`.
k:
The reproductive rate of the hosts.
a:
The searching efficiency of the parasitoids.
c:
The number of viable eggs a parasitoid lays in a host.
Examples:
>>> from skcomplex.models.biology.ecology import ParasitismInteraction
>>> ParasitismInteraction('parasitism', host='moths', parasitoid='wasps', k=2.0).k
2.0
"""
def __init__(
self,
label: str,
*,
host: Annotated[str, RoleSpec()],
parasitoid: Annotated[str, RoleSpec()],
k: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 2.0,
a: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 0.05,
c: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
) -> None:
super().__init__(label)
self.host = host
self.parasitoid = parasitoid
self.k = k
self.a = a
self.c = c
def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
"""Return the next generations `k H exp(-a P)` and `c H (1 - exp(-a P))`.
Args:
s:
The namespace of the symbols the laws can use.
Returns:
laws:
The laws of the component.
"""
hosts, parasitoids = s.host.n, s.parasitoid.n
escape = exp(-s.a * parasitoids)
return [UpdateLaw(hosts, s.k * hosts * escape), UpdateLaw(parasitoids, s.c * hosts * (1 - escape))]
Equations
hosts.n: n_hosts(t + 1) == k_parasitism*e^(-a_parasitism*n_parasitoids(t))*n_hosts(t)
parasitoids.n: n_parasitoids(t + 1) == -c_parasitism*(e^(-a_parasitism*n_parasitoids(t)) - 1)*n_hosts(t)