Skip to main content

Population Pharmacokinetic / Pharmacodynamic Modeling Framework

Project description

License: MIT Python 3.9+ Code style: black CI Docs Release GitHub release

OpenDose-PopPK 🔬💊

A modular, open-source Python framework for Population Pharmacokinetic-Pharmacodynamic (PopPK/PD) modeling.

The library bridges classical compartmental pharmacology and modern control theory by integrating state-space representation, stochastic Monte Carlo simulations, and Bayesian individual parameter estimation.


✨ Features

  • 1-compartment PK model — first-order analytical solution with state-space formalism
  • Multiple-dose regimen support — repeated dosing at fixed intervals
  • IV bolus and infusion simulation — dedicated intravenous input modes
  • Steady-state metrics — Cmax/trough/AUCτ estimation after repeated dosing
  • Nonlinear PK simulation (Michaelis-Menten) — saturable elimination profiles
  • Emax Hill PD model — sigmoidal pharmacodynamic effects
  • Monte Carlo simulation — inter-individual variability with 90% prediction intervals
  • Covariate modeling — weight, renal function (CrCl), age, hepatic markers (Power Model)
  • MAP estimation — individual Bayesian fitting from sparse observed samples
  • Clinical TDM input validation — robust CSV cleaning with alias mapping and automatic unit normalization
  • Drug dataset validation — schema and value checks for drug parameter CSV
  • Batch TDM fitting — MAP estimation per patient from real-world monitoring tables
  • TDM prediction diagnostics — per-observation predictions and residual error tables
  • Observed-vs-predicted diagnostic plot — quick visual goodness-of-fit check
  • Population PK fitting (naive pooled) — estimate typical PK parameters from TDM datasets
  • Population PK mixed-effects fitting — estimate fixed effects (theta) and random effects (omega/eta)
  • Bootstrap uncertainty for population fit — confidence intervals for F/ka/ke/Vd
  • External validation toolkit — compare model predictions with observed and reference-software concentrations (direct NONMEM/Monolix/Pumas benchmarking requires paid licenses)
  • Web app baseline — lightweight local browser interface for quick PK profile exploration
  • Reproducible validation report — protocol + metrics + limitations in JSON/Markdown
  • Release readiness checks — strict semver/version alignment and asset checks before publishing
  • End-to-end TDM workflow command — run full clinical pipeline in one execution
  • Multi-drug regimen benchmarking — compare Cmax/trough/AUC across selected compounds
  • Mixed-drug TDM fitting — run MAP fits when a single CSV contains multiple drugs
  • Dose recommendation engine — suggest dose for target Cmax/AUC (with covariate adjustment)
  • Regimen dose recommendation — suggest repeated-dose amount for target Cmax/trough
  • Therapeutic-window regimen recommendation — suggest repeated-dose amount for trough/Cmax window
  • Local sensitivity analysis — quantify how PK parameters impact Cmax/AUC
  • Project health report — generate JSON/Markdown diagnostics (dataset + smoke + sensitivity)
  • Dose sweep analysis — evaluate dose-response trends for Cmax/AUC
  • Cohort simulation from CSV — compute patient-level Cmax/AUC with covariate adjustment
  • DrugDatabase — loads and manages parameters from CSV
  • Publication-ready figures — all plots from the companion paper

📊 Results

Population Simulation with Covariates

Covariate Simulation

Monte Carlo — Paracetamol 1000mg (N=1000)

Monte Carlo

MAP Estimation — Individual Patient

MAP Estimation

Multi-Drug Comparison

Drug Comparison


🛠️ Installation

git clone https://github.com/redkk123/OpenDose-PopPK.git
cd OpenDose-PopPK
# Recommended: install the package for local development
pip install -e .
# or install the package (non-editable)
pip install .
# runtime-only dependencies (optional explicit install)
pip install -r requirements.txt
# development tooling (tests/lint/type-check)
pip install -e ".[dev]"

Running tests

python -m pytest -q

On Windows: make.bat test or python -m pytest -q

With coverage: pip install .[dev] then pytest --cov=opendose_poppk --cov-report=term-missing

Latest local validation: March 5, 2026 (Python 3.14.2), python -m pytest -q -> 199 passed.

PyPI publishing (maintainers)

The release pipeline publishes to PyPI via Trusted Publishing (OIDC) when a tag v* is pushed.

  1. In PyPI, create (or open) project opendose-poppk.
  2. Add a Trusted Publisher with:
    • Owner: redkk123
    • Repository: OpenDose-PopPK
    • Workflow: release.yml
    • Environment: pypi
  3. Bump version in pyproject.toml and opendose_poppk/__init__.py.
  4. Create and push a tag, e.g. git tag v1.0.1 && git push origin v1.0.1.

CLI

