Skip to content

fix: keep derived values real-valued in f64 buffers - #372

Merged
Siel merged 1 commit into
mainfrom
fix/constant-derived-slots
Sep 15, 2026
Merged

Siel merged 1 commit into
mainfrom
fix/constant-derived-slots

Conversation

@Siel

@Siel Siel commented Sep 15, 2026

Copy link
Copy Markdown
Member

A derived value initialised with a whole number was typed Int from its assignment, while derived storage is the f64 buffer. The value was therefore written as a double and read back as an integer, returning the f64 bit pattern instead of the value:

z1 = 1.0
out(cp) = z1        # returned 4.607e18

z1 = if (p1 > 0.0) 1.0 else 2.0
out(cp) = z1        # same

Derived and output symbols are now registered as real-valued, matching the buffer they live in. Parameter-derived and fractional-constant values were already typed Real and are unaffected.

Found while adding conditional-desugaring support to Pmetrics, where a hoisted helper such as _pmt1 = if (c) 1.0 else 2.0 produced garbage. tests/dsl_constant_derived.rs covers whole-number, fractional and conditional forms. Full workspace suite: 938 passed, 0 failed.

Derived and output symbols took their type from the expression assigned to
them, so a whole-number initialiser narrowed them to Int. Their storage is the
f64 derived buffer, so the value was written as a double and read back as an
integer: `z1 = 1.0` then `out(cp) = z1` returned the bit pattern of 1.0
(4.607e18) instead of 1.0.

A conditional with whole-number branches hit the same path:

    z1 = if (p1 > 0.0) 1.0 else 2.0
    out(cp) = z1

Derived and output symbols are now registered as real-valued, which matches the
buffer they live in. Whole-number constants and parameter-derived values are
unaffected.
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 84.75%. Comparing base (453023c) to head (445dd7b).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #372      +/-   ##
==========================================
- Coverage   84.75%   84.75%   -0.01%     
==========================================
  Files          78       78              
  Lines       35818    35818              
==========================================
- Hits        30357    30356       -1     
- Misses       5461     5462       +1     
Files with missing lines Coverage Δ
pharmsol-dsl/src/analyze.rs 83.12% <100.00%> (-0.03%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Siel
Siel added this pull request to the merge queue Sep 15, 2026
Merged via the queue into main with commit 026224e Sep 15, 2026
5 checks passed
@github-actions github-actions Bot mentioned this pull request Sep 15, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Projectpharmsol
Branchfix/constant-derived-slots
Testbedmhovd-pgx

⚠️ WARNING: Truncated view!

The full continuous benchmarking report exceeds the maximum length allowed on this platform.

🐰 View full continuous benchmarking report in Bencher

Siel added a commit to LAPKB/Pmetrics that referenced this pull request Sep 15, 2026
Picks up the derived-value type fix (LAPKB/pharmsol#372): a derived value
initialised with a whole number was typed Int while stored in the f64 derived
buffer, so reading it returned the bit pattern. The hoisted helpers this branch
introduces rely on that fix.

pharmsol 0.29.3 pins its sibling crates exactly, so pharmsol-dsl and
pharmsol-macros move with it. pmcore 0.29.0 requires `pharmsol = "0.29.2"` (a
caret), so no pmcore change is needed and pmcore stays at 0.29.0.
mhovd pushed a commit to LAPKB/Pmetrics that referenced this pull request Sep 16, 2026
* feat: Upgrade to PMcore 0.29

Pin pmcore =0.29.0, which brings pharmsol 0.29.2. ODE dose inputs and their
scales now stay on the derivative RHS instead of being declared as separate
routes; analytical models keep explicit routes, lag and fa.

- `R[j]`/`rateiv[j]` and `B[j]`/`bolus[j]` may be a standalone additive term
  or one exact product with a scale, in either operand order.
- A bolus scale may not depend on state or dose inputs.
- A PM_model rebuilt from another PM_model renders its DSL from `arg_list`
  rather than inheriting a previously rendered string, which could be stale.
- Regenerate data/*.rda, which the data-currency test requires after any
  change to the R6 method bodies.

* feat: read like R, and explain what cannot be translated

Model equations now behave like R wherever the DSL can follow, and every
rejection names the call and the fix rather than generated DSL text.

Expressions
- Conditionals work in any position. The DSL accepts one only as a whole
  right-hand side, so nested conditionals are hoisted into derived helpers.
- pmin/pmax, variadic max/min, ifelse, log(x, base), trunc and sign are
  translated instead of rejected.
- round(x, digits) follows R's half-to-even rule, not the DSL intrinsic, which
  rounds halves away from zero.
- estimate_effect_2/estimate_effect_3 are available with a checked signature.
  The retired six-argument get_e2 spelling gets a migration message that states
  the internal w = alpha * u * v convention.
- Unsupported calls quote the R expression. Every unknown function previously
  crashed with `subscript out of bounds`, so the message written for it was
  never reached.
- Missing and non-finite values are rejected with a fix.
- A dose input inside a conditional is refused with the supported alternative.

Diagnostics
- compile() validates the rendered source, so an invalid model fails where it is
  defined instead of at the start of a fit.
- Every emitted line records the R statement behind it, so backend diagnostics
  name that statement.
- The fit-time note about generated line numbers is removed: it is no longer
  true.

Requires pharmsol with the derived-value type fix (LAPKB/pharmsol#372): a
derived value initialised with a whole number was typed Int while stored in an
f64 buffer, so hoisted helpers returned the bit pattern instead of the value.

data/*.rda is regenerated, which the data-currency test requires after a change
to the R6 method bodies.

* chore: build against pharmsol 0.29.3

Picks up the derived-value type fix (LAPKB/pharmsol#372): a derived value
initialised with a whole number was typed Int while stored in the f64 derived
buffer, so reading it returned the bit pattern. The hoisted helpers this branch
introduces rely on that fix.

pharmsol 0.29.3 pins its sibling crates exactly, so pharmsol-dsl and
pharmsol-macros move with it. pmcore 0.29.0 requires `pharmsol = "0.29.2"` (a
caret), so no pmcore change is needed and pmcore stays at 0.29.0.
@github-actions github-actions Bot mentioned this pull request Oct 19, 2026
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