Pharmacokinetic and Pharmacodynamic Modelling

Pharmacokinetic and pharmacodynamic modelling and simulation - non-compartmental analysis, compartmental and population PK, PK/PD and exposure-response, TMDD, PBPK orientation, bioequivalence, allometric scaling and first-in-human dose, drug interaction prediction, and Bayesian therapeutic drug monitoring.

How to use it

  1. Hit Copy SKILL.md — or use the Claude Code line below to get every file.
  2. Claude: ⋯ → Download .md, then Customize → Skills → Add → Upload skill.
    ChatGPT: make a Project and paste it into Instructions.
    Neither? Paste it at the top of a new chat — it works for that chat.
  3. Describe your job in plain words. The AI follows the skill from there.
Claude Code — installs the whole folder, not just SKILL.md
npx degit K-Dense-AI/scientific-agent-skills/skills/pkpd-modeling#main ~/.claude/skills/pkpd-modeling

For one project only, change the path to .claude/skills/pkpd-modeling. This skill also uses tdm_bayes.py, nca.py, fit_compartmental.py, simulate_regimen.py, check_popk_dataset.py, exposure_response.py — copying SKILL.md alone won't be enough. See the folder on GitHub.

Not working?
  • Check which app you pasted it into — the steps above name the right one.
  • Some skills need the paid tier of Claude or ChatGPT.
Step-by-step guide with screenshots · Ask in the forum

Paste into Claude, ChatGPT or Cursor.

Show the full text399 lines
pkpd-modeling/SKILL.md399 lines21.5 KBpushed 19d agoRawView on GitHub

Pharmacokinetic and Pharmacodynamic Modelling

When to use

Any question about what the body does to a drug or what the drug does to the body: deriving exposure metrics from concentration-time data, fitting a structural model, building or checking a population analysis, choosing a dose or a regimen, relating exposure to effect, comparing formulations, or scaling to a new population.

The three rules

1. Fix the exposure metric and the analysis population before computing anything. AUC(0-t), AUC(0-inf), AUC(0-tau) at steady state, and Cavg are different quantities and answer different questions. So do AUCinf based on observed versus predicted Clast. Choosing after seeing the numbers is how a negative study becomes positive.

2. Structural model, variability model, and covariate model are three separate decisions. They get conflated constantly — an extra compartment added to absorb what is really unmodelled between-occasion variability, a covariate added to fix what is really a misspecified absorption model. Diagnose which one is wrong before changing any of them.

3. Convergence is not identifiability. A fit that converges with 200% relative standard error on a parameter, or a correlation of 0.99 between two, has told you the data cannot separate them. Every fitting script here reports both and flags them, because the parameter table alone looks fine in exactly this situation.

Scope

This skill computes, diagnoses, and structures. It does not decide that a formulation is bioequivalent, select a dose for a trial, recommend a dose for a patient, conclude that a drug has no QT liability, or replace a qualified pharmacometrician, clinical pharmacologist, or the regulatory review. The scripts report; none of them concludes. tdm_bayes.py in particular is a modelling aid — any change to a patient's regimen is the treating clinician's decision.

Scripts

cd skills/pkpd-modeling/scripts
Script Question answered
nca.py What are the exposure metrics, and is the terminal phase good enough to report them?
fit_compartmental.py Which structural model do these data support, and are its parameters identifiable?
simulate_regimen.py What does this regimen do at steady state, and to what fraction of the population?
check_popk_dataset.py Will NONMEM read this dataset the way I think it will?
exposure_response.py Is there an exposure-response relationship, and is the plateau in the data?
bioequivalence.py Does the 90% CI meet the criterion, and which criterion applies?
allometry_and_fih.py What is the starting dose, or the dose in a smaller/younger population?
ddi_static.py Does the in vitro data trigger a clinical DDI study under ICH M12?
tdm_bayes.py What are this patient's individual parameters from their measured levels?

All take --format table|tsv|json. Data goes to stdout, provenance and findings to stderr, so > out.tsv keeps them separate. Exit code is 0 for no findings, 1 when findings were raised, 2 for bad input, so any of them can gate a workflow.

Two private modules carry the shared machinery: _models.py (analytical solutions for linear mammillary models, plus integrated Michaelis-Menten, TMDD and indirect-response structures) and _common.py (I/O and reporting). Import them rather than re-deriving a Bateman function.

Workflow

1. Non-compartmental analysis

python3 nca.py -i profile.csv --dose 100 --route extravascular --partial-auc 0-24

Four choices decide the answer and are usually left implicit. This script makes all four explicit: --auc-method (default linup-logdown), --blq-rule, --lambda-z-points or an explicit --lambda-z-window, and whether you report auc_inf_obs or auc_inf_pred.

Lambda_z selection uses the standard rule: start from the last three quantifiable points, extend backwards, keep the longer window only if adjusted r-squared improves by more than 0.0001. Plain r-squared can only rise as points are added, so it would always pick the longest window. Points at or before Tmax are never eligible — including Tmax fits the tail of absorption and biases half-life, Vz and AUCinf downward.

On a noiseless simulated one-compartment oral profile with CL/F = 5, V/F = 20, ka = 1.2:

id  cmax     tmax  auc_last  lambda_z  t_half   r2_adj  auc_inf_obs  pct_auc_extrap  cl_f     vz_f
1   3.29678  1.5   19.8737   0.25      2.77259  1       19.8739      0.000781037     5.03173  20.1269

