Repository navigation
Commit 19df050
assignments_to_experiments: fix lost switches, dropped changes and late evaluation of condition changes (#574)
Follows #573. Fixes in `assignments_to_experiments`, found while
reviewing #573's refactor and by differential testing against
simulation. Each one changes the converted problem, and each has a
regression test that fails without the fix. None was introduced by the
refactor.
## Fixes
- **A switch exactly at the start of a later period was lost** when that
period did not change any symbol of the assignment. With `k1 :=
piecewise(0, time < 10, 1)` and periods at 0 and 10, k1 stayed 0 after t
= 10. Likewise `time <= 0` after preequilibration. A later period now
also sets the value when it differs from the value at the end of the
previous period.
- **Condition changes of symbols still used elsewhere were dropped.**
With `k1 := dose * piecewise(0, time < 10, 1)` and a reaction that also
uses `dose`, the condition's `dose = 2` was removed, so the reaction
silently used the model value. Changes are now only dropped if nothing
else (the model's math or conversion factors, an observable, a condition
change) refers to the symbol.
- **A condition change was re-evaluated wherever a switch used it**,
with the values at the switch. PEtab applies a condition once, at the
start of its period, and all its changes simultaneously, with the values
from before the period. With `d1 = b1 + 0.5` at t = 2 and `b1 = 2` at t
= 3 (or `b1 = 2` in the same condition), a switch to `d1` at t = 5 used
2.5 instead of 1.7. Changes are now evaluated at the start of their
period; one whose value refers to something not known there, e.g. a
species, is rejected. As a side effect, switching times set to
expressions such as `t1 = t2 + 1` now convert instead of being rejected.
- **Time-dependent initial assignments were converted into switches**,
but SBML evaluates an initial assignment once, at the start of the
simulation, so `k1 = piecewise(0, time < 10, 1)` never switches. They
now stay in the model, as the docstring's scope ("assignment rules")
already said.
- **At preequilibration, a value referring to model parameters outside
the parameter table failed the lint** (`CheckInitialChangeSymbols`),
since preequilibration starts the experiment. Those parameters now take
their model values, as at the first period.
- **An assignment to a species or compartment crashed** with an
`AttributeError`; it now raises `NotImplementedError`.
- **A problem without time-dependent assignments was still changed**: an
experiment that only preequilibrates got an empty period at t = 0, and
periods were re-sorted. It is now returned as it was, after the lint.
- **A ramp's slope parameter could clash with PEtab IDs**, e.g. an
observable parameter `k1_slope`, since it was only checked against SBML
IDs. It is now named with `Problem.get_unused_id`.
- **A switching time that a condition set from a parameter fixed by the
parameter table was rejected**, e.g. `t1 = p + 1`, although `t1 := p +
1` in the assignment itself converts with `p`'s nominal value. Fixed
parameters now take their nominal values in condition changes too;
switching times that depend on estimated parameters are still rejected.
- **New condition values used the symbols parsed from SBML**, without
the assumptions of PEtab's symbols (`petab_symbol`, real), so they
compared unequal to the same expressions parsed by PEtab, e.g. after
reading the condition table back. They now use PEtab's symbols.
Test changes: `test_assignments_to_experiments` and
`_unused_condition_id` used an initial assignment (`k1 = ...`) to mean a
switching rule and now use an assignment rule (`k1 := ...`). `_validate`
relied on an estimated parameter with a time-dependent initial
assignment; it now uses an existing experiment `default_experiment`, to
which the measurements without an experiment ID would also be assigned,
which the final lint catches.
## Module
As suggested in review, `assignments_to_experiments` and its helpers now
live in their own module, `petab.v2.assignments_to_experiments` (moved
unchanged; still exported from `petab.v2.converters`), and their tests
in `tests/v2/test_assignments_to_experiments.py`.
`SbmlModel.get_referenced_ids()` and `ObservableTable.free_symbols` are
new, also from review.
## Checked
- `pytest tests/v2 tests/v1/test_petab.py tests/v1/test_sbml.py`: 266
passed; pre-commit clean.
- The benchmark problems Brannmark_JBC2010, Fujita_SciSignal2010,
Isensee_JCB2018 and Raimundez_PCB2020 (and Oliveira_NatCommun2021's
error) convert identically before and after.
- Differential testing against simulation: random problems with dose
sums, several and nested pieces, ramps, every inequality form, switching
times set by conditions (often exactly at period starts), symbols shared
with reactions, two converted assignments in one model, and condition
values that are expressions in other parameters. Each experiment of the
original and the converted problem is turned into SBML events with
`ExperimentsToSbmlConverter`, simulated in BasiCO, and compared (rtol
1e-6). On this PR's head, 2093 problems match and none is rejected; in 7
more, COPASI fails on the original model, and the converted one matches
it up to there. The same generator finds 59 mismatches in 300 problems
on `main`. Not covered: preequilibration, experiments that start at t ≠
0, and first periods with expression values (only parameter table
symbols are allowed there).
- A second pass with an independent reference simulator (SciPy,
integrating piecewise between all switching times and period starts; it
agrees with BasiCO on 94 cross-checked simulations), covering what the
first could not: experiments that start at t = -5, 2 or 5,
preequilibration (with the documented semantics: converted assignments
hold their value at time 0), parameter table entries whose estimated or
fixed nominal values differ from the model's (in values, switching times
and condition values), condition values that refer to parameter table
symbols at the start and to a species in unrelated changes. Each problem
also checks that the input stays unchanged, that conversion is
deterministic and idempotent, and that the converted condition and
experiment tables survive a round trip through their DataFrames. On this
PR's head, 1415 of 1500 problems match and pass all checks; the other 85
are rejected because a switching time depends on an estimated parameter,
as intended. On `main`, the same pass finds 21 mismatches and 16 lint
failures of the converted problem in 200 problems.
- Where BasiCO disagrees with the reference simulator on an original
problem (a switch of the assignment rule exactly at the time of a
period's event, which COPASI mis-simulates; moving the switch by 0.001
removes the disagreement), the reference simulator agrees with the
converted problem.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>1 parent 597c0a6 commit 19df050
10 files changed
Lines changed: 1919 additions & 1341 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
59 | 59 | | |
60 | 60 | | |
61 | 61 | | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
62 | 71 | | |
63 | 72 | | |
64 | 73 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| 35 | + | |
35 | 36 | | |
36 | 37 | | |
37 | 38 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
262 | 262 | | |
263 | 263 | | |
264 | 264 | | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
265 | 296 | | |
266 | 297 | | |
267 | 298 | | |
| |||
0 commit comments