Population Pharmacokinetic / Pharmacodynamic Modeling Framework
Project description
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
Monte Carlo — Paracetamol 1000mg (N=1000)
MAP Estimation — Individual Patient
Multi-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.
- In PyPI, create (or open) project
opendose-poppk. - Add a Trusted Publisher with:
- Owner:
redkk123 - Repository:
OpenDose-PopPK - Workflow:
release.yml - Environment:
pypi
- Owner:
- Bump version in
pyproject.tomlandopendose_poppk/__init__.py. - 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4fcdbb7186c861135a4c16c11f3fd3db5a7e940351219fc3132604d7652b0143
|
|
| MD5 |
5df9280375e796bdf7a953b1bbae0884
|
|
| BLAKE2b-256 |
9f3921d635b0868de559992e757c0bdc8ae5654d2e26bfe58b16401b15113393
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d4d59bb234e723efa46c64069299135c78af00d839d5aba0638636c4a3efdf4a
|
|
| MD5 |
f5a2bf8b09f3ea5523a240dcfd2414a3
|
|
| BLAKE2b-256 |
4494595b5c4efd5c8cb9490e3a3b0556c9ff55bc3561b02c9ce2627567ceb0b2
|