Changelog

All notable changes to OpenPKPD are documented here. This project follows Semantic Versioning.

0.3.4 — 2026-06-26

Fixed

  • GUI install hint now actually fires on a core-only install: in 0.3.3 the openpkpd-gui entry point eagerly imported the Qt shell at module load, so a core-only environment died on a raw ModuleNotFoundError for platformdirs (another [gui]-only dependency) before the actionable hint could run. The GUI imports are now deferred into main() and wrapped, so any missing [gui] dependency — platformdirs, matplotlib, or PySide6 — surfaces the same guidance: pip install "openpkpd[gui]". Added a regression test that blocks platformdirs and asserts the entry point reports the install command.

0.3.3 — 2026-06-26

Changed

  • Actionable GUI install hint: when the optional desktop GUI is launched without PySide6 installed, the error now names the exact remedy — pip install "openpkpd[gui]" — instead of only stating that Qt modules are unavailable. The message is shared by every GUI entry path (the openpkpd-gui script, all GUI workflows, and the Qt-based PDF export), so the guidance surfaces consistently. The desktop GUI remains an optional extra; PySide6 is intentionally not a core dependency.

  • README: the Desktop GUI section now shows the pip install "openpkpd[gui]" command alongside the openpkpd-gui launch command so the snippet is self-contained.

0.3.2 — 2026-06-26

Fixed

  • Wheel packaging (bundled datasets): the example datasets are now copied into the package tree at src/openpkpd/data/datasets/ and resolved via importlib.resources, so openpkpd.data.load_theophylline() and openpkpd.data.load_warfarin() work on a clean pip install. Previously the loader pointed at a repo-relative tests/external_validation/data/ path that was never packaged into the wheel, raising FileNotFoundError on the tutorial’s first dataset call. The packaged CSVs are byte-identical to the validation copies under tests/.

0.3.1 — 2026-06-26

Fixed

  • Wheel packaging (CLI and GUI entry points): anchored the main.py ignore rule in .gitignore to /main.py so it matches only the root-level scratch wrapper. The previously unanchored pattern caused maturin’s .gitignore-aware file walker to silently drop openpkpd/cli/main.py and openpkpd_gui/app/main.py from the wheel, leaving both the openpkpd and openpkpd-gui console scripts non-functional on a clean pip install (ModuleNotFoundError). All 254 tracked source modules are now bundled and both entry points resolve.

0.3.0 — 2026-06-26

Added

  • Bundled dataset loaders: openpkpd.data.load_theophylline() and openpkpd.data.load_warfarin() return the example datasets as NONMEMDataset objects, so the tutorial and quickstart workflows run without a separate data download.

  • Plot convenience exports: openpkpd.plots.gof_panel (an alias for the seven-panel diagnostic_panel goodness-of-fit figure) and openpkpd.plots.vpc_plot are now exported from the top-level openpkpd.plots namespace.

Fixed

  • SAEM dosing scale: aligned the SAEM control-stream path with absolute-mg dosing so SAEM recovers the FOCEI optimum on the theophylline benchmark. Added tests/integration/test_theophylline.py::test_control_stream_saem_recovers_focei_optimum to guard the dosing-scale fix against regression.

  • GUI packaging: the maturin build configuration now sets python-packages = ["openpkpd", "openpkpd_gui"] so the desktop GUI package is bundled into the wheel; the openpkpd-gui entry point now resolves on a clean PyPI install.

  • manylinux release build: set a CMake policy floor (CMAKE_POLICY_VERSION_MINIMUM=3.5) so the vendored SUNDIALS sub-projects configure under CMake 4.x, and restore host-user ownership of dist/ after the root-owned Docker build step.

  • GOF panel comment: corrected the gof_panel alias comment to describe the actual seven-panel GOF figure (was “four-panel”).

Documentation

  • API reference: documented the bundled-dataset loaders (data) and the simulation-based VPC plots (vpc_plot, pcvpc_plot, stratified_vpc_plot) plus the gof_panel alias.

0.2.9 — 2026-05-09

Fixed

  • PyPI project metadata: updated project URLs from GitLab to GitHub in the package metadata so the PyPI project page points to the current canonical repository and issue tracker.

Documentation

  • README: switched the logo image URL and development clone example to the GitHub repository.

0.2.8 — 2026-05-09