The 0.6% overestimate of CL/F is the trapezoidal rule on a sparsely sampled absorption phase, not an error — it is the irreducible bias of NCA on that sampling schedule, and it is why NCA and compartmental estimates of clearance never agree exactly.

The findings are the point. A steady-state profile truncated at tau produces:

finding: subject A: 25.2% of AUCinf is extrapolated (above 20%); AUCinf is driven by the
         lambda_z fit, not by data
finding: subject A: lambda_z window spans 0.58 half-lives (below 2.0); the terminal phase may
         not have been reached

Both are correct and both are routinely ignored. At steady state the reportable exposure metric is AUC(0-tau), not AUCinf; the script computes AUCinf anyway and tells you not to trust it.

2. Compartmental fitting and model selection

python3 fit_compartmental.py -i profile.csv --dose 500 --route iv-bolus --compare 1cmt,2cmt,3cmt

Parameters are estimated on the log scale, so they cannot go negative and their confidence intervals come out asymmetric. Weighting defaults to 1/y2 (constant CV), which is the right default for PK and the wrong one for a homoscedastic PD endpoint.

Fitting simulated two-compartment data (CL 4, V1 12, Q 6, V2 40, 8% proportional error):

model  parameters  wssr       aic       bic       f_vs_simpler  f_p_value    compared_with
1cmt   2           3.13201    -19.4956  -18.0795  n/a           n/a          n/a
2cmt   4           0.0309579  -84.7477  -81.9155  550.936       9.38016e-12  1cmt
3cmt   6           0.0232859  -85.0193  -80.771   1.4826        0.27762      2cmt

AIC picks the three-compartment model. BIC and the F test both reject it. AIC's fixed penalty of 2 per parameter is weak at this sample size, and it selects the overparameterised model more often than practitioners expect. The parameter table settles it:

finding: fit: Q3 has 98% RSE - not estimable from these data at this model size
finding: fit: V3 has 71% RSE - not estimable from these data at this model size

The one-compartment fit meanwhile earns:

finding: fit: residual signs are not random (runs test p = 0.0036) - a structural
         misspecification, which no amount of reweighting will fix

That distinction — structural misspecification versus a wrong error model — is the one to get right. A residual-versus-time plot with runs of the same sign means the model shape is wrong. Heteroscedastic residuals with random signs mean the weighting is wrong. Reweighting the first case hides it without fixing it.

3. Population PK

Check the dataset before running anything. This is where the time actually goes.

python3 check_popk_dataset.py -i nmdata.csv --covariates WT,CRCL --time-varying WT

The defects that matter are the silent ones. NM-TRAN does not reject a non-numeric DV — it reads BLQ as zero and fits it as a genuine zero concentration. A blank covariate becomes 0, so a missing body weight becomes a 0 kg patient. ADDL without II places no additional doses. Records sharing a timestamp are applied in file order, so whether a level is pre- or post-dose depends on which row came first. None of these stop a run.

severity  check                            detail
error     non-numeric DV                   DV contains text... NM-TRAN reads them as 0
error     subject with no dose             1 subject(s) have observations but no dose: 2
error     TIME not sorted                  1 subject(s) have out-of-order TIME: 1
error     covariate WT missing             1 record(s) have no value...
warning   duplicate TIME within a subject  NONMEM applies them in file order...

For the estimation itself, this skill does not reimplement NLME — see references/population-pk.md for estimation methods, the BLQ M1-M7 methods, covariate model building, and the diagnostics that decide whether a model is acceptable, and references/software-ecosystem.md for which tool to reach for.

4. Simulation and regimen selection

python3 simulate_regimen.py --cl 5 --v 40 --dose 500 --interval 12 --n-doses 10 --steady-state
python3 simulate_regimen.py --cl 5 --v 40 --dose 500 --interval 12 --n-doses 10 \
    --simulate 2000 --omega-cl 0.35 --omega-v 0.25 --target-trough 4.0

Deterministic simulation answers "what does the typical patient look like", which is almost never the question:

metric             p5        p25      median   p75      p95      geo_mean
peak               11.861    14.6018  16.6702  19.1476  22.9339  16.6453
trough             0.863226  2.15191  3.64412  5.49655  9.21053  3.27035

target       fraction_attaining
trough >= 4  0.444

The typical trough is 3.6 and the target is 4, so 44% of the population attains it. A regimen tuned on the typical patient leaves about half the population on the wrong side of the target. Reported attainment is still optimistic here: this is between-subject variability only, with no residual or between-occasion component.

Linear models are solved analytically and superposed, which is exact. --nonlinear switches to integrated Michaelis-Menten elimination, where superposition is invalid and multiple-dose behaviour cannot be inferred from a single dose at all.

5. Exposure-response

python3 exposure_response.py --emax -i er.csv --sigmoid
python3 exposure_response.py --cqtc -i qt.csv --cmax 250

The Emax fit reports fraction_of_emax_reached and flags a fit whose plateau is outside the data. When the highest observed exposure reaches only a third of the estimated Emax, Emax and EC50 are extrapolations that are strongly correlated with each other; quoting them as independent estimates is not supportable, and a "linear" exposure-response is simply the low-concentration limb of the same curve.

