Skip to content

refactor: read solution variables through per-model observer caches - #5786

Merged
BradyPlanden merged 7 commits into
mainfrom
a4/observation-seam
Oct 8, 2026
Merged

BradyPlanden merged 7 commits into
mainfrom
a4/observation-seam

Conversation

@BradyPlanden

@BradyPlanden BradyPlanden commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

Description

Reading a variable back from a Solution was spread across Solution, ProcessedVariable and IDAKLUSolver, each re-deriving the same facts. This gathers it into two modules, with identical values:

  • solvers/variable_observer.py: VariableObserver and CasadiObserver, moved from ProcessedVariable. An observer serialises its CasADi functions and selects its segments once, so a repeat solution["Voltage [V]"](t) on an SPMe discharge is about 40% faster.
  • solvers/observation.py: build_variable, which reads each segment through its own model's ObserverCache (replacing model._variables_casadi), and OutputAssembly, moved from IDAKLUSolver. A Solution holds no observation state.
  • Time integrals are now cached too, so each is converted to CasADi once per model rather than once per segment.

ProcessedVariable's base_variables_casadi is deprecated, and solutions pickled by earlier versions still load. The Rust path (#5732) will add a second reader to build_variable.

Type of change

Add an entry under # [Unreleased] in CHANGELOG.md, in one of ## Breaking changes, ## Deprecated, ## Features, or ## Bug fixes (include PR number; breaking and deprecation entries need a one-line migration note). Internal-only PRs — refactor, docs, CI, tests — can skip this. See RELEASE.md for the full policy.

Important checks:

Please confirm the following before marking the PR as ready for review:

  • No style issues: nox -s pre-commit
  • All tests pass: nox -s tests
  • The documentation builds: nox -s doctests
  • Code is commented for hard-to-understand areas
  • Tests added that prove fix is effective or that feature works

@BradyPlanden
BradyPlanden requested a review from a team as a code owner September 23, 2026 19:09
@codecov

codecov Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.11%. Comparing base (f669fd8) to head (2d1f76d).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5786      +/-   ##
==========================================
+ Coverage   98.09%   98.11%   +0.01%     
==========================================
  Files         346      348       +2     
  Lines       35407    35610     +203     
==========================================
+ Hits        34733    34939     +206     
+ Misses        674      671       -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.

@review-notebook-app

Copy link
Copy Markdown

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@BradyPlanden
BradyPlanden changed the base branch from main to a3b/processed-variable-followups October 2, 2026 12:52
@BradyPlanden
BradyPlanden added this pull request to stack #5795 October 2, 2026 12:53
@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

ProjectPyBaMM
Brancha4/observation-seam
Testbedbare-metal

⚠️ WARNING: Truncated view!

The full continuous benchmarking report exceeds the maximum length allowed on this platform.

🐰 View full continuous benchmarking report in Bencher

@BradyPlanden BradyPlanden changed the title refactor: put solution observation behind an ObservationBackend seam refactor: read solution variables through per-model observer caches Oct 2, 2026

@rtimms rtimms left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The refactor matches the code it replaces as far as I can trace: build_variable against _update_variable, CasadiObserver against the old observe/sensitivity paths, and OutputAssembly.attach against the old IDAKLU loop. Caching time integrals per model is safe because ProcessedVariableTimeIntegral holds only model-level data. The deprecation shim and old-pickle loading are tested. Two minor points inline.

@BradyPlanden
BradyPlanden requested a review from rtimms October 8, 2026 13:40
Base automatically changed from a3b/processed-variable-followups to main October 8, 2026 16:39
A Solution reads variables through one ObservationBackend and a ProcessedVariable
through one VariableObserver (CasADi only); OutputAssembly owns the output_variables
layout and keys its sensitivities by sensitivity parameter.

Solution carries its backend in an _observation slot, which a solution
pickled before it existed loads as CASADI_OBSERVATION.
A Solution carried an observation backend, the module-level
CASADI_OBSERVATION, which first_state, last_state, +, from_sub_solutions
and copy sliced and joined. The backend held no state, so every slice
and join returned it unchanged, while the CasADi leaves it compiled were
cached on each model.

A Solution now holds no observation state. build_variable reads each
segment through its own model's ObserverCache, which replaces
model._variables_casadi and keeps its rules: keyed by variable name,
copied by new_copy, and skipping time integrals. A model pickled before
the cache existed builds one on first read.

ObservationBackend, CasadiObservation, CASADI_OBSERVATION and
join_observations are removed with their tests.
ObserverCache skipped time integrals: it cached only the CasADi leaf, so
a hit lost the integral and would have read its integrand. Every read
of a time integral therefore converted it again for each segment, 20
times for a 20-step experiment, and again for every solve of the model.

The cache now keeps a leaf with the expression it evaluates and its
time integral. The integral's analysis depends only on its expression,
and nothing changes it after conversion, so solutions of one model can
share it. Reading an ExplicitTimeIntegral from a 20-step experiment
drops from 1.9 ms to 0.6 ms on first read and from 1.5 ms to 0.01 ms on
later solves of the model, with identical values.
Each changelog entry is now one line, leaving the detail to the PR, and
the performance entry moves from Features to Optimizations. Eight
two-line comments become one line, and four that restated the code they
moved with are removed. VariableObserver holds its leaves rather than
owning them: the model's ObserverCache shares them.
The solver returns no rows for such a variable, so its payload is
(n_t, 0). Covers it as the only output and alongside a regular one.
@BradyPlanden
BradyPlanden merged commit b2bd07d into main Oct 8, 2026
43 checks passed
@BradyPlanden
BradyPlanden deleted the a4/observation-seam branch October 8, 2026 18:08
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