BaseModel defines the core equation containers, BaseBatteryModel extends it with battery-specific geometry and mesh defaults, and concrete models like SPM or DFN live at the top. You can plug in at any layer.
Model hierarchy
BaseModel.__init__ initialises all equation containers to empty dicts and sets sensible defaults (use_jacobian=True, convert_to_format="casadi").
Core equation containers
Every model instance exposes four primary dictionaries you must populate:| Attribute | Type | Purpose |
|---|---|---|
model.rhs | dict | Maps a state variable to its time derivative expression (dy/dt = f) |
model.algebraic | dict | Maps a state variable to a residual that must equal zero |
model.initial_conditions | dict | Maps each state variable to its initial value |
model.boundary_conditions | dict | Maps a variable to {"left": (expr, type), "right": (expr, type)} |
model.variables | dict | Maps string names to symbolic expressions for post-processing |
Building a custom ODE model from scratch
The example below builds a single-state ODE for a lumped thermal model: the cell temperatureT evolves according to
Adding algebraic equations
For DAE systems, usemodel.algebraic. The key must be the variable being solved for, and the value is the residual (set to zero):
algebraic_constraint.py
Boundary conditions
Boundary conditions are set per-variable with side ("left" / "right") and a type string:
boundary_conditions.py
"Dirichlet" (prescribed value) and "Neumann" (prescribed flux).
Submodel architecture
Large battery models are assembled from submodels — isolated components that each contribute equations and variables. Each submodel subclassespybamm.BaseSubModel.
The submodel interface
BaseSubModel exposes six methods you override selectively:
| Method | Called with | Purpose |
|---|---|---|
get_fundamental_variables() | — | Create variables that depend only on this submodel |
get_coupled_variables(variables) | full variables dict | Create variables that need other submodels’ variables |
set_rhs(variables) | full variables dict | Write into self.rhs |
set_algebraic(variables) | full variables dict | Write into self.algebraic |
set_boundary_conditions(variables) | full variables dict | Write into self.boundary_conditions |
set_initial_conditions(variables) | full variables dict | Write into self.initial_conditions |
All methods default to
pass, so you only override the ones relevant to your submodel.Writing a submodel
my_submodel.py
Registering submodels in a model
model_with_submodels.py
Building a model
Callingmodel.build_model() runs three internal passes in order:
build_fundamental()— callsget_fundamental_variables()on every submodelbuild_coupled_variables()— callsget_coupled_variables(variables)on every submodelbuild_model_equations()— callsset_rhs,set_algebraic,set_boundary_conditions,set_initial_conditions, andadd_events_fromon every submodel
BaseModel instances), setting the equation dicts directly means there is nothing to build — Simulation will build for you implicitly.
Model serialization
PyBaMM can save a discretised model to a JSON file and reload it without re-discretising.model.save_model() parameters:
| Parameter | Type | Description |
|---|---|---|
filename | str, optional | Output path. Defaults to <model_name>_<datetime>.json |
mesh | pybamm.Mesh, optional | Include mesh data to enable post-processing and plotting |
variables | dict, optional | Processed variable expressions to embed (requires mesh) |
convert_to_format options
convert_to_format options
The
model.convert_to_format attribute controls how the expression tree is compiled before handing to the solver:"casadi"(default) — converts to a CasADi expression graph; required forCasadiSolver"python"— converts to Python callable; used withScipySolver"jax"— converts to JAX pytree; used withJaxSolverNone— retains the PyBaMM expression tree; useful for debugging