Skip to content

Fix JAX PEtab backend: discontinuities, PEtab v1 import, event assignments#3196

Open
FFroehlich wants to merge 2 commits into
mainfrom
fix/jax-events-and-petab-v1-v2
Open

Fix JAX PEtab backend: discontinuities, PEtab v1 import, event assignments#3196
FFroehlich wants to merge 2 commits into
mainfrom
fix/jax-events-and-petab-v1-v2

Conversation

@FFroehlich

Copy link
Copy Markdown
Member

Summary

A cluster of related fixes to the JAX simulation/PEtab backend, found while investigating how explicit (time-triggered) discontinuities are handled:

  • Precise discontinuity stepping: clip the diffrax step-size controller onto known (explicit) discontinuity times (ClipStepSizeController(controller, jump_ts=...)) instead of relying solely on root-finding. Verified 10 orders of magnitude accuracy improvement and zero rejected steps on a synthetic explicit-discontinuity model.
  • Explicit heavisides as tracked variables: time-triggered Heaviside placeholders are now generated as tracked variables (like implicit/state-dependent ones) instead of being inlined as symbolic jnp.select functions of time. Keeps the vector field smooth within each integration segment.
  • PEtab v1 → v2 conversion: PetabImporter now upgrades an in-memory petab.v1.Problem to v2 (via to_files_generic + petab.v2.Problem.from_yaml, which auto-upgrades) instead of raising NotImplementedError. This lets the JAX backend (which requires v2) consume v1 PEtab problems, e.g. from the PEtab benchmark collection.
  • Noise/observable placeholder bug: JAX gave wrong sigmas (and thus wrong chi2/log-likelihood) for any model using PEtab's standard noiseParameter{n}_{observableId} / observableParameter{n}_{observableId} naming convention. The anchored classification regex only matched the bare noiseParameter{n} form, so these placeholders leaked into ordinary model parameters instead of being resolved via the per-measurement override arrays — every measurement ended up using the first observable's noise value. Fixed by porting the substitution already used by the legacy PEtab v1 JAX importer (v1/_sbml_import.py) into the newer PetabImporter, JAX-gated, operating only on an ephemeral importer-internal copy (never touching the PEtab problem itself or the SUNDIALS path). Verified against the PEtab benchmark model Boehm_JProteomeRes2014: chi2/llh now match SUNDIALS exactly (was off by ~7.4 in llh).
  • Event assignments silently dropped: _apply_event_assignments processed roots in fixed windows of two, assuming every event contributes a Heaviside +/− root pair. A plain SBML <event> with an assignment (no associated Heaviside) contributes a single root, so whenever a model's total root count was odd, the last root (in declaration order) was silently dropped — no error, no warning, no state update. Fixed by iterating over every root individually. Investigated and confirmed this doesn't regress the paired-Heaviside mechanism, including when Heaviside pairs and plain events are mixed in the same model (explicit events always sort before Heaviside pairs internally, so a dropped tail root there is always a Heaviside root with a zero delta).

Test plan

  • python/tests/test_jax.py: existing discontinuity tests pass; added test_explicit_discontinuity and test_event_assignments_odd_root_count (verified the latter fails without the fix and passes with it)
  • python/tests/petab_/test_petab_v2.py: added test_petab_importer_upgrades_v1_problem and test_jax_matches_sundials_with_per_observable_noise_parameters (JAX vs SUNDIALS llh agreement)
  • Verified SUNDIALS (v1 and v2-via-conversion) llh unaffected by the JAX-gated noise-parameter fix, on Boehm_JProteomeRes2014 and Fujita_SciSignal2010

Notes for reviewers

