Skip to content

Acquired and induced resistance with phenotype switching

Sensitive cells and cells resistant by mutation or by a reversible phenotype switch under a dose.

  • Name: drug_resistance_switching
  • Field: biology/oncology
  • Formalism: ordinary differential equations
  • References: Padmanabhan, Meskin, and Al Moustafa 2021; Greene, Sanchez-Tapia, and Sontag 2018; Greene, Gevertz, and Sontag 2019

Factory

@register_model(
    field='biology/oncology',
    tags=('chemotherapy', 'ode', 'drug resistance', 'phenotype switching'),
    references=(BOOK, 'Greene, Sanchez-Tapia, and Sontag 2018', 'Greene, Gevertz, and Sontag 2019'),
    defaults={'until': 400.0, 'dt': 1.0},
)
def drug_resistance_switching(
    S: float = 0.01,
    *,
    u: float = 1.0,
    m1: float = 1e-6,
    m2: float = 0.0,
    alpha_m: float = 0.01,
    alpha_p: float = 0.0,
    m5: float = 0.0,
    r2: float = 0.2,
    r3: float = 0.2,
) -> System:
    """Return sensitive cells and cells resistant by mutation or by a reversible phenotype switch under a dose.

    The drug kills the sensitive cells, so the resistant cells, which grow slower, take the tumour over. Resistance is
    acquired spontaneously at the rates `m1` and `m2` and induced by the drug at the rates `alpha_m` and `alpha_p` per
    unit of dose, and switched cells return to sensitivity at the rate `m5`. Time and the cells are dimensionless, the
    carrying capacity being 1. The book gives values only for the two-population model of Greene, Sanchez-Tapia, and
    Sontag, its Table 3.5, which is this model without switched cells. The defaults are that model, with the
    induced rate `alpha_m = 0.01` of the table and an effective dose of 1, so switching is off until `m2`, `alpha_p`,
    or `m5` is set.

    Args:
        S:
            The initial sensitive cells, the resistant cells starting at 0.

        u:
            The effective dose.

        m1:
            The rate of spontaneous resistance by mutation.

        m2:
            The rate of spontaneous resistance by phenotype switching.

        alpha_m:
            The rate per unit of dose of resistance induced by mutation.

        alpha_p:
            The rate per unit of dose of resistance induced by phenotype switching.

        m5:
            The rate at which switched cells become sensitive again.

        r2:
            The growth rate of mutated cells relative to sensitive cells.

        r3:
            The growth rate of switched cells relative to sensitive cells.

    Returns:
        system:
            The model, with the cells `tumour.S`, `tumour.Rm`, and `tumour.Rp`.

    Examples:
        >>> from skcomplex.models.biology.oncology import drug_resistance_switching
        >>> drug_resistance_switching(alpha_m=0.0).elements[0].alpha_m
        0.0
    """
    tumour = ResistanceSwitchingElement(
        'tumour',
        S=S,
        u=u,
        m1=m1,
        m2=m2,
        alpha_m=alpha_m,
        alpha_p=alpha_p,
        m5=m5,
        r2=r2,
        r3=r3,
    )
    return System(elements=[tumour])

Components