opendose list-drugs
opendose validate-dataset --output-clean output/tables/drugs_parameters_clean.csv
opendose simulate --drug Paracetamol --n-subjects 200 --t-max 12 --output output/tables/paracetamol_cli.csv
opendose simulate-iv --drug Paracetamol --mode bolus --dose 1000 --output-csv output/tables/paracetamol_iv_bolus.csv
opendose simulate-nonlinear --drug Paracetamol --dose 1000 --vmax 200 --km 15 --output-csv output/tables/paracetamol_nonlinear.csv
opendose steady-state --drug Paracetamol --interval-h 12 --n-doses 20 --output-csv output/tables/paracetamol_steady_state.csv
opendose init-cohort-template --output data/cohort_template.csv
opendose simulate-cohort --drug Paracetamol --input data/cohort.csv --output-csv output/tables/cohort_simulation.csv
opendose sensitivity --drug Paracetamol --dose 1000 --rel-step 0.1 --output-csv output/tables/sensitivity_paracetamol.csv
opendose dose-sweep --drug Paracetamol --doses 250,500,750,1000 --output-csv output/tables/dose_sweep_paracetamol.csv
opendose simulate-regimen --drug Paracetamol --interval-h 12 --n-doses 4 --output-csv output/tables/paracetamol_regimen.csv --plot-png output/figures/paracetamol_regimen.png
opendose fit --drug Paracetamol --times 0.5,1,2,4 --obs 4.2,6.8,7.5,5.9 --weight 80 --crcl 70 --age 55
opendose validate-tdm --input data/tdm.csv --output-clean output/tables/tdm_clean.csv
opendose validate-tdm --input data/tdm_raw.csv --time-unit min --conc-unit ng/mL --dose-unit g --output-clean output/tables/tdm_clean.csv
opendose fit-tdm --drug Paracetamol --input data/tdm.csv --output output/tables/tdm_fit.csv --predictions-csv output/tables/tdm_predictions.csv --plot-png output/figures/tdm_obs_vs_pred.png --report-md output/reports/tdm_fit_report.md
opendose fit-population --input data/tdm.csv --maxiter 2000 --bootstrap-n 200 --output-json output/reports/pop_fit.json
opendose fit-population-mixed --drug Paracetamol --input data/tdm.csv --maxiter 1200 --eta-csv output/tables/pop_mixed_eta.csv --output-json output/reports/pop_mixed_fit.json
opendose init-external-template --output data/external_validation_template.csv
opendose validate-external --drug Paracetamol --input data/external_validation.csv --predictions-csv output/tables/external_predictions.csv --output-json output/reports/external_validation.json
opendose web-app --drug Paracetamol --dose 750 --t-end 12 --output-html output/web/web_app.html --dry-run
opendose validation-report --drug Paracetamol --output-md output/reports/validation_report.md --output-json output/reports/validation_report.json
opendose release-readiness --repo-root . --output-md output/reports/release_readiness.md --strict
opendose init-tdm-template --output data/tdm_template.csv
opendose init-tdm-template --format clinical --output data/tdm_template_clinical.csv
opendose run-tdm-workflow --drug Paracetamol --input data/tdm.csv --outdir output/workflows/tdm_paracetamol
opendose benchmark-regimen --drugs Paracetamol,Ibuprofen,Diazepam --interval-h 12 --n-doses 4 --output-csv output/tables/regimen_benchmark.csv
opendose fit-tdm-mixed --input data/tdm_mixed.csv --output output/tables/tdm_mixed_fit.csv
opendose doctor --strict
opendose project-report --drug Paracetamol --output-md output/reports/project_report.md
opendose recommend-dose --drug Paracetamol --target-cmax 10 --weight 80 --crcl 70 --age 55 --output-json output/reports/dose_recommendation.json
opendose recommend-regimen-dose --drug Paracetamol --target-trough 1.0 --interval-h 12 --n-doses 4 --output-json output/reports/regimen_dose_recommendation.json
opendose recommend-regimen-window --drug Paracetamol --target-trough-min 0.05 --target-cmax-max 12.0 --interval-h 12 --n-doses 4 --strategy midpoint --output-json output/reports/regimen_window_recommendation.json

Drug-specific runnable examples are available in examples/drugs/ (for example, paracetamol.py and ibuprofen.py).

External validation licensing note

Direct one-to-one benchmarking against licensed software (NONMEM/Monolix/Pumas) requires paid licenses. Without licenses, external validation remains limited to public datasets and/or precomputed reference columns (ref_conc).

Cloud CI/CD billing note

If cloud CI/CD billing or credits are unavailable, cloud pipelines may be blocked/intermittent. In this scenario, local test execution (pytest) is the primary validation path until billing is enabled.

Generating figures

python main.py

Figures are saved to figures/. On Windows: make.bat figures


🚀 Quick Start

