Skip to content

Fix: half-cell DFN with surface form + lumped thermal fails to build (#5414) - #5540

Open
gaoflow wants to merge 3 commits into
pybamm-team:mainfrom
gaoflow:fix/5414-half-cell-surface-form-phi-e-bc
Open

Fix: half-cell DFN with surface form + lumped thermal fails to build (#5414)#5540
gaoflow wants to merge 3 commits into
pybamm-team:mainfrom
gaoflow:fix/5414-half-cell-surface-form-phi-e-bc

Conversation

@gaoflow

@gaoflow gaoflow commented May 28, 2026

Copy link
Copy Markdown

Problem

In half-cell mode ({"working electrode": "positive"}) the planar Li-metal negative electrode causes two submodels to be skipped, both of which previously registered key boundary conditions:

  • The negative branch of BaseSurfaceFormConductivity.set_boundary_conditions was the only place that registered the inter-domain BCs on phi_e (the concatenated electrolyte potential). With no BC key on the concatenation, Discretisation.set_internal_boundary_conditions cannot split it into per-orphan BCs, so grad(phi_e_s) / grad(phi_e_p) end up internal-edge-only at discretisation time.
  • base_ohm.BaseModel.set_boundary_conditions returned the moment the negative electrode was planar, silently dropping the BCs on the porous positive electrode potential phi_s_p too.

In the surface form, phi_s_p is built as -IndefiniteIntegral(i_s / sigma, x_p) + boundary_value(...), so the solid Ohmic-heating term -inner(i_s_p, grad(phi_s_p)) and the electrolyte Ohmic-heating term -inner(i_e_X, grad(phi_e_X)) in base_thermal.BaseThermal._get_standard_coupled_variables both ended up multiplying an IndefiniteIntegral matrix of shape (n, n+1) by an internal-edge integrand of shape (n-1, 1). Building any DFN with this combination then crashed:

pybamm.expression_tree.exceptions.ShapeError: Cannot find shape
(original error: matmul: dimension mismatch with signature
(n,k=21),(k=19,m)->(n,m))

The bug fires whenever all three of these are on:

working electrode surface form thermal
positive algebraic / differential lumped (or any non-isothermal option that triggers the Ohmic heating path)

Originally reported in #5414 with the standard Xu2019 parameter set and a DFN + "thermal": "lumped".

Root cause

Two independent BC-registration sites silently no-op in the half-cell path:

  1. src/pybamm/models/submodels/electrolyte_conductivity/surface_potential_form/full_surface_form_conductivity.pyphi_e BC is registered inside if self.domain == "negative": (lines 206–215 before this PR). Half-cell DFN skips the negative submodel via if self.options.electrode_types.get(domain) == "planar": continue, so the BC is never added.
  2. src/pybamm/models/submodels/electrode/ohm/base_ohm.pyset_boundary_conditions bails out as soon as the negative electrode is planar (if self.options.electrode_types["negative"] == "planar": return), instead of checking the current submodel's domain. So the positive instance — which is porous in a positive half-cell — also returns without registering its phi_s_p BC.

Fix

  1. full_surface_form_conductivity.py: register the phi_e BC from the separator submodel's set_boundary_conditions instead. The separator submodel is the one that is always instantiated for both full-cell and half-cell models. The BC values mirror the half-cell convention used by BaseElectrolyteConductivity.set_boundary_conditions: Dirichlet(phi_e_ref) on the left when the negative is planar, Neumann(0) otherwise; Neumann(0) on the right. Use update() rather than assignment to preserve the c_e_s Neumann BCs that get_coupled_variables set earlier in the same submodel's lifecycle.

  2. base_ohm.py: gate the early-return on self.options.electrode_types[self.domain] == "planar" instead of on the negative electrode globally. Now the planar submodel still skips (correctly — Li metal has no porous phi_s BC to set), and the porous positive submodel in a half-cell can register its phi_s_p BCs the same way it does in a full-cell.

Together these make the half-cell set_internal_boundary_conditions split work the same way the full-cell one already does, and grad(phi_s_p) / grad(phi_e_p) / grad(phi_e_s) all discretise to full edges.

Verification

The reproducer from #5414 (DFN + Xu2019 + missing thermal parameters + half-cell + surface form + lumped thermal) now builds and solves cleanly:

>>> import pybamm
>>> params = pybamm.ParameterValues("Xu2019")
>>> params.update(missing_keys, check_already_exists=False)
>>> model = pybamm.lithium_ion.DFN({
...     "working electrode": "positive",
...     "surface form": "differential",
...     "thermal": "lumped",
... })
>>> sim = pybamm.Simulation(model, parameter_values=params, C_rate=1.0)
>>> sim.solve([0, 3600])  # finishes cleanly

A 12-entry triage matrix over {DFN} × {full-cell, half-cell} × {surface form: false/algebraic/differential} × {thermal: isothermal/lumped} all build and solve. Full-cell numerics on Chen2020 are unchanged (V_end ≈ 3.51–3.53 V across the matrix, identical to main).

Targeted regression tests live in tests/unit/test_models/test_submodels/test_electrolyte_conductivity/test_half_cell_surface_form_5414.py and assert:

  • phi_e is a BC key on the half-cell DFN (covers the surface-form fix).
  • phi_s_p is a BC key on the half-cell DFN even with "surface form": "false" (covers the base_ohm fix).
  • The full half-cell × surface form × thermal matrix builds and solves with finite, physically-plausible voltages.
  • Full-cell + lumped + surface-form numerics stay within physical bounds (non-regression sanity).

Test impact

$ python -m pytest tests/unit/test_models/test_submodels/test_electrolyte_conductivity test/unit/test_models/test_submodels/test_electrode -q
2 passed in 2.83s

$ python -m pytest tests/unit/test_models/test_full_battery_models/test_lithium_ion -q
634 passed, 12 skipped in 8.16s

$ python -m pytest tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py -q
58 passed, 1 failed in 203.65s   # the one failure is `skfem` optional-dep,
                                 # also fails on `main`

$ python -m pytest tests/unit/test_models/test_submodels/test_electrolyte_conductivity/test_half_cell_surface_form_5414.py -v
4 passed in 4.25s

Scope

Three production files plus a new regression test:

  • src/pybamm/models/submodels/electrolyte_conductivity/surface_potential_form/full_surface_form_conductivity.py — move the phi_e BC into the separator branch and unify the half-cell vs full-cell BC values with the base-class convention.
  • src/pybamm/models/submodels/electrode/ohm/base_ohm.py — make the planar-skip per-domain instead of per-model.
  • CHANGELOG.md — unreleased bug-fix entry.
  • tests/unit/test_models/test_submodels/test_electrolyte_conductivity/test_half_cell_surface_form_5414.py — new regression suite.

No public API changes.

@gaoflow
gaoflow requested a review from a team as a code owner May 28, 2026 13:59
@gaoflow
gaoflow force-pushed the fix/5414-half-cell-surface-form-phi-e-bc branch 3 times, most recently from e5ff576 to 900a139 Compare June 12, 2026 15:22
gaoflow and others added 3 commits June 19, 2026 00:19
…ybamm-team#5414)

In half-cell mode (`"working electrode": "positive"`) the planar
negative electrode skips both the negative-domain electrolyte conductivity
submodel and the base electrode-Ohm submodel's BC registration, leaving
two key potentials with no boundary conditions:

1. `phi_e` (the concatenated electrolyte potential) -- registered only
   inside `BaseSurfaceFormConductivity.set_boundary_conditions`'s
   `self.domain == "negative"` branch. With no BC on the concatenation,
   `Discretisation.set_internal_boundary_conditions` cannot split it into
   per-orphan BCs, so `grad(phi_e_s)` (and `grad(phi_e_p)`) discretise to
   the internal mesh edges only.

2. `phi_s_p` (the porous positive electrode potential) -- the entire
   `base_ohm.BaseModel.set_boundary_conditions` method returned early
   the moment the negative electrode was planar, so the positive
   instance's BCs were never set even though the positive electrode is
   porous.

In the surface form, `phi_s_p` is built as
`-IndefiniteIntegral(i_s / sigma, x_p) + boundary_value(...)`, so the
solid Ohmic-heating term `-inner(i_s_p, grad(phi_s_p))` and the
electrolyte Ohmic-heating term `-inner(i_e_X, grad(phi_e_X))` both ended
up trying to multiply the IndefiniteIntegral matrix (shape (n, n+1)) by
internal-edge integrand vectors (shape (n-1, 1)), giving:

    ShapeError: Cannot find shape (original error: matmul: dimension
    mismatch with signature (n,k=21),(k=19,m)->(n,m))

Both BCs are now registered in the half-cell path:

- The `phi_e` BC is moved into the separator branch of the full
  surface-form's `set_boundary_conditions` (which is always
  instantiated) and respects the half-cell Dirichlet convention used by
  `BaseElectrolyteConductivity`.
- The base electrode-Ohm submodel's planar-skip is gated on the *current*
  submodel's domain rather than the negative electrode globally.

Includes a regression test that builds and solves the failing
`(working electrode positive, surface form, lumped thermal)` matrix on
Xu2019, asserts both BC keys are present, and pins full-cell numerics
as a non-regression check.

Fixes pybamm-team#5414
@gaoflow
gaoflow force-pushed the fix/5414-half-cell-surface-form-phi-e-bc branch from 900a139 to b74e6fa Compare June 18, 2026 22:19
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.

1 participant