--cqtc evaluates the upper bound of the two-sided 90% confidence interval of predicted placebo-corrected change-from-baseline QTc against the 10 ms threshold, which is the question ICH E14 actually asks. A point estimate, or a 95% interval, answers a different one. The bundled model is an ordinary linear regression for screening; a submission-grade C-QTc analysis needs a mixed model with random intercept and slope per subject.

Every mode carries the same caveat, because it is the one that gets forgotten: patients are randomised to dose, not to exposure. Exposure-response across quantiles is observational even inside a randomised trial, and can reflect the covariates that drive clearance.

6. Bioequivalence

python3 bioequivalence.py -i be.csv --design 2x2 --metric AUC
python3 bioequivalence.py -i be.csv --design replicate --metric Cmax --scaling both
python3 bioequivalence.py --power --cv 0.30 --gmr 0.95 --target-power 0.80

Three criteria share the word "bioequivalence" and are not interchangeable: average BE (90% CI inside 80.00-125.00%), EMA's ABEL (limits widened as a function of CVwR, capped at 69.84-143.19%, point estimate still within 80-125%), and FDA's RSABE (a scaled linearised bound via Hyslop's method, not an interval at all). --scaling refuses to run on a 2x2 design:

error: reference-scaling requires --design replicate. High observed variability in a 2x2 study
does not license widening: without replicated reference administrations there is no estimate of
within-subject reference variability to scale to.

Sample size reproduces the published tables exactly (CV 30%, GMR 0.95, 80% power → N = 40 for a 2x2). Power is computed by integrating over the sampling distribution of the estimated standard deviation rather than treating the standard error as known — the normal approximation overstates power at realistic sample sizes. Note that N is driven far more by the assumed GMR than by CV; assuming 1.00 instead of 0.95 roughly halves the calculated N and is the usual reason a BE study comes in underpowered.

7. Scaling, paediatrics, and first-in-human

python3 allometry_and_fih.py --scale --cl 5 --weight-from 70 --weight-to 6 --pma-weeks 44
python3 allometry_and_fih.py --fih --noael rat=50,dog=10 --safety-factor 10

Scaling by size alone below about 2 years of age overpredicts clearance, in a neonate by several fold, because clearance is limited by enzyme and renal maturation rather than by size. Supplying --pma-weeks adds the Anderson-Holford sigmoidal maturation term; omitting it below 20 kg raises a finding.

parameter  reference  exponent  size_scaled  maturation_factor  final
CL         5          0.75      0.792063     0.30634            0.242641
V          40         1         3.42857      1                  3.42857

Size alone would predict 0.79 L/h; with maturation at 44 weeks post-menstrual age it is 0.24 L/h, a 3.3-fold difference. Volume is not matured — maturation describes eliminating capacity, not distribution space.

--fih uses the body-surface-area conversion from FDA's 2005 maximum-safe-starting-dose guidance and always emits a finding that a NOAEL-derived MRSD is not sufficient on its own for agonist immunomodulators: compute MABEL with --mabel and take the lower value.

8. Drug interactions

python3 ddi_static.py --basic --ki 0.5 --imax 2.0 --fu 0.05 --dose 0.4
python3 ddi_static.py --msm --ki 0.5 --imax 2.0 --fu 0.05 --dose 0.4 --fm 0.9 --fg 0.7

ICH M12 basic models with their cut-offs (R1 ≥ 1.02 hepatic, ≥ 11 intestinal; R2 ≥ 1.25 for TDI; R3 ≤ 0.8 for induction; transporter cut-offs by site), plus the mechanistic static model. The basic models are deliberately conservative: a negative is meaningful, a positive is a trigger for further work, not a prediction of clinical magnitude.

The mechanistic static model reports the ceiling alongside the prediction:

note: With fm = 0.9, no inhibitor of this pathway can raise the victim AUC above 10.00-fold. If
the prediction approaches that ceiling, fm is doing more work than the inhibition constants.

fm and Fg dominate the answer far more than the inhibition constants, and are usually the least well established numbers in the calculation.

9. Therapeutic drug monitoring

python3 tdm_bayes.py --model vancomycin-adult --weight 80 --crcl 75 \
    --dose 1500 --interval 12 --level [email protected] --level 42@2 --target-auc24 500

MAP Bayesian estimation shrinks towards the population when the data are uninformative and follows the data when they are not, which is why it beats both a trough read against population parameters and log-linear regression on two points. A single level raises a finding: it cannot separate clearance from volume, and whichever parameter the sample is uninformative about has simply returned its prior.

The bundled vancomycin parameterisation is explicitly labelled illustrative. Substitute a model validated in your population before the output means anything.

Software ecosystem

Verified against live sources on 2026-07-27; see references/software-ecosystem.md for the full map and references/source-ledger.md for provenance.

  • Pharmpy 2.1.1 (2026-05-19) is the practical Python entry point — model-agnostic, drives NONMEM/nlmixr2/rxode2, and ships 19 run_* tools including run_amd, run_modelsearch, run_covsearch, run_structsearch, run_pdsearch, run_modelrank, run_vpc and run_qa. Two breaking changes are recent enough to catch you out: 2.0.0 (2026-02-12) changed dataset row indices to start at 1, and 2.1.0 (2026-05-08) renamed add_placebo_model to set_placebo_model and now requires numpy ≥ 2.
  • NONMEM 7.6 (user guides dated November 2025) remains the regulatory default. New since 7.5: ADVAN16 (RADAR5 implicit Runge-Kutta for stiff delay differential equations), ADVAN17 (stiff delay differential-algebraic), NUTS Bayesian sampling, and SAEM storage of individual samples.
  • nlmixr2 (requires rxode2 ≥ 5.0.0) is the credible open-source NLME alternative; babelmixr2 and monolix2rx translate models between it, NONMEM and Monolix.
  • PKPy (PeerJ, 2025) is a Python popPK framework but is GitHub-only — not on PyPI, so uv pip install pkpy fails. chi-drm (1.0.3) is on PyPI for Bayesian PKPD.
  • Open Systems Pharmacology Suite v12 (PK-Sim/MoBi) is the open-source PBPK platform; Simcyp and GastroPlus are the commercial ones. ospsuite is R-only and needs .NET 8.

Python has no mature NCA or NLME package of regulatory standing. That gap is why this skill ships its own validated NCA and fitting implementations rather than wrapping one.

What this skill exists to prevent

  1. Lambda_z chosen by plain r-squared, or fitted through Tmax.
  2. AUCinf reported from a profile where 25% of it was extrapolated.
  3. AIC allowed to select a compartment whose intercompartmental clearance has 98% RSE.
  4. Reweighting used to fix non-random residuals, which are a structural problem.
  5. BLQ left in a DV column, where NM-TRAN reads it as a real zero.
  6. A regimen chosen on the typical patient, with no attainment estimate for the population.
  7. Emax and EC50 quoted as independent estimates when the plateau was never observed.
  8. Reference-scaled bioequivalence limits applied to a 2x2 study.
  9. Allometric scaling to a neonate with no maturation term.
  10. An MRSD from a NOAEL used as the starting dose for an agonist immunomodulator.

References

  • references/nca-conventions.md — parameter definitions, lambda_z rules, BLQ handling, steady state
  • references/structural-models.md — closed-form solutions, parameterisations, NONMEM ADVAN/TRANS map
  • references/population-pk.md — NLME estimation, covariate building, BLQ M1-M7, diagnostics, VPC
  • references/pd-and-exposure-response.md — Emax, indirect response, effect compartment, ER analysis
  • references/tmdd-and-biologics.md — TMDD approximations, monoclonal antibody PK, immunogenicity
  • references/pbpk.md — when PBPK earns its cost, platforms, and what verification requires
  • references/bioequivalence.md — designs, ABE/ABEL/RSABE, ICH M13 series, highly variable drugs
  • references/special-populations.md — paediatrics, renal and hepatic impairment, obesity, pregnancy
  • references/dataset-standards.md — CDISC PC/PP and ADPC/ADPP, NONMEM data items, common defects
  • references/ddi-and-qt.md — ICH M12 stepwise assessment, static models, ICH E14/S7B C-QTc
  • references/antimicrobial-and-tdm.md — PK/PD indices, PTA/CFR, vancomycin AUC-guided dosing, MIPD
  • references/software-ecosystem.md — every tool, what it is for, licensing, and verified versions
  • references/regulatory-guidance.md — the guidance ledger with dates, status, and what each requires
  • references/source-ledger.md — provenance and research dates for every claim in this skill

Assets

  • assets/popk-analysis-plan.md — population analysis plan structure, with the decisions stated up front
  • assets/nca-reporting-checklist.md — what an NCA report has to state for the numbers to be interpretable

Citing Scientific Agent Skills

This skill is part of Scientific Agent Skills by K-Dense. If it materially contributed to a manuscript, report, presentation, or code release, add the paper to the references or software section and tell the user you did so:

Kassis, T., Agarwal, V., He, Y., Patel, D., & Brueckner, A. M. (2026). Scientific Agent Skills: A Library of Procedural Knowledge for Research Agents. arXiv:2609.00065. https://doi.org/10.48550/arXiv.2609.00065

Always cite the current version. The DOI and https://arxiv.org/abs/2609.00065 resolve to the latest arXiv version, so never append a version suffix such as v1. When network access is available, fetch https://arxiv.org/abs/2609.00065 (or http://export.arxiv.org/api/query?id_list=2609.00065) before writing the reference and take the author list, year, and version from that record. If the record lists a journal reference or publisher DOI, cite the published version instead.

1---
2name: pkpd-modeling
3description: Pharmacokinetic and pharmacodynamic modelling and simulation - non-compartmental analysis, compartmental and population PK, PK/PD and exposure-response, TMDD, PBPK orientation, bioequivalence, allometric scaling and first-in-human dose, drug interaction prediction, and Bayesian therapeutic drug monitoring. Use when analysing concentration-time data, deriving exposure metrics, fitting PK or PD models, or evaluating dosing regimens. Triggers include "pharmacokinetics", "pharmacodynamics", "PK/PD", "NCA", "non-compartmental", "AUC", "Cmax", "lambda z", "half-life", "clearance", "volume of distribution", "compartmental model", "population PK", "popPK", "NONMEM", "nlmixr2", "Pharmpy", "Monolix", "exposure-response", "Emax", "EC50", "indirect response", "effect compartment", "TMDD", "PBPK", "bioequivalence", "RSABE", "ABEL", "allometric scaling", "first-in-human", "MABEL", "drug-drug interaction", "DDI", "ICH M12", "concentration-QTc", "therapeutic drug monitoring", "MIPD", and "dosing regimen".
4license: MIT
5compatibility: Requires Python 3.11+ with numpy and scipy. No network access and no proprietary software. The estimation tools this skill orients you towards (NONMEM, Monolix, Phoenix, Simcyp, GastroPlus) are licensed separately and are never invoked by these scripts.
6allowed-tools: Read Write Edit Bash
7metadata:
8 version: "1.2"
9 skill-author: K-Dense Inc.
10 last-reviewed: "2026-07-27"
11---
12 
13# Pharmacokinetic and Pharmacodynamic Modelling
14 
15## When to use
16 
17Any question about what the body does to a drug or what the drug does to the body: deriving
18exposure metrics from concentration-time data, fitting a structural model, building or checking a
19population analysis, choosing a dose or a regimen, relating exposure to effect, comparing
20formulations, or scaling to a new population.
21 
22## The three rules
23 
24**1. Fix the exposure metric and the analysis population before computing anything.** AUC(0-t),
25AUC(0-inf), AUC(0-tau) at steady state, and Cavg are different quantities and answer different
26questions. So do AUCinf based on observed versus predicted Clast. Choosing after seeing the
27numbers is how a negative study becomes positive.
28 
29**2. Structural model, variability model, and covariate model are three separate decisions.** They
30get conflated constantly — an extra compartment added to absorb what is really unmodelled
31between-occasion variability, a covariate added to fix what is really a misspecified absorption
32model. Diagnose which one is wrong before changing any of them.
33 
34**3. Convergence is not identifiability.** A fit that converges with 200% relative standard error
35on a parameter, or a correlation of 0.99 between two, has told you the data cannot separate them.
36Every fitting script here reports both and flags them, because the parameter table alone looks
37fine in exactly this situation.
38 
39## Scope
40 
41This skill computes, diagnoses, and structures. It does **not** decide that a formulation is
42bioequivalent, select a dose for a trial, recommend a dose for a patient, conclude that a drug has
43no QT liability, or replace a qualified pharmacometrician, clinical pharmacologist, or the
44regulatory review. The scripts report; none of them concludes. `tdm_bayes.py` in particular is a
45modelling aid — any change to a patient's regimen is the treating clinician's decision.
46 
47## Scripts
48 
49```bash
50cd skills/pkpd-modeling/scripts
51```
52 
53| Script | Question answered |
54| --- | --- |
55| `nca.py` | What are the exposure metrics, and is the terminal phase good enough to report them? |
56| `fit_compartmental.py` | Which structural model do these data support, and are its parameters identifiable? |
57| `simulate_regimen.py` | What does this regimen do at steady state, and to what fraction of the population? |
58| `check_popk_dataset.py` | Will NONMEM read this dataset the way I think it will? |
59| `exposure_response.py` | Is there an exposure-response relationship, and is the plateau in the data? |
60| `bioequivalence.py` | Does the 90% CI meet the criterion, and which criterion applies? |
61| `allometry_and_fih.py` | What is the starting dose, or the dose in a smaller/younger population? |
62| `ddi_static.py` | Does the in vitro data trigger a clinical DDI study under ICH M12? |
63| `tdm_bayes.py` | What are this patient's individual parameters from their measured levels? |
64 
65All take `--format table|tsv|json`. Data goes to stdout, provenance and findings to stderr, so
66`> out.tsv` keeps them separate. Exit code is `0` for no findings, `1` when findings were raised,
67`2` for bad input, so any of them can gate a workflow.
68 
69Two private modules carry the shared machinery: `_models.py` (analytical solutions for linear
70mammillary models, plus integrated Michaelis-Menten, TMDD and indirect-response structures) and
71`_common.py` (I/O and reporting). Import them rather than re-deriving a Bateman function.
72 
73## Workflow
74 
75### 1. Non-compartmental analysis
76 
77```bash
78python3 nca.py -i profile.csv --dose 100 --route extravascular --partial-auc 0-24
79```
80 
81Four choices decide the answer and are usually left implicit. This script makes all four explicit:
82`--auc-method` (default `linup-logdown`), `--blq-rule`, `--lambda-z-points` or an explicit
83`--lambda-z-window`, and whether you report `auc_inf_obs` or `auc_inf_pred`.
84 
85Lambda_z selection uses the standard rule: start from the last three quantifiable points, extend
86backwards, keep the longer window only if **adjusted** r-squared improves by more than 0.0001.
87Plain r-squared can only rise as points are added, so it would always pick the longest window.
88Points at or before Tmax are never eligible — including Tmax fits the tail of absorption and
89biases half-life, Vz and AUCinf downward.
90 
91On a noiseless simulated one-compartment oral profile with CL/F = 5, V/F = 20, ka = 1.2:
92 
93```
94id cmax tmax auc_last lambda_z t_half r2_adj auc_inf_obs pct_auc_extrap cl_f vz_f
951 3.29678 1.5 19.8737 0.25 2.77259 1 19.8739 0.000781037 5.03173 20.1269
96```
97 
98The 0.6% overestimate of CL/F is the trapezoidal rule on a sparsely sampled absorption phase, not
99an error — it is the irreducible bias of NCA on that sampling schedule, and it is why NCA and
100compartmental estimates of clearance never agree exactly.
101 
102The findings are the point. A steady-state profile truncated at tau produces:
103 
104```
105finding: subject A: 25.2% of AUCinf is extrapolated (above 20%); AUCinf is driven by the
106 lambda_z fit, not by data
107finding: subject A: lambda_z window spans 0.58 half-lives (below 2.0); the terminal phase may
108 not have been reached
109```
110 
111Both are correct and both are routinely ignored. At steady state the reportable exposure metric is
112AUC(0-tau), not AUCinf; the script computes AUCinf anyway and tells you not to trust it.
113 
114### 2. Compartmental fitting and model selection
115 
116```bash
117python3 fit_compartmental.py -i profile.csv --dose 500 --route iv-bolus --compare 1cmt,2cmt,3cmt
118```
119 
120Parameters are estimated on the log scale, so they cannot go negative and their confidence
121intervals come out asymmetric. Weighting defaults to `1/y2` (constant CV), which is the right
122default for PK and the wrong one for a homoscedastic PD endpoint.
123 
124Fitting simulated two-compartment data (CL 4, V1 12, Q 6, V2 40, 8% proportional error):
125 
126```
127model parameters wssr aic bic f_vs_simpler f_p_value compared_with
1281cmt 2 3.13201 -19.4956 -18.0795 n/a n/a n/a
1292cmt 4 0.0309579 -84.7477 -81.9155 550.936 9.38016e-12 1cmt
1303cmt 6 0.0232859 -85.0193 -80.771 1.4826 0.27762 2cmt
131```
132 
133**AIC picks the three-compartment model. BIC and the F test both reject it.** AIC's fixed penalty
134of 2 per parameter is weak at this sample size, and it selects the overparameterised model more
135often than practitioners expect. The parameter table settles it:
136 
137```
138finding: fit: Q3 has 98% RSE - not estimable from these data at this model size
139finding: fit: V3 has 71% RSE - not estimable from these data at this model size
140```
141 
142The one-compartment fit meanwhile earns:
143 
144```
145finding: fit: residual signs are not random (runs test p = 0.0036) - a structural
146 misspecification, which no amount of reweighting will fix
147```
148 
149That distinction — structural misspecification versus a wrong error model — is the one to get
150right. A residual-versus-time plot with runs of the same sign means the *model shape* is wrong.
151Heteroscedastic residuals with random signs mean the *weighting* is wrong. Reweighting the first
152case hides it without fixing it.
153 
154### 3. Population PK
155 
156Check the dataset before running anything. This is where the time actually goes.
157 
158```bash
159python3 check_popk_dataset.py -i nmdata.csv --covariates WT,CRCL --time-varying WT
160```
161 
162The defects that matter are the silent ones. NM-TRAN does not reject a non-numeric DV — it reads
163`BLQ` as zero and fits it as a genuine zero concentration. A blank covariate becomes 0, so a
164missing body weight becomes a 0 kg patient. `ADDL` without `II` places no additional doses.
165Records sharing a timestamp are applied in file order, so whether a level is pre- or post-dose
166depends on which row came first. None of these stop a run.
167 
168```
169severity check detail
170error non-numeric DV DV contains text... NM-TRAN reads them as 0
171error subject with no dose 1 subject(s) have observations but no dose: 2
172error TIME not sorted 1 subject(s) have out-of-order TIME: 1
173error covariate WT missing 1 record(s) have no value...
174warning duplicate TIME within a subject NONMEM applies them in file order...
175```
176 
177For the estimation itself, this skill does not reimplement NLME — see
178`references/population-pk.md` for estimation methods, the BLQ M1-M7 methods, covariate model
179building, and the diagnostics that decide whether a model is acceptable, and
180`references/software-ecosystem.md` for which tool to reach for.
181 
182### 4. Simulation and regimen selection
183 
184```bash
185python3 simulate_regimen.py --cl 5 --v 40 --dose 500 --interval 12 --n-doses 10 --steady-state
186python3 simulate_regimen.py --cl 5 --v 40 --dose 500 --interval 12 --n-doses 10 \
187 --simulate 2000 --omega-cl 0.35 --omega-v 0.25 --target-trough 4.0
188```
189 
190Deterministic simulation answers "what does the typical patient look like", which is almost never
191the question:
192 
193```
194metric p5 p25 median p75 p95 geo_mean
195peak 11.861 14.6018 16.6702 19.1476 22.9339 16.6453
196trough 0.863226 2.15191 3.64412 5.49655 9.21053 3.27035
197 
198target fraction_attaining
199trough >= 4 0.444
200```
201 
202The typical trough is 3.6 and the target is 4, so **44% of the population attains it**. A regimen
203tuned on the typical patient leaves about half the population on the wrong side of the target.
204Reported attainment is still optimistic here: this is between-subject variability only, with no
205residual or between-occasion component.
206 
207Linear models are solved analytically and superposed, which is exact. `--nonlinear` switches to
208integrated Michaelis-Menten elimination, where superposition is invalid and multiple-dose
209behaviour cannot be inferred from a single dose at all.
210 
211### 5. Exposure-response
212 
213```bash
214python3 exposure_response.py --emax -i er.csv --sigmoid
215python3 exposure_response.py --cqtc -i qt.csv --cmax 250
216```
217 
218The Emax fit reports `fraction_of_emax_reached` and flags a fit whose plateau is outside the data.
219When the highest observed exposure reaches only a third of the estimated Emax, Emax and EC50 are
220extrapolations that are strongly correlated with each other; quoting them as independent estimates
221is not supportable, and a "linear" exposure-response is simply the low-concentration limb of the
222same curve.
223 
224`--cqtc` evaluates the **upper bound of the two-sided 90% confidence interval** of predicted
225placebo-corrected change-from-baseline QTc against the 10 ms threshold, which is the question ICH
226E14 actually asks. A point estimate, or a 95% interval, answers a different one. The bundled model
227is an ordinary linear regression for screening; a submission-grade C-QTc analysis needs a mixed
228model with random intercept and slope per subject.
229 
230Every mode carries the same caveat, because it is the one that gets forgotten: patients are
231randomised to **dose**, not to **exposure**. Exposure-response across quantiles is observational
232even inside a randomised trial, and can reflect the covariates that drive clearance.
233 
234### 6. Bioequivalence
235 
236```bash
237python3 bioequivalence.py -i be.csv --design 2x2 --metric AUC
238python3 bioequivalence.py -i be.csv --design replicate --metric Cmax --scaling both
239python3 bioequivalence.py --power --cv 0.30 --gmr 0.95 --target-power 0.80
240```
241 
242Three criteria share the word "bioequivalence" and are not interchangeable: average BE (90% CI
243inside 80.00-125.00%), EMA's ABEL (limits widened as a function of CVwR, capped at
24469.84-143.19%, point estimate still within 80-125%), and FDA's RSABE (a scaled linearised bound
245via Hyslop's method, not an interval at all). `--scaling` refuses to run on a 2x2 design:
246 
247```
248error: reference-scaling requires --design replicate. High observed variability in a 2x2 study
249does not license widening: without replicated reference administrations there is no estimate of
250within-subject reference variability to scale to.
251```
252 
253Sample size reproduces the published tables exactly (CV 30%, GMR 0.95, 80% power → N = 40 for a
2542x2). Power is computed by integrating over the sampling distribution of the estimated standard
255deviation rather than treating the standard error as known — the normal approximation overstates
256power at realistic sample sizes. Note that **N is driven far more by the assumed GMR than by CV**;
257assuming 1.00 instead of 0.95 roughly halves the calculated N and is the usual reason a BE study
258comes in underpowered.
259 
260### 7. Scaling, paediatrics, and first-in-human
261 
262```bash
263python3 allometry_and_fih.py --scale --cl 5 --weight-from 70 --weight-to 6 --pma-weeks 44
264python3 allometry_and_fih.py --fih --noael rat=50,dog=10 --safety-factor 10
265```
266 
267Scaling by size alone below about 2 years of age overpredicts clearance, in a neonate by several
268fold, because clearance is limited by enzyme and renal maturation rather than by size. Supplying
269`--pma-weeks` adds the Anderson-Holford sigmoidal maturation term; omitting it below 20 kg raises
270a finding.
271 
272```
273parameter reference exponent size_scaled maturation_factor final
274CL 5 0.75 0.792063 0.30634 0.242641
275V 40 1 3.42857 1 3.42857
276```
277 
278Size alone would predict 0.79 L/h; with maturation at 44 weeks post-menstrual age it is 0.24 L/h,
279a 3.3-fold difference. Volume is not matured — maturation describes eliminating capacity, not
280distribution space.
281 
282`--fih` uses the body-surface-area conversion from FDA's 2005 maximum-safe-starting-dose guidance
283and always emits a finding that a NOAEL-derived MRSD is not sufficient on its own for agonist
284immunomodulators: compute MABEL with `--mabel` and take the lower value.
285 
286### 8. Drug interactions
287 
288```bash
289python3 ddi_static.py --basic --ki 0.5 --imax 2.0 --fu 0.05 --dose 0.4
290python3 ddi_static.py --msm --ki 0.5 --imax 2.0 --fu 0.05 --dose 0.4 --fm 0.9 --fg 0.7
291```
292 
293ICH M12 basic models with their cut-offs (R1 ≥ 1.02 hepatic, ≥ 11 intestinal; R2 ≥ 1.25 for TDI;
294R3 ≤ 0.8 for induction; transporter cut-offs by site), plus the mechanistic static model. The
295basic models are deliberately conservative: a negative is meaningful, a positive is a trigger for
296further work, not a prediction of clinical magnitude.
297 
298The mechanistic static model reports the ceiling alongside the prediction:
299 
300```
301note: With fm = 0.9, no inhibitor of this pathway can raise the victim AUC above 10.00-fold. If
302the prediction approaches that ceiling, fm is doing more work than the inhibition constants.
303```
304 
305`fm` and `Fg` dominate the answer far more than the inhibition constants, and are usually the
306least well established numbers in the calculation.
307 
308### 9. Therapeutic drug monitoring
309 
310```bash
311python3 tdm_bayes.py --model vancomycin-adult --weight 80 --crcl 75 \
312 --dose 1500 --interval 12 --level [email protected] --level 42@2 --target-auc24 500
313```
314 
315MAP Bayesian estimation shrinks towards the population when the data are uninformative and follows
316the data when they are not, which is why it beats both a trough read against population parameters
317and log-linear regression on two points. A single level raises a finding: it cannot separate
318clearance from volume, and whichever parameter the sample is uninformative about has simply
319returned its prior.
320 
321The bundled vancomycin parameterisation is explicitly labelled illustrative. Substitute a model
322validated in your population before the output means anything.
323 
324## Software ecosystem
325 
326Verified against live sources on 2026-07-27; see `references/software-ecosystem.md` for the full
327map and `references/source-ledger.md` for provenance.
328 
329- **Pharmpy 2.1.1** (2026-05-19) is the practical Python entry point — model-agnostic, drives
330 NONMEM/nlmixr2/rxode2, and ships 19 `run_*` tools including `run_amd`, `run_modelsearch`,
331 `run_covsearch`, `run_structsearch`, `run_pdsearch`, `run_modelrank`, `run_vpc` and `run_qa`.
332 Two breaking changes are recent enough to catch you out: **2.0.0 (2026-02-12) changed dataset
333 row indices to start at 1**, and **2.1.0 (2026-05-08) renamed `add_placebo_model` to
334 `set_placebo_model`** and now requires numpy ≥ 2.
335- **NONMEM 7.6** (user guides dated November 2025) remains the regulatory default. New since 7.5:
336 ADVAN16 (RADAR5 implicit Runge-Kutta for stiff delay differential equations), ADVAN17 (stiff
337 delay differential-algebraic), NUTS Bayesian sampling, and SAEM storage of individual samples.
338- **nlmixr2** (requires rxode2 ≥ 5.0.0) is the credible open-source NLME alternative;
339 `babelmixr2` and `monolix2rx` translate models between it, NONMEM and Monolix.
340- **PKPy** (PeerJ, 2025) is a Python popPK framework but is **GitHub-only — not on PyPI**, so
341 `uv pip install pkpy` fails. `chi-drm` (1.0.3) is on PyPI for Bayesian PKPD.
342- **Open Systems Pharmacology Suite v12** (PK-Sim/MoBi) is the open-source PBPK platform; Simcyp
343 and GastroPlus are the commercial ones. `ospsuite` is R-only and needs .NET 8.
344 
345Python has no mature NCA or NLME package of regulatory standing. That gap is why this skill ships
346its own validated NCA and fitting implementations rather than wrapping one.
347 
348## What this skill exists to prevent
349 
3501. Lambda_z chosen by plain r-squared, or fitted through Tmax.
3512. AUCinf reported from a profile where 25% of it was extrapolated.
3523. AIC allowed to select a compartment whose intercompartmental clearance has 98% RSE.
3534. Reweighting used to fix non-random residuals, which are a structural problem.
3545. `BLQ` left in a DV column, where NM-TRAN reads it as a real zero.
3556. A regimen chosen on the typical patient, with no attainment estimate for the population.
3567. Emax and EC50 quoted as independent estimates when the plateau was never observed.
3578. Reference-scaled bioequivalence limits applied to a 2x2 study.
3589. Allometric scaling to a neonate with no maturation term.
35910. An MRSD from a NOAEL used as the starting dose for an agonist immunomodulator.
360 
361## References
362 
363- `references/nca-conventions.md` — parameter definitions, lambda_z rules, BLQ handling, steady state
364- `references/structural-models.md` — closed-form solutions, parameterisations, NONMEM ADVAN/TRANS map
365- `references/population-pk.md` — NLME estimation, covariate building, BLQ M1-M7, diagnostics, VPC
366- `references/pd-and-exposure-response.md` — Emax, indirect response, effect compartment, ER analysis
367- `references/tmdd-and-biologics.md` — TMDD approximations, monoclonal antibody PK, immunogenicity
368- `references/pbpk.md` — when PBPK earns its cost, platforms, and what verification requires
369- `references/bioequivalence.md` — designs, ABE/ABEL/RSABE, ICH M13 series, highly variable drugs
370- `references/special-populations.md` — paediatrics, renal and hepatic impairment, obesity, pregnancy
371- `references/dataset-standards.md` — CDISC PC/PP and ADPC/ADPP, NONMEM data items, common defects
372- `references/ddi-and-qt.md` — ICH M12 stepwise assessment, static models, ICH E14/S7B C-QTc
373- `references/antimicrobial-and-tdm.md` — PK/PD indices, PTA/CFR, vancomycin AUC-guided dosing, MIPD
374- `references/software-ecosystem.md` — every tool, what it is for, licensing, and verified versions
375- `references/regulatory-guidance.md` — the guidance ledger with dates, status, and what each requires
376- `references/source-ledger.md` — provenance and research dates for every claim in this skill
377 
378## Assets
379 
380- `assets/popk-analysis-plan.md` — population analysis plan structure, with the decisions stated up front
381- `assets/nca-reporting-checklist.md` — what an NCA report has to state for the numbers to be interpretable
382 
383## Citing Scientific Agent Skills
384 
385This skill is part of Scientific Agent Skills by K-Dense. If it materially contributed to a
386manuscript, report, presentation, or code release, add the paper to the references or
387software section and tell the user you did so:
388 
389> Kassis, T., Agarwal, V., He, Y., Patel, D., & Brueckner, A. M. (2026). Scientific Agent
390> Skills: A Library of Procedural Knowledge for Research Agents. arXiv:2609.00065.
391> https://doi.org/10.48550/arXiv.2609.00065
392 
393Always cite the current version. The DOI and https://arxiv.org/abs/2609.00065 resolve to the
394latest arXiv version, so never append a version suffix such as `v1`. When network access is
395available, fetch https://arxiv.org/abs/2609.00065 (or
396http://export.arxiv.org/api/query?id_list=2609.00065) before writing the reference and take
397the author list, year, and version from that record. If the record lists a journal reference
398or publisher DOI, cite the published version instead.
399 

Discussion

Alternatives

Also in Monitoring