Skip to content

chore: Use PMcore from LAPKB/PMcore#333 - #403

Draft
mhovd wants to merge 4 commits into
mainfrom
chore/pmcore-pharmsol-main
Draft

mhovd wants to merge 4 commits into
mainfrom
chore/pmcore-pharmsol-main

Conversation

@mhovd

@mhovd mhovd commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Updates Pmetrics to the PMcore in LAPKB/PMcore#333 (chore/pharmsol-main). That branch currently depends on pharmsol branch claude/sweet-davinci-o6m0ej (7233a77); the exact revisions are pinned in Cargo.lock. A copy, src/rust/Cargo.lock, ships in the source package (.Rbuildignore drops the root one), so install_github and R CMD build builds are pinned too; keep the two identical. Draft: this uses git dependencies and can't be released until PMcore and pharmsol cut releases.

Rust (src/rust)

  • estimate_predictions_dense → simulate(subject, params, None).
  • Prediction::outeq() now returns an OutputLabel. Simulation rows map it back to the output's position in the model, so PM_model$sim() output is unchanged.
  • Error models are added under the model's declared output label instead of a dense slot. With the new pharmsol this means gamma/lambda is actually optimised (cycles.csv gets a gamlam.<label> column).

Labels: no more input_n / outeq_n

pharmsol now matches labels exactly and no longer accepts outeq_1 for 1, so the old prefixes are dropped:

  • Generated DSL uses bare labels: out(1), bolus(1), infusion(1), lag(1).
  • Data INPUT/OUTEQ values are used as labels as-is (trimmed; 01 → 1). This also covers the planned alphanumeric labels, e.g. INPUT = tacrolimus or OUTEQ = plasma.
  • Back-compat: label_data_csv() and the DSL error-model matching resolve against the labels the model actually declares. A numeric identifier n also matches input_n / outeq_n, so user DSL models (and saved DSL) written with the prefixes keep working.

Models saved with the current release

Saved PM_model objects keep their own $fit/$sim bodies and a cached DSL with input_1/outeq_1. These now keep working:

  • label_data_csv() called without labels (as the old methods do) takes them from the calling model's DSL.
  • A numeric error-model label that isn't declared binds to the output at that position.
  • Result parsers read a legacy outeq_N label as output N.

Checked with the shipped objects: modEx$fit() runs with op and cycle populated, and modEx$sim() and NPex$model$sim() run.

Output parsing

pred.csv now holds the output label in outeq (previously a 0-based index), and cycles.csv has gamlam.<label>.

  • settings.json now records the model's output labels.
  • New engine_outeq_index() converts labels to Pmetrics output numbers: numeric labels are used as-is, named labels are numbered by their position in the model's outputs, and legacy 0-based files are still shifted. It is used in PM_op, PM_pop, PM_post and PM_cycle.
  • gamlam columns are split on the gamlam. prefix only, so labels containing dots don't break. The cycle number comes from cycles.csv, so a declared output without observations no longer drops PM_cycle.
  • PM_model$from_dsl() requires a numeric output label to equal its position (outputs = 1, 2, not 2, 1 or 1, 3), so numeric and named outputs number consistently. It also rejects error-model names that resolve to the same output (e.g. 1 and outeq_1).

New DSL functions in R-defined models

pharmsol's new built-ins were only usable in DSL text (PM_model$from_dsl()). The R-to-DSL translator now accepts them as well:

  • effect50(min = 0, max = 1, e50 = 0.5, h = 1, x): the sigmoid Emax (Hill) model.
  • sigmoid(x, k = 1) and logit(p).
  • tanh(x).

Arguments can be given by position, by name, or both; names are kept in the generated DSL. Each call is checked against its signature, so a mistake is reported against the user's R expression. All of these are documented in ?PM_model. R's own ifelse was already translated (to an if) and is unchanged.

Testing

  • Smoke fit (one-comp IV, NPAG, 3 cycles): op/pop/post have outeq = 1 and finite weighted residuals; cycle$gamlam is populated and gamma moves (4.55 → 3.79); sim is unchanged.
  • New tests check the translated calls, compare the computed values with the formulas evaluated in R, and cover the error messages (DSL language tests: 218 passed).
  • testthat: 752 passed, 1 skipped (existing), 1 failed: test-data-currency.R. The shipped example objects (NPex, modEx) embed the old PM_model$fit/$sim bodies. They need regenerating with Rscript data-raw/data-raw.R (a full NPAG fit), which I'd do once the PMcore version is final.

