Skip to content

Disease Progression Model

PD model for tumor growth dynamics with drug-induced cell kill, supporting multiple growth models.


Function Signature

neopkpd.simulate_pkpd_disease_progression(
    cl: float,
    v: float,
    doses: list[dict],
    growth_model: str,
    s0: float,
    kgrow: float,
    smax: float,
    alpha: float,
    kdrug: float,
    t0: float,
    t1: float,
    saveat: list[float],
    pk_kind: str = "OneCompIVBolus",
    ka: float | None = None,
    q: float | None = None,
    v2: float | None = None,
    alg: str = "Tsit5",
    reltol: float = 1e-10,
    abstol: float = 1e-12,
    maxiters: int = 10**7,
) -> dict

Parameters

Parameter Type Description
cl float Clearance (L/h)
v float Volume of distribution (L)
doses list[dict] Dose events
growth_model str Growth model type (see below)
s0 float Initial tumor size
kgrow float Growth rate constant
smax float Maximum size (carrying capacity)
alpha float Linear growth rate (for linear model)
kdrug float Drug-induced cell kill rate constant

Growth Models

Model Equation
"exponential" \(dS/dt = k_{grow} \cdot S - k_{drug} \cdot C \cdot S\)
"linear" \(dS/dt = \alpha - k_{drug} \cdot C \cdot S\)
"logistic" \(dS/dt = k_{grow} \cdot S \cdot (1 - S/S_{max}) - k_{drug} \cdot C \cdot S\)
"gompertz" \(dS/dt = k_{grow} \cdot S \cdot \ln(S_{max}/S) - k_{drug} \cdot C \cdot S\)
"asymptotic" \(dS/dt = k_{grow} \cdot (S_{max} - S) - k_{drug} \cdot C \cdot S\)

Returns

{
    "t": [0.0, 1.0, ...],
    "states": {
        "A_central": [...],   # Drug amount
        "S": [...]            # Tumor size
    },
    "observations": {
        "conc": [...],        # Drug concentration
        "tumor_size": [...]   # Tumor size
    },
    "metadata": {...}
}

Basic Example: Exponential Growth

import neopkpd

result = neopkpd.simulate_pkpd_disease_progression(
    cl=5.0,
    v=50.0,
    doses=[{"time": 0.0, "amount": 500.0}],
    growth_model="exponential",
    s0=100.0,         # Initial tumor size
    kgrow=0.02,       # Growth rate (1/h) = doubling ~35h
    smax=1000.0,      # Not used for exponential
    alpha=0.0,        # Not used for exponential
    kdrug=0.005,      # Drug kill rate
    t0=0.0,
    t1=336.0,         # 2 weeks
    saveat=[i * 2.0 for i in range(169)]
)

tumor = result['observations']['tumor_size']
t = result['t']

print("Exponential Growth Model:")
print(f"  Initial size: {s0:.0f}")
print(f"  Size at 7 days: {tumor[84]:.1f}")
print(f"  Size at 14 days: {tumor[-1]:.1f}")

Comparing Growth Models

import neopkpd

models = ["exponential", "logistic", "gompertz", "asymptotic"]
s0 = 100.0

print("Growth Model | Day 7 Size | Day 14 Size | Max Size")
print("-" * 55)

for model in models:
    result = neopkpd.simulate_pkpd_disease_progression(
        cl=5.0, v=50.0,
        doses=[],  # No treatment
        growth_model=model,
        s0=s0, kgrow=0.02, smax=1000.0, alpha=1.0, kdrug=0.0,
        t0=0.0, t1=672.0,  # 4 weeks
        saveat=[i * 4.0 for i in range(169)]
    )

    tumor = result['observations']['tumor_size']
    max_size = max(tumor)

    print(f"{model:12s} | {tumor[42]:.1f} | {tumor[84]:.1f} | {max_size:.1f}")

Treatment Effect

import neopkpd

