Skip to content

Gompertz growth with a vascular carrying capacity

Gompertz growth under a carrying capacity that the vessels on the tumour surface keep raising.

  • Name: dynamic_carrying_capacity
  • Field: biology/oncology
  • Formalism: ordinary differential equations
  • References: Padmanabhan, Meskin, and Al Moustafa 2021; Benzekry et al. 2014; Hahnfeldt et al. 1999

Factory

@register_model(
    field='biology/oncology',
    tags=('tumour growth', 'ode', 'angiogenesis', 'sigmoid'),
    references=(*GROWTH_REFERENCES, 'Hahnfeldt et al. 1999'),
    defaults={'until': 20.0, 'dt': 0.05},
)
def dynamic_carrying_capacity(n: float = 1.0, K: float = 12.7, *, r: float = 2.63, d: float = 0.829) -> System:
    """Return Gompertz growth under a carrying capacity that the vessels on the tumour surface keep raising.

    The tumour follows `dn/dt = r n log(K / n)` and the capacity `dK/dt = d n^(2/3)`, so the tumour first fills its
    initial capacity and then grows as fast as its vasculature, roughly as `t^3`. The defaults are the fit to breast
    cancer volumes in mm³ and days.

    Args:
        n:
            The initial volume.

        K:
            The initial carrying capacity.

        r:
            The rate at which the growth slows.

        d:
            The rate at which the surface of the tumour raises the capacity.

    Returns:
        system:
            The model, with the volume `tumour.n` and the capacity `vessels.K`.

    Examples:
        >>> from skcomplex.models.biology.oncology import dynamic_carrying_capacity
        >>> dynamic_carrying_capacity().elements[1].K
        12.7
    """
    return System(
        elements=[PopulationElement('tumour', n=n), VasculatureElement('vessels', K=K)],
        interactions=[VascularSupportInteraction('support', tumour='tumour', vasculature='vessels', r=r, 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 VasculatureElement(Element):
    """Vasculature of a tumour, which sets its carrying capacity.

    Args:
        label:
            The label of the vasculature.

        visual:
            How the element is drawn, or `None` for `default_visual_`.

        K:
            The initial carrying capacity.

    Examples:
        >>> from skcomplex.models.biology.oncology import VasculatureElement
        >>> VasculatureElement('vessels', K=12.7).K
        12.7
    """

    def __init__(
        self,
        label: str,
        visual: VisualSpec | None = None,
        *,
        K: Annotated[float, StateSpec(domain=RealSet.open(0, oo))] = 1.0,
    ) -> None:
        super().__init__(label, visual=visual)
        self.K = K

class VascularSupportInteraction(Interaction):
    """Gompertz growth under a carrying capacity that the vessels on the tumour surface raise.

    The tumour follows `dn/dt = r n log(K / n)` and the capacity `dK/dt = d n^(2/3)`.

    Args:
        label:
            The label of the interaction.

        tumour:
            The path of the tumour.

        vasculature:
            The path of the vasculature.

        r:
            The rate at which the growth slows.

        d:
            The rate at which the surface of the tumour raises the capacity.

    Examples:
        >>> from skcomplex.models.biology.oncology import VascularSupportInteraction
        >>> VascularSupportInteraction('support', tumour='tumour', vasculature='vessels').d
        0.829
    """

    def __init__(
        self,
        label: str,
        *,
        tumour: Annotated[str, RoleSpec()],
        vasculature: Annotated[str, RoleSpec()],
        r: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 2.63,
        d: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.829,
    ) -> None:
        super().__init__(label)
        self.tumour = tumour
        self.vasculature = vasculature
        self.r = r
        self.d = d

    def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
        """Return the Gompertz growth of the tumour and the growth of the capacity.

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

        Returns:
            laws:
                The laws of the component.
        """
        n, K = s.tumour.n, s.vasculature.K
        return [RateLaw(n, s.r * n * log(K / n)), RateLaw(K, s.d * n ** (2 / 3))]

Equations

tumour.n: diff(n_tumour(t), t) == r_support*log(K_vessels(t)/n_tumour(t))*n_tumour(t)
vessels.K: diff(K_vessels(t), t) == d_support*n_tumour(t)^0.6666666666666666