Known gaps / follow-ups

  • Downstream plotting/summary code still treats outeq as a number; full alphanumeric support will need the label carried through to those.

Points pm_rs at the PMcore `chore/pharmsol-main` branch, which moves to a
pharmsol where output labels are matched exactly and error models are keyed
by label.

- Rust: `simulate` replaces `estimate_predictions_dense`; simulation rows map
  the prediction's output label back to its position; error models are added
  under the model's declared output label.
- Labels: drop the `input_n` / `outeq_n` prefixes. Data identifiers are used as
  labels verbatim, so `INPUT = 1` matches route `1` and `OUTEQ = plasma`
  matches output `plasma`. Numeric identifiers still resolve to `input_n` /
  `outeq_n` in models that declare them that way.
- Parsers: pred.csv and cycles.csv now carry output labels instead of 0-based
  indices. settings.json records the output labels, and the parsers convert
  labels to output numbers (numeric labels as-is, named labels by position).
Copilot AI balanced review requested due to automatic review settings October 29, 2026 08:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Output mapping collisions, cycle reconstruction failures, and stale serialized model methods can break results, while route resolution and dependency pinning also remain unresolved.

5 open issues
1 file not reviewed
  • man/PM_model.Rd: Generated file
What changed in this PR

Updates Pmetrics for the PMcore/pharmsol migration in LAPKB/PMcore#333, adapting its Rust backend and R interfaces to label-based inputs and outputs. The dependency switch remains a draft pending upstream releases.

Changes:

  • Adopts the new simulation API and label-keyed error models.
  • Emits bare numeric labels while resolving legacy prefixed model labels.
  • Records output labels and updates result parsing, documentation, and regression tests.
File Description
tests/​testthat/​test-pmcore-pharmsol-migration.R Updates generated DSL label expectations.
tests/​testthat/​test-pm-model-from-dsl.R Adjusts numeric and legacy-label expectations.
tests/​testthat/​test-model-dsl-language.R Updates effect-function output syntax expectations.
tests/​testthat/​test-model-dsl-dose-scaling.R Updates dose-route label assertions.
tests/​testthat/​test-io-labels.R Tests label resolution, output numbering, and settings metadata.
tests/​testthat/​test-analytical-indexing.R Updates analytical output-label assertions.
src/​rust/​src/​simulation.rs Converts prediction labels to simulation output positions.
src/​rust/​src/​settings.rs Registers error models under declared output labels.
src/​rust/​src/​executor.rs Uses the new simulation API.
src/​rust/​Cargo.toml Switches PMcore to its migration branch.
R/​PM_post.R Converts prediction labels for posterior results.
R/​PM_pop.R Converts prediction labels for population results.
R/​PM_parse.R Records declared output labels in settings.
R/​PM_op.R Converts labels for observed-versus-predicted results.
R/​PM_model.R Resolves model labels during validation, fitting, and simulation.
R/​PM_cycle.R Parses label-based gamma/lambda columns, including dotted labels.
R/​model_dsl.R Emits bare labels and adds legacy-label resolution.
R/​engine_indices.R Adds output-label conversion and metadata readers.
man/​PM_model.Rd Documents current and legacy label matching.
Cargo.lock Records git revisions and updated transitive dependencies.

Review effort: Balanced