# Weekly dosing
doses = [{"time": i * 168, "amount": 200.0} for i in range(4)]

# No treatment baseline
result_no_tx = neopkpd.simulate_pkpd_disease_progression(
    cl=5.0, v=50.0, doses=[],
    growth_model="gompertz",
    s0=100.0, kgrow=0.02, smax=1000.0, alpha=0.0, kdrug=0.0,
    t0=0.0, t1=672.0,
    saveat=[i * 4.0 for i in range(169)]
)

# With treatment
result_tx = neopkpd.simulate_pkpd_disease_progression(
    cl=5.0, v=50.0, doses=doses,
    growth_model="gompertz",
    s0=100.0, kgrow=0.02, smax=1000.0, alpha=0.0, kdrug=0.01,
    t0=0.0, t1=672.0,
    saveat=[i * 4.0 for i in range(169)]
)

no_tx = result_no_tx['observations']['tumor_size']
tx = result_tx['observations']['tumor_size']
t = result_no_tx['t']

print("Treatment Effect (Gompertz Model):")
print(f"  Day 0: {no_tx[0]:.1f}")
print(f"  Day 28 no treatment: {no_tx[-1]:.1f}")
print(f"  Day 28 with treatment: {tx[-1]:.1f}")
print(f"  Tumor growth inhibition: {(1 - tx[-1]/no_tx[-1]) * 100:.1f}%")

Dose-Response Relationship

import neopkpd

doses_list = [50, 100, 200, 400, 800]

print("Dose (mg) | Final Size | TGI (%)")
print("-" * 40)

# No treatment reference
result_ref = neopkpd.simulate_pkpd_disease_progression(
    cl=5.0, v=50.0, doses=[],
    growth_model="logistic",
    s0=100.0, kgrow=0.02, smax=1000.0, alpha=0.0, kdrug=0.0,
    t0=0.0, t1=336.0,
    saveat=[i * 4.0 for i in range(85)]
)
ref_final = result_ref['observations']['tumor_size'][-1]

for dose in doses_list:
    weekly_doses = [{"time": i * 168, "amount": float(dose)} for i in range(2)]

    result = neopkpd.simulate_pkpd_disease_progression(
        cl=5.0, v=50.0, doses=weekly_doses,
        growth_model="logistic",
        s0=100.0, kgrow=0.02, smax=1000.0, alpha=0.0, kdrug=0.01,
        t0=0.0, t1=336.0,
        saveat=[i * 4.0 for i in range(85)]
    )

    final = result['observations']['tumor_size'][-1]
    tgi = (1 - final / ref_final) * 100

    print(f"{dose:9d} | {final:10.1f} | {tgi:7.1f}")

Complete Response Detection

import neopkpd

# Intensive treatment
doses = [{"time": i * 24, "amount": 300.0} for i in range(14)]

result = neopkpd.simulate_pkpd_disease_progression(
    cl=5.0, v=50.0, doses=doses,
    growth_model="exponential",
    s0=100.0, kgrow=0.02, smax=1000.0, alpha=0.0, kdrug=0.02,
    t0=0.0, t1=672.0,
    saveat=[i * 2.0 for i in range(337)]
)

tumor = result['observations']['tumor_size']
t = result['t']

# Find if tumor shrinks below detection threshold
detection_threshold = 10.0
min_size = min(tumor)
t_min = t[tumor.index(min_size)]

print("Tumor Response:")
print(f"  Initial: {tumor[0]:.1f}")
print(f"  Minimum: {min_size:.2f} at day {t_min/24:.1f}")

if min_size < detection_threshold:
    print("  Response: Complete Response (CR)")
elif min_size < tumor[0] * 0.5:
    print("  Response: Partial Response (PR)")
elif min_size < tumor[0] * 1.25:
    print("  Response: Stable Disease (SD)")
else:
    print("  Response: Progressive Disease (PD)")

Regrowth After Treatment

import neopkpd