from opendose_poppk import DrugDatabase, PKModel, PDModel
import numpy as np

# Load parameters from CSV
db   = DrugDatabase("datasets/drugs_parameters.csv")
drug = db.get_drug("Paracetamol")

# Build PK/PD models
pk = PKModel(**drug.pk_kwargs)
pd = PDModel(drug.EC50, drug.n_hill)

# Simulate
t = np.linspace(0, 12, 300)
C = pk.concentration(t, D=drug.dose)
E = pd.effect(C)

# Analytical metrics
cmax, tmax = pk.cmax(D=drug.dose)
auc        = pk.auc(D=drug.dose)
print(f"Cmax = {cmax:.2f} µg/mL at Tmax = {tmax:.2f} h")
print(f"AUC₀→∞ = {auc:.1f} µg·h/mL")

Radioactive Isotope Modeling (Physical Decay)

OpenDose-PopPK supports pharmacokinetic modeling of radioactive pharmaceuticals (e.g., Lu-177, I-131, Y-90) by incorporating physical decay of the isotope.

Theory

For radioactive drugs, the activity (MBq) decays due to both biological elimination and physical decay of the isotope:

$$\frac{dA}{dt} = -\left(k_e + \lambda_{\text{phys}}\right)A(t)$$

where:

  • $k_e$ — biological elimination rate constant (h⁻¹)
  • $\lambda_{\text{phys}}$ — physical decay constant (h⁻¹) = $\frac{\ln(2)}{t_{1/2}}$
  • $t_{1/2}$ — physical half-life of the isotope (hours)

The overall clearance is the sum of biological and physical elimination.

Example: Lu-177 (Lutetium-177)

from opendose_poppk import PKModel
import numpy as np

# Lu-177 half-life: 6.647 days = 159.528 hours
pk = PKModel(
    F=1.0,                      # 100% bioavailability (IV injection)
    ka=0.0,                     # no absorption (IV)
    ke=0.01,                    # biological clearance (h⁻¹)
    Vd=5.0,                     # volume of distribution (L)
    Q=0.5,                      # inter-compartment flow (L/h)
    V2=2.0,                     # peripheral volume (L)
    phys_half_life_h=159.528    # Lu-177 physical half-life in hours
)

# Dose: 7400 MBq (typical therapeutic activity)
t = np.linspace(0, 168, 500)  # 7 days
A = pk.concentration(t, D=7400.0)  # Activity profile (MBq)

# AUC accounts for both biological and physical elimination
auc = pk.auc(D=7400.0)
print(f"AUC₀→∞ = {auc:.1f} MBq·h")

Key Parameters (Unit Consistency)

Parameter Unit Description
D MBq (or Bq) Initial activity dose
t h (hours) Time post-injection
C or A1 MBq or MBq/L Activity in compartment (central, peripheral)
CL L/h Biological clearance rate
V1, V2 L Central and peripheral volumes
Q L/h Inter-compartmental flow
phys_half_life_h h (hours) Physical half-life of isotope
lambda_phys h⁻¹ Physical decay constant = ln(2) / t_1/2

Decay Impact on PK Metrics

Without decay: AUC = F·D / CL
With decay: AUC is reduced; computed numerically: $$\text{AUC}{0→\infty} = \int_0^∞ C(t) , dt \quad \text{(includes} , \lambda{\text{phys}})$$

# Compare models with/without decay
pk_no_decay = PKModel(F=1.0, ka=0.0, ke=0.01, Vd=5.0)
pk_with_decay = PKModel(F=1.0, ka=0.0, ke=0.01, Vd=5.0,
                        phys_half_life_h=159.528)

auc_no_decay = pk_no_decay.auc(D=7400.0)
auc_with_decay = pk_with_decay.auc(D=7400.0)

print(f"AUC without decay: {auc_no_decay:.1f} MBq·h")
print(f"AUC with decay:    {auc_with_decay:.1f} MBq·h")
print(f"Reduction: {100*(auc_no_decay - auc_with_decay)/auc_no_decay:.1f}%")

Balance of Mass (Mass Balance Check)

The model enforces conservation of mass across compartments:

$$\frac{d(A_1 + A_2)}{dt} = -\text{CL} \frac{A_1}{V_1} - \lambda_{\text{phys}}(A_1 + A_2)$$

This ensures:

  • Biological clearance acts only on central concentration
  • Physical decay acts on activity in all compartments
  • Total activity decreases exponentially when no input

Common Radioisotopes

Isotope Half-Life Application
Lu-177 6.647 days Peptide receptor radionuclide therapy (PRRT)
I-131 8.0 days Thyroid cancer, hyperthyroidism
Y-90 64.1 hours Radioembolization, monoclonal antibody therapy
Tc-99m 6.0 hours Diagnostic imaging
F-18 110 minutes PET imaging