class ResistanceSwitchingElement(Element):
    """Sensitive cells and cells resistant by mutation or by a reversible phenotype switch, under a constant dose.

    With the total `V = S + Rm + Rp`, the sensitive cells follow
    `dS/dt = r1 (1 - b V) S - (m1 + m2) S - (alpha_m + alpha_p) u S - a u S + m5 Rp`, the mutated cells
    `dRm/dt = r2 (1 - b V) Rm + m1 S + alpha_m u S - am u Rm`, and the switched cells
    `dRp/dt = r3 (1 - b V) Rp + m2 S + alpha_p u S - m5 Rp - ap u Rp`. The rates `m1` and `m2` are the spontaneous
    (acquired) resistance, `alpha_m` and `alpha_p` the resistance the drug induces, and `m5` the resensitization of
    switched cells.

    Args:
        label:
            The label of the tumour.

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

        S:
            The initial sensitive cells.

        Rm:
            The initial cells resistant by mutation.

        Rp:
            The initial cells resistant by phenotype switching.

        r1:
            The growth rate of sensitive cells.

        r2:
            The growth rate of mutated cells.

        r3:
            The growth rate of switched cells.

        b:
            The reciprocal carrying capacity of the tumour.

        m1:
            The rate of spontaneous resistance by mutation.

        m2:
            The rate of spontaneous resistance by phenotype switching.

        alpha_m:
            The rate per unit of dose of resistance induced by mutation.

        alpha_p:
            The rate per unit of dose of resistance induced by phenotype switching.

        m5:
            The rate at which switched cells become sensitive again.

        a:
            The kill rate of sensitive cells per unit of dose.

        am:
            The kill rate of mutated cells per unit of dose.

        ap:
            The kill rate of switched cells per unit of dose.

        u:
            The effective dose.

    Examples:
        >>> from skcomplex.models.biology.oncology import ResistanceSwitchingElement
        >>> ResistanceSwitchingElement('tumour').alpha_m
        0.01
    """

    def __init__(
        self,
        label: str,
        visual: VisualSpec | None = None,
        *,
        S: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.01,
        Rm: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
        Rp: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
        r1: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
        r2: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.2,
        r3: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.2,
        b: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
        m1: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1e-06,
        m2: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
        alpha_m: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.01,
        alpha_p: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
        m5: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
        a: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
        am: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
        ap: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
        u: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
    ) -> None:
        super().__init__(label, visual=visual)
        self.S = S
        self.Rm = Rm
        self.Rp = Rp
        self.r1 = r1
        self.r2 = r2
        self.r3 = r3
        self.b = b
        self.m1 = m1
        self.m2 = m2
        self.alpha_m = alpha_m
        self.alpha_p = alpha_p
        self.m5 = m5
        self.a = a
        self.am = am
        self.ap = ap
        self.u = u

    def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
        """Return the growth, transitions, and kill of the sensitive, mutated, and switched cells.

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

        Returns:
            laws:
                The laws of the component.
        """
        S, Rm, Rp, u = s.S, s.Rm, s.Rp, s.u
        room = 1 - s.b * (S + Rm + Rp)
        return [
            RateLaw(
                S,
                s.r1 * room * S - (s.m1 + s.m2) * S - (s.alpha_m + s.alpha_p) * u * S - s.a * u * S + s.m5 * Rp,
            ),
            RateLaw(Rm, s.r2 * room * Rm + s.m1 * S + s.alpha_m * u * S - s.am * u * Rm),
            RateLaw(Rp, s.r3 * room * Rp + s.m2 * S + s.alpha_p * u * S - s.m5 * Rp - s.ap * u * Rp),
        ]

Equations

tumour.S: diff(S_tumour(t), t) == -(b_tumour*(Rm_tumour(t) + Rp_tumour(t) + S_tumour(t)) - 1)*r1_tumour*S_tumour(t) - a_tumour*u_tumour*S_tumour(t) - (alpha_m_tumour + alpha_p_tumour)*u_tumour*S_tumour(t) + m5_tumour*Rp_tumour(t) - (m1_tumour + m2_tumour)*S_tumour(t)
tumour.Rm: diff(Rm_tumour(t), t) == -(b_tumour*(Rm_tumour(t) + Rp_tumour(t) + S_tumour(t)) - 1)*r2_tumour*Rm_tumour(t) - am_tumour*u_tumour*Rm_tumour(t) + alpha_m_tumour*u_tumour*S_tumour(t) + m1_tumour*S_tumour(t)
tumour.Rp: diff(Rp_tumour(t), t) == -(b_tumour*(Rm_tumour(t) + Rp_tumour(t) + S_tumour(t)) - 1)*r3_tumour*Rp_tumour(t) - ap_tumour*u_tumour*Rp_tumour(t) + alpha_p_tumour*u_tumour*S_tumour(t) - m5_tumour*Rp_tumour(t) + m2_tumour*S_tumour(t)