Two related pre-existing bugs were found during this investigation but are out of scope for this PR (tracked separately):

  • _known_discs(self, p) crashes with NameError when an explicit trigger time depends on a model expression (w) rather than pure parameters (e.g. Fujita_SciSignal2010's EGF pulse timing) — predates this change (PR Update PEtab SciML support to PEtab v2 #3165).

🤖 Generated with Claude Code

Several related fixes to the JAX simulation backend surfaced while
investigating discontinuity handling:

* Clip the step-size controller onto known (explicit) discontinuity
  times so time-triggered events are integrated precisely instead of
  relying solely on root-finding.
* Represent explicit (time-triggered) Heaviside placeholders as
  tracked variables instead of inlining them as `jnp.select`, matching
  how implicit/state-dependent triggers are already handled. This
  keeps the vector field smooth within each integration segment.
* `PetabImporter` now upgrades in-memory PEtab v1 problems to v2
  instead of raising `NotImplementedError`, so the JAX backend (which
  requires v2) can consume v1 PEtab problems.
* Fix wrong sigmas/log-likelihood for observables using PEtab's
  standard `noiseParameter{n}_{observableId}` /
  `observableParameter{n}_{observableId}` placeholder naming: these
  were misclassified as ordinary model parameters instead of
  per-measurement overrides, so every measurement got the first
  observable's noise value. Ported the substitution already used by
  the legacy v1 JAX importer to the newer PEtab v2 importer.
* Fix event assignments being silently dropped whenever a model's
  total root count is odd (e.g. any odd number of plain SBML events
  with assignments): `_apply_event_assignments` processed roots in
  fixed windows of two, assuming every event comes in a Heaviside
  +/- pair. Now processes every root individually.

Adds regression tests for each of the above.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 4, 2026 08:08
@FFroehlich FFroehlich requested a review from a team as a code owner July 4, 2026 08:08
@FFroehlich

Copy link
Copy Markdown
Member Author

known_discs fix is here #3195

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR delivers a set of targeted fixes to AMICI’s JAX simulation/PEtab integration to improve correctness around discontinuities/events and broaden PEtab compatibility (v1→v2 upgrade), while aligning JAX results with SUNDIALS for PEtab-standard noise/observable placeholder naming.

Changes:

  • Clip Diffrax step-size control to known explicit discontinuity times and keep explicit time-triggered Heaviside terms as tracked variables (avoid inlined symbolic time functions).
  • Fix event assignment application for models with an odd number of roots by applying assignments per-root instead of in fixed pairs.
  • Upgrade in-memory PEtab v1 problems to v2 during import and correct JAX handling of per-observable noise/observable placeholder parameters.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
python/tests/test_jax.py Adds regression tests for explicit discontinuity handling and odd-root-count event assignments.
python/tests/petab_/test_petab_v2.py Adds tests for v1→v2 in-memory upgrade and JAX vs SUNDIALS agreement for per-observable noise placeholders.
python/sdist/amici/sim/jax/_simulation.py Clips the step-size controller to known discontinuities; fixes per-root event assignment application logic.
python/sdist/amici/importers/petab/_petab_importer.py Implements PEtab v1 in-memory upgrade; adds JAX-only placeholder substitution/filtering; validates upgraded problem.
python/sdist/amici/exporters/jax/ode_export.py Stops inlining Heaviside variables as symbolic functions of time so discontinuities can be handled via tracked variables/events.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread python/tests/test_jax.py
@codecov

codecov Bot commented Jul 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.45455% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.40%. Comparing base (9d4ab62) to head (0b3cad9).

Files with missing lines Patch % Lines
...hon/sdist/amici/importers/petab/_petab_importer.py 95.65% 1 Missing ⚠️
python/sdist/amici/sim/jax/_simulation.py 85.71% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3196      +/-   ##
==========================================
- Coverage   77.05%   76.40%   -0.65%     
==========================================
  Files         317      317              
  Lines       20903    20941      +38     
  Branches     1484     1483       -1     
==========================================
- Hits        16106    16001     -105     
- Misses       4789     4932     +143     
  Partials        8        8              
Flag Coverage Δ
cpp 71.57% <74.19%> (-0.03%) ⬇️
cpp_python 36.67% <6.45%> (-0.04%) ⬇️
petab 46.88% <95.45%> (+0.09%) ⬆️
python 68.31% <74.19%> (-0.04%) ⬇️
sbmlsuite-jax ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
python/sdist/amici/exporters/jax/ode_export.py 77.90% <100.00%> (-0.26%) ⬇️
python/tests/petab_/test_petab_v2.py 100.00% <100.00%> (ø)
...hon/sdist/amici/importers/petab/_petab_importer.py 86.57% <95.65%> (+0.49%) ⬆️
python/sdist/amici/sim/jax/_simulation.py 93.61% <85.71%> (-0.83%) ⬇️

... and 7 files with indirect coverage changes

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

Comment thread python/tests/test_jax.py
assert stats["num_rejected_steps"] == 0

except NotImplementedError as err:
if "The JAX backend does not support" in str(err):

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.

It's supposed to be supported - do we need these checks?

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