Repository navigation
assignments_to_experiments: fix lost switches, dropped changes and late evaluation of condition changes - #574
Merged
Conversation
FFroehlich
added this pull request to stack #575
October 25, 2026 16:51
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #574 +/- ##
==========================================
+ Coverage 78.97% 79.06% +0.09%
==========================================
Files 69 70 +1
Lines 8096 8155 +59
Branches 1450 1461 +11
==========================================
+ Hits 6394 6448 +54
- Misses 1215 1218 +3
- Partials 487 489 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Base automatically changed from
claude/assignments-to-experiments-refactor
to
main
October 25, 2026 17:15
…ope gaps - A switch exactly at the start of a later period was lost when that period did not change any symbol of the assignment, e.g. `piecewise(0, time < 10, 1)` with a period at t = 10, or `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. - The condition changes of every symbol of a converted assignment were dropped, even when the model still used the symbol elsewhere, e.g. a dose that also enters a reaction. They are now only dropped if nothing else (the model, an observable, a condition change) refers to the symbol. - Time-dependent initial assignments were converted into switches, but SBML evaluates them once, at the start of the simulation. They now stay in the model. - At preequilibration, which starts the experiment, a value that referred to model parameters outside the parameter table failed the lint. 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. `test_assignments_to_experiments` and `_unused_condition_id` used an initial assignment to mean a switching rule and now use an assignment rule. `_validate` relied on an estimated parameter with a time-dependent initial assignment; it now uses a ramp whose generated slope parameter collides with an observable parameter. The converted benchmark problems (Brannmark_JBC2010, Fujita_SciSignal2010, Isensee_JCB2018, Raimundez_PCB2020) are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nconverted problems alone, pick an unused slope ID - A parameter used as a model or species conversion factor counted as unused, so a condition change of it was dropped when the parameter also appeared in a converted assignment. Conversion factors now count as references. - A problem without time-dependent assignments was still changed: its experiments' periods were re-sorted. It is now returned as it was, after the lint. - A ramp's slope parameter was only named so as not to clash with SBML IDs, and clashed with, e.g., an observable parameter `<target>_slope`. It is now named with `Problem.get_unused_id`. `test_assignments_to_experiments_validate` relied on that slope clash to fail the lint; it now uses an existing experiment `default_experiment`, which the measurements without an experiment ID are assigned to as well. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…start of its period The converter kept a condition change's value as an expression and substituted it wherever a switch used it, so the value was re-evaluated at the switch with the values at that time. A condition change is evaluated once, at the start of its period, and all changes of a period simultaneously, with the values from before the period. E.g., with `d1 = b1 + 0.5` at t = 2 and `b1 = 2` at t = 3, a switch to `d1` at t = 5 used 2.5 instead of 1.7; likewise with `b1 = 2` in the same condition as `d1`. A change's value is now evaluated at the start of its period: symbols set by an earlier condition take that value, other constant model parameters their model values, and parameter table symbols stay. A change of a symbol of the assignment whose value still refers to anything else, e.g. a species, is rejected, since its value is not known there. Found by differential testing against simulations of the original and the converted problem. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FFroehlich
added a commit
that referenced
this pull request
Oct 25, 2026
Follows #559. A refactor of `assignments_to_experiments` that does not change its behaviour. The fixes for bugs found while reviewing and stress-testing it are in #574, stacked on this PR. ## Slimming down `assignments_to_experiments` The function had grown to 340 lines, most of them one loop body passing around the parsed assignment, its switches, its value before every switch, the switching times' fixed values and the slope parameter of a ramp. It is now about 75 lines (plus its docstring) that run the steps in order: - `_add_start_periods`: give every experiment the period that starts it, and find the periods that may be dropped; - for each time-dependent assignment rule, a `_PiecewiseAssignment`: - its constructor checks that the assignment is supported and works out the value before every switch, the switching times' fixed values and whether the value ramps; - `replace_in_model()` replaces the rule in the model (the slope parameter and rate rule for a ramp, the target's value); - `changes(experiment)` returns the condition changes that replace it in an experiment. The old module-level `_changes` and `_value_from` became its methods. - `_drop_changes` / `_add_conditions`: replace the condition changes the new conditions cover; - drop empty periods, add the default experiment, lint. The `redundant` / `after_preequilibration` double negative became one decision per period: one branch for the first period, one for the later ones. On the review comment on #557 about not turning this into a class: `assignments_to_experiments` stays a function. `_PiecewiseAssignment` only holds the values of one assignment that the old loop body passed between its parts. If you would rather not have it, it can become a few free functions that take those values. No change in behaviour: on all 40 conversions in the tests, the old and new implementations give the same experiment table, condition table, measurement table, SBML, errors and warnings. The benchmark problems Brannmark_JBC2010, Fujita_SciSignal2010, Isensee_JCB2018, Raimundez_PCB2020 (and Oliveira_NatCommun2021's error) also convert identically. ## Checked - `pytest tests/v2 tests/v1/test_petab.py`: 244 passed; pre-commit clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Fabian Fröhlich <frohlif@login006.nemo.thecrick.org>
FFroehlich
force-pushed
the
claude/assignments-to-experiments-fixes
branch
from
October 25, 2026 17:15
d60a383 to
0bd974e
Compare
…fixed parameter table values in condition changes - The values of the new condition changes used the symbols parsed from SBML, without the assumptions of the symbols PEtab creates (`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. - A condition change evaluated at the start of its period kept parameter table symbols, also those of fixed parameters. A switching time set by a condition to, e.g., `p + 1` with `p` fixed by the parameter table was therefore rejected, although `t1 := p + 1` in the assignment itself is converted with `p`'s nominal value. Fixed parameters now take their nominal values there as well; estimated ones stay, and switching times that depend on them are still rejected. Both found by differential testing with a reference simulator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator
Author
|
did not review code in detail, but has now survived multiple rounds of fuzzing/adversarial review+testing. |
FFroehlich
marked this pull request as ready for review
October 25, 2026 19:02
dweindl
approved these changes
Oct 26, 2026
dweindl
left a comment
Member
There was a problem hiding this comment.
By now, I'd say that assignments_to_experiments deserves a separate module and separate test module. Fine to export it from petab.v2.converters.
`assignments_to_experiments` collected the IDs that the SBML model refers to, and the symbols in the observable and noise formulas, itself. These are now `SbmlModel.get_referenced_ids()` (IDs in any math of the model, and conversion factors) and `ObservableTable.free_symbols`, in line with `ConditionTable.free_symbols`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`assignments_to_experiments` and its helpers move unchanged from `petab.v2.converters` to `petab.v2.assignments_to_experiments`, and its tests from `tests/v2/test_conversion.py` to `tests/v2/test_assignments_to_experiments.py`. `petab.v2.converters` still exports `assignments_to_experiments` and `DEFAULT_EXPERIMENT_ID`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
k1 := piecewise(0, time < 10, 1)and periods at 0 and 10, k1 stayed 0 after t = 10. Likewisetime <= 0after preequilibration. A later period now also sets the value when it differs from the value at the end of the previous period.k1 := dose * piecewise(0, time < 10, 1)and a reaction that also usesdose, the condition'sdose = 2was 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.d1 = b1 + 0.5at t = 2 andb1 = 2at t = 3 (orb1 = 2in the same condition), a switch tod1at 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 ast1 = t2 + 1now convert instead of being rejected.k1 = piecewise(0, time < 10, 1)never switches. They now stay in the model, as the docstring's scope ("assignment rules") already said.CheckInitialChangeSymbols), since preequilibration starts the experiment. Those parameters now take their model values, as at the first period.AttributeError; it now raisesNotImplementedError.k1_slope, since it was only checked against SBML IDs. It is now named withProblem.get_unused_id.t1 = p + 1, althought1 := p + 1in the assignment itself converts withp's nominal value. Fixed parameters now take their nominal values in condition changes too; switching times that depend on estimated parameters are still rejected.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_experimentsand_unused_condition_idused an initial assignment (k1 = ...) to mean a switching rule and now use an assignment rule (k1 := ...)._validaterelied on an estimated parameter with a time-dependent initial assignment; it now uses an existing experimentdefault_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_experimentsand its helpers now live in their own module,petab.v2.assignments_to_experiments(moved unchanged; still exported frompetab.v2.converters), and their tests intests/v2/test_assignments_to_experiments.py.SbmlModel.get_referenced_ids()andObservableTable.free_symbolsare new, also from review.Checked
pytest tests/v2 tests/v1/test_petab.py tests/v1/test_sbml.py: 266 passed; pre-commit clean.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 onmain. Not covered: preequilibration, experiments that start at t ≠ 0, and first periods with expression values (only parameter table symbols are allowed there).main, the same pass finds 21 mismatches and 16 lint failures of the converted problem in 200 problems.🤖 Generated with Claude Code