Fixed

  • Release automation: added GitHub Actions workflows for manual multi-platform wheel builds and tagged PyPI releases covering manylinux_2_28 x86_64, macOS x86_64, macOS arm64, Windows x64, and source distributions.

  • Wheel builds: passed --manifest-path rust/Cargo.toml to all maturin commands in local build recipes, CI workflows, and the manylinux helper script so Rust-extension packaging resolves the correct manifest reliably.

  • manylinux CVODES packaging: bumped sundials-sys from 0.2.3 to 0.2.5 and adjusted the Linux build pipeline to keep vendored SUNDIALS builds working under current CMake behavior.

Documentation

  • Capabilities diagram: switched the generated SVG text to a standard sans-serif stack for more consistent rendering across platforms.

0.2.7 — 2026-04-02

Added2

Steady-state (SS=1, II) dosing — ADVAN1/2/3/4/6

  • ADVAN1: scale bolus by 1/(1-exp(-K·τ)) at periodic SS.

  • ADVAN2: per-pole accumulation factors ss_k, ss_ka; limit form for KA≈K.

  • ADVAN3: _biexp_central_ss() with per-eigenvalue factors; degenerate fallback.

  • ADVAN4: _triexp_oral_ss() + _decay_difference_ss() for per-pole SS oral dosing.

  • ADVAN6: iterative periodic-orbit solver _find_ss_state() — integrates up to 500 dosing cycles until the pre-dose state converges (rtol 1e-5); verified against the ADVAN2 analytical SS result for a 1-cmt oral model.

  • All subroutines warn once on SS=1 with infusion dosing (not yet implemented).

Numerical accuracy tests

  • TestADVAN4VsODE::test_degenerate_eigenvalue_infusion_matches_ode: validates the abs(dl) < 1e-10 branch of _infusion_triexp against ADVAN6 ODE (rtol 1e-3).

Fixed

  • NCA: compute_dataset now filters strictly to EVID==0 rows before trapezoidal integration; EVID=2/3/4 dose/reset records no longer corrupt AUC estimates.

  • SAEM convergence: phase-2 phi vector includes full omega lower-triangle and sigma diagonal, preventing premature convergence when off-diagonal omegas or residual variances drift.

  • SAEM OFV monitoring: OFV now uses the Rao-Blackwell chain-mean η̂ instead of a single chain (chain 0), giving a lower-variance monitoring signal.

  • SAEM n_eta=0 fast-path: MH E-step loop is skipped entirely for models with no random effects, avoiding redundant per-subject RNG calls.

  • IMP ESS feedback: warn when ESS/isample remains low after reaching MAX_ISAMPLE_DOUBLINGS so users know to increase isample manually.

  • FO Cholesky solve: replaced explicit C_i^{-1}·I construction with a Cholesky factorisation that provides both log|C_i| and the quadratic form in one pass.

  • Laplacian: removed duplicate LOG2PI_LOCAL; now uses shared LOG2PI constant.

  • FO hasattr dead code: result.message if hasattr(result, "message") else "" simplified to getattr(result, "message", "").

  • pcVPC: warn when simulated data lacks a PRED column and skip correction rather than silently producing uncorrected output.

  • Rust neg2ll_obs_loop: replaced silent min-truncation with an explicit length check (returns 1e30 on mismatch); Python-side ValueError guard added.

  • Rust transit PD RHS: emax clamped to [0, 1] to prevent negative PD production and ODE destabilisation.

Documentation

  • docs/user_guide/analysis_validation_gaps.md: added Recently resolved gaps table.

  • docs/changelog.md: this entry.

0.2.6 — 2026-04-02

Added

P1.4 — Native acceleration for user-defined $DES ODE models

  • CompiledDESCallable.as_multidose_probe() — compiles the user’s NM-TRAN $DES block to Numba @njit and wraps it in a piecewise multi-dose integration engine. Returns four probe callables (bolus + infusion variants, state + sensitivity variants) that satisfy the _NativeOdeTemplate contract already used by the built-in Rust probes.

  • IndividualModel._try_build_user_ode_template() — detects ADVAN6 + CompiledDESCallable, auto-derives the volume parameter name (V, V1, V2, or V3) and output compartment index, and builds a lazily-cached _NativeOdeTemplate with eligible_advans={6}.

  • IndividualModel._iter_templates() — prepends the user template to the static _NATIVE_ODE_TEMPLATES list so all four dispatch loops (_try_native_ode_probe, native_advan6_prediction_eta_jacobian, _native_gauss_newton_hessian, _native_eta_objective_value_grad) pick it up automatically.

  • _build_native_ode_contract() gate updated to admit ADVAN6 + CompiledDESCallable even when no compiled Rust template is present.

  • User ODE template excluded from __getstate__ (closures cannot be pickled); rebuilt lazily in parallel worker processes on first probe call.

