Fix #4930: discretise Variable.reference / scale so r/x in initial concentration don't leak - #5546
Open
gaoflow wants to merge 1 commit into
Open
Conversation
gaoflow
force-pushed
the
fix/4930-discretise-variable-reference-and-scale
branch
2 times, most recently
from
June 9, 2026 11:09
392e6c9 to
3d232b2
Compare
gaoflow
force-pushed
the
fix/4930-discretise-variable-reference-and-scale
branch
2 times, most recently
from
June 18, 2026 22:30
c74f5de to
45b00c6
Compare
…bstituting
When Initial concentration in <Domain> electrode [mol.m-3] is a
Python callable f(r, x), the corresponding
:attr:`LithiumIonParameters.c_init` is a
:class:`pybamm.FunctionParameter` invoked on
:class:`pybamm.SpatialVariable` arguments. Its value is a *symbolic*
expression carrying r and x leaves, and the average it feeds
into,
c_init_av = xyz_average(r_average(c_init))
never folds to a scalar at parameter-substitution time (PyBaMM keeps
xyz_average / r_average as deferred :class:`Integral`
operators). U_init is computed from c_init_av, and every
electrolyte- / electrode-potential :class:`Variable` is constructed
with reference=-self.param.n.prim.U_init (see
electrolyte_conductivity/full_conductivity.py).
The discretisation then substituted those Variables via
return symbol.reference + symbol.scale * StateVector(...)
without first running reference and scale through
process_symbol. As a result the un-discretised r_n / x_n
SpatialVariable leaves leaked into the discretised rhs of
Porosity times concentration [mol.m-3] (its source term depends on
the electrolyte potential's reference), and into the discretised
initial conditions of Positive electrode potential /
Electrolyte potential (process_equation subtracts name.reference
when ics=True). The model then crashed at test_shape →
check_initial_conditions with::
NotImplementedError: method self.evaluate() not implemented for
symbol r_n of type <class 'SpatialVariable'>
Fix: call self.process_symbol on symbol.reference and
symbol.scale before combining them, in three discretisation
branches:
* _process_symbol for Variable,
* _process_symbol for VariableDot,
* _process_symbol for ConcatenationVariable,
and likewise in process_equation for the initial-condition
processed_eqn - name.reference and processed_eqn / name.scale
operations.
The change is a no-op for the common case where reference and
scale are :class:`Scalar` (cached in _discretised_symbols so
this adds no measurable overhead) and unblocks callable initial
concentrations both with and without pybamm.Experiment.
Regression tests pin: (a) the OP's DFN reproducer, (b) the same
reproducer wrapped in pybamm.Experiment, (c) SPM/SPMe with the
same callable, (d) a direct discretisation invariant that no
discretised rhs / algebraic / initial-condition expression carries
an undiscretised SpatialVariable leaf, and (e) the symmetric
positive-electrode side.
gaoflow
force-pushed
the
fix/4930-discretise-variable-reference-and-scale
branch
from
July 31, 2026 22:29
45b00c6 to
59ec8af
Compare
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.
Description
Fixes #4930.
pybamm.lithium_ion.DFN(and adjacent reduced-order models) crashes withas soon as
Initial concentration in <Domain> electrode [mol.m-3]is set to a Python callable of(r, x). The exact reproducer from the issue:pybamm.lithium_ion.BasicDFNworks fine with the same callable.Root cause
LithiumIonParametersbuildsc_initas aFunctionParameterevaluated onpybamm.SpatialVariablearguments (r,PrimaryBroadcast(x, ...)), so the user'sf(r, x)becomes a symbolic expression carryingr_n/x_nleaves. Its derived averagedoes not fold to a scalar at parameter-substitution time — PyBaMM keeps
xyz_average/r_averageas deferredIntegraloperators.U_initis computed fromc_init_av, and every electrolyte- / electrode-potentialpybamm.Variableis constructed withreference=-self.param.n.prim.U_init(seeelectrolyte_conductivity/full_conductivity.py:35), so its.referenceattribute carriesr_n/x_nleaves wheneverc_initdoes.Discretisation._process_symbolforVariablethen did:without first running
reference/scalethroughprocess_symbol. The un-discretised SpatialVariable leaves leaked into the discretised rhs ofPorosity times concentration [mol.m-3](its source term references the electrolyte potential, which carries the leaky reference) and into the discretised initial conditions ofPositive electrode potential/Electrolyte potential(process_equationsubtractsname.referencewhenics=True). The model then crashed attest_shape(line 880) →check_initial_conditions(line 1292) at runtime.Fix
Discretise
referenceandscalethroughprocess_symbolbefore combining them with the state-vector slice / initial-condition equation, in three branches of_process_symbol:VariableVariableDotConcatenationVariableand the matching
processed_eqn - name.reference/processed_eqn / name.scalestep inprocess_equation.xyz_average/r_averageget evaluated against the actual mesh, foldingr_n/x_nleaves into numeric vectors.The change is a no-op for the common case where
reference/scaleareScalar(process_symbol caches the result in_discretised_symbolsso it adds no measurable overhead) and unblocks callable initial concentrations on bothSimulationandSimulation+Experimentbuild paths.Tests
New regression file
tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_initial_concentration_function_4930.py(+157 lines, 5 tests):test_dfn_solves_with_function_of_r_and_x_for_initial_concentration— OP's reproducer minus the experiment.test_dfn_with_experiment_solves_with_function_of_r_and_x— OP's exact reproducer.test_spm_and_spme_solve_with_function_of_r_and_x— guards reduced-order models too (the discretisation fix is on the generic Variable branch).test_discretised_rhs_has_no_spatial_variable_leaves— direct discretisation invariant: norhs/algebraic/initial_conditionsexpression may carry an un-foldedSpatialVariableleaf afterSimulation.build().test_positive_electrode_callable_initial_concentration— symmetric positive-electrode side, pins bothself.param.nandself.param.proutes.All 5 new tests pass. Pre-existing test suites stay green:
tests/unit/test_discretisations/— 40 passed (+1 pre-existingskfemoptional-dep skip).tests/unit/test_models/test_full_battery_models/test_lithium_ion/— 634 passed, 12 pre-existing skips.tests/unit/test_experiments/+tests/unit/test_simulation.py— 210 passed (+4 pre-existingtqdm/skfemoptional-dep failures, present onmainHEAD too).ruff checkclean.Type of change
Key checklist
ruff checkpasses on touched files# [Unreleased]→## Bug fixes