Logistic growth with a varying capacity
Logistic growth under a seasonal carrying capacity, which settles on a cycle of the season's period.
- Name:
varying_capacity_logistic - Field:
biology/ecology - Formalism: ordinary differential equations
- References: Coleman 1979; Trzaska 2023
Factory
@register_model(
field='biology/ecology',
tags=('population', 'ode', 'forcing', 'periodic'),
references=('Coleman 1979', 'Trzaska 2023'),
defaults={'until': 20.0, 'dt': 0.05},
)
def varying_capacity_logistic(
n: float = 10.0,
*,
r: float = 2.0,
K: float = 100.0,
amplitude: float = 0.5,
period: float = 1.0,
) -> System:
"""Return logistic growth under a seasonal carrying capacity, which settles on a cycle of the season's period.
Whatever its initial size, the population tends to a unique periodic solution, which lags behind the capacity and
varies less than it.
Args:
n:
The initial size.
r:
The intrinsic growth rate.
K:
The mean carrying capacity.
amplitude:
The relative amplitude of the seasonal variation of the capacity, below 1.
period:
The period of the seasons.
Returns:
system:
The model, with the population `population`.
Examples:
>>> from skcomplex.models.biology.ecology import varying_capacity_logistic
>>> varying_capacity_logistic(amplitude=0.2).interactions[0].amplitude
0.2
"""
return System(
elements=[PopulationElement('population', n=n)],
interactions=[
SeasonalLogisticGrowthInteraction(
'growth',
target='population',
r=r,
K=K,
amplitude=amplitude,
period=period,
),
],
)
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 SeasonalLogisticGrowthInteraction(LogisticGrowthInteraction):
"""Logistic growth under a carrying capacity that varies periodically, `K(t) = K (1 + amplitude sin(2 pi t / T))`.
Args:
label:
The label of the interaction.
target:
The path of the population.
r:
The per capita growth rate at low density.
K:
The mean carrying capacity.
amplitude:
The relative amplitude of the variation of the capacity, below 1.
period:
The period `T` of the variation.
Examples:
>>> from skcomplex.models.biology.ecology import SeasonalLogisticGrowthInteraction
>>> SeasonalLogisticGrowthInteraction('growth', target='rabbits', period=12.0).period
12.0
"""
def __init__(
self,
label: str,
*,
target: Annotated[str, RoleSpec()],
r: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
K: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 100.0,
amplitude: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.5,
period: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
) -> None:
super().__init__(label, target=target, r=r, K=K)
self.amplitude = amplitude
self.period = period
def _get_capacity(self, namespace: SimpleNamespace) -> Expression:
"""Return the capacity `K (1 + amplitude sin(2 pi t / T))` at the time of the namespace.
Args:
namespace:
The namespace of the symbols the rate can use.
Returns:
capacity:
The carrying capacity at the time `t`.
"""
s = namespace
capacity: Expression = s.K * (1 + s.amplitude * sin(2 * pi * s.t / s.period))
return capacity
Equations
population.n: diff(n_population(t), t) == -r_growth*(n_population(t)/((amplitude_growth*sin(2*pi*t/period_growth) + 1)*K_growth) - 1)*n_population(t)