Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,8 @@

# The pybammsolvers package is owned by the IDAKLU maintainers (last match wins)
/packages/pybammsolvers/ @pybamm-team/idaklu-maintainers

# The model zoo: the zoo's own machinery is owned by the maintainers, and each
# model folder by its maintainer (last match wins)
/packages/pybamm-model-zoo/ @pybamm-team/maintainers
/packages/pybamm-model-zoo/src/pybamm_model_zoo/linearised_spm/ @pybamm-team/maintainers
62 changes: 62 additions & 0 deletions .github/ISSUE_TEMPLATE/new_zoo_model.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: New Model Zoo Entry
description: Propose a contributed model for the PyBaMM model zoo
labels: ["model zoo"]
body:
- type: markdown
attributes:
value: |
The [model zoo](https://docs.pybamm.org/en/latest/source/model_zoo/index.html)
is where community- and partner-contributed models live: one self-contained
folder per model, with its own maintainer, tests, examples, and citation.
See [contributing a model](https://docs.pybamm.org/en/latest/source/model_zoo/contributing.html)
for the workflow — `nox -s zoo-new` generates a skeleton that passes the
contract suite as rendered.
- type: input
id: model-name
attributes:
label: What is the model?
description: A one-line description of the physics it adds
placeholder: Stacked pouch cell with through-stack thermal transport
validations:
required: true
- type: input
id: code-location
attributes:
label: Where is the code?
description: A GitHub URL, pull request, or PyPI package name
placeholder: https://github.com/AwesomeOrg/AwesomeModel
validations:
required: true
- type: input
id: license
attributes:
label: What licence is it under?
description: See the [OSI's list of approved licences](https://opensource.org/licenses)
placeholder: BSD-3-Clause
validations:
required: true
- type: input
id: citation
attributes:
label: Is the model published?
description: A DOI or preprint link, if there is one. Leave blank if not.
- type: input
id: maintainer
attributes:
label: Who will maintain it?
description: The GitHub handle to add to CODEOWNERS for the model's folder
placeholder: "@ahandle"
validations:
required: true
- type: textarea
id: dependencies
attributes:
label: Does it need any third-party packages?
description: These become a `zoo-<slug>` optional extra, never a base dependency
- type: textarea
id: validation
attributes:
label: What has been validated, and against what?
description: |
A known limit, an analytic solution, a conservation law, or a published
figure. Please also say what has *not* been validated.
11 changes: 10 additions & 1 deletion .github/workflows/_nox.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ on:
required: true
type: string
fetch_depth:
description: "Checkout depth; the docs build needs the full history (0)."
description: >-
Checkout depth. Sessions needing git history pass 0: the docs build,
and the zoo, which holds a manifest's `pybamm_requires` to the PyBaMM
version hatch-vcs derives from the release tags.
default: 1
required: false
type: number
Expand All @@ -41,6 +44,11 @@ on:
default: false
required: false
type: boolean
timeout_minutes:
description: "Per-leg timeout. GitHub's own default is 360."
default: 360
required: false
type: number
secrets:
CODECOV_TOKEN:
description: "Codecov upload token; only needed when upload_coverage is true."
Expand All @@ -55,6 +63,7 @@ env:
jobs:
nox:
runs-on: ${{ matrix.leg.os }}
timeout-minutes: ${{ inputs.timeout_minutes }}
permissions:
contents: read

Expand Down
190 changes: 190 additions & 0 deletions .github/workflows/model_zoo_status.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
# Nothing is pushed to main: the results land in a reviewable pull request, so a
# newly-red model needs a human to accept it.
name: Model zoo status

on:
schedule:
# Mondays, 04:00 UTC — after the weekly lychee sweep.
- cron: "0 4 * * 1"
workflow_dispatch:

permissions: {}

env:
PYBAMM_DISABLE_TELEMETRY: "true"
FORCE_COLOR: 3

jobs:
# Manifests are parsed, not imported, so the matrix needs no pybamm install and
# pairs a model only with the releases its `pybamm_requires` admits.
discover:
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
versions: ${{ steps.matrix.outputs.versions }}
cells: ${{ steps.matrix.outputs.include }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "latest"
enable-cache: true

- id: matrix
run: |
uv run --no-project --with packaging --python 3.13 \
packages/pybamm-model-zoo/scripts/matrix.py \
--github-output >> "$GITHUB_OUTPUT"

test:
needs: discover
runs-on: ubuntu-latest
timeout-minutes: 90
permissions:
contents: read
strategy:
fail-fast: false
# One cell per version, looping over models inside it: a cell is almost all
# environment setup, while a model's checks take about a second.
matrix:
version: ${{ fromJSON(needs.discover.outputs.versions) }}
name: PyBaMM ${{ matrix.version }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: 'recursive'
# The `main` cell installs PyBaMM from this checkout, and hatch-vcs
# needs the release tags to give it a version the zoo can check.
fetch-depth: 0
persist-credentials: false

- name: Install Linux system dependencies
uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9 # v1.6.3
with:
packages: gfortran gcc make cmake libopenblas-dev
execute_install_scripts: true

- name: Set up Python 3.13
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: 3.13

- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "latest"
enable-cache: true

# --no-sources stops the workspace routing substituting the in-repo package
# for the released one; `main` is the checkout itself.
- name: Install PyBaMM ${{ matrix.version }} and the zoo
env:
VERSION: ${{ matrix.version }}
run: |
if [ "$VERSION" = main ]; then
uv sync --frozen --extra all --extra zoo-all --group dev
else
uv venv
uv pip install \
--no-sources \
"pybamm[all]==$VERSION" \
-e "./packages/pybamm-model-zoo[zoo-all]" \
--group packages/pybamm/pyproject.toml:dev
fi

- name: Run the contract suite for each model this version admits
env:
MPLBACKEND: Agg
VERSION: ${{ matrix.version }}
CELLS: ${{ needs.discover.outputs.cells }}
run: |
mkdir -p results
echo "$CELLS" | uv run --no-sync python -c \
'import json,os,sys; print("\n".join(c["model"] for c in json.load(sys.stdin) if c["version"] == os.environ["VERSION"]))' \
> models.txt
# A frozen release can warn where `main` no longer does, which is no
# zoo regression, so only released legs downgrade. `main` stays strict.
filters=()
if [ "$VERSION" != main ]; then
filters=(-W default::DeprecationWarning -W default::PendingDeprecationWarning)
fi
while read -r model; do
[ -n "$model" ] || continue
if uv run --no-sync python -m pytest -m zoo "${filters[@]}" \
packages/pybamm-model-zoo --zoo-model="$model"; then
result=pass
else
result=fail
fi
printf '{"model": "%s", "version": "%s", "result": "%s"}\n' \
"$model" "$VERSION" "$result" > "results/$model--$VERSION.json"
done < models.txt

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: zoo-status-${{ matrix.version }}
path: results/
retention-days: 7

collect:
needs: [discover, test]
if: always()
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# The status pull request is pushed from this checkout.
persist-credentials: true

- name: Set up Python 3.13
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: 3.13

- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: zoo-status-*
merge-multiple: true
path: results

# The same generator the pre-commit hook runs, so the schema and the
# renderers that read it cannot drift apart. `--expect` is what stops a leg
# that died before uploading from vanishing with the badge left green.
- name: Fold the results into status.json, badges, and the docs table
env:
CELLS: ${{ needs.discover.outputs.cells }}
run: |
printf '%s' "$CELLS" > cells.json
python packages/pybamm-model-zoo/scripts/generate.py \
--collect results --expect cells.json

- name: Open or update the status pull request
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: chore/model-zoo-status
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -B "$BRANCH"
git add packages/pybamm-model-zoo/status.json \
packages/pybamm-model-zoo/badges \
docs/source/model_zoo
if git diff --cached --quiet; then
echo "Model zoo status unchanged; nothing to open." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
git commit -m "chore: update model zoo status"
git push --force origin "$BRANCH"
if [ -z "$(gh pr list --head "$BRANCH" --state open --json number --jq '.[].number')" ]; then
gh pr create \
--head "$BRANCH" \
--base main \
--title "chore: update model zoo status" \
--body "Weekly model zoo compatibility matrix. Generated by \`model_zoo_status.yml\`; review the table before merging — a newly-red model needs an issue, not a merge."
fi
49 changes: 48 additions & 1 deletion .github/workflows/test_on_push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,13 @@ jobs:
solver: ${{ steps.filter.outputs.solver }}
pybamm: ${{ steps.filter.outputs.pybamm }}
docs: ${{ steps.filter.outputs.docs }}
model_zoo: ${{ steps.filter.outputs.model_zoo }}
# Routing decisions, resolved once here rather than repeated in every job's `if`.
run_tests: ${{ github.event_name == 'push' || steps.filter.outputs.pybamm == 'true' || steps.filter.outputs.solver == 'true' }}
run_docs_tests: ${{ github.event_name == 'push' || steps.filter.outputs.pybamm == 'true' || steps.filter.outputs.solver == 'true' || steps.filter.outputs.docs == 'true' }}
# A core change runs the zoo too, so an upstream regression shows up here
# rather than in a contributor's next pull request.
run_zoo_tests: ${{ github.event_name == 'push' || steps.filter.outputs.pybamm == 'true' || steps.filter.outputs.solver == 'true' || steps.filter.outputs.model_zoo == 'true' }}
# Solver-wheel platforms each scenario's downstream jobs actually consume.
platforms: ${{ steps.route.outputs.platforms }}
macos_runners: ${{ steps.route.outputs.macos_runners }}
Expand All @@ -65,6 +69,16 @@ jobs:
- '.github/workflows/_nox.yml'
docs:
- 'docs/**'
model_zoo:
- 'packages/pybamm-model-zoo/**'
- '.github/workflows/test_on_push.yml'
# Ownership is a contract check, so dropping an owner line has to
# run the check that would have caught it.
- '.github/CODEOWNERS'
# The weekly job consumes the matrix and the docs generator, and
# nothing else validates it on a pull request.
- '.github/workflows/model_zoo_status.yml'
- 'docs/source/model_zoo/**'

# Map "what changed" to the solver wheels to build: solver PRs validate every
# platform; pybamm/push need the sparse matrix's runners; docs-only needs Linux.
Expand Down Expand Up @@ -121,7 +135,7 @@ jobs:
# old standalone unit/integration workflows.
build_solver:
needs: changes
if: ${{ needs.changes.outputs.run_docs_tests == 'true' }}
if: ${{ needs.changes.outputs.run_docs_tests == 'true' || needs.changes.outputs.run_zoo_tests == 'true' }}
name: Solver wheels
# Pass contents:read down to the called workflow (this workflow's top-level
# permissions are {}, so the reusable workflow's checkout would otherwise
Expand Down Expand Up @@ -282,6 +296,38 @@ jobs:
texlive: false
legs: '[{"os": "ubuntu-latest", "python": "3.13"}]'

# The `core` tier: models the maintainers have adopted, so a failure blocks a
# merge exactly as a core test failure would.
run_zoo_gating:
needs: [changes, build_solver]
if: ${{ needs.changes.outputs.run_zoo_tests == 'true' }}
name: Model zoo (core tier)
permissions:
contents: read
uses: ./.github/workflows/_nox.yml
with:
sessions: zoo-gating
texlive: false
fetch_depth: 0
timeout_minutes: 30
legs: '[{"os": "ubuntu-latest", "python": "3.13"}]'

# Advisory like run_unit_tests_advisory above: `community` reports red without
# blocking. The gate above covers `core`, so this runs only what it does not.
run_zoo_tests_advisory:
needs: [changes, build_solver]
if: ${{ needs.changes.outputs.run_zoo_tests == 'true' }}
name: Model zoo
permissions:
contents: read
uses: ./.github/workflows/_nox.yml
with:
sessions: zoo-community
texlive: false
fetch_depth: 0
timeout_minutes: 60
legs: '[{"os": "ubuntu-latest", "python": "3.13"}]'

# Single required check for branch protection. Green when every gated job either
# succeeded or was routed around by `changes`; red on any failure or cancellation.
ci_gate:
Expand All @@ -298,6 +344,7 @@ jobs:
- run_example_tests
- run_scripts_tests
- run_memory_tests
- run_zoo_gating
runs-on: ubuntu-latest
permissions: {}
name: CI gate
Expand Down
4 changes: 4 additions & 0 deletions .lycheeignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ https://us.i.posthog.com
# Live site behind a LiteSpeed anti-bot WAF that returns 415 to CI/datacenter
# IPs (works fine from browsers and locally) — false positive, not a dead link
https://bpxstandard.com/

# shields.io endpoint badges for model zoo status; the ?url= form confuses the
# checker, and the JSON they read is generated by the model_zoo_status workflow
https://img.shields.io/endpoint
Loading
Loading