Skip to content

Option to convert model assignments to experiments in petab1to2 - #527

Merged
BSnelling merged 12 commits into
mainfrom
bes/models_to_exps
Oct 14, 2026
Merged

BSnelling merged 12 commits into
mainfrom
bes/models_to_exps

Conversation

@BSnelling

Copy link
Copy Markdown
Collaborator

The option identifies piecewise time dependent assignments from the model, converts them into rows in the experiments/condition tables in the petab v1 problem, and removes those assignments from the model.

@BSnelling
BSnelling requested a review from a team as a code owner October 7, 2026 10:45
@codecov-commenter

codecov-commenter commented Oct 7, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.30769% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.43%. Comparing base (e46ef71) to head (5720c99).

Files with missing lines Patch % Lines
petab/v2/converters.py 92.30% 3 Missing and 5 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #527      +/-   ##
==========================================
+ Coverage   77.16%   77.43%   +0.26%     
==========================================
  Files          67       67              
  Lines        7568     7670     +102     
  Branches     1349     1374      +25     
==========================================
+ Hits         5840     5939      +99     
  Misses       1242     1242              
- Partials      486      489       +3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@dilpath dilpath left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I did not receive a notification to review this somehow...

Thanks! I think it's a useful addition but I am a bit unsure whether some cases are handled currently.

Comment thread petab/v2/petab1to2.py Outdated
Comment thread petab/v2/petab1to2.py Outdated
Comment thread petab/v2/petab1to2.py Outdated
replaced_columns |= {str(s) for s in expr.free_symbols}

for sim_id, preeq_id in pairs:
t_sim = float(t_switch.subs(overrides[sim_id]))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it possible that an error arises here (or downstream) if t_switch has species, i.e. t_switch cannot be solved for a specific float time but might still be some sp.Expr involving species?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The overrides could also be problematic. If a species appears in a v1 condition table column, then its initial condition is being set there. Hence, the initial value of a species might be substituted here via overrides I think, which could silently cause an error because species should rather raise an error.

More specifically, a t_switch that contains species cannot be solved in general for specific time(s), before simulation.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I've added a check that the switch can be resolved to a number and throw if not. I think that should also cover the case where t_switch contains species?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks. I am wondering if overrides can contain an "override" for a species. If so, that override (which represents the species at the initial condition) will be implemented into t_switch. This would be a difference to the SBML model, which would implement the dynamic value of the species into t_switch over the course of the simulation.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

That sounds like it might have to be highlighted as a case we can't support with this conversion?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Agreed, was just pointing out that the case should be handled with some NotImplementedError.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Not sure if this is too strict but do you think this covers it? 1fc1744

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks! I think it's good like this -- if someone complains, then we can broaden the implementation at that point.

Comment thread petab/v2/petab1to2.py Outdated
shutil.copy(str(src), str(dest))


