From ce39ee0622b9728325b5b1a9f8e64cc74d146b27 Mon Sep 17 00:00:00 2001 From: rtimms Date: Fri, 9 Oct 2026 14:57:00 +0100 Subject: [PATCH] Release v26.10.0.0 Move the Unreleased entries under v26.10.0.0 and bump CITATION.cff. Move the #5781, #5784, #5813 and #5817 entries out of v26.9.0.0, which was tagged before they merged, and add the missing pybammsolvers>=0.11.0 breaking change. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 27 +++++++++++++++------------ packages/pybamm/CITATION.cff | 2 +- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c075e68f5c..e9f8ccc197 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ # [Unreleased](https://github.com/pybamm-team/PyBaMM/) +# [v26.10.0.0](https://github.com/pybamm-team/PyBaMM/tree/pybamm-v26.10.0.0) - 2026-10-09 + ## Breaking changes +- `pybamm` now requires `pybammsolvers>=0.11.0`, for the IDAKLU solver statistics and self-scheduled input-set sweeps. Upgrade `pybammsolvers` together with `pybamm`. ([#5782](https://github.com/pybamm-team/PyBaMM/pull/5782)) - `Symbol.children`, `Symbol.domain` (and `secondary_domain`, `tertiary_domain`, `quaternary_domain`) and `FunctionParameter.input_names` return immutable tuples that compare equal to lists, and `Symbol.domains` returns a read-only copy, as do `to_json()`, `get_children_domains()`, `read_domain_or_domains()` and `pybamm.EMPTY_DOMAINS`. Editing them raises `TypeError` instead of emitting `SymbolMutationDeprecationWarning`, and not even `list` or `dict` methods can change a symbol through them. This ends the deprecation of these edits early; before 26.9 they left the symbol's identity stale. The sequences are no longer `list` instances and slicing them gives a plain tuple; tuples are accepted wherever domains are given. Build a new symbol with `with_domains`, `create_copy(new_children=...)` or `pybamm.replace`. The deprecated setters (`name`, `domains`, `scale`, ...) are unchanged. ([#5825](https://github.com/pybamm-team/PyBaMM/pull/5825)) - `Solution` and `EISSolution` no longer have an instance `__dict__`, so attributes they do not define cannot be attached to them. Subclass `pybamm.Solution` to add attributes. A solution pickled with an attached attribute still loads, without that attribute and with a logged warning. ([#5827](https://github.com/pybamm-team/PyBaMM/pull/5827)) @@ -9,6 +12,7 @@ - Setting `Array.entries_string` or `Interpolant.entries_string`, and calling `StateVector.set_evaluation_array`, are in-place symbol updates and now emit `SymbolMutationDeprecationWarning`. Construct a new symbol instead. ([#5825](https://github.com/pybamm-team/PyBaMM/pull/5825)) - `ProcessedVariable`'s `base_variables_casadi` argument and attribute are deprecated; pass the CasADi functions positionally or as `observer=`, and call the variable to evaluate it. ([#5786](https://github.com/pybamm-team/PyBaMM/pull/5786)) +- Setting `model.convert_to_format = None` is deprecated and raises a `DeprecationWarning`. `None` was documented as keeping the PyBaMM expression tree, but it has evaluated the same way as `"python"` since 2022. Set `convert_to_format = "python"` instead, or leave the default. ([#5784](https://github.com/pybamm-team/PyBaMM/pull/5784)) ## Features @@ -17,6 +21,7 @@ - A multi-input `IDAKLUSolver` solve now hands each thread its next input set as soon as it is free, instead of splitting the sets into equal static blocks plus a serial remainder solved on the calling thread, so a heterogeneous sweep (for example a current sweep whose high currents stop early on a voltage cut-off) no longer waits on its slowest block. A failing set no longer hides the others: the `SolverError` names every set that failed, whether it failed from the start or part-way through, each prefixed `input set N: `, and a single-set solve's message now starts with `input set 0: `. Each `on_failure="warn"` warning names its set the same way. ([#5782](https://github.com/pybamm-team/PyBaMM/pull/5782)) - Added `Solution.sensitivity_names`, the sensitivity inputs in the column order of `sensitivities["all"]`. ([#5785](https://github.com/pybamm-team/PyBaMM/pull/5785)) - An outputs-only `pybammsolvers` solution also returns `y_init`, `yS_init` and `yS_term`: its initial states and its states' sensitivities at both ends. ([#5794](https://github.com/pybamm-team/PyBaMM/pull/5794)) +- Solving several input sets with `nproc=1` runs them in a loop in the calling process instead of a one-process `multiprocessing` pool, which ran the same solves in the same order while spawning an interpreter and pickling the model and every solution. This applies to consistent initial conditions found by a `root_method`, as with `IDAKLUSolver(options={"calc_ic": False})` or an explicit `root_method="nonlinear_solver"`, and to `solve(..., nproc=1)` on solvers without their own multi-input integration; the default `IDAKLUSolver` initialises inside IDA and is unaffected. For a 32-set DFN solve with `calc_ic=False`, finding consistent initial conditions drops from 1.8 s to 3 ms and the whole solve from 1.8 s to 0.12 s. On platforms that spawn worker processes (macOS, Windows), these solves also no longer hang in a script without an `if __name__ == "__main__":` guard. ([#5784](https://github.com/pybamm-team/PyBaMM/pull/5784)) ## Bug fixes @@ -43,6 +48,16 @@ - Fixed sensitivities with respect to only some of the inputs, for `output_variables` and for expressions of a time integral. ([#5794](https://github.com/pybamm-team/PyBaMM/pull/5794)) - Fixed a `ValueError` when reading variables from a solve with both scalar and vector inputs. ([#5785](https://github.com/pybamm-team/PyBaMM/pull/5785)) - The equivalent circuit model's diffusion element now lowers the voltage on discharge and raises it on charge, instead of the reverse, so a closed cycle no longer delivers net energy. Its heat, `-I * eta_diffusion`, is now included in `"Irreversible heat generation [W]"`. ([#5869](https://github.com/pybamm-team/PyBaMM/pull/5869)) +- Repeated identical experiment steps now keep their own `start_time` schedule. Before, every repeat took the `next_start_time` and `end_time` of its first occurrence, so padding rests were skipped or steps were cut off at an end time already in the past. ([#5817](https://github.com/pybamm-team/PyBaMM/pull/5817)) +- `x_average`, `z_average`, `yz_average` and `r_average` no longer pull a factor out of a product when that factor depends on the averaged coordinate through an auxiliary domain (e.g. a particle concentration varying in x), and split a quotient only when its denominator is constant. This fixes nonzero `LLI [%]` and wrong total particle lithium in the DFN when the active material volume fraction varies in x. ([#5813](https://github.com/pybamm-team/PyBaMM/pull/5813)) +- `FiniteVolume` on a mesh with a symbolic length (a `SymbolicUniform1DSubMesh`, e.g. for an `InputParameter` particle radius) no longer puts the mesh scaling inside a Kronecker product or on the left of a matrix product. Such a model could only be evaluated through CasADi: with `convert_to_format` set to `"python"` or `"jax"` the generated code was invalid, and pybamm's Jacobian raised. Gradients, divergences, spatial variables, definite and indefinite integrals, boundary values and gradients, harmonic means and internal boundary fluxes now use a constant matrix and scale its operand or its result. Values are unchanged, and an integral over a secondary dimension with a symbolic mesh, which raised a `ShapeError`, now works. ([#5781](https://github.com/pybamm-team/PyBaMM/pull/5781)) +- Setting `model.convert_to_format` to anything other than `"python"`, `"casadi"`, `"jax"` or the deprecated `None` now raises a `pybamm.OptionError`. An unrecognised value, such as a typo, used to be accepted and only surfaced at solve time, if at all. Models and simulations pickled by earlier versions still load. ([#5784](https://github.com/pybamm-team/PyBaMM/pull/5784)) +- `save_model`/`serialise_model` and `save_custom_model`/`to_json` store `convert_to_format`, so a model saved with a non-default format loads with that format rather than `"casadi"`. Files written by earlier versions load as before. ([#5784](https://github.com/pybamm-team/PyBaMM/pull/5784)) +- `IDAKLUSolver` and `NonlinearSolver` now raise a `pybamm.SolverError` listing the known options when `options` contains an unknown key. A misspelt key, such as `"num_thread"`, was accepted and ignored, leaving the solver on the default. ([#5784](https://github.com/pybamm-team/PyBaMM/pull/5784)) +- Continuing a sensitivity solve across a step boundary from a solve that used `output_variables` now raises a `SolverError` naming the cause; in an `Experiment` it ends the experiment after its first step, as other step failures do. Such a solve keeps sensitivities for the output variables only, and the next step seeded its state sensitivities from them: repeated `step` calls failed with an unrelated error or returned wrong gradients, and an SPM discharge-then-rest `Experiment` returned a voltage sensitivity 19% below its finite-difference value. ([#5784](https://github.com/pybamm-team/PyBaMM/pull/5784)) +- The initial-condition root solver (`root_method="nonlinear_solver"`, the `IDAKLUSolver` default when `calc_ic` is off) takes the tightest entry of a per-state `atol` array, where an array `atol` previously raised a `ValueError`. ([#5784](https://github.com/pybamm-team/PyBaMM/pull/5784)) +- `AlgebraicSolver` with a `"minimize"` method passes the correct gradient of the squared residual, `2 J^T f`, to `scipy.optimize.minimize` for non-CasADi formats. It scaled each Jacobian column by the matching residual entry instead, so the gradient was wrong for any system with more than one equation. ([#5784](https://github.com/pybamm-team/PyBaMM/pull/5784)) +- The check that events are positive at the initial conditions handles `convert_to_format=None`; a model with a termination event in that format raised an `UnboundLocalError` before solving. ([#5784](https://github.com/pybamm-team/PyBaMM/pull/5784)) ## Optimizations @@ -66,7 +81,6 @@ ## Deprecated - In-place symbol updates remain available with `SymbolMutationDeprecationWarning` and migration guidance, and are planned for removal no earlier than `26.11.0.0` after at least two feature releases. Prefer `with_domains`, `without_domains`, `with_mesh`, `Variable.create_copy`, constructors and `pybamm.replace`. Legacy updates invalidate live symbols' cached identities and derived values, including ancestor caches, and are deliberately slower. All PyBaMM test execution forbids these updates independently of warning filters; compatibility tests run in isolated subprocesses. ([#5779](https://github.com/pybamm-team/PyBaMM/pull/5779)) -- Setting `model.convert_to_format = None` is deprecated and raises a `DeprecationWarning`. `None` was documented as keeping the PyBaMM expression tree, but it has evaluated the same way as `"python"` since 2022. Set `convert_to_format = "python"` instead, or leave the default. ([#5784](https://github.com/pybamm-team/PyBaMM/pull/5784)) ## Features @@ -78,18 +92,14 @@ - Added the [`comment-slop`](https://github.com/ionworks/comment-slop) detector as a developer check, in two layers: a `pre-commit` hook that gates every commit and pull request, and a `PostToolUse` hook in the newly tracked `.claude/settings.json` that reports to coding agents as they write. It reports comments that restate the code, narrate an edit, or leak process chatter, on changed lines only, and never edits a file. ([#5764](https://github.com/pybamm-team/PyBaMM/pull/5764)) - `DiffSLExport` now supports `Interpolant` nodes, so models with interpolated parameters (e.g. OCP or diffusivity lookup tables) can be exported to DiffSL. 1D interpolants use DiffSL's native `interp1d` over the table data; 2D interpolants use successive 1D interpolation. ([#5756](https://github.com/pybamm-team/PyBaMM/pull/5756)) - Added the model zoo (`packages/pybamm-model-zoo/`), a home for community- and partner-contributed models: one self-contained folder per model, with a declarative `model.toml` manifest as the only boilerplate a contributor writes. Models are either `community` tier (advisory CI) or `core` tier (in the merge gate); nothing in `pybamm` itself changed. ([#5727](https://github.com/pybamm-team/PyBaMM/pull/5727)) -- Solving several input sets with `nproc=1` runs them in a loop in the calling process instead of a one-process `multiprocessing` pool, which ran the same solves in the same order while spawning an interpreter and pickling the model and every solution. This applies to consistent initial conditions found by a `root_method`, as with `IDAKLUSolver(options={"calc_ic": False})` or an explicit `root_method="nonlinear_solver"`, and to `solve(..., nproc=1)` on solvers without their own multi-input integration; the default `IDAKLUSolver` initialises inside IDA and is unaffected. For a 32-set DFN solve with `calc_ic=False`, finding consistent initial conditions drops from 1.8 s to 3 ms and the whole solve from 1.8 s to 0.12 s. On platforms that spawn worker processes (macOS, Windows), these solves also no longer hang in a script without an `if __name__ == "__main__":` guard. ([#5784](https://github.com/pybamm-team/PyBaMM/pull/5784)) - Added symbol-tree utilities (`tree_flatten`, `tree_unflatten`, `tree_map` and `replace`) with iterative, memoised traversal and shared unchanged subtrees. Replacement accepts a single symbol root; process multiple expressions with explicit loops and a shared cache. Parameter substitution and discretisation use the same traversal. Symbol identity is computed uniformly from class layout and lazily cached. Internal domain storage is interned to reduce memory and identity-hashing cost. ([#5779](https://github.com/pybamm-team/PyBaMM/pull/5779)) - Added `FiniteVolumeUnstructured` spatial method and unstructured processed-variable support for cell-centered data on arbitrary meshes. The TPFA Laplacian carries an implicit non-orthogonal correction (`"non-orthogonal correction"` option: `"over-relaxed"` or `"minimum"`) and gradients use a least-squares reconstruction, so both are exact on linear fields and second-order on skewed triangle and tetrahedral meshes. Diffusion coefficients reach faces through the distance-weighted harmonic mean, as in `FiniteVolume`, so material interfaces carry the exact series flux. ([#5688](https://github.com/pybamm-team/PyBaMM/pull/5688)) - Re-implemented the benchmark suite with `pytest-benchmark` and `pytest-memray` in `tests/benchmarks/`, replacing `asv`. Timing benchmarks are tracked with Bencher on bare metal hardware instead of on GitHub runners. ([#5630](https://github.com/pybamm-team/PyBaMM/pull/5630)) ## Bug fixes -- Repeated identical experiment steps now keep their own `start_time` schedule. Before, every repeat took the `next_start_time` and `end_time` of its first occurrence, so padding rests were skipped or steps were cut off at an end time already in the past. ([#5817](https://github.com/pybamm-team/PyBaMM/pull/5817)) -- `x_average`, `z_average`, `yz_average` and `r_average` no longer pull a factor out of a product when that factor depends on the averaged coordinate through an auxiliary domain (e.g. a particle concentration varying in x), and split a quotient only when its denominator is constant. This fixes nonzero `LLI [%]` and wrong total particle lithium in the DFN when the active material volume fraction varies in x. ([#5813](https://github.com/pybamm-team/PyBaMM/pull/5813)) - Per-phase `"particle mechanics"` options, e.g. `(("swelling and cracking", "swelling only"), "none")`, now set the mechanics submodel of each phase. Before, the option was read per electrode, so a per-phase tuple built no mechanics submodel and the model failed to build. ([#5694](https://github.com/pybamm-team/PyBaMM/pull/5694)) - `BasicDFN2D` reports the interfacial current density as `"Negative/Positive electrode interfacial current density [A.m-2]"`. It was stored under `"Negative/Positive electrode current density [A.m-2]"`, which every other model uses for the solid-phase current density. ([#5690](https://github.com/pybamm-team/PyBaMM/pull/5690)) -- `FiniteVolume` on a mesh with a symbolic length (a `SymbolicUniform1DSubMesh`, e.g. for an `InputParameter` particle radius) no longer puts the mesh scaling inside a Kronecker product or on the left of a matrix product. Such a model could only be evaluated through CasADi: with `convert_to_format` set to `"python"` or `"jax"` the generated code was invalid, and pybamm's Jacobian raised. Gradients, divergences, spatial variables, definite and indefinite integrals, boundary values and gradients, harmonic means and internal boundary fluxes now use a constant matrix and scale its operand or its result. Values are unchanged, and an integral over a secondary dimension with a symbolic mesh, which raised a `ShapeError`, now works. ([#5781](https://github.com/pybamm-team/PyBaMM/pull/5781)) - `FiniteVolumeUnstructured` no longer floors `cos(theta)` in the over-relaxed two-point weight. The floor of 0.05 capped the weight on sliver faces, such as the tetrahedra of a thin pouch-cell slab, and the cross term then gave the diffusion operator growing modes: a tetrahedral `BasicDFN3DUnstructured` on the default geometry blew up within seconds. Meshes whose faces never reached the floor are unchanged. ([#5774](https://github.com/pybamm-team/PyBaMM/pull/5774)) - Simplified EIS setup using an implicit current step and the normal simulation parameter-processing path. Removed the auxiliary voltage state; impedance uses linearised voltage/current outputs and a current forcing index cached from `y_slices`. ([#5779](https://github.com/pybamm-team/PyBaMM/pull/5779)) - Symbol replacement now combines its private rule and result memo, avoids redundant traversal and rebuild checks, and preserves lazy value conversion and shared-cache behavior. Lazy symbol identity calculation is iterative, so cold expression trees deeper than Python's recursion limit can be hashed and replaced. ([#5779](https://github.com/pybamm-team/PyBaMM/pull/5779)) @@ -107,13 +117,6 @@ - The `integration` nox session no longer installs the `pydiffsol` extra on macOS Intel CI runners, where it has no working build. ([#5726](https://github.com/pybamm-team/PyBaMM/pull/5726)) - The Read the Docs build uses Read the Docs' native `uv` support (`python.install` with `method: uv`) instead of installing `uv` from a GitHub release tarball through `asdf` in `build.jobs`. The tarball fetch failed the build whenever GitHub's release CDN returned a 5xx; `uv` now ships in the build image. The generated command, `uv sync --group docs --extra all`, is unchanged. ([#5727](https://github.com/pybamm-team/PyBaMM/pull/5727)) - The memray memory benchmarks no longer fail at random. memray saw Python's small-object allocations only as whole 1 MiB pymalloc arenas, so a test's result jumped by 1 MiB whenever the heap left by earlier tests ran out of space: `test_discretise_memory` intermittently reported 1.0 MiB against its 50 KiB limit. The `benchmark-memory` session now traces every Python allocation, each memory benchmark module warms up under memray before its tests are measured, and the limits are recalibrated to the traced figures. ([#5777](https://github.com/pybamm-team/PyBaMM/pull/5777)) -- Setting `model.convert_to_format` to anything other than `"python"`, `"casadi"`, `"jax"` or the deprecated `None` now raises a `pybamm.OptionError`. An unrecognised value, such as a typo, used to be accepted and only surfaced at solve time, if at all. Models and simulations pickled by earlier versions still load. ([#5784](https://github.com/pybamm-team/PyBaMM/pull/5784)) -- `save_model`/`serialise_model` and `save_custom_model`/`to_json` store `convert_to_format`, so a model saved with a non-default format loads with that format rather than `"casadi"`. Files written by earlier versions load as before. ([#5784](https://github.com/pybamm-team/PyBaMM/pull/5784)) -- `IDAKLUSolver` and `NonlinearSolver` now raise a `pybamm.SolverError` listing the known options when `options` contains an unknown key. A misspelt key, such as `"num_thread"`, was accepted and ignored, leaving the solver on the default. ([#5784](https://github.com/pybamm-team/PyBaMM/pull/5784)) -- Continuing a sensitivity solve across a step boundary from a solve that used `output_variables` now raises a `SolverError` naming the cause; in an `Experiment` it ends the experiment after its first step, as other step failures do. Such a solve keeps sensitivities for the output variables only, and the next step seeded its state sensitivities from them: repeated `step` calls failed with an unrelated error or returned wrong gradients, and an SPM discharge-then-rest `Experiment` returned a voltage sensitivity 19% below its finite-difference value. ([#5784](https://github.com/pybamm-team/PyBaMM/pull/5784)) -- The initial-condition root solver (`root_method="nonlinear_solver"`, the `IDAKLUSolver` default when `calc_ic` is off) takes the tightest entry of a per-state `atol` array, where an array `atol` previously raised a `ValueError`. ([#5784](https://github.com/pybamm-team/PyBaMM/pull/5784)) -- `AlgebraicSolver` with a `"minimize"` method passes the correct gradient of the squared residual, `2 J^T f`, to `scipy.optimize.minimize` for non-CasADi formats. It scaled each Jacobian column by the matching residual entry instead, so the gradient was wrong for any system with more than one equation. ([#5784](https://github.com/pybamm-team/PyBaMM/pull/5784)) -- The check that events are positive at the initial conditions handles `convert_to_format=None`; a model with a termination event in that format raised an `UnboundLocalError` before solving. ([#5784](https://github.com/pybamm-team/PyBaMM/pull/5784)) - `IDAKLUSolver` now hands IDAS each differentiated parameter's magnitude as its sensitivity scale `pbar` (`IDASetSensParams`), where it left every scale at 1. IDAS holds `pbar * dy/dp` to the state tolerances, so the unscaled column of a parameter as small as `Chen2020`'s positive particle diffusivity (4e-15, `dy/dp` around 1e14) broke the corrector: a DFN solve for that sensitivity failed with `IDA_CONV_FAIL` at every `rtol = atol` from 1e-8 to 1e-12 and now completes, matching finite differences to 3e-7, and at the default tolerances it runs 4.7x faster. The sensitivity to a large parameter, such as a temperature, is now held to the state tolerances too, which costs up to 1.6x at the default tolerances. ([#5783](https://github.com/pybamm-team/PyBaMM/pull/5783)) # [v26.8.0.0](https://github.com/pybamm-team/PyBaMM/tree/pybamm-v26.8.0.0) - 2026-08-13 diff --git a/packages/pybamm/CITATION.cff b/packages/pybamm/CITATION.cff index 6492f0dd8f..81b58650d0 100644 --- a/packages/pybamm/CITATION.cff +++ b/packages/pybamm/CITATION.cff @@ -24,6 +24,6 @@ keywords: - "expression tree" - "python" - "symbolic differentiation" -version: "26.9.0.0" +version: "26.10.0.0" repository-code: "https://github.com/pybamm-team/PyBaMM" title: "Python Battery Mathematical Modelling (PyBaMM)"