Skip to content

fix: make CasADi's RegPower and uniform-grid interpolants match evaluate - #5955

Merged
BradyPlanden merged 1 commit into
stack/00-function-copy-fixfrom
stack/00b-casadi-numerics-fix
Oct 28, 2026
Merged

BradyPlanden merged 1 commit into
stack/00-function-copy-fixfrom
stack/00b-casadi-numerics-fix

Conversation

@BradyPlanden

Copy link
Copy Markdown
Member

Description

CasADi evaluated RegPower, and linear and pchip interpolants on a uniform grid, differently from evaluate. Both changes reach every user on the default CasADi path.

  • RegPower:
    • evaluate computed x * (x² + δ²)^((a−1)/2) * scale^a, with x = base / scale, while CasADi computed x * hypot(x, δ)^(a−1) * scale^a, so the two rounded differently.
    • Both now evaluate one sequence of operations, RegPower._decomposition, each with its own sqrt, power and constants. The Python/JAX evaluator calls evaluate's form, whose _array_sqrt traces JAX arrays.
    • A literal exponent of 0.5 or 2 takes its powers as square roots (x / sqrt(sqrt(x² + δ²)) * sqrt(scale) for 0.5), so CasADi and evaluate agree bitwise, and for 0.5 the relative error stays below 6·2⁻⁵³.
    • With delta == 0, _decomposition returns power(base, exponent), so evaluate, JAX and CasADi all take the plain power. _function_diff and _function_jac differentiate base**exponent there too. Before, CasADi alone kept this fallback: evaluate gave NaN at base = 0 with exponent 0.5, and −16 rather than 16 at base = −4 with exponent 2, and the symbolic Jacobian disagreed with CasADi's values for negative bases.
    • The DiffSL export writes the pow form for every delta and exponent, unchanged.
    • Values move by round-off on both backends. _is_constant_value, which only the old CasADi form used, is removed.
  • Interpolant lookup:
    • CasADi linear interpolants (on every axis, 1D to 3D) and 1D pchip interpolants find the segment holding x by binary search on every grid.
    • On a uniform grid they used CasADi's "exact" lookup. That can pick the neighbouring segment at a knot or one ulp beside it, and so put that segment's slope in the Jacobian. _is_uniform_grid is removed.
    • The pchip table stores each segment's left knot on every grid, so pchip values also move by round-off on uniform grids.
    • CasADi's cubic (B-spline) interpolant is unchanged.

#5959 lowers RegPower to Rust through the same _decomposition, and its parity tests hold Rust to these forms. This PR uses nothing from the Rust stack and can merge on its own.

Type of change

Bug fix, under ## Bug fixes in CHANGELOG.md.

Key checklist

  • No style issues: pre-commit run
  • Unit tests pass (N)
  • Tests added: 9 Python tests. The lookup, bitwise and delta = 0 tests fail before this change.
    • CasADi MX and SX RegPower bitwise equal to evaluate, for exponents 0.5 and 2 at scales 1 and 33133;
    • RegPower at 0.5 against a 60-digit Decimal reference;
    • with delta = 0, values and the Jacobian against CasADi at base 0, −4 and 4, for exponents 0.5 and 2;
    • the segment CasADi picks at and one ulp either side of each knot of a uniform grid, by slope (linear) and curvature (pchip).
Rust execution engine stack: proposal #5667, showcase #5732

Each PR is one commit on the one above it.

# PR Adds
0 #5954 Function.create_copy keeps the function's class
0b #5955 CasADi's RegPower and uniform-grid interpolant lookups match evaluate ← this PR
1 #5956 pybamm-core: expression graph, typed IR, interpreters, forward-mode AD
2 #5957 pybamm-core: CompiledModel, coloured Jacobians, the C ABI
3 #5958 pybamm.rust bindings, built into the pybamm wheel
4 #5959 Symbol.to_rust
5 #5960 convert_to_format = "rust" for ScipySolver, AlgebraicSolver and EIS
6 #5961 pybammsolvers: IDAKLU over the C ABI
7 #5962 IDAKLUSolver on rust models
8 #5963 variables and sensitivities read in Rust
9 #5964 pybamm-core: diffsol DAE solver
10 #5965 pybamm.DiffsolSolver
11 #5966 "rust" as the default convert_to_format

@codecov

codecov Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.87097% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.23%. Comparing base (743663a) to head (a72301b).

Files with missing lines Patch % Lines
...ges/pybamm/src/pybamm/expression_tree/functions.py 80.76% 5 Missing ⚠️
Additional details and impacted files
@@                      Coverage Diff                       @@
##           stack/00-function-copy-fix    #5955      +/-   ##
==============================================================
- Coverage                       98.25%   98.23%   -0.02%     
==============================================================
  Files                             350      350              
  Lines                           36109    36104       -5     
==============================================================
- Hits                            35478    35468      -10     
- Misses                            631      636       +5     

☔ 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.

@BradyPlanden
BradyPlanden marked this pull request as ready for review October 22, 2026 15:41
@BradyPlanden
BradyPlanden requested a review from a team as a code owner October 22, 2026 15:41
@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

ProjectPyBaMM
Branchstack/00b-casadi-numerics-fix
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

MarcBerliner
MarcBerliner previously approved these changes Oct 23, 2026
Comment on lines +292 to +293
# CasADi's "exact" lookup can pick a neighbouring segment beside a knot
lookup_modes = ["binary"] * len(self.x)

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 there anything simple we can do to keep the exact lookups? It's not a big deal but it'd be nice to use them if we can instead of the slower binary search

RegPower's CasADi form, x * hypot(x, delta)^(a-1) * scale^a, rounded
differently from evaluate's x * (x^2 + delta^2)^((a-1)/2) * scale^a. Both
now evaluate one sequence of operations, and a literal exponent of 0.5 or 2
takes its powers as square roots, so CasADi and evaluate agree bitwise for
those exponents.

Linear and pchip interpolants on a uniform grid used CasADi's "exact"
lookup, which can pick the neighbouring segment for a point at or one ulp
beside a knot, so the Jacobian took that segment's slope. They now use
binary search, as on a non-uniform grid.
@BradyPlanden
BradyPlanden merged commit 5b81c32 into main Oct 28, 2026
68 of 79 checks passed
@BradyPlanden
BradyPlanden deleted the stack/00b-casadi-numerics-fix branch October 28, 2026 15:28
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