Skip to content

Refuse linCmtB(which1 = -3) when linCmt() compartments lag differently - #1272

Merged
mattfidler merged 1 commit into
mainfrom
alag-1237
Aug 19, 2026
Merged

Refuse linCmtB(which1 = -3) when linCmt() compartments lag differently#1272
mattfidler merged 1 commit into
mainfrom
alag-1237

Conversation

@mattfidler

Copy link
Copy Markdown
Member

Summary

Fixes #1237.

linCmtB(which1 = -3) (the dose-time / moving-boundary sensitivity added in #1235) is the derivative of a linCmt() model wrt ONE delay applied to every dose feeding the linear system -- valid only when every dosed linCmt() compartment shares the same alag(). A model that lags two linCmt() compartments differently (e.g. alag(depot) and alag(central) driven by distinct parameters) previously built and solved silently to the wrong, single-delay answer instead of erroring.

The full architectural fix -- per-compartment sensitivity vectors carried through the linCmt solve (extending numLinSens, the linCmtFlg/.rxLinCmt()/op->linOffset plumbing, saveJac/restoreJac) -- is a much larger change explicitly shared with #1236 and is not attempted here. Instead this implements the "meanwhile" guard the issue itself scoped out: refuse to build the offending model instead of silently returning a wrong answer.

  • rxode2() now detects, at build time, when a model calls linCmtB() with a (possibly indirect, via simple constant folding) literal which1 = -3 while its linCmt() compartments carry more than one distinct alag() expression, and raises a clear error instead of compiling.
  • A single compartment's alag() assigned conditionally (different text per if/else branch) does not by itself trip the guard -- only a mismatch across compartments does.
  • Updated the linCmtB header comment in src/linCmt.cpp to point at the new guard.

This underwent an independent review pass (Antigravity/Gemini) that surfaced two real gaps, both fixed and covered by new tests: a which1 value reached indirectly through a constant variable, and a false positive on a single compartment's conditionally-reassigned alag().

Test plan

  • New test file tests/testthat/test-lincmt-dose-time-sens-guard.R (7 tests): errors on distinct alag() across two linCmt() compartments, allows a shared alag(), allows distinct alag()s when which1 = -3 is never used, allows a single lagged compartment, allows no modeled alag() at all, still errors when which1 is reached indirectly through a constant, and allows a single compartment's conditionally-reassigned alag().
  • Existing tests/testthat/test-lincmt-dose-time-sens.R (20 tests, unmodified) still pass.
  • Full lincmt/event-sensitivities/prior-density filtered suite (6730 tests) passes after merging latest main.
  • Full repository test suite (~39k tests) passes (aside from an unrelated, intentionally-failing demo test).

linCmtB(which1 = -3), the dose-time (moving boundary) sensitivity added
in #1235, is the derivative wrt ONE delay applied to every dose feeding
the linear system -- valid only when every dosed linCmt() compartment
shares the same alag(). A model that lags two linCmt() compartments
differently (e.g. alag(depot) and alag(central) driven by distinct
parameters) previously built and solved silently to the wrong,
single-delay answer.

rxode2() now detects this at build time: when the model calls linCmtB()
with a (possibly indirect) literal which1 = -3 and its linCmt()
compartments carry more than one distinct alag() expression, it errors
instead of compiling. The full architectural fix -- per-compartment
sensitivity vectors carried through the linCmt solve -- is a much larger
change shared with #1236 and is not attempted here.

Fixes #1237.
@mattfidler
mattfidler merged commit 7c6f1b3 into main Aug 19, 2026
10 checks passed
@mattfidler
mattfidler deleted the alag-1237 branch August 19, 2026 02:26
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.

linCmtB(which1 = -3) assumes every dose shares one alag(): no per-compartment dose-time sensitivity

1 participant