Comment thread R/PM_cycle.R Resolved
Comment thread R/engine_indices.R
Comment on lines +24 to +28
numeric_label <- !is.na(x) & grepl("^[0-9]+$", x)
idx[numeric_label] <- as.numeric(x[numeric_label])
if (!is.null(outputs)) {
named <- !is.na(x) & !numeric_label
idx[named] <- match(x[named], as.character(outputs))
Comment thread R/model_dsl.R Resolved
Comment thread R/PM_model.R
Comment on lines +1113 to +1115
data_inputs <- pm_resolve_labels(
data$standard_data$input, model_input_labels(self), "input_"
)
Comment thread src/rust/Cargo.toml
[dependencies]
extendr-api = "=0.9.0"
pmcore = { version = "=0.29.0", features = ["dsl"] }
pmcore = { git = "https://github.com/LAPKB/PMcore", branch = "chore/pharmsol-main", features = ["dsl"] }
The new pharmsol DSL functions were only usable in DSL text. The R-to-DSL
translator now accepts them, keeps argument names so calls can use the DSL's
named and default arguments, and checks each call against its signature so
mistakes are reported against the R expression. Documented in PM_model.
Copilot AI balanced review requested due to automatic review settings October 29, 2026 08:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Output-label collisions can merge predictions or apply incorrect error coefficients, while previously serialized models can fail under the new label matching.

6 open issues
2 previously missed issues

In code that hasn't changed since last review

Medium severity Exclude gamlam columns from cycle-data statistics pivot

R/​PM_cycle.R:264

Output labels ending in mean, median, or sd are still picked up by the earlier cycle_data pivot, before this prefix handling runs. For example, gamlam.mean becomes a spurious parameter statistic, while gamlam.effect.mean makes separate_wider_delim() fail on three components. At line 201, restrict the pivot to ends_with(c("mean", "median", "sd")) & !starts_with("gamlam.").

Medium severity Preserve dotted output labels during missing-marker checks

R/​model_dsl.R:1267

Dotted output labels such as plasma.free cannot reach this relabeling step through the normal fit path: the malformed-NA check in R/PM_data.R:1155 flags them, and PM_data$save() refuses to write the backend CSV. Restrict missing-marker detection and correction for INPUT/OUTEQ to whole-cell markers so dots inside labels are preserved.

1 file not reviewed
  • man/PM_model.Rd: Generated file

Review effort: Balanced

Comment thread R/PM_model.R Resolved
@codecov-commenter

codecov-commenter commented Oct 29, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.62500% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.82%. Comparing base (ef474a1) to head (1d28572).

Files with missing lines Patch % Lines
R/PM_model.R 88.88% 3 Missing ⚠️
R/PM_cycle.R 83.33% 2 Missing ⚠️
R/engine_indices.R 88.23% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #403      +/-   ##
==========================================
+ Coverage   64.77%   64.82%   +0.04%     
==========================================
  Files          52       52              
  Lines       22964    23083     +119     
==========================================
+ Hits        14876    14964      +88     
- Misses       8088     8119      +31     
Files with missing lines Coverage Δ
R/PM_op.R 77.89% <100.00%> (-0.38%) ⬇️
R/PM_parse.R 83.78% <100.00%> (+0.29%) ⬆️
R/PM_pop.R 78.03% <100.00%> (-0.19%) ⬇️
R/PM_post.R 78.05% <100.00%> (+0.02%) ⬆️
R/model_dsl.R 90.00% <100.00%> (+0.98%) ⬆️
R/PM_cycle.R 78.77% <83.33%> (+0.35%) ⬆️
R/engine_indices.R 78.26% <88.23%> (+20.56%) ⬆️
R/PM_model.R 67.32% <88.88%> (-0.08%) ⬇️

... and 10 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

- Parse cycles.csv gamlam when a declared output has no observations
- Require numeric DSL output labels to equal their position
- Reject error-model names that resolve to the same output
- Keep models saved by earlier releases working: take data labels from the
  caller's DSL, bind numeric error-model labels by position, and read legacy
  outeq_N labels in result files
- Ship src/rust/Cargo.lock so source-package builds are pinned
Copilot AI balanced review requested due to automatic review settings October 29, 2026 10:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread R/PM_cycle.R
Comment on lines +270 to 272
mutate(outeq = engine_outeq_index(outeq, engine_output_labels(path))) |>
arrange(cycle, outeq) |>
mutate(outeq = normalize_engine_index(as.numeric(outeq))) |>
dplyr::right_join(model_types, by = "outeq")
Comment thread R/PM_model.R
Comment on lines 1444 to +1447
label_data_csv(
normalizePath(file.path(path_run, "inputs", "gendata.csv"), mustWork = FALSE)
normalizePath(file.path(path_run, "inputs", "gendata.csv"), mustWork = FALSE),
inputs = model_input_labels(self),
outputs = model_output_labels(self)
Comment thread R/PM_cycle.R
cols = -cycle,
values_to = "value", names_to = "outeq",
# Split on the prefix only: output labels may themselves contain dots.
names_prefix = "gamlam\\."
Comment thread R/PM_model.R
Comment on lines +1126 to +1128
data_inputs <- pm_resolve_labels(
data$standard_data$input, model_input_labels(self), "input_"
)
Comment thread R/model_dsl.R
Comment on lines 1189 to +1190
# Canonical pharmsol output label for a Pmetrics `OUTEQ` identifier.
pm_output_label <- function(x) pm_label(x, "outeq_")
pm_output_label <- function(x) pm_label(x)
Comment thread R/model_dsl.R
Comment on lines +1207 to +1209
candidates <- id
if (!is.null(legacy_prefix) && grepl("^[0-9]+$", id)) {
candidates <- c(candidates, paste0(legacy_prefix, id))

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants