Skip to content

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)