Von Bertalanffy growth
A tumour that grows by the balance of anabolism and catabolism, dn/dt = r n^p - d n.
- Name:
von_bertalanffy - Field:
biology/oncology - Formalism: ordinary differential equations
- References: Padmanabhan, Meskin, and Al Moustafa 2021; Benzekry et al. 2014; von Bertalanffy 1957
Factory
@register_model(
field='biology/oncology',
tags=('tumour growth', 'ode', 'allometry'),
references=(*GROWTH_REFERENCES, 'von Bertalanffy 1957'),
defaults={'until': 80.0, 'dt': 0.4},
)
def von_bertalanffy(n: float = 1.0, *, r: float = 2.32, p: float = 0.918, d: float = 0.808) -> System:
"""Return a tumour that grows by the balance of anabolism and catabolism, `dn/dt = r n^p - d n`.
The volume tends to `(r / d)^(1/(1-p))`. The defaults are the fit to breast cancer volumes in mm³ and days.
Args:
n:
The initial volume.
r:
The growth rate.
p:
The exponent of the growth, below 1.
d:
The loss rate.
Returns:
system:
The model, with the volume `tumour.n`.
Examples:
>>> from skcomplex.models.biology.oncology import von_bertalanffy
>>> von_bertalanffy().interactions[1].d
0.808
"""
return System(
elements=[PopulationElement('tumour', n=n)],
interactions=[
PowerLawGrowthInteraction('growth', target='tumour', r=r, p=p),
MortalityInteraction('loss', target='tumour', d=d),
],
)
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 PowerLawGrowthInteraction(GrowthInteraction):
"""Growth that scales as a power of the size, `dn/dt = r n ** p`, without harvest.
Args:
label:
The label of the interaction.
target:
The path of the element or group whose population grows.
r:
The growth rate.
p:
The exponent, `2/3` for a growing rim and 1 for exponential growth.
state:
The name of the state of the target that holds the population.
Examples:
>>> from skcomplex.core.elements import CompartmentElement
>>> from skcomplex.core.system import System
>>> from skcomplex.engine.runtime import Simulation
>>> from skcomplex.models.biology.common.populations import PowerLawGrowthInteraction
>>> system = System(
... elements=[CompartmentElement('tumour', n=1.0)],
... interactions=[PowerLawGrowthInteraction('growth', target='tumour', r=1.0, p=0.5)],
... )
>>> trajectory = Simulation(system, dt=0.01).check().prepare().run(until=2.0).trajectory_
>>> round(float(trajectory.values('tumour.n')[-1]), 3)
4.0
"""
def __init__(
self,
label: str,
*,
target: Annotated[str, RoleSpec()],
r: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.32,
p: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 0.58,
state: str = 'n',
) -> None:
super().__init__(label, target=target, r=r, p=p, form='power', state=state)
def _get_rate(self, namespace: SimpleNamespace) -> Expression:
"""Return the power-law growth, with no harvest.
Args:
namespace:
The namespace of the symbols the rate can use, with the namespace of the target.
Returns:
rate:
The rate of the population.
"""
return self._get_growth(namespace)
class MortalityInteraction(RateInteraction):
"""Death or loss at a constant per capita rate, `dn/dt = -d n`.
Args:
label:
The label of the interaction.
target:
The path of the element or group whose population dies.
d:
The per capita death rate.
state:
The name of the state of the target that holds the population.
Examples:
>>> from skcomplex.core.elements import CompartmentElement
>>> from skcomplex.core.system import System
>>> from skcomplex.engine.runtime import Simulation
>>> from skcomplex.models.biology.common.populations import MortalityInteraction
>>> system = System(
... elements=[CompartmentElement('foxes', n=8.0)],
... interactions=[MortalityInteraction('death', target='foxes', d=0.5)],
... )
>>> trajectory = Simulation(system, dt=0.01).check().prepare().run(until=2.0).trajectory_
>>> round(float(trajectory.values('foxes.n')[-1]), 3)
2.943
"""
def __init__(
self,
label: str,
*,
target: Annotated[str, RoleSpec()],
d: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.1,
state: str = 'n',
) -> None:
super().__init__(label, target=target, state=state)
self.d = d
def _get_rate(self, namespace: SimpleNamespace) -> Expression:
"""Return the loss `-d n`.
Args:
namespace:
The namespace of the symbols the rate can use, with the namespace of the target.
Returns:
rate:
The rate of the population.
"""
loss: Expression = -namespace.d * getattr(namespace.target, self.state)
return loss
Equations
tumour.n: diff(n_tumour(t), t) == r_growth*n_tumour(t)^p_growth - d_loss*n_tumour(t)