feat: add the model zoo for contributed models - #5727
Open
BradyPlanden wants to merge 7 commits into
Open
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #5727 +/- ##
=======================================
Coverage 98.20% 98.20%
=======================================
Files 340 340
Lines 32743 32743
=======================================
Hits 32156 32156
Misses 587 587 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Adds `packages/pybamm-model-zoo/`, a uv workspace member importable as `pybamm_model_zoo`, as the home for community- and partner-contributed models. One self-contained folder per model holds its code, tests, examples, citation, and a declarative `model.toml` manifest. The manifest is the only boilerplate a contributor writes. The registry, the contract test suite, the docs pages, the CI routing, and the status badges are all derived from it, and manifests are parsed rather than imported, so a model whose dependencies are missing still appears in the registry and reports a clean failure instead of taking the zoo down. `pybamm_model_zoo.testing.contract.CHECKS` is the single definition of the contract: ten checks, each scoped as portable (`model`), in-tree wiring (`packaging`), or repository hygiene (`repo`). The test suite, the manifest's `skip_contract` validation, and the documented table all derive from that registry, and the scope is what lets the same shipped module hold a third-party collection to only the portable rules. Models are `community` tier (advisory CI) or `core` tier (in the merge gate). Advisory-ness lives in the CI job, never in an `xfail` marker, so a red community model reports red without blocking a PyBaMM merge. `spm_series_resistance` is the reference entry, at `core` tier so the gating path is exercised from the start. `nox -s zoo-new` renders the template and appends the CODEOWNERS line; `tests/test_template.py` renders that same template and runs the whole portable contract plus Ruff against the result, so "follow the template and CI is green on day one" is tested rather than hoped. The weekly `model_zoo_status` workflow matrixes on PyBaMM version and pairs a model only with releases its `pybamm_requires` admits, then opens a reviewable pull request with `status.json`, refreshed badges, and the docs compatibility table. Nothing under `packages/pybamm/src/pybamm/` changes; everything outside the zoo is config, CI, or docs. Addresses #5511
The zoo's manifest contract compared `pybamm_requires` against `pybamm.__version__`, which hatch-vcs derives from `git describe --match "pybamm-v*"`. The CI checkout is shallow — `_nox.yml` defaults to `fetch-depth: 1` — so no tag is in reach, setuptools_scm falls back to guessing `0.0.1.dev1+g<sha>`, and `>=26.0` went unsatisfied, taking the merge gate with it. It passed locally only because a full clone has the tags. `names_a_release` recognises that guess (PyBaMM has never published a `0.0` series) and skips the range comparison, so an sdist, a fork, or a shallow clone no longer reports a model as incompatible with the PyBaMM it is actually running against. This is the carve-out `scripts/matrix.py` already makes for its `main` cell, which the contract check was missing. Specifier *validity* stays unconditional — that half never needed git. The zoo CI jobs then pass `fetch_depth: 0` so the version resolves and the comparison is exercised rather than silently skipped, and the weekly status workflow does the same for the `main` cell, which installs PyBaMM from the checkout.
BradyPlanden
force-pushed
the
feat/model-zoo
branch
from
August 20, 2026 11:48
fceb865 to
9c356e5
Compare
Codacy's quality gate allows zero new issues and the zoo commit added five, all from Bandit. Reproduced locally with the repo's own `bandit.yml`, which reports exactly the same five, and none is a real vulnerability: - B105 in `_docs.py`: Bandit reads a dict key of `"pass"` as a password field, so the shields.io colour `"brightgreen"` looks like a hardcoded credential. - B404, B603 and B607 in `test_template.py`: importing and calling `subprocess` to run the repo's own ruff over a rendered template. The argv is a literal list; no external input reaches it. - B310 in `scripts/matrix.py`: `urlopen` on a module-level https constant. Suppressed inline as `# nosec <code> - <reason>`, the convention already used in `src/pybamm/codegen/compilation.py` and the unit tests, rather than widening `bandit.yml`, which would disable these rules repo-wide. Bandit now reports zero over the zoo. Nothing changes behaviour: the one code edit is splitting `BADGE_COLORS` across lines so the suppression sits on the key that triggers it.
Replace the reference entry, and close the gaps the review found in the
zoo's own machinery.
`spm_series_resistance` applied its `I R` drop in `set_voltage_variables`,
which `build_model` calls *after* `_build_model` has built the external
circuit's equations, so every control law that reads the terminal voltage
back was holding the pre-drop one. Measured at the `R = 0.05` its own
tests used: a requested 4 W delivered 3.94 W, 3.5 Ohm held 3.49 Ohm, 3.6 V
held 3.59 V. Experiments were unaffected, since `Simulation` attaches the
experiment controller to an already-built model. PyBaMM has this physics
already as `{"contact resistance": "true"}`, which applies the drop in
`get_coupled_variables` and so gets all of that right, plus the `I^2 R`
heating and `"voltage as a state"`.
The reference entry is now `linearised_spm`, from #3187: SPM with both
porous electrodes' open-circuit potential replaced by its tangent at the
starting stoichiometry, for fitting a diffusivity to a GITT pulse. The
gradient is PyBaMM's symbolic derivative of whichever `U` the parameter
set supplies, so the model adds no parameters, and it reports the `dE/dd`
a Weppner-Huggins fit otherwise has to read off a titration curve. It
substitutes a submodel instead of rewriting variables after the build, so
no control law can reach a stale voltage, and it duplicates nothing in
core -- #3187 was declined for core as too narrow, which is the case for
a zoo entry. A 5 s pulse recovers an imposed 1e-14 m2.s-1 to within 5%,
and the residual shrinks monotonically as the pulse shortens (-28.5% at
80 s, -11.4% at 20 s, -2.9% at 5 s), which is the sqrt(t) approximation's
own spherical-geometry bias rather than model error.
Then the machinery:
- Gate the zoo's own tests. `gating` was derived from a `core` slug
alone, so the registry, template, contract-infrastructure and
generated-file tests -- which belong to no model -- were advisory,
including the one asserting the contract suite is not vacuous. A test
with no slug now gates however the models happen to be tiered, and
`zoo-gating` no longer excludes a core model's examples.
- Reject an external model whose *slug* shadows an in-tree one, not just
its name. `by_slug` is what resolves citations and the generated
per-model files, so a differently-named entry with a colliding slug
displaced a built-in model and left `register_citation` reading the
wrong folder.
- Record a matrix cell that never reported as `missing` instead of
dropping it, and colour the badge for it. A leg that died before
uploading its artifact used to vanish from the table with the badge
left green; the collector now passes the discovered matrix through
`--expect`.
- Hold a model's manifest and the extra behind it to the same
requirements, compared both ways and on the specifier rather than the
distribution name. CI installs every model's extra at once, so a
one-way name-only comparison let a model lean on a package it never
declared.
- Keep the previous `status.json` timestamp when no result changed, so
the weekly workflow stops opening a pull request that says nothing.
- Say what the compatibility matrix covers: the newest releases a
manifest admits, which is what `scripts/matrix.py --releases` defaults
to, not every release it admits.
The status pipeline had no tests at all; `tests/test_status.py` now
covers collection, badge precedence and stamping. 55 zoo tests to 73.
Correctness:
- `LinearisedSPM` no longer raises `TypeError` under
`{"particle size": "distribution"}`: `dUdT` is broadcast onto the
potential's domains, which is what the base class size-averages against.
- The tangent's slope is taken at `T_ref`, so it is a constant and the
published entropic change is the exact temperature derivative of the
linearised potential. Previously the slope carried `T`, leaving the
reversible heat inconsistent once the stoichiometry moved.
- `--zoo-tier` prunes a model's folder before pytest imports anything in it,
so a community model that fails to import can no longer abort the merge
gate or poison every weekly compatibility cell. Marker deselection alone
ran too late.
- The compatibility matrix filters releases per model before taking each
model's newest N, so a model with an upper bound keeps the released cells
it still supports.
- `missing_dependencies` evaluates environment markers, so a
platform-specific dependency no longer skips every check elsewhere.
- Examples run as `__main__`, so a main-guarded example is executed.
- The scaffold and the `manifest` check reject Python keywords, which are
identifier-shaped but render invalid code.
- The generated floor pins major.minor rather than the bare major.
Test suite and CI:
- Warning filters are strict: the three blanket class ignores were unused.
The weekly job downgrades deprecations on released legs only.
- The advisory job runs only what the gate does not, rather than every core
model twice.
- The zoo filter routes `.github/CODEOWNERS`, `model_zoo_status.yml`, and
the generated docs, all of which the contract checks cover.
- New tests: an inherited-options matrix and a non-isothermal pair for
`LinearisedSPM`, collection selection, the release window, environment
markers, keyword rejection, and the main-guard.
Cleanups:
- `_versions.py` owns the release ordering and window that `_docs.py` and
`matrix.py` had duplicated.
- The registry cache is `_registry_instance`, so it stops shadowing the
`_registry` submodule.
- The docs landing page and README give a supported install path.
- Both changelog bullets end with the pull request link.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds
packages/pybamm-model-zoo/, a uv workspace member importable aspybamm_model_zoo, as the home for community- and partner-contributed models. One self-contained folder per model holds its code, tests, examples, citation, and a declarativemodel.tomlmanifest. An SPM linearised about its starting stoichiometry, for GITT fitting is provided as a reference entry atcoretier, so the gating path is exercised from the start.Nothing under
packages/pybamm/src/pybamm/changes; everything outside the zoo is config, CI, or docs.Addresses #5511
Using a zoo model
Not on PyPI yet, so
pip install pybammdoes not provide it:Models come from the registry rather than the
pybammnamespace:zoo.list_models()for what is installed,zoo.info(name)for maintainer, tier, and supported PyBaMM range. Acoremodel is in PyBaMM's merge gate. Acommunitymodel is tested on every pull request but advisory, so its badge and the compatibility table in the model zoo docs are where to see what it currently passes on. Adding one isnox -s zoo-newplus a filled-inmodel.toml.Reviewing the implementation
The manifest is the only boilerplate: registry, contract suite, docs, CI routing, and badges all derive from it, and manifests are parsed, never imported (
_registry.py), so a model with missing dependencies still lists and fails cleanly instead of taking the zoo down.Weekly,
model_zoo_status.ymlpairs each model with the newest releases its ownpybamm_requiresadmits, then opens a reviewable pull request withstatus.json, badges, and the docs table. Contributor and reviewer policy is the packageREADME.md, which the docs include verbatim.Type of change
Add an entry under
# [Unreleased]in CHANGELOG.md, in one of## Breaking changes,## Deprecated,## Features, or## Bug fixes(include PR number; breaking and deprecation entries need a one-line migration note). Internal-only PRs — refactor, docs, CI, tests — can skip this. See RELEASE.md for the full policy.Important checks:
Please confirm the following before marking the PR as ready for review:
nox -s pre-commitnox -s testsnox -s doctests