The acceleration is transparent — existing models using .des(…) benefit automatically when openpkpd[jit] is installed. No API changes for users.

Downstream benefits activated for user $DES models:

  • Single-probe IPRED prediction (replaces full Python evaluate() loop)

  • Native G_i = ∂IPRED/∂η for FOCE/FOCEI inner loop

  • Native Gauss-Newton Hessian for Laplacian/BAYES

  • Native eta gradient for IMPMAP MAP optimization

Nine new unit tests in tests/unit/test_native_cvodes.py (Section 18): gate activation, template caching, state-probe accuracy vs. analytical 1-cmt IV (rtol 1e-4), _try_native_pk_backend dispatch, sensitivity-probe shape and FD agreement (rtol 1e-3), and G_i vs. FD reference (rtol 1e-2).

Documentation

  • Added “Native acceleration for user-defined $DES models” section to docs/user_guide/pk_subroutines.md covering activation, eligibility conditions, and limitations.

  • Updated Model workflow tooltip in the GUI to mention that openpkpd[jit] enables automatic Numba acceleration for $DES models.

0.2.5 — 2026-04-01

Added

GUI — BLQ/M3 support (P2-C)

  • Added a scalar LOQ spinner to the Data workflow options row. When set, the value is injected as a constant LLOQ column at fit time if the dataset does not already contain one.

  • Added a BLQ method combo to the Model workflow estimation settings row, exposing M1 (ignore BLQ, default) and M3 (censored likelihood). The selection is persisted in ModelSpec.estimation.options and applied automatically at fit time by setting population_model.blq_method.

GUI — Interactive GOF subject highlighting (P2-B)

  • Added a Subject filter combo to the Diagnostics workflow filter row. Selecting a subject ID re-renders the active GOF plot with that subject’s observations overlaid in red, without leaving the Diagnostics page. Supported plot types: DV vs IPRED, DV vs PRED, CWRES vs TIME, CWRES vs PRED, and |IWRES| vs IPRED.

GUI — VPC stratification and pcVPC (P3-F)

  • Added a Stratify by combo to the Advanced workflow VPC tab, populated from the active dataset columns (mandatory NONMEM columns excluded). The selected column is passed to VPCEngine.compute(stratify_by=...). Repopulates and restores selection on every refresh.

  • Renamed the prediction-corrected checkbox to pcVPC and added a descriptive tooltip.

  • Added stratify_by: str | None to VPCConfig; run summary text includes stratify=<column> when stratification is active.

Documentation

  • Rewrote docs/user_guide/gui.md to fully reflect all current workflow pages, controls, and behaviors including: LOQ spinner, BLQ method combo, subject highlighting, VPC stratification/pcVPC, named model presets, advanced FOCE/FOCEI optimizer controls, NCA options, Results comparison and delta panels, Diagnostics NPDE controls, Advanced Design tab controls, Artifacts tab scope filtering, keyboard shortcuts, and BLQ troubleshooting guidance.

0.2.4 — 2026-03-28

Changed

Notebooks and documentation

  • Refreshed the full marimo notebook suite for the current APIs, added solver and FOCEI advanced-option examples, and strengthened notebook integration tests so they check both successful execution and expected outputs.

  • Updated the README and user-facing docs to reflect the current example suite, notebook extra, GUI review flow, validation coverage, and the new method-level validation matrix.

GUI and examples

  • Added results-page comparison navigation so the GUI can jump directly to a strong sibling scenario for side-by-side review.

  • Added a PFIM-backed optimal-design example and refreshed the advanced example inventory, including the renumbered four-compartment ADVAN5 workflow.

Validation

  • Expanded external validation with additional advanced-estimator checks, PFIM/design reference tests, Monolix benchmark safeguards, and a warfarin FOCEI diagnostic harness to document the current validated basin behavior.

0.2.3 — 2026-03-28

Changed

FOCE / FOCEI estimation

  • Corrected FOCE/FOCEI objective handling for the interaction path and expanded analytic and cross-tool regression coverage against nlmixr2, NONMEM, Monolix, PKNCA, WinNonlin, and Pharmpy-backed workflows.

  • Added configurable FOCEI outer-optimizer controls, fallback/polish settings, best-iterate retention, and structured retry options across the Python API, control-stream runtime, and parser.