Covariate-Adjusted Simulation

from opendose_poppk import CovariateModel, PopulationSimulator

cov = CovariateModel(pk)
sim = PopulationSimulator(pk, pd, cov, dose=drug.dose)

result = sim.run(
    n_subjects=1000,
    t_max=12.0,
    covariates={
        "weight": ("normal", 70.0, 15.0),   # kg
        "crcl":   ("normal", 90.0, 30.0),   # mL/min — renal function
        "age":    ("normal", 45.0, 15.0),   # years
    }
)

🧑‍⚕️ Individual MAP Estimation

from opendose_poppk import MAPEstimator
import numpy as np

t_obs = np.array([0.5, 1.0, 2.0, 4.0, 6.0, 8.0])
c_obs = np.array([4.2, 6.8, 7.5, 5.9, 4.1, 2.8])

est = MAPEstimator(pk, covariate_model=cov, sigma_obs=0.8)
res = est.fit(
    times=t_obs, obs=c_obs,
    patient_covariates={"weight": 95.0, "crcl": 45.0, "age": 68.0},
    dose=drug.dose
)

print(res["params_map"])

📐 Mathematical Background

PK Model (Eq. 1)

$$C(t) = \frac{F \cdot D \cdot k_a}{V_d(k_a - k_e)}\left(e^{-k_e t} - e^{-k_a t}\right)$$

State-Space Representation (Section 3)

$$\dot{\mathbf{x}} = \mathbf{A}\mathbf{x} + \mathbf{B}u, \quad \mathbf{A} = \begin{bmatrix}-k_a & 0 \ k_a & -k_e\end{bmatrix}$$

Eigenvalues $\lambda = {-k_a, -k_e}$ guarantee asymptotic stability.

Covariate Power Model

$$\theta_i = \theta_{pop} \cdot \prod_k\left(\frac{COV_k}{ref_k}\right)^{\beta_k} \cdot e^{\eta_i}, \quad \eta_i \sim \mathcal{N}(0, \omega^2)$$


📁 Project Structure

OpenDose-PopPK/
├── opendose_poppk/         ← Core package (PK, PD, covariate, population, bayesian)
├── main.py                 ← Full pipeline (generates all figures)
├── docs/                   ← Sphinx documentation
├── notebooks/
│   └── demo_paracetamol.ipynb
├── datasets/
│   └── drugs_parameters.csv
└── figures/
    ├── monte_carlo_paracetamol.png
    ├── drug_comparison_panel.png
    ├── covariate_simulation.png
    └── map_estimation.png

📚 Documentation

Full documentation is available at opendose-poppk.readthedocs.io, GitHub Pages, or build locally:

pip install -e ".[docs]"
sphinx-build -b html docs docs/_build/html

📜 Citation

If you use this framework in your research, please cite: GitHub citation metadata is available in CITATION.cff.

@article{gomes2026opendose,
  title  = {OpenDose-PopPK: A Modular Open-Source Framework for
             Population Pharmacokinetic-Pharmacodynamic Modeling},
  author = {Gomes, Angelo Gabriel C. Silva},
  year   = {2026},
  note   = {arXiv preprint}
}

🤝 Contributing

See CONTRIBUTING.md for guidelines. CHANGELOG.md lists version history.


👤 Author

Angelo Gabriel C. Silva Gomes
Federal Institute of Brasília (IFB)
angelogabriel860@gmail.com

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

opendose_poppk-1.1.1.tar.gz (83.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

opendose_poppk-1.1.1-py3-none-any.whl (64.4 kB view details)

Uploaded Python 3

File details

Details for the file opendose_poppk-1.1.1.tar.gz.

File metadata

  • Download URL: opendose_poppk-1.1.1.tar.gz
  • Upload date:
  • Size: 83.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for opendose_poppk-1.1.1.tar.gz
Algorithm Hash digest
SHA256 4fcdbb7186c861135a4c16c11f3fd3db5a7e940351219fc3132604d7652b0143
MD5 5df9280375e796bdf7a953b1bbae0884
BLAKE2b-256 9f3921d635b0868de559992e757c0bdc8ae5654d2e26bfe58b16401b15113393

See more details on using hashes here.

File details

Details for the file opendose_poppk-1.1.1-py3-none-any.whl.

File metadata

  • Download URL: opendose_poppk-1.1.1-py3-none-any.whl
  • Upload date:
  • Size: 64.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for opendose_poppk-1.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 d4d59bb234e723efa46c64069299135c78af00d839d5aba0638636c4a3efdf4a
MD5 f5a2bf8b09f3ea5523a240dcfd2414a3
BLAKE2b-256 4494595b5c4efd5c8cb9490e3a3b0556c9ff55bc3561b02c9ce2627567ceb0b2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page