This document is the source of truth for how PyBaMM is versioned, when releases happen, what counts as a breaking change, and how breaking changes and deprecations are communicated. For a quick summary aimed at users, see the Versioning section of the README.
YY.MM.N.P — year, month, feature release index within that month, patch level.YY.MM.0.0).## Breaking changes in CHANGELOG.md. Public APIs that are removed or renamed ship a DeprecationWarning for at least two prior feature releases first (see the Deprecation policy section for the documented exceptions).# [Unreleased] section at the top of CHANGELOG.md on main is the canonical preview of what’s about to ship.PyBaMM versions take the form YY.MM.N.P:
| Component | Meaning | Example |
|---|---|---|
YY | Two-digit year | 27 |
MM | Month of release (1–12, no leading zero) | 1, 11 |
N | Feature release within that month, 0-indexed — 0 for the first | 0, 1 |
P | Patch level — 0 for the feature release itself; 1, 2, … for patches off it | 0, 1, 2 |
Worked examples:
27.1.0.0 — first feature release in January 2027.27.1.0.1 — first patch off 27.1.0.0.27.1.1.0 — second feature release in January 2027 (only when two feature releases land in the same calendar month).27.1.1.1 — first patch off 27.1.1.0.Cutover. The first feature release tagged after the policy lands is the first to use YY.MM.N.P. Earlier tags (v26.x.N) remain in their original form; we do not retroactively retag.
PyBaMM and pybammsolvers live in one repository (a UV workspace under
packages/) but release independently to PyPI, discriminated by tag
namespace. See docs/superpowers/specs/2026-06-11-pybamm-monorepo-design.md for
the full design.
github.repository guard no longer distinguishes them).pybammsolvers releases use the pybammsolvers-v* namespace.v0.8.0, v0.8.1, v0.8.2) carried over from the
standalone pybammsolvers repo are not part of PyBaMM’s release history and
must not drive PyBaMM’s computed version. They are either dropped or re-tagged
under pybammsolvers-v* during the migration.YY.MM.0.0). Subsequent feature releases in the same month (YY.MM.1.0, YY.MM.2.0, …) should not contain breaking changes unless absolutely required. Exceptions must be justified in the PR description and in the ## Breaking changes changelog entry, alongside the standard deprecation note.A change is breaking if it does any of the following:
A change is not breaking if it:
Public API = anything documented at docs.pybamm.org.
If a class, function, or method appears in the rendered API reference, it is in PyBaMM’s public contract. Anything else — internal modules, leading-underscore names, undocumented helpers — is private and may change between any two releases without a ## Breaking changes entry.
The rendered docs build is the auditable source of truth. A PR reviewer can answer “is this public?” by checking whether the symbol is reachable from the API reference index.
Removing or renaming a public API must ship a DeprecationWarning in at least two prior feature releases before the removal release.
## Deprecated entry in the changelog.## Breaking changes entry that references the original deprecation.Exceptions. The two-release floor may be skipped only when one of the following applies. Each exception must be justified in the PR description and in the ## Breaking changes changelog entry:
The # [Unreleased] section at the top of CHANGELOG.md on main is the canonical pre-announcement channel. Downstream package maintainers, users, and contributors can read that section at any time to see what is about to ship.
The following are not required by policy but may be chosen by maintainers for exceptional breaks (e.g. removing a long-standing core class, dropping a supported Python version):
[BREAKING] prefix on GitHub release titles.Within each release block (and within # [Unreleased]), sections appear in this order, omitting any that are empty:
## Breaking changes
## Deprecated
## Features
## Bug fixesThe ordering is scariest first: anyone scanning the changelog sees breaks and deprecations before features.
Entry format. Each entry is a single bullet ending in a PR link:
- Short imperative description of the change. ([#1234](https://github.com/pybamm-team/PyBaMM/pull/1234))Use full GitHub URLs (not bare #1234) so the rendered markdown on docs.pybamm.org links correctly. ## Breaking changes and ## Deprecated entries must include a one-line migration note describing how users adapt.
Historical entries are not rewritten. The new section ordering and the new ## Deprecated section apply from the next release block forward. Existing release blocks keep whatever they currently have.
The version string in the built distribution is supplied by hatch-vcs from the VCS tag (pyproject.toml has dynamic = ["version"] with version.source = "vcs", writing to src/pybamm/_version.py). The scripts/update_version.py helper updates CITATION.cff and prepends a new dated heading to CHANGELOG.md; its version-string handling is format-agnostic and accepts the four-component form unchanged.
A feature release is YY.MM.N.0 — the patch component is 0. The first feature release in a given calendar month uses N=0; subsequent feature releases in the same month use N=1, N=2, etc.
# [Unreleased] in CHANGELOG.md accurately reflects what’s about to ship — every breaking change, deprecation, feature, and bug fix has an entry with a PR link, and entries are grouped under the four sections (## Breaking changes, ## Deprecated, ## Features, ## Bug fixes) in that order.main: git checkout -b release/vYY.MM.N.0.uv run python scripts/update_version.py YY.MM.N.0 to update CITATION.cff and prepend a dated heading to CHANGELOG.md.main. Ensure CI passes, then merge.main at the merge commit, create a GitHub release with the tag pybamm-vYY.MM.N.0. Copy the relevant CHANGELOG.md block into the release description. This triggers publish_pypi.yml and creates the PyPI release automatically. (Monorepo: PyBaMM release tags use the pybamm-v prefix so they are distinct from pybammsolvers-v*; see “Monorepo: independent package releases” above.)pip install pybamm==YY.MM.N.0.A patch release is YY.MM.N.P where P >= 1. Patches are cut from the previous tag in the same feature line so the release contains only the bug fixes, not unrelated changes that have landed on main since the feature release.
main first via normal PRs.git checkout -b release/vYY.MM.N.P pybamm-vYY.MM.N.{P-1} (e.g. release/v27.1.0.1 from pybamm-v27.1.0.0).-x:git cherry-pick -x <commit-sha-from-main>uv run python scripts/update_version.py YY.MM.N.P to update CITATION.cff and prepend a dated heading to CHANGELOG.md. Commit the result on the release branch.pybamm-vYY.MM.N.P from the release/vYY.MM.N.P branch (NOT from main). Copy the relevant CHANGELOG.md block into the release description. This triggers publish_pypi.yml.pip install pybamm==YY.MM.N.P.main separately. Do not merge the release branch back to main — that would duplicate commits with new hashes. Instead:git checkout main
git checkout -b update-changelog-vYY.MM.N.PCHANGELOG.md to add the new dated vYY.MM.N.P block (moving the entries out of # [Unreleased]), and update CITATION.cff. Open a PR to main.pybammsolvers release#pybammsolvers releases independently of PyBaMM. Its published version is read from packages/pybammsolvers/src/pybammsolvers/version.py (via the regex in packages/pybammsolvers/pyproject.toml), not from the release tag — the pybammsolvers-v* tag namespace only routes the workflow. Keep the tag and version.py in lockstep, or the wrong version ships.
__version__ in packages/pybammsolvers/src/pybammsolvers/version.py and record the change in CHANGELOG.md. Open a PR to main, ensure CI passes, then merge.main at the merge commit, create a GitHub release with the tag pybammsolvers-vX.Y.Z, where X.Y.Z exactly matches the new version.py value. This triggers release_solvers.yml, which builds wheels + sdist and publishes to PyPI. The check_version job in that workflow fails the release if the tag and version.py disagree; PyPI separately rejects a re-upload of an already-published version.pip install pybammsolvers==X.Y.Z.The conda-forge release flow is triggered automatically after a stable PyPI release: the conda-forge bot opens a PR against pybamm-feedstock, which maintainers review and approve.
When a release touches the API, console scripts, entry points, optional dependencies, supported Python versions, or core project metadata, update meta.yaml in pybamm-feedstock following the conda-forge maintainer docs and re-render the recipe. Push updates directly to the bot’s automated PR where possible. Manual PRs must bump the build number in meta.yaml and be opened from a personal fork.