GUI and examples

  • Expanded the GUI advanced estimation surface to expose the new FOCEI controls, improved error visibility in post-fit workflows, and added regression tests for the new behavior.

  • Added new runnable examples covering FOCEI optimizer controls, persisted control-stream optimizer extensions, phenobarbital population PK, and indometh NCA.

Validation and developer tooling

  • Added stronger example integration checks, live R-backed validation coverage, and a local R dependency installer with just install-r-test-deps and just check-r-test-deps.

  • Restored the Sphinx docs theme to Read the Docs style and improved release tooling so the version bump script now updates Rust and changelog release metadata as well.

0.2.2 — 2026-03-24

Changed

IMP estimation — corrected marginal likelihood normalisation

  • Fixed a systematic bias in IMPMethod._importance_sample() where the log-prior contribution was missing the −n_eta/2 · log(2π) normalisation term.

  • The bias was proportional to the number of random effects (~11 OFV units per ETA per subject for a 12-subject run), causing IMP to converge to a false mode or fail to converge at all on models where FOCE/SAEM converge cleanly.

  • Updated the Theophylline IMP regression reference from the placeholder value (OFV = 5736, theta at initials) to the true minimum (OFV = 3381).

ETA de-shrinkage (Combes 2013)

  • EstimationResult.compute_deshrinkage_etas() returns a subject-keyed dict of de-shrunken EBEs using the Combes (2013) rescaling correction: eta_adj_ik = eta_ik / (1 shrinkage_k).

  • This adjusts the EBE dispersion to match sqrt(omega_kk) exactly, making covariate plots and ETA histograms valid even when FOCE shrinkage exceeds 30%.

  • A warning note (“Consider de-shrinkage”) is now shown in the HTML report for any ETA row whose shrinkage exceeds 30%.

  • See docs/user_guide/estimation_methods.md for full usage documentation and the Combes (2013) reference.

Fast-path error model evaluation (observation-model loop bypass)

  • IndividualModel._fast_obs_model() detects standard $ERROR patterns (proportional, additive, proportional_theta, additive_theta, combined_theta, combined_eps) and evaluates them with vectorized NumPy instead of the per-observation Python loop.

  • The fast path is used automatically when eps=0 (estimation path); the full per-observation loop is retained for simulation.

  • Measured on the Theophylline benchmark (12 subjects, 7 obs, proportional error): evaluate_observation_model reduced from ~50 µs/call to ~18 µs/call (~2.8×).

0.2.1 — 2026-03-22

Changed

Control-stream prior support

  • $PRIOR runtime support now includes a documented, tested Gaussian-prior subset: $THETAP/$THETAPV for THETA priors and $OMEGAP/$OMEGAPD for OMEGA lower-triangle priors during control-stream execution.

  • OMEGA prior blocks are now wired through Problem.from_control_stream(...) into PriorSpec / PriorAugmentedModel rather than remaining parse-only.

  • $SIGMAP* prior blocks remain parse-only at this stage.

Control-stream simulation support

  • $SIMULATION now has a documented runtime subset in the runner: first-seed handling, ONLYSIMULATION, SUBPROBLEMS=n, and a default <run>.sim.csv output artifact.

  • Simulation-only control streams now execute without an estimation step by simulating from the active control-stream parameter state.

  • TRUE=FINAL remains parseable but is not yet given separate runtime behavior.

Control-stream mixture support

  • $MIXTURE now has a documented runtime subset in the runner for NSPOP=n finite mixtures using dedicated <run>.mix.json and <run>.mix_assignments.csv artifacts.

  • The current subset supports FO, FOCE/FOCEI, and LAPLACIAN as the inner estimation method.

  • PMIX=THETA(n) remains parseable but is not yet used to drive runtime mixing.

GUI VPC workflow

  • The GUI Advanced page now provides a real post-fit VPC workflow rather than a pure placeholder.

  • Users can generate VPC artifacts from the latest successful fit, configure replicate/bin/seed settings, request prediction-corrected VPC, and preview the latest plot/summary artifacts directly in the GUI.

GUI bootstrap workflow

  • The GUI Advanced page now also provides a post-fit bootstrap workflow.

  • Users can generate bootstrap summary, CI-table, and raw-sample artifacts from the latest successful fit and configure replicate count, worker count, seed, and CI level directly in the GUI.

