Skip to main content
PyBaMM’s model system is layered: 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: All four dicts validate their contents on assignment.

Building a custom ODE model from scratch

The example below builds a single-state ODE for a lumped thermal model: the cell temperature T evolves according to dTdt=Q−hA(T−Tamb)mcp\frac{dT}{dt} = \frac{Q - h A (T - T_{\text{amb}})}{m c_p}
1

Define the state variable and parameters

custom_ode.py
2

Set the right-hand side

custom_ode.py
3

Set initial conditions

custom_ode.py
4

Expose variables

custom_ode.py
5

Provide parameter values and solve

custom_ode.py

Adding algebraic equations

For DAE systems, use model.algebraic. The key must be the variable being solved for, and the value is the residual (set to zero):
algebraic_constraint.py
Every variable in model.algebraic must also appear in model.initial_conditions. The initial conditions for algebraic variables are used as the starting guess for the root-finding step.

Boundary conditions

Boundary conditions are set per-variable with side ("left" / "right") and a type string:
boundary_conditions.py
Valid type strings are "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 subclasses pybamm.BaseSubModel.

The submodel interface

BaseSubModel exposes six methods you override selectively:
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
model.build_model() raises pybamm.ModelError if called on a model that has already been built. Use model.update(new_submodels) when adding submodels to an already-built model.

Building a model

Calling model.build_model() runs three internal passes in order:
  1. build_fundamental() — calls get_fundamental_variables() on every submodel
  2. build_coupled_variables() — calls get_coupled_variables(variables) on every submodel
  3. build_model_equations() — calls set_rhs, set_algebraic, set_boundary_conditions, set_initial_conditions, and add_events_from on every submodel
For models that don’t use the submodel pattern (custom 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:
Use model.to_json() to get the raw JSON-serialisable dict without writing to disk. Use model.to_config() for the wrapped config format that includes geometry, spatial methods, and mesh information.
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 for CasadiSolver
  • "python" — converts to Python callable; used with ScipySolver
  • "jax" — converts to JAX pytree; used with JaxSolver
  • None — retains the PyBaMM expression tree; useful for debugging