def _assignments_to_periods(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this replaces SBML piecewise terms with their values from the simulation condition. Maybe this can change the simulation (and thereby likelihood). For example, I think these piecewises would have triggered during preequilibration in v1. If we move them to the experiment table, then they will no longer trigger during preequilibration.

So maybe the only safe way to implement this is to only perform _assignments_to_periods for v1 simulations that do not involve preequilibration. But this seems complicated...

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I'm not sure I understand the use case, but now that I've added the real number check around switching times, I think a piecewise triggered during preequilibration would raise a NotImplemented. I could make it more explicit and not support problems with prequilibration at all if you think that would be safer.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I also do not understand the use case -- but when events are in the SBML model, they are triggered during both preequilibration and simulation. However, when your code converts these to v2 experiments, they are no longer "triggered" during preequilibration. One would need to hack them in somehow so that they also trigger during preequilibration...

Thanks for @dweindl for clarifying elsewhere -- for this case, could you add a warning in bold to the docstring that, in the converted problem, the changes are not implemented during preequilibration? And also detect whether the PEtab v1 problem has preeq and if so, issue a warning about this.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added warning in the docstring and in the code.

Comment thread petab/v2/petab1to2.py Outdated
Comment on lines +455 to +458
elif {str(s) for s in values[t_sim].free_symbols} <= parameter_ids:
# the model already has the pre-switch value, and the switch
# is valid as first period (only parameter table symbols)
del values[0.0]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am not sure I follow the logic here but could there be an issue if, for example, the model contains an SBML event that changes the value of a PEtab parameter at some timepoint $t: 0 &lt; t &lt; t_{sim}$? @dweindl ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think that could be an issue. Assignment rules would get re-evaluated after the event triggerd but the condition wouldn't.

I could protect against that by iterating through the event assignments and checking that no petab parameters in there appear in the converted piecewise assignments?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nevermind, v2 explicitly excludes this case: the parameter table must contain truly constant parameters (e.g. the parameters cannot be changed by an SBML event).

Hence, throw an error if it looks like you are about to implement an estimated parameter in the condition table (which is anyway invalid, so alternatively you could detect the issue by validating your generated PEtab problem --this approach is also fine for me). In any case, please add a small test for this.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added a test with this case and a linting step at the end of the function.

@dweindl

dweindl commented Oct 9, 2026

Copy link
Copy Markdown
Member

I see how this can be useful. However, I am wondering if it wouldn't be substantially more maintainable if we would separate that from the v1->v2 conversion, and do that as an independent post-conversion pass. In that case, the input would be a v2 problem with a couple of additional things to handle, but those could be rejected (non-zero starting time, non-sbml models, ...).

@BSnelling

Copy link
Copy Markdown
Collaborator Author

Thanks both! I've moved the feature into its own function as @dweindl suggested and added safety guards around certain cases. For now I'm keeping the scope pretty narrow, just piecewise assignments that are resolvable to a specific time are supported, other cases raise NotImplemented.

@BSnelling
BSnelling requested a review from dilpath October 12, 2026 11:01

@dilpath dilpath left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks! Happy to review again after the discussed changes. As discussed, I would suggest we explicitly support the following cases only:

A single piecewise function with exactly 3 arguments, where the 2nd argument is an inequality that can be re-arranged explicitly into either of t > ... or t >= ..., and error on anything else.
The first and third arguments should be some numeric value or anything else that is a valid targetValue in the v2 condition table (also check this -- linting the problem afterwards is fine for me).
If there is preequilibration, then there will be the discussed breaking change to warn about.

Also write this scope into the docstring of the function.

Comment thread petab/v2/converters.py Outdated

t_end = periods[i + 1].time if i + 1 < len(periods) else sp.oo
t = t_switch.subs(subs)
if not (t.is_number and t.is_real):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would additionally suggest t.is_finite. If t evaluates to negative infinity then maybe preequilibration is erroneously implemented 🤷

Comment thread petab/v2/petab1to2.py Outdated
replaced_columns |= {str(s) for s in expr.free_symbols}

for sim_id, preeq_id in pairs:
t_sim = float(t_switch.subs(overrides[sim_id]))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks. I am wondering if overrides can contain an "override" for a species. If so, that override (which represents the species at the initial condition) will be implemented into t_switch. This would be a difference to the SBML model, which would implement the dynamic value of the species into t_switch over the course of the simulation.

Comment thread petab/v2/petab1to2.py Outdated
shutil.copy(str(src), str(dest))


def _assignments_to_periods(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I also do not understand the use case -- but when events are in the SBML model, they are triggered during both preequilibration and simulation. However, when your code converts these to v2 experiments, they are no longer "triggered" during preequilibration. One would need to hack them in somehow so that they also trigger during preequilibration...

Thanks for @dweindl for clarifying elsewhere -- for this case, could you add a warning in bold to the docstring that, in the converted problem, the changes are not implemented during preequilibration? And also detect whether the PEtab v1 problem has preeq and if so, issue a warning about this.

Comment thread petab/v2/petab1to2.py Outdated
Comment on lines +455 to +458
elif {str(s) for s in values[t_sim].free_symbols} <= parameter_ids:
# the model already has the pre-switch value, and the switch
# is valid as first period (only parameter table symbols)
del values[0.0]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nevermind, v2 explicitly excludes this case: the parameter table must contain truly constant parameters (e.g. the parameters cannot be changed by an SBML event).

Hence, throw an error if it looks like you are about to implement an estimated parameter in the condition table (which is anyway invalid, so alternatively you could detect the issue by validating your generated PEtab problem --this approach is also fine for me). In any case, please add a small test for this.

Comment thread tests/v2/test_conversion.py Outdated
Comment on lines +890 to +891
"piecewise(0, time < 10, 1) + piecewise(0, time < 20, 1)",
"piecewise(0, time < 10, 1, time < 20, 2)",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this case handled? e.g. time < 10 || time > 15?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This will now throw a NotImplemented because it is not a single inequality.

@BSnelling
BSnelling requested a review from dilpath October 14, 2026 08:47

@dilpath dilpath left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks! Fine to merge now I think

Comment thread petab/v2/converters.py Outdated
Comment thread petab/v2/converters.py
Comment thread tests/v2/test_conversion.py Outdated
@BSnelling
BSnelling merged commit 1c3d7b9 into main Oct 14, 2026
13 checks passed
@BSnelling
BSnelling deleted the bes/models_to_exps branch October 14, 2026 13:29
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.

4 participants