GUI design workflow

  • The GUI Advanced page now also provides a post-fit optimal-design workflow.

  • Users can generate design summary, metrics, schedule, FIM, and expected-SE artifacts from the latest successful fit and configure sample count, subject count, time window, criterion, and optimization method directly in the GUI.

GUI Advanced hub cleanup

  • The GUI Advanced page is now organized as a tabbed hub with dedicated VPC, bootstrap, design, and artifact-browser tabs.

  • The shared artifact browser now supports workflow-specific filtering while preserving direct preview/open/export actions.

NPDE / VPC validation notes

  • Added a user-guide validation note that links the current NPDE and VPC checks to the canonical literature and explains the current validation scope.

  • NPDE tests now include a multi-seed misspecification-separation check, and the NPDE/VPC regression baselines now carry literature-aligned validation metadata.

NCA validation notes

  • Extended the validation note with the current NCA validation scope, including analytic one-compartment checks and reference-workflow formula alignment.

  • Added stronger oral-reference NCA assertions for Lambda_z, CL/F, and Vz/F, and enriched the NCA regression baseline with validation metadata/provenance.

Added

Data handling

  • CovariateImputer (openpkpd.data.impute): fills missing covariate values with mean, median, locf (last-observation-carried-forward), nocb (next-observation-carried-backward), or knn (k-nearest-neighbours via scikit-learn).

  • NONMEMDataset.impute_covariates(columns, method='locf'): convenience wrapper that returns a new dataset with imputed values.

NCA

  • to_cdisc_pp() (openpkpd.nca.cdisc_pp): converts NCA results to CDISC PP domain format (long-format DataFrame with STUDYID, USUBJID, DOMAIN, PARAMCD, PARAM, AVAL, DTYPE).

  • SparseNCAEngine (openpkpd.nca.sparse): model-based sparse-sampling NCA. Optimises post-hoc ETAs over sparse observations, reconstructs a dense predicted profile, then delegates to the standard NCAEngine.

Output

  • write_cdisc_adppk() (openpkpd.output.cdisc_writer): writes a CDISC ADPPK-style CSV with observation rows, population parameter rows (THETA/OMEGA/SIGMA), and post-hoc ETA rows.

  • openpkpd.output package now exports write_cdisc_adppk.

Tests

  • 895 unit tests passing (up from 860).

  • New test modules: tests/unit/data/test_impute.py, tests/unit/nca/test_cdisc_pp.py, tests/unit/nca/test_sparse_nca.py, tests/unit/output/test_cdisc_writer.py, tests/unit/prior/test_control_stream_prior.py.

0.1.0 — 2026-03-03

Added

Core estimation engine

  • FO, FOCE, FOCEI, Laplacian, SAEM, and IMP estimation methods

  • ADVAN1–4 (1-compartment IV/oral, 2-compartment IV/oral) with TRANS1–6

  • NM-TRAN code compiler: $PK / $ERROR blocks → Python callables

  • ModelBuilder fluent Python API (no .ctl file required)

  • NONMEM control stream parser ($PROBLEM, $DATA, $INPUT, $SUBROUTINES, $PK, $ERROR, $THETA, $OMEGA, $SIGMA, $ESTIMATION, $COVARIANCE, $TABLE)

  • NONMEMDataset: CSV loading with EVID/MDV auto-generation, ADDL/II expansion

  • R/S sandwich covariance estimator

  • NONMEM 7.x-compatible output files: .lst, .ext, .phi, .cov, .cor

  • $TABLE output writer

  • CLI: openpkpd run model.ctl, openpkpd parse model.ctl

Diagnostic plots (OpenPKPD[plots])

  • compute_diagnostics(): PRED/IPRED/CWRES/WRES/IWRES/ETA DataFrame

  • GOF: diagnostic_panel, dv_vs_ipred, dv_vs_pred, cwres_vs_time, cwres_vs_pred, cwres_qq, abs_iwres_vs_ipred

  • PK: spaghetti_plot, concentration_time, mean_profile

  • PD: effect_time, emax_curve, hysteresis_loop, pd_individual

  • ETA: eta_histograms, eta_pairs, eta_vs_covariate

  • Model performance: ofv_history, vpc

Testing

  • 162 tests passing (unit + integration); 1 skipped (regression baseline)

  • Unit coverage: parser, data, PK subroutines, estimation, plots

  • Integration: theophylline, warfarin, 2-compartment, Emax PD

Documentation

  • Full Sphinx documentation with ReadTheDocs theme

  • Getting started, user guide, 7 annotated examples, API reference