# Treatment for 2 weeks, then observe
doses = [{"time": i * 24, "amount": 200.0} for i in range(14)]

result = neopkpd.simulate_pkpd_disease_progression(
    cl=5.0, v=50.0, doses=doses,
    growth_model="gompertz",
    s0=100.0, kgrow=0.03, smax=1000.0, alpha=0.0, kdrug=0.015,
    t0=0.0, t1=1008.0,  # 6 weeks total
    saveat=[i * 4.0 for i in range(253)]
)

tumor = result['observations']['tumor_size']
t = result['t']

print("Tumor Dynamics:")
print(f"  Day 0: {tumor[0]:.1f}")
print(f"  Day 14 (end of treatment): {tumor[84]:.1f}")
print(f"  Day 28: {tumor[168]:.1f}")
print(f"  Day 42: {tumor[-1]:.1f}")

# Time to regrow to initial size
for i, s in enumerate(tumor):
    if i > 84 and s > tumor[0]:
        print(f"  Regrowth to initial size: day {t[i]/24:.0f}")
        break

Combination Treatment

import neopkpd

# Single agent
result_single = neopkpd.simulate_pkpd_disease_progression(
    cl=5.0, v=50.0,
    doses=[{"time": i * 168, "amount": 200.0} for i in range(4)],
    growth_model="logistic",
    s0=100.0, kgrow=0.025, smax=800.0, alpha=0.0, kdrug=0.008,
    t0=0.0, t1=672.0,
    saveat=[i * 4.0 for i in range(169)]
)

# Combination (simulate as higher effective kdrug)
result_combo = neopkpd.simulate_pkpd_disease_progression(
    cl=5.0, v=50.0,
    doses=[{"time": i * 168, "amount": 200.0} for i in range(4)],
    growth_model="logistic",
    s0=100.0, kgrow=0.025, smax=800.0, alpha=0.0, kdrug=0.015,
    t0=0.0, t1=672.0,
    saveat=[i * 4.0 for i in range(169)]
)

single = result_single['observations']['tumor_size']
combo = result_combo['observations']['tumor_size']

print("Single Agent vs Combination:")
print(f"  Single agent day 28: {single[-1]:.1f}")
print(f"  Combination day 28: {combo[-1]:.1f}")
print(f"  Additional benefit: {(single[-1] - combo[-1])/single[-1] * 100:.1f}%")

Survival Surrogate

import neopkpd

# Tumor doubling time as survival surrogate
doses = [{"time": i * 168, "amount": 200.0} for i in range(4)]

result = neopkpd.simulate_pkpd_disease_progression(
    cl=5.0, v=50.0, doses=doses,
    growth_model="exponential",
    s0=100.0, kgrow=0.025, smax=1000.0, alpha=0.0, kdrug=0.01,
    t0=0.0, t1=2016.0,  # 12 weeks
    saveat=[i * 8.0 for i in range(253)]
)

tumor = result['observations']['tumor_size']
t = result['t']

# Time to reach lethal tumor burden
lethal_burden = 1000.0
for i, s in enumerate(tumor):
    if s > lethal_burden:
        print(f"Time to lethal burden: {t[i]/24:.0f} days ({t[i]/168:.1f} weeks)")
        break
else:
    print(f"Tumor below lethal burden at end of simulation")
    print(f"Final tumor size: {tumor[-1]:.1f}")

Equations Summary

Model dS/dt (without drug) Steady State
Exponential \(k_{grow} \cdot S\) Infinite
Linear \(\alpha\) Infinite
Logistic \(k_{grow} \cdot S \cdot (1 - S/S_{max})\) \(S_{max}\)
Gompertz \(k_{grow} \cdot S \cdot \ln(S_{max}/S)\) \(S_{max}\)
Asymptotic \(k_{grow} \cdot (S_{max} - S)\) \(S_{max}\)

Drug effect: \(-k_{drug} \cdot C \cdot S\) (concentration-dependent cell kill)


See Also