Skip to content

Competition

Two species that compete for the same resources, which coexist or exclude each other.

  • Name: competition
  • Field: biology/ecology
  • Formalism: ordinary differential equations
  • References: Lotka 1925; Volterra 1926; Argun et al. 2021

Factory

@register_model(
    field='biology/ecology',
    tags=('competition', 'ode', 'exclusion'),
    references=('Lotka 1925', 'Volterra 1926', 'Argun et al. 2021'),
    defaults={'until': 50.0, 'dt': 0.1},
)
def competition(
    first: float = 10.0,
    second: float = 10.0,
    *,
    r1: float = 1.0,
    r2: float = 0.8,
    K1: float = 100.0,
    K2: float = 80.0,
    alpha12: float = 0.5,
    alpha21: float = 0.6,
) -> System:
    """Return two species that compete for the same resources, which coexist or exclude each other.

    The species coexist at `n1 = (K1 - alpha12 K2) / (1 - alpha12 alpha21)` and
    `n2 = (K2 - alpha21 K1) / (1 - alpha12 alpha21)` when both are positive and `alpha12 alpha21 < 1`. Otherwise one
    species excludes the other.

    Args:
        first:
            The initial size of the first population.

        second:
            The initial size of the second population.

        r1:
            The growth rate of the first population.

        r2:
            The growth rate of the second population.

        K1:
            The carrying capacity of the first population.

        K2:
            The carrying capacity of the second population.

        alpha12:
            The effect of one member of the second population on the first, relative to one of its own.

        alpha21:
            The effect of one member of the first population on the second, relative to one of its own.

    Returns:
        system:
            The model, with the populations `first` and `second`.

    Examples:
        >>> from skcomplex.models.biology.ecology import competition
        >>> competition(alpha12=0.2).interactions[0].alpha12
        0.2
    """
    return System(
        elements=[PopulationElement('first', n=first), PopulationElement('second', n=second)],
        interactions=[
            CompetitionInteraction(
                'competition',
                first='first',
                second='second',
                r1=r1,
                r2=r2,
                K1=K1,
                K2=K2,
                alpha12=alpha12,
                alpha21=alpha21,
            ),
        ],
    )

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 CompetitionInteraction(Interaction):
    """Lotka-Volterra competition of two species for the same resources.

    Each species grows logistically, and the other species fills its carrying capacity in proportion to a competition
    coefficient, `dn1/dt = r1 n1 (1 - (n1 + alpha12 n2) / K1)` and `dn2/dt = r2 n2 (1 - (n2 + alpha21 n1) / K2)`.

    Args:
        label:
            The label of the interaction.

        first:
            The path of the first population.

        second:
            The path of the second population.

        r1:
            The growth rate of the first population.

        r2:
            The growth rate of the second population.

        K1:
            The carrying capacity of the first population.

        K2:
            The carrying capacity of the second population.

        alpha12:
            The effect of one member of the second population on the first, relative to one of its own.

        alpha21:
            The effect of one member of the first population on the second, relative to one of its own.

    Examples:
        >>> from skcomplex.models.biology.ecology import CompetitionInteraction
        >>> CompetitionInteraction('competition', first='deer', second='elk', alpha12=0.5).alpha12
        0.5
    """

    def __init__(
        self,
        label: str,
        *,
        first: Annotated[str, RoleSpec()],
        second: Annotated[str, RoleSpec()],
        r1: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
        r2: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
        K1: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 100.0,
        K2: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 100.0,
        alpha12: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.5,
        alpha21: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.5,
    ) -> None:
        super().__init__(label)
        self.first = first
        self.second = second
        self.r1 = r1
        self.r2 = r2
        self.K1 = K1
        self.K2 = K2
        self.alpha12 = alpha12
        self.alpha21 = alpha21

    def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
        """Return the logistic rates with the competitor in the crowding term.

        Args:
            s:
                The namespace of the symbols the laws can use.

        Returns:
            laws:
                The laws of the component.
        """
        first, second = s.first.n, s.second.n
        return [
            RateLaw(first, s.r1 * first * (1 - (first + s.alpha12 * second) / s.K1)),
            RateLaw(second, s.r2 * second * (1 - (second + s.alpha21 * first) / s.K2)),
        ]

Equations

first.n: diff(n_first(t), t) == -r1_competition*((alpha12_competition*n_second(t) + n_first(t))/K1_competition - 1)*n_first(t)
second.n: diff(n_second(t), t) == -r2_competition*((alpha21_competition*n_first(t) + n_second(t))/K2_competition - 1)*n_second(t)