Skip to main content

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

Metadata

Release files for opendose-poppk 1.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for opendose-poppk 1.1.1
File Size Uploaded
opendose_poppk-1.1.1.tar.gz 83.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for opendose-poppk 1.1.1
File Interpreter ABI Platform
opendose_poppk-1.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 147.5 kB

Release files / opendose_poppk-1.1.1.tar.gz

Download URL opendose_poppk-1.1.1.tar.gz
Size 83.2 kB
Tags Source
SHA-256 checksum
How to use checksums
4fcdbb7186c861135a4c16c11f3fd3db5a7e940351219fc3132604d7652b0143
BLAKE2b-256 checksum
How to use checksums
9f3921d635b0868de559992e757c0bdc8ae5654d2e26bfe58b16401b15113393
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.2

Release files / opendose_poppk-1.1.1-py3-none-any.whl

Download URL opendose_poppk-1.1.1-py3-none-any.whl
Size 64.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d4d59bb234e723efa46c64069299135c78af00d839d5aba0638636c4a3efdf4a
BLAKE2b-256 checksum
How to use checksums
4494595b5c4efd5c8cb9490e3a3b0556c9ff55bc3561b02c9ce2627567ceb0b2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.2

Release history Release notifications | RSS feed

This release

1.1.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page