From 6c7b113307a22842f1a4e803fe1e4958cabc3dee Mon Sep 17 00:00:00 2001 From: Mohit-Lakra Date: Wed, 25 Feb 2026 00:37:01 +0530 Subject: [PATCH 01/15] Generated Autodocs using autoapi --- .github/workflows/docs.yml | 106 ++++++++++++++++++++++++++ docs/_config.yml | 93 ++++++++++++++++++++++ docs/_toc.yml | 8 ++ docs/intro.md | 14 ++++ docs/scripts/autoapi_astroid_patch.py | 26 +++++++ docs/scripts/generate_docs.sh | 57 ++++++++++++++ pyproject.toml | 6 ++ 7 files changed, 310 insertions(+) create mode 100644 .github/workflows/docs.yml create mode 100644 docs/_config.yml create mode 100644 docs/_toc.yml create mode 100644 docs/intro.md create mode 100644 docs/scripts/autoapi_astroid_patch.py create mode 100755 docs/scripts/generate_docs.sh diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 000000000..e0063e564 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,106 @@ +name: Documentation + +on: + push: + branches: ["main", "Auto-doc"] + paths: + - "neural_lam/**" + - "docs/**" + - "pyproject.toml" + - ".github/workflows/docs.yml" + pull_request: + branches: [main] + paths: + - "neural_lam/**" + - "docs/**" + - "pyproject.toml" + - ".github/workflows/docs.yml" + workflow_dispatch: + +concurrency: + group: "docs-${{ github.ref }}" + cancel-in-progress: true + +jobs: + build-docs: + name: Build docs + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: "pip" + + - name: Install pdm + CPU torch + docs deps + run: | + python -m pip install --upgrade pip + pip install pdm + pdm venv create --with-pip + pdm run python -m pip install torch --index-url https://download.pytorch.org/whl/cpu + pdm install --group docs + + - name: interrogate (fail-under 50) + run: | + pdm run interrogate neural_lam/ \ + --fail-under 50 --verbose --generate-badge docs/ \ + 2>&1 | tee docs/interrogate_report.txt + + - name: pydocstyle (non-blocking) + run: | + pdm run pydocstyle neural_lam/ --convention=numpy --add-ignore=D100,D104,D105 || true + + - name: jupyter-book build + run: pdm run jupyter-book build docs/ + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: docs-html + path: docs/_build/html/ + retention-days: 7 + + deploy-docs: + name: Deploy to GitHub Pages + runs-on: ubuntu-latest + needs: build-docs + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/Auto-doc') + permissions: + contents: read + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: "pip" + + - name: Install pdm + CPU torch + docs deps + run: | + python -m pip install --upgrade pip + pip install pdm + pdm venv create --with-pip + pdm run python -m pip install torch --index-url https://download.pytorch.org/whl/cpu + pdm install --group docs + + - name: jupyter-book build + run: pdm run jupyter-book build docs/ + + - uses: actions/configure-pages@v4 + - uses: actions/upload-pages-artifact@v3 + with: + path: docs/_build/html/ + - id: deployment + uses: actions/deploy-pages@v4 diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 000000000..ee5bb8f7c --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,93 @@ +# docs/_config.yml + +title: Neural-LAM Documentation +author: mllam +copyright: "2024" + +exclude_patterns: + - _build + - Thumbs.db + - .DS_Store + - "**.ipynb_checkpoints" + +execute: + execute_notebooks: "off" + +repository: + url: "https://github.com/mllam/neural-lam" + path_to_book: "docs" + branch: "main" + +html: + use_issues_button: true + use_repository_button: true + use_edit_page_button: true + home_page_in_navbar: true + +parse: + myst_enable_extensions: + - colon_fence + - dollarmath + - linkify + - smartquotes + myst_url_schemes: [mailto, http, https] + +sphinx: + extra_extensions: + - "docs.scripts.autoapi_astroid_patch" + - "autoapi.extension" + - "sphinx.ext.napoleon" + - "sphinx.ext.viewcode" + - "sphinx.ext.intersphinx" + - "sphinx_copybutton" + + config: + html_theme: "sphinx_book_theme" + html_theme_options: + repository_url: "https://github.com/mllam/neural-lam" + use_repository_button: true + use_issues_button: true + use_edit_page_button: true + repository_branch: "main" + path_to_docs: "docs" + show_navbar_depth: 2 + + # ── AutoAPI ── + autoapi_dirs: + - "../neural_lam" + autoapi_root: "autoapi" + autoapi_type: "python" + + autoapi_options: + - "members" + - "undoc-members" + - "show-inheritance" + + autoapi_python_class_content: "both" + autoapi_member_order: "groupwise" + autoapi_python_use_implicit_namespaces: false + + autoapi_keep_files: true + + autoapi_add_toctree_entry: true + + # ── Napoleon ── + napoleon_numpy_docstring: true + napoleon_google_docstring: true + napoleon_include_init_with_doc: true + napoleon_include_private_with_doc: false + napoleon_include_special_with_doc: false + napoleon_use_param: true + napoleon_use_rtype: true + + # ── Intersphinx ── + intersphinx_mapping: + python: + - "https://docs.python.org/3" + - null + numpy: + - "https://numpy.org/doc/stable" + - null + torch: + - "https://pytorch.org/docs/stable" + - null diff --git a/docs/_toc.yml b/docs/_toc.yml new file mode 100644 index 000000000..ec77d88dd --- /dev/null +++ b/docs/_toc.yml @@ -0,0 +1,8 @@ +# docs/_toc.yml +format: jb-book +root: intro + +parts: + - caption: API Reference + chapters: + - file: autoapi/index diff --git a/docs/intro.md b/docs/intro.md new file mode 100644 index 000000000..b7ae1d75b --- /dev/null +++ b/docs/intro.md @@ -0,0 +1,14 @@ +# Neural-LAM + +**Graph-based neural weather prediction for Limited Area Modeling.** +Built with PyTorch, PyTorch Lightning, and PyG. + +```{admonition} Quick install +:class: tip +pip install neural_lam +``` + +- **[API Reference](autoapi/index)** — full auto-generated docs from source +- **[Slack](https://kutt.it/mllam)** — join the mllam community +- **[Issues](https://github.com/mllam/neural-lam/issues)** — bug reports & feature requests +- **[GitHub](https://github.com/mllam/neural-lam)** — source code diff --git a/docs/scripts/autoapi_astroid_patch.py b/docs/scripts/autoapi_astroid_patch.py new file mode 100644 index 000000000..66e31b600 --- /dev/null +++ b/docs/scripts/autoapi_astroid_patch.py @@ -0,0 +1,26 @@ +"""Patch AutoAPI for astroid>=4.""" # codespell:ignore astroid + +from __future__ import annotations + +# Standard library +import inspect + +# Third-party +from astroid import builder as astroid_builder # codespell:ignore astroid +from astroid.manager import AstroidManager # codespell:ignore astroid + + +def setup(app): + builder_init = astroid_builder.AstroidBuilder.__init__ + if "manager" not in inspect.signature(builder_init).parameters: + return {"version": "0.1"} + original_builder = astroid_builder.AstroidBuilder + + class AutoapiAstroidBuilder(original_builder): + def __init__(self, *args, **kwargs): + if not args and "manager" not in kwargs: + kwargs["manager"] = AstroidManager() + super().__init__(*args, **kwargs) + + astroid_builder.AstroidBuilder = AutoapiAstroidBuilder + return {"version": "0.1"} diff --git a/docs/scripts/generate_docs.sh b/docs/scripts/generate_docs.sh new file mode 100755 index 000000000..56eae0bbb --- /dev/null +++ b/docs/scripts/generate_docs.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# docs/scripts/generate_docs.sh + +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +BOLD='\033[1m' +NC='\033[0m' +info() { echo -e "${BLUE}[INFO]${NC} $*"; } +success() { echo -e "${GREEN}[OK]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +NEURAL_LAM_DIR="$REPO_ROOT/neural_lam" +DOCS_DIR="$REPO_ROOT/docs" + +for tool in interrogate pydocstyle jupyter-book; do + command -v "$tool" &>/dev/null || { + error "$tool not found. Run: pdm install --group docs" + exit 1 + } +done + +# ── 1. interrogate ── +info "interrogate: docstring coverage audit (fail-under 50)" +interrogate "$NEURAL_LAM_DIR" \ + --fail-under 50 \ + --verbose \ + --generate-badge "$DOCS_DIR/" \ + 2>&1 | tee "$DOCS_DIR/interrogate_report.txt" && + success "Coverage ≥ 50% ✓" || + { + error "Coverage below 50%! See docs/interrogate_report.txt" + exit 1 + } + +# ── 2. pydocstyle ── +info "pydocstyle: style check (non-blocking)" +pydocstyle "$NEURAL_LAM_DIR" --convention=numpy --add-ignore=D100,D104,D105 && + success "pydocstyle passed ✓" || + warn "pydocstyle issues (non-blocking)" + +# ── 3. jupyter-book build ── +info "jupyter-book: building site" +jupyter-book build "$DOCS_DIR/" && + success "Build succeeded ✓" || + { + error "Build FAILED" + exit 1 + } + +echo -e "\n${BOLD} Done — open: $DOCS_DIR/_build/html/index.html${NC}" diff --git a/pyproject.toml b/pyproject.toml index 55cd7642f..12aba7144 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,9 @@ requires-python = ">=3.10" [dependency-groups] dev = ["pre-commit>=3.8.0", "pytest>=8.3.2", "pooch>=1.8.2"] +[tool.pdm.dev-dependencies] +docs = [ "jupyter-book<2.0.0", "sphinx-autoapi>=3.0.0", "myst-nb>=1.0.0", "sphinx-copybutton>=0.5.2","pydata-sphinx-theme>=0.15.2","interrogate>=1.7.0","pydocstyle>=6.3.0","sphinx>=7.2.6","sphinxcontrib-mermaid>=0.9.2"] + [tool.setuptools] py-modules = ["neural_lam"] @@ -117,3 +120,6 @@ fallback_version = "0.0.0" [build-system] requires = ["pdm-backend"] build-backend = "pdm.backend" + +[tool.pytest.ini_options] +norecursedirs = ["docs"] From b16b3dd31374600fd79113a26feab7af7ba79a4e Mon Sep 17 00:00:00 2001 From: Mohit-Lakra Date: Sun, 1 Mar 2026 12:53:25 +0530 Subject: [PATCH 02/15] torch dependency removed --- .github/workflows/docs.yml | 24 ++++-------------------- docs/_config.yml | 5 ++++- docs/scripts/__init__.py | 1 + 3 files changed, 9 insertions(+), 21 deletions(-) create mode 100644 docs/scripts/__init__.py diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e0063e564..2c6a58d29 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,5 +1,4 @@ name: Documentation - on: push: branches: ["main", "Auto-doc"] @@ -16,11 +15,9 @@ on: - "pyproject.toml" - ".github/workflows/docs.yml" workflow_dispatch: - concurrency: group: "docs-${{ github.ref }}" cancel-in-progress: true - jobs: build-docs: name: Build docs @@ -31,40 +28,32 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-python@v5 with: python-version: "3.11" cache: "pip" - - - name: Install pdm + CPU torch + docs deps + - name: Install pdm + docs deps run: | python -m pip install --upgrade pip pip install pdm pdm venv create --with-pip - pdm run python -m pip install torch --index-url https://download.pytorch.org/whl/cpu - pdm install --group docs - + pdm install --group docs --no-default - name: interrogate (fail-under 50) run: | pdm run interrogate neural_lam/ \ --fail-under 50 --verbose --generate-badge docs/ \ 2>&1 | tee docs/interrogate_report.txt - - name: pydocstyle (non-blocking) run: | pdm run pydocstyle neural_lam/ --convention=numpy --add-ignore=D100,D104,D105 || true - - name: jupyter-book build run: pdm run jupyter-book build docs/ - - uses: actions/upload-artifact@v4 if: always() with: name: docs-html path: docs/_build/html/ retention-days: 7 - deploy-docs: name: Deploy to GitHub Pages runs-on: ubuntu-latest @@ -81,23 +70,18 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-python@v5 with: python-version: "3.11" cache: "pip" - - - name: Install pdm + CPU torch + docs deps + - name: Install pdm + docs deps run: | python -m pip install --upgrade pip pip install pdm pdm venv create --with-pip - pdm run python -m pip install torch --index-url https://download.pytorch.org/whl/cpu - pdm install --group docs - + pdm install --group docs --no-default - name: jupyter-book build run: pdm run jupyter-book build docs/ - - uses: actions/configure-pages@v4 - uses: actions/upload-pages-artifact@v3 with: diff --git a/docs/_config.yml b/docs/_config.yml index ee5bb8f7c..2cf79660c 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -34,13 +34,16 @@ parse: sphinx: extra_extensions: - - "docs.scripts.autoapi_astroid_patch" + - "scripts.autoapi_astroid_patch" - "autoapi.extension" - "sphinx.ext.napoleon" - "sphinx.ext.viewcode" - "sphinx.ext.intersphinx" - "sphinx_copybutton" + local_extensions: + scripts.autoapi_astroid_patch: "." + config: html_theme: "sphinx_book_theme" html_theme_options: diff --git a/docs/scripts/__init__.py b/docs/scripts/__init__.py new file mode 100644 index 000000000..359aa9312 --- /dev/null +++ b/docs/scripts/__init__.py @@ -0,0 +1 @@ +"""Helper scripts used during documentation builds.""" From dd1b02a72fec5f1e2145cb45d93f2d077b3abc3e Mon Sep 17 00:00:00 2001 From: Mohit-Lakra Date: Fri, 3 Apr 2026 10:52:19 +0530 Subject: [PATCH 03/15] build: migrate docs workflow from pdm to hatchling and fix duplicate build-system --- .github/workflows/docs.yml | 21 +++++++++------------ pyproject.toml | 19 +++++++++++++------ 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 2c6a58d29..eb56e06d4 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,7 +1,7 @@ name: Documentation on: push: - branches: ["main", "Auto-doc"] + branches: ["main"] paths: - "neural_lam/**" - "docs/**" @@ -32,12 +32,11 @@ jobs: with: python-version: "3.11" cache: "pip" - - name: Install pdm + docs deps + - name: Install docs dependencies run: | python -m pip install --upgrade pip - pip install pdm - pdm venv create --with-pip - pdm install --group docs --no-default + pip install -e ".[docs]" + - name: interrogate (fail-under 50) run: | pdm run interrogate neural_lam/ \ @@ -47,7 +46,7 @@ jobs: run: | pdm run pydocstyle neural_lam/ --convention=numpy --add-ignore=D100,D104,D105 || true - name: jupyter-book build - run: pdm run jupyter-book build docs/ + run: jupyter-book build docs/ - uses: actions/upload-artifact@v4 if: always() with: @@ -58,7 +57,7 @@ jobs: name: Deploy to GitHub Pages runs-on: ubuntu-latest needs: build-docs - if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/Auto-doc') + if: github.event_name == 'push' && (github.ref == 'refs/heads/main') permissions: contents: read pages: write @@ -74,14 +73,12 @@ jobs: with: python-version: "3.11" cache: "pip" - - name: Install pdm + docs deps + - name: Install docs dependencies run: | python -m pip install --upgrade pip - pip install pdm - pdm venv create --with-pip - pdm install --group docs --no-default + pip install -e ".[docs]" - name: jupyter-book build - run: pdm run jupyter-book build docs/ + run: jupyter-book build docs/ - uses: actions/configure-pages@v4 - uses: actions/upload-pages-artifact@v3 with: diff --git a/pyproject.toml b/pyproject.toml index 582d0fe5b..a71c455d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,8 +44,6 @@ requires-python = ">=3.10" [dependency-groups] dev = ["pre-commit>=3.8.0", "pytest>=8.3.2", "pooch>=1.8.2"] -[tool.pdm.dev-dependencies] -docs = [ "jupyter-book<2.0.0", "sphinx-autoapi>=3.0.0", "myst-nb>=1.0.0", "sphinx-copybutton>=0.5.2","pydata-sphinx-theme>=0.15.2","interrogate>=1.7.0","pydocstyle>=6.3.0","sphinx>=7.2.6","sphinxcontrib-mermaid>=0.9.2"] [tool.setuptools] py-modules = ["neural_lam"] @@ -125,10 +123,6 @@ core-metadata-version = "2.4" source = "vcs" fallback-version = "0.0.0" -[build-system] -requires = ["pdm-backend"] -build-backend = "pdm.backend" - [tool.pytest.ini_options] norecursedirs = ["docs"] [tool.hatch.build.targets.sdist] @@ -142,3 +136,16 @@ exclude = [ ".venv/", "venv/", ] + +[project.optional-dependencies] +docs = [ + "jupyter-book<2.0.0", + "sphinx-autoapi>=3.0.0", + "myst-nb>=1.0.0", + "sphinx-copybutton>=0.5.2", + "pydata-sphinx-theme>=0.15.2", + "interrogate>=1.7.0", + "pydocstyle>=6.3.0", + "sphinx>=7.2.6", + "sphinxcontrib-mermaid>=0.9.2", +] From 15fe5add5e536399df2f05eadf3b2e8ad20c3b99 Mon Sep 17 00:00:00 2001 From: Mohit-Lakra Date: Fri, 19 Jun 2026 22:43:08 +0530 Subject: [PATCH 04/15] docs: Overhaul documentation system - Implement automatic UML class diagram generation via pyreverse - Configure ReadTheDocs integration for automated per-PR previews - Restructure Jupyter Book configuration and MyST markdown pages - Integrate sphinxext-opengraph for SEO and social sharing cards - Apply custom high-contrast scientific CSS theme - Refactor CI deployment script to utilize uv environments --- .github/workflows/docs.yml | 95 ++++--- .gitignore | 5 + .pre-commit-config.yaml | 1 + .readthedocs.yaml | 13 + docs/_config.yml | 96 ++++--- docs/_static/custom.css | 260 ++++++++++++++++++ docs/_static/favicon.ico | 0 docs/_static/logo.png | Bin 0 -> 177523 bytes docs/_static/uml/classes_models.mmd | 133 +++++++++ docs/_static/uml/packages_models.mmd | 51 ++++ docs/_toc.yml | 33 ++- docs/about/changelog.md | 1 + docs/about/glossary.md | 60 ++++ docs/architecture/data-flow.md | 38 +++ docs/architecture/graph-construction.md | 66 +++++ docs/architecture/models.md | 60 ++++ docs/architecture/overview.md | 51 ++++ docs/architecture/theory.md | 47 ++++ docs/contributing/coding-standards.md | 41 +++ docs/contributing/development-setup.md | 102 +++++++ docs/getting-started/installation.md | 72 +++++ docs/getting-started/quickstart.md | 57 ++++ docs/guides/configuration.md | 58 ++++ docs/intro.md | 75 ++++- .../create_reduced_meps_dataset.ipynb | 2 +- docs/scripts/autoapi_astroid_patch.py | 23 +- docs/scripts/generate_docs.sh | 85 +++--- pyproject.toml | 29 +- 28 files changed, 1406 insertions(+), 148 deletions(-) create mode 100644 .readthedocs.yaml create mode 100644 docs/_static/custom.css create mode 100644 docs/_static/favicon.ico create mode 100644 docs/_static/logo.png create mode 100644 docs/_static/uml/classes_models.mmd create mode 100644 docs/_static/uml/packages_models.mmd create mode 120000 docs/about/changelog.md create mode 100644 docs/about/glossary.md create mode 100644 docs/architecture/data-flow.md create mode 100644 docs/architecture/graph-construction.md create mode 100644 docs/architecture/models.md create mode 100644 docs/architecture/overview.md create mode 100644 docs/architecture/theory.md create mode 100644 docs/contributing/coding-standards.md create mode 100644 docs/contributing/development-setup.md create mode 100644 docs/getting-started/installation.md create mode 100644 docs/getting-started/quickstart.md create mode 100644 docs/guides/configuration.md diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index eb56e06d4..272f4ac0c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -8,16 +8,18 @@ on: - "pyproject.toml" - ".github/workflows/docs.yml" pull_request: - branches: [main] + branches: ["main"] paths: - "neural_lam/**" - "docs/**" - "pyproject.toml" - ".github/workflows/docs.yml" workflow_dispatch: + concurrency: group: "docs-${{ github.ref }}" cancel-in-progress: true + jobs: build-docs: name: Build docs @@ -28,36 +30,61 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-python@v5 + + - name: Set up uv + uses: astral-sh/setup-uv@v5 + + - name: Set up Python 3.11 + uses: actions/setup-python@v5 with: python-version: "3.11" - cache: "pip" + - name: Install docs dependencies - run: | - python -m pip install --upgrade pip - pip install -e ".[docs]" + run: uv sync --extra cpu --extra docs --group dev --no-cache - - name: interrogate (fail-under 50) - run: | - pdm run interrogate neural_lam/ \ - --fail-under 50 --verbose --generate-badge docs/ \ - 2>&1 | tee docs/interrogate_report.txt - - name: pydocstyle (non-blocking) - run: | - pdm run pydocstyle neural_lam/ --convention=numpy --add-ignore=D100,D104,D105 || true - - name: jupyter-book build - run: jupyter-book build docs/ - - uses: actions/upload-artifact@v4 - if: always() + - name: Build docs with pyreverse and jupyter-book + run: bash docs/scripts/generate_docs.sh + + - name: Upload docs artifact + uses: actions/upload-artifact@v4 with: name: docs-html path: docs/_build/html/ - retention-days: 7 + + linkcheck: + name: Link check + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up uv + uses: astral-sh/setup-uv@v5 + + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install docs dependencies + run: uv sync --extra cpu --extra docs --group dev --no-cache + + - name: Generate UML diagrams + run: | + mkdir -p docs/_static/uml + uv run pyreverse -o mmd -p models neural_lam.models -d docs/_static/uml/ || true + + - name: Run linkcheck + run: uv run jupyter-book build docs/ --builder linkcheck || true + deploy-docs: name: Deploy to GitHub Pages runs-on: ubuntu-latest needs: build-docs - if: github.event_name == 'push' && (github.ref == 'refs/heads/main') + if: github.event_name == 'push' && github.ref == 'refs/heads/main' permissions: contents: read pages: write @@ -66,22 +93,20 @@ jobs: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: actions/setup-python@v5 + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Download artifact + uses: actions/download-artifact@v4 with: - python-version: "3.11" - cache: "pip" - - name: Install docs dependencies - run: | - python -m pip install --upgrade pip - pip install -e ".[docs]" - - name: jupyter-book build - run: jupyter-book build docs/ - - uses: actions/configure-pages@v4 - - uses: actions/upload-pages-artifact@v3 + name: docs-html + path: docs/_build/html/ + + - name: Upload pages artifact + uses: actions/upload-pages-artifact@v3 with: path: docs/_build/html/ - - id: deployment + + - name: Deploy to GitHub Pages + id: deployment uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index a2c5bb26e..c493be3c5 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,11 @@ share/python-wheels/ # mkdocs documentation /site +# Jupyter Book / Sphinx documentation +docs/_build/ +docs/api/ + + ### Vim ### # Swap [._]*.s[a-v][a-z] diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b75a383ff..76c05a206 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,6 +17,7 @@ repos: hooks: - id: codespell description: Check for spelling errors + args: ["--ignore-words-list", "astroid"] - repo: https://github.com/psf/black rev: 25.11.0 diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..a56ede2d6 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,13 @@ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.10" + commands: + - pip install uv + - uv pip install --system -e ".[cpu,docs]" + - mkdir -p docs/_static/uml + - pyreverse -o mmd -p models neural_lam.models -d docs/_static/uml/ || true + - jupyter-book build docs/ + - cp -r docs/_build/html/* $READTHEDOCS_OUTPUT/html/ diff --git a/docs/_config.yml b/docs/_config.yml index 2cf79660c..88fa2c021 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,35 +1,45 @@ # docs/_config.yml -title: Neural-LAM Documentation -author: mllam -copyright: "2024" +title: "Neural-LAM" +author: "MLLAM Community" +copyright: "2024–2026" -exclude_patterns: - - _build - - Thumbs.db - - .DS_Store - - "**.ipynb_checkpoints" - -execute: - execute_notebooks: "off" +logo: "_static/logo.png" +favicon: "_static/favicon.ico" repository: url: "https://github.com/mllam/neural-lam" - path_to_book: "docs" branch: "main" + path_to_book: "docs" -html: - use_issues_button: true - use_repository_button: true - use_edit_page_button: true - home_page_in_navbar: true +launch_buttons: + binderhub_url: "https://mybinder.org" + colab_url: "https://colab.research.google.com" + +execute: + execute_notebooks: "off" + +exclude_patterns: + - _build + - Thumbs.db + - .DS_Store + - "**.ipynb_checkpoints" + - _static + - scripts parse: myst_enable_extensions: - colon_fence - dollarmath - linkify + - substitution + - tasklist + - deflist + - fieldlist + - html_admonition + - html_image - smartquotes + - attrs_inline myst_url_schemes: [mailto, http, https] sphinx: @@ -40,6 +50,8 @@ sphinx: - "sphinx.ext.viewcode" - "sphinx.ext.intersphinx" - "sphinx_copybutton" + - "sphinxcontrib.mermaid" + - "sphinxext.opengraph" local_extensions: scripts.autoapi_astroid_patch: "." @@ -54,29 +66,37 @@ sphinx: repository_branch: "main" path_to_docs: "docs" show_navbar_depth: 2 + navigation_with_keys: false + show_toc_level: 2 + logo: + text: "Neural-LAM" + announcement: "" + extra_footer: | +

Built with Jupyter Book | + Source

+ + html_static_path: ["_static"] + html_css_files: ["custom.css"] # ── AutoAPI ── - autoapi_dirs: - - "../neural_lam" - autoapi_root: "autoapi" + autoapi_dirs: ["../neural_lam"] + autoapi_root: "api" autoapi_type: "python" - autoapi_options: - "members" - "undoc-members" - "show-inheritance" - + - "show-module-summary" autoapi_python_class_content: "both" autoapi_member_order: "groupwise" autoapi_python_use_implicit_namespaces: false - autoapi_keep_files: true - autoapi_add_toctree_entry: true + autoapi_ignore: ["**/tests/**", "**/conftest.py"] # ── Napoleon ── napoleon_numpy_docstring: true - napoleon_google_docstring: true + napoleon_google_docstring: false napoleon_include_init_with_doc: true napoleon_include_private_with_doc: false napoleon_include_special_with_doc: false @@ -85,12 +105,20 @@ sphinx: # ── Intersphinx ── intersphinx_mapping: - python: - - "https://docs.python.org/3" - - null - numpy: - - "https://numpy.org/doc/stable" - - null - torch: - - "https://pytorch.org/docs/stable" - - null + python: ["https://docs.python.org/3", null] + numpy: ["https://numpy.org/doc/stable", null] + torch: ["https://pytorch.org/docs/stable", null] + pytorch_lightning: ["https://lightning.ai/docs/pytorch/stable/", null] + torch_geometric: ["https://pytorch-geometric.readthedocs.io/en/latest/", null] + + # ── Mermaid ── + mermaid_d3_zoom: false + mermaid_version: "11" + + # ── Suppress Warnings ── + suppress_warnings: ["autoapi.python_import_resolution"] + + # ── OpenGraph ── + ogp_site_url: "https://mllam.github.io/neural-lam/" + ogp_image: "_static/logo.png" + ogp_use_first_image: true diff --git a/docs/_static/custom.css b/docs/_static/custom.css new file mode 100644 index 000000000..e42ae7d0d --- /dev/null +++ b/docs/_static/custom.css @@ -0,0 +1,260 @@ +/* Neural-LAM Documentation — High-Visibility Custom Styles */ + +/* ========================================================================== + 1. OVERRIDE SPHINX BOOK THEME (PYDATA) VARIABLES + ========================================================================== */ +:root { + /* High contrast colors */ + --pst-color-primary: #1d4ed8; /* Strong Blue */ + --pst-color-primary-bg: #eff6ff; + --pst-color-secondary: #0f172a; /* Slate 900 */ + + /* Text colors - Pure high contrast */ + --pst-color-text-base: #0f172a; /* Nearly black */ + --pst-color-text-muted: #475569; /* Slate 600 */ + + /* Backgrounds */ + --pst-color-background: #ffffff; + --pst-color-surface: #f8fafc; /* Slate 50 */ + --pst-color-border: #cbd5e1; /* Slate 300 */ + + /* Typography */ + --pst-font-family-base: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + --pst-font-family-heading: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + --pst-font-family-monospace: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace; + + --pst-font-size-base: 17px; /* Increased for better visibility */ +} + +/* Dark mode overrides (PyData theme automatically adds data-theme="dark") */ +html[data-theme="dark"] { + --pst-color-primary: #60a5fa; /* Light Blue */ + --pst-color-primary-bg: #1e3a8a; + + --pst-color-text-base: #f8fafc; /* Nearly white */ + --pst-color-text-muted: #cbd5e1; + + --pst-color-background: #0f172a; /* Slate 900 */ + --pst-color-surface: #1e293b; /* Slate 800 */ + --pst-color-border: #334155; /* Slate 700 */ +} + +/* ========================================================================== + 2. GLOBAL TYPOGRAPHY & LAYOUT + ========================================================================== */ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap'); + +html { + scroll-behavior: smooth; +} + +body { + font-family: var(--pst-font-family-base); + font-size: var(--pst-font-size-base); + line-height: 1.75; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* Make headings stand out more */ +h1, h2, h3, h4, h5, h6 { + color: var(--pst-color-text-base); + font-weight: 700; + letter-spacing: -0.02em; +} + +h1 { + font-size: 2.5rem; + font-weight: 800; + margin-bottom: 1.5rem; + line-height: 1.2; +} + +h2 { + font-size: 1.8rem; + margin-top: 2.5rem; + margin-bottom: 1.25rem; + border-bottom: 2px solid var(--pst-color-border); + padding-bottom: 0.5rem; +} + +h3 { + font-size: 1.4rem; + margin-top: 2rem; +} + +p { + margin-bottom: 1.25rem; +} + +a { + color: var(--pst-color-primary); + text-decoration: none; + font-weight: 500; +} + +a:hover { + text-decoration: underline; + text-decoration-thickness: 2px; + text-underline-offset: 4px; +} + +/* ========================================================================== + 3. SIDEBAR NAVIGATION + ========================================================================== */ +.bd-sidebar { + background-color: var(--pst-color-surface); + border-right: 1px solid var(--pst-color-border); +} + +.bd-sidebar .nav-link { + font-size: 1rem; + color: var(--pst-color-text-muted); + padding: 0.5rem 1rem; + margin: 0.25rem 0; + border-radius: 6px; + transition: all 0.2s; +} + +.bd-sidebar .nav-link:hover { + background-color: var(--pst-color-primary-bg); + color: var(--pst-color-primary); +} + +.bd-sidebar .nav-link.active { + background-color: var(--pst-color-primary-bg); + color: var(--pst-color-primary); + font-weight: 600; + border-left: 4px solid var(--pst-color-primary); +} + +/* ========================================================================== + 4. CODE BLOCKS & INLINE CODE + ========================================================================== */ +pre { + background-color: var(--pst-color-surface); + border: 1px solid var(--pst-color-border); + border-radius: 8px; + padding: 1.25rem; + font-size: 0.9rem; + line-height: 1.6; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05); +} + +code { + font-family: var(--pst-font-family-monospace); + background-color: var(--pst-color-surface); + border: 1px solid var(--pst-color-border); + color: var(--pst-color-primary); + padding: 0.2em 0.4em; + border-radius: 4px; + font-size: 0.9em; + font-weight: 500; +} + +pre code { + background-color: transparent; + border: none; + color: inherit; + padding: 0; +} + +/* ========================================================================== + 5. TABLES + ========================================================================== */ +table.table, table.docutils { + width: 100%; + border-collapse: separate; + border-spacing: 0; + border: 1px solid var(--pst-color-border); + border-radius: 8px; + overflow: hidden; + margin-bottom: 2rem; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05); +} + +th { + background-color: var(--pst-color-surface); + color: var(--pst-color-text-base); + font-weight: 700; + text-transform: uppercase; + font-size: 0.85rem; + letter-spacing: 0.05em; + padding: 1rem; + border-bottom: 2px solid var(--pst-color-border); +} + +td { + padding: 1rem; + border-bottom: 1px solid var(--pst-color-border); + vertical-align: top; +} + +tr:last-child td { + border-bottom: none; +} + +tr:hover td { + background-color: var(--pst-color-surface); +} + +/* ========================================================================== + 6. ADMONITIONS (Callouts) + ========================================================================== */ +.admonition { + border-radius: 8px; + border: 1px solid var(--pst-color-border); + border-left-width: 6px; + padding: 1.5rem; + margin-bottom: 2rem; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05); + background-color: var(--pst-color-background); +} + +.admonition-title { + font-weight: 700 !important; + font-size: 1.1rem !important; + margin-bottom: 0.75rem !important; + margin-top: 0 !important; + text-transform: none !important; +} + +/* ========================================================================== + 7. GRID CARDS (Landing Page) + ========================================================================== */ +.sd-card { + border: 1px solid var(--pst-color-border) !important; + border-radius: 12px !important; + transition: transform 0.2s, box-shadow 0.2s !important; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05) !important; + background-color: var(--pst-color-surface) !important; +} + +.sd-card:hover { + transform: translateY(-5px); + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1) !important; + border-color: var(--pst-color-primary) !important; +} + +.sd-card-title { + font-size: 1.3rem !important; + font-weight: 700 !important; + color: var(--pst-color-text-base) !important; +} + +.sd-card-text { + color: var(--pst-color-text-muted) !important; + font-size: 1.05rem !important; +} + +/* ========================================================================== + 8. MERMAID DIAGRAMS + ========================================================================== */ +.mermaid { + text-align: center; + padding: 1.5rem; + background-color: var(--pst-color-surface); + border: 1px solid var(--pst-color-border); + border-radius: 8px; + margin-bottom: 2rem; +} diff --git a/docs/_static/favicon.ico b/docs/_static/favicon.ico new file mode 100644 index 000000000..e69de29bb diff --git a/docs/_static/logo.png b/docs/_static/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..8e65f4608290c0284782e458d1843455bb7b65da GIT binary patch literal 177523 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 z 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 Forecaster + BaseGraphModel --|> StepPredictor + GraphLAM --|> BaseGraphModel + HiLAM --|> BaseHiGraphModel + HiLAMParallel --|> BaseHiGraphModel + BaseHiGraphModel --|> BaseGraphModel + Forecaster --o ForecasterModule : forecaster + StepPredictor --o ARForecaster : predictor diff --git a/docs/_static/uml/packages_models.mmd b/docs/_static/uml/packages_models.mmd new file mode 100644 index 000000000..955e7a966 --- /dev/null +++ b/docs/_static/uml/packages_models.mmd @@ -0,0 +1,51 @@ +classDiagram + class models { + } + class forecasters { + } + class autoregressive { + } + class base { + } + class module { + } + class step_predictors { + } + class base { + } + class graph { + } + class base { + } + class graph_lam { + } + class hi_lam { + } + class hi_lam_parallel { + } + class hierarchical { + } + models --> autoregressive + models --> base + models --> module + models --> base + models --> base + models --> graph_lam + models --> hi_lam + models --> hi_lam_parallel + models --> hierarchical + forecasters --> autoregressive + forecasters --> base + autoregressive --> base + module --> base + step_predictors --> base + graph --> base + graph --> graph_lam + graph --> hi_lam + graph --> hi_lam_parallel + graph --> hierarchical + base --> base + graph_lam --> base + hi_lam --> hierarchical + hi_lam_parallel --> hierarchical + hierarchical --> base diff --git a/docs/_toc.yml b/docs/_toc.yml index ec77d88dd..2ef150f02 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -1,8 +1,35 @@ -# docs/_toc.yml format: jb-book root: intro parts: - - caption: API Reference + - caption: Getting Started chapters: - - file: autoapi/index + - file: getting-started/installation + - file: getting-started/quickstart + + - caption: Tutorials + chapters: + - file: notebooks/create_reduced_meps_dataset + + - caption: Architecture + chapters: + - file: architecture/overview + - file: architecture/theory + - file: architecture/data-flow + - file: architecture/models + - file: architecture/graph-construction + + - caption: Resources + chapters: + - file: guides/configuration + - file: api/index + + - caption: Contributing + chapters: + - file: contributing/development-setup + - file: contributing/coding-standards + + - caption: About + chapters: + - file: about/changelog + - file: about/glossary diff --git a/docs/about/changelog.md b/docs/about/changelog.md new file mode 120000 index 000000000..699cc9e7b --- /dev/null +++ b/docs/about/changelog.md @@ -0,0 +1 @@ +../../CHANGELOG.md \ No newline at end of file diff --git a/docs/about/glossary.md b/docs/about/glossary.md new file mode 100644 index 000000000..13f8b8a66 --- /dev/null +++ b/docs/about/glossary.md @@ -0,0 +1,60 @@ +# Glossary + +```{glossary} +ARModel + The autoregressive base model implemented in PyTorch Lightning. + +BaseDatastore + The abstract base class for data loaders and datastores. + +Datastore + The overarching data handler component for loading weather data. + +Encode-Process-Decode + An architecture style often used in graph neural networks involving encoding inputs into latent graphs, processing them with message passing, and decoding back to the target space. + +Forcing Variables + External variables providing boundary or context conditions to the {term}`Forecaster`. + +Forecaster + The core component or model responsible for generating predictions over time. + +GNN + Graph Neural Network. A type of neural network designed to operate on graph structures. + +GraphLAM + The main Graph-based Limited Area Model implementation. + +HiLAM + Hierarchical Limited Area Model. + +HiLAMParallel + A parallelized version of {term}`HiLAM`. + +LAM + Limited Area Model. A weather prediction model focused on a specific geographic region rather than global scope. + +MDPDatastore + Datastore designed to read Zarr formats via the {term}`mllam-data-prep` module. + +Mesh + The structured or unstructured graph grid onto which the weather data is projected and processed. + +mllam-data-prep + The tool/module responsible for preparing and formatting raw weather data into a format ingestible by Neural-LAM. + +PyG + PyTorch Geometric, a library for deep learning on irregular input data such as graphs. + +State Variables + The set of variables that describe the current internal state of the weather system in the model. + +StepPredictor + A model component designed to predict the next single time step given a current state. + +WeatherDataModule + The PyTorch Lightning data module encapsulating the {term}`WeatherDataset`. + +WeatherDataset + The PyTorch dataset class representing the prepared weather data. +``` diff --git a/docs/architecture/data-flow.md b/docs/architecture/data-flow.md new file mode 100644 index 000000000..9cdfe7acd --- /dev/null +++ b/docs/architecture/data-flow.md @@ -0,0 +1,38 @@ +# Data Flow + +## Overview + +The Neural-LAM data pipeline is designed to efficiently load raw meteorological data and feed it into the models for training and inference. It abstracts data sources, normalizes variables, and prepares batches using PyTorch Lightning. + +## Datastore Abstraction + +The {py:class}`neural_lam.datastore.BaseDatastore` provides an abstract interface for data access. It defines methods for fetching input and target tensors (`get_xy`), as well as metadata like variable names and units. Subclasses like `MDPDatastore` implement this interface to support specific formats like Zarr. + +## WeatherDataset + +The {py:class}`neural_lam.weather_dataset.WeatherDataset` acts as a PyTorch wrapper around a `BaseDatastore`. It handles indexing, temporal batching, and any necessary on-the-fly transformations required before the data reaches the Lightning module. + +## WeatherDataModule + +The {py:class}`neural_lam.weather_dataset.WeatherDataModule` is a PyTorch Lightning DataModule that encapsulates the `WeatherDataset`s for training, validation, and testing splits. It manages the dataloaders and ensures data is correctly distributed across devices during distributed training. + +## Configuration + +The entire data pipeline is driven by type-safe YAML configurations defined in {py:mod}`neural_lam.config`. Dataclasses define expected paths, batch sizes, and data normalization statistics, allowing easy experimentation without code changes. + +## Data Format Requirements + +| Component | Format/Shape Expected | +|-----------|-----------------------| +| Model Input | `(batch, sequence_length, num_grid_nodes, num_features)` | +| Target Output | `(batch, sequence_length, num_grid_nodes, num_features)` | +| Static Features | `(num_grid_nodes, num_static_features)` | + +```{mermaid} +flowchart TD + A["Raw Data Files"] -->|Read by| B["BaseDatastore"] + B -->|get_xy| C["WeatherDataset"] + C -->|__getitem__| D["DataLoader"] + D -->|B, T, N, F| E["WeatherDataModule"] + E -->|Batch| F["Model Forward Pass"] +``` diff --git a/docs/architecture/graph-construction.md b/docs/architecture/graph-construction.md new file mode 100644 index 000000000..d50daad0e --- /dev/null +++ b/docs/architecture/graph-construction.md @@ -0,0 +1,66 @@ +# Graph Construction + +## Why Graphs? + +Graph Neural Networks (GNNs) leverage graph-based message passing, which is highly suited for weather prediction. Unlike standard CNNs on rigid grids, graphs can naturally represent irregular spatial distributions and complex geometries, common in Limited Area Modeling (LAM). + +The graph structure dictates how information flows across spatial regions. A well-designed mesh ensures that localized weather phenomena correctly influence neighboring regions, while hierarchical structures allow long-range interactions (like large-scale pressure systems) to propagate efficiently across the domain without requiring hundreds of standard grid steps. + +## The create_graph Module + +The `create_graph` script is used to pre-compute and build the mesh graphs required before training any models. This ensures that the complex spatial structures are generated once and loaded efficiently during training. + +Reference: {py:mod}`neural_lam.create_graph` + +## Graph Types + +### Flat Mesh +- Single level of mesh nodes +- Used by GraphLAM + +### Hierarchical Mesh +- Multiple levels of mesh nodes at increasing spatial scales +- Used by HiLAM and HiLAMParallel + +```{mermaid} +graph TD + subgraph grid ["Grid Level"] + G1["Grid Node"] --- G2["Grid Node"] --- G3["Grid Node"] + end + subgraph mesh1 ["Mesh Level 1"] + M1["Mesh Node"] --- M2["Mesh Node"] + end + subgraph mesh2 ["Mesh Level 2"] + M3["Mesh Node"] + end + G1 -.-> M1 + G2 -.-> M1 + G2 -.-> M2 + G3 -.-> M2 + M1 -.-> M3 + M2 -.-> M3 +``` + +## Edge Features + +For each edge type in the constructed graph, specific features are computed to assist message passing: +- **Spatial Distance**: The physical distance between connected nodes. +- **Directional Vectors**: Vector representations of the direction between nodes, enabling the GNN to understand flow and gradients (e.g., wind direction). +- **Elevation Differences**: Changes in altitude between nodes, which is critical for orographic effects in weather. + +## Usage + +```bash +python -m neural_lam.create_graph \ + --config_path \ + --name +``` + +- `--config_path`: Path to the YAML configuration file which dictates the dataset properties and graph parameters. +- `--name`: The name assigned to the generated graph, which is used to load it during training. + +## GNN Layers + +The `InteractionNet` represents the core GNN layer implementation in Neural-LAM. It utilizes PyTorch Geometric's `MessagePassing` interface to aggregate features from neighboring nodes and update node states, incorporating edge features natively to refine the spatial interactions. + +Reference: {py:mod}`neural_lam.gnn_layers` diff --git a/docs/architecture/models.md b/docs/architecture/models.md new file mode 100644 index 000000000..331a8f787 --- /dev/null +++ b/docs/architecture/models.md @@ -0,0 +1,60 @@ +# Model Architectures + +## Overview + +Neural-LAM employs a modular model hierarchy based on the **encode-process-decode** paradigm. This paradigm allows the models to encode grid-based weather data into a graph structure, perform spatial and temporal message passing to process the data, and finally decode the updated graph states back into the original grid representation. + +## Autoregressive Framework + +The core training and evaluation loop is handled by `ARModel`, a PyTorch Lightning module defined in `neural_lam/models/module.py`. This autoregressive framework is responsible for unrolling predictions over multiple timesteps. + +```{mermaid} +sequenceDiagram + participant Trainer as Lightning Trainer + participant AR as ARModel + participant F as Forecaster + participant SP as StepPredictor + + Trainer->>AR: training_step(batch) + loop for each timestep + AR->>F: forward(prev_state) + F->>SP: predict_step(state, graph) + SP-->>F: next_state_delta + F-->>AR: next_state + end + AR->>AR: compute_loss() + AR-->>Trainer: loss +``` + +## Encode-Process-Decode + +The base graph model, defined in `neural_lam/models/step_predictors/base.py`, implements the standard encode-process-decode steps: +1. **Encode**: Features from the weather grid are mapped onto the nodes and edges of the mesh graph. +2. **Process**: A Graph Neural Network (GNN) performs multiple rounds of message passing to propagate information across the spatial domain. +3. **Decode**: The updated mesh features are mapped back to the grid to produce the next state prediction. + +## GraphLAM + +GraphLAM is the fundamental model architecture utilizing a single-level flat graph. It is effective for standard resolution forecasting without multi-scale processing. + +Reference: {py:class}`neural_lam.models.step_predictors.graph.GraphLAM` + +## HiLAM + +HiLAM introduces a hierarchical model design, utilizing multiple levels of mesh nodes at increasing spatial scales. This allows the network to efficiently capture both local, fine-grained interactions and long-range, global atmospheric patterns. + +Reference: {py:class}`neural_lam.models.step_predictors.graph.HiLAM` + +## HiLAMParallel + +HiLAMParallel is a parallel hierarchical variant of HiLAM. It processes multi-scale information simultaneously across different hierarchy levels, rather than sequentially, potentially improving computational efficiency and long-range interaction modeling. + +Reference: {py:class}`neural_lam.models.step_predictors.graph.HiLAMParallel` + +## Choosing a Model + +| Model | Graph Type | Complexity | Best For | +|---|---|---|---| +| **GraphLAM** | Flat Mesh | Low | Baseline forecasting, single-scale dynamics | +| **HiLAM** | Hierarchical Mesh | Medium | Capturing both local and global dependencies efficiently | +| **HiLAMParallel** | Hierarchical Mesh | High | Highly parallel environments, very large spatial domains | diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md new file mode 100644 index 000000000..7d5c8abcb --- /dev/null +++ b/docs/architecture/overview.md @@ -0,0 +1,51 @@ +# Architecture Overview + +## System Design + +Neural-LAM is designed with a modular architecture that separates data handling, model architecture, and training logic. This separation of concerns allows for easy experimentation with different graph structures, model components, and datasets without requiring extensive changes to the core system. + +The core components include a robust data pipeline utilizing an abstract datastore interface, a flexible set of graph-based neural network models (such as `GraphLAM`, `HiLAM`, and `HiLAMParallel`), and a training module built on top of PyTorch Lightning. This design ensures scalability and ease of use for both researchers and practitioners. + +## Data Flow + +```{mermaid} +flowchart LR + A["Raw Data
(zarr / numpy)"] --> B["Datastore
(BaseDatastore)"] + B --> C["WeatherDataset"] + C --> D["WeatherDataModule
(Lightning)"] + D --> E["ARModel
(Autoregressive)"] + E --> F["StepPredictor
(GNN)"] + F --> G["Predictions"] + G --> H["Loss & Metrics"] +``` + +## Module Map + +| Module | Responsibility | +|--------|----------------| +| `datastore` | Handles reading from diverse data sources (e.g., Zarr, NetCDF) via the `BaseDatastore` interface. | +| `weather_dataset` | Wraps the datastore in a PyTorch `Dataset` and Lightning `DataModule` for training. | +| `models` | Contains the core neural network architectures (`ARModel`, `BaseGraphModel`, `GraphLAM`, etc.). | +| `create_graph` | Utility to build the hierarchical mesh graphs used by the GNN models. | +| `config` | Manages the YAML-based configuration via dataclasses. | + +## Component Interaction + +This diagram is **automatically generated** from the Python source code using `pyreverse`, ensuring it never goes stale! + +```{eval-rst} +.. mermaid:: ../_static/uml/classes_models.mmd +``` + +## Key Design Decisions + +- **Modular Datastores**: `BaseDatastore` abstracts away data loading intricacies. +- **PyTorch Lightning**: Used to reduce boilerplate and scale training easily. +- **Hierarchical Graphs**: `create_graph` decouples graph structure generation from model logic. +- **Dataclass Configurations**: Type-safe YAML configurations managed by `dataclass-wizard`. + +## See Also + +- {doc}`data-flow` for detailed data pipeline documentation +- {doc}`models` for model architecture details +- {doc}`graph-construction` for graph creation details diff --git a/docs/architecture/theory.md b/docs/architecture/theory.md new file mode 100644 index 000000000..a636c5374 --- /dev/null +++ b/docs/architecture/theory.md @@ -0,0 +1,47 @@ +# Theory & Methods + +This section describes the mathematical formulations and algorithms underlying the Neural-LAM graph models. + +## The Encode-Process-Decode Paradigm + +All graph-based models in Neural-LAM (`GraphLAM`, `HiLAM`, and `HiLAMParallel`) follow the classic "Encode-Process-Decode" paradigm for graph neural networks. + +1. **Encode**: The input grid state \( X^t \) at timestep \( t \) is mapped to latent node features on the graph: + \[ H_{grid} = \text{Encoder}(X^t) \] + +2. **Process**: A series of message passing steps updates the latent node representations based on the graph connectivity: + \[ H'_{grid} = \text{Processor}(H_{grid}, \mathcal{G}) \] + +3. **Decode**: The updated latent features are mapped back to predict the residual change for the next timestep: + \[ \Delta X^{t+1} = \text{Decoder}(H'_{grid}) \] + \[ X^{t+1} = X^t + \Delta X^{t+1} \] + +## Message Passing Functions + +Neural-LAM supports several GNN layers for message passing along different edge types (e.g., Grid-to-Mesh, Mesh-to-Mesh). + +### InteractionNet + +The default layer is the `InteractionNet` (based on Interaction Networks). Given a sender node \( v_s \), a receiver node \( v_r \), and an edge feature \( e_{s,r} \), the message passing works as follows: + +1. **Edge Update (Message Formulation)**: + \[ m_{s,r} = \text{MLP}_{edge}\left([h_s, h_r, e_{s,r}]\right) \] + +2. **Node Update (Aggregation)**: + \[ h'_r = \text{MLP}_{node}\left([h_r, \sum_{s \in \mathcal{N}(r)} m_{s,r}]\right) \] + +### PropagationNet + +`PropagationNet` modifies the Interaction Network to strongly incentivize directional information flow from senders to receivers, which is crucial for moving information up and down the hierarchical mesh levels: + +\[ h'_r = h_r + \text{MLP}_{prop}\left([h_r, \sum_{s \in \mathcal{N}(r)} m_{s,r}]\right) \] + +This residual connection ensures that nodes maintain their internal state while selectively incorporating new information from neighbors. + +## Loss Weighting + +To balance predictions across variables with different physical scales and variances, the loss function uses a weighted Mean Squared Error (MSE): + +\[ \mathcal{L} = \frac{1}{V \cdot N} \sum_{v=1}^V \sum_{i=1}^N w_v \cdot \left( \hat{X}^{t+1}_{i,v} - X^{t+1}_{i,v} \right)^2 \] + +Where \( w_v \) are variable-specific weights defined in the `loss_weighting` module, and the errors are computed on standardized variables. diff --git a/docs/contributing/coding-standards.md b/docs/contributing/coding-standards.md new file mode 100644 index 000000000..c1655d728 --- /dev/null +++ b/docs/contributing/coding-standards.md @@ -0,0 +1,41 @@ +# Coding Standards + +## Code Style + +Our codebase enforces strict styling guidelines using the following tools: +- **Formatter**: `black` +- **Import Sorting**: `isort` +- **Linting**: `flake8` +- **Type Checking**: `mypy` +- **Docstring Coverage**: `interrogate` +- **Spell Checking**: `codespell` + +## Pull Request Process + +1. **Search before creating**: Search existing issues or PRs to avoid duplicates. +2. **Every PR requires an issue**: Open an issue first if none exists. +3. **Link the issue**: Include `closes #` or `refs #` in the PR body. +4. **Use the PR template**: Fill out every section of the provided template. +5. **Run pre-commit hooks**: Ensure code passes locally via `uvx pre-commit run --all-files`. +6. **Run tests**: Run `pytest tests/` and fix any failures before opening the PR. +7. **Update CHANGELOG**: Add a line to `CHANGELOG.md` in the appropriate section. + +## Docstring Standard + +- We use **NumPy-style** docstrings. +- We require **100% docstring coverage** for public functions, methods, and classes. +- Always include `Parameters`, `Returns`, and `Raises` sections if applicable. +- Make sure to specify **tensor shapes** in the docstrings when passing or returning tensors. + +## Commit Messages + +- Must be in the **imperative form** (e.g., "Add test for feature X" instead of "Added test..."). +- Keep **one concern per PR** to ensure unrelated changes are not mixed. +- AI attribution of tool names is mandatory if used and should be mentioned in the commit message trailer as `Co-authored-by `. + +## Adding Documentation + +When adding new modules or classes: +1. Include detailed docstrings directly in the code. +2. If appropriate, create a new Markdown page under `docs/architecture/` (or update an existing one). +3. Ensure the new page is added to the `_toc.yml` so it appears in the documentation navigation structure. diff --git a/docs/contributing/development-setup.md b/docs/contributing/development-setup.md new file mode 100644 index 000000000..93f3a2c03 --- /dev/null +++ b/docs/contributing/development-setup.md @@ -0,0 +1,102 @@ +# Development Setup + +## Prerequisites + +- Python >=3.10 +- Git +- uv (recommended) or pip + +## Clone and Install + +```bash +git clone https://github.com/mllam/neural-lam.git +cd neural-lam +uv sync --extra cpu --group dev --locked +source .venv/bin/activate +``` + +## Pre-commit Hooks + +We use `pre-commit` to ensure code formatting and quality before commits. The hooks include: +- `black` for code formatting. +- `isort` for import sorting. +- `flake8` for linting. +- `mypy` for static type checking. +- `codespell` for spell checking. +- `interrogate` for docstring coverage. + +To install and run the pre-commit hooks: + +```bash +pre-commit install +uvx pre-commit run --all-files +``` + +## Running Tests + +We use `pytest` for running our test suite. Note that Weights & Biases (W&B) is automatically disabled during tests. + +Run all tests: +```bash +pytest -vv -s --doctest-modules +``` + +Run tests in a single file: +```bash +pytest tests/test_training.py -vv -s +``` + +Run a single function test: +```bash +pytest tests/test_training.py::test_fn -vv +``` + +## Building Documentation + +We use `jupyter-book` to build the documentation. You can build it using `uv`: + +```bash +uv run jb build docs +``` + +## Project Structure + +- `docs/` - Project documentation. +- `neural_lam/` - Main source code directory containing core modules, models, and data logic. + - `datastore/` - Datastore classes for loading data. + - `models/` - Core neural network models. +- `tests/` - Unit tests and test data examples. + +## Writing Docstrings + +We follow the NumPy-style format for docstrings. All functions and classes must have docstrings including Parameters, Returns, Raises, and Tensor shapes where applicable. + +Example: +```python +import torch + +def process_state(state: torch.Tensor, threshold: float = 0.5) -> torch.Tensor: + """ + Process the given state tensor by applying a threshold. + + Parameters + ---------- + state : torch.Tensor + The input state tensor of shape (batch_size, num_features). + threshold : float, optional + The threshold value to apply, by default 0.5. + + Returns + ------- + torch.Tensor + The processed state tensor of shape (batch_size, num_features). + + Raises + ------ + ValueError + If the state tensor is empty. + """ + if state.numel() == 0: + raise ValueError("State tensor cannot be empty.") + return torch.where(state > threshold, state, torch.zeros_like(state)) +``` diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md new file mode 100644 index 000000000..d55e0259e --- /dev/null +++ b/docs/getting-started/installation.md @@ -0,0 +1,72 @@ +# Installation + +## Prerequisites +- Python >=3.10 +- Git +- (Optional) CUDA-capable GPU for training + +## Quick Install with uv (Recommended) + +uv is the recommended tool for installing Neural-LAM and its dependencies. + +```{code-block} bash +# Clone the repository +git clone https://github.com/mllam/neural-lam.git +cd neural-lam + +# CPU-only install +uv sync --extra cpu --group dev --locked + +# GPU install (CUDA 13.0, default) +uv sync --extra gpu --group dev --locked + +# GPU install (CUDA 12.8) +uv sync --extra gpu-cu128 --group dev --locked +``` + +```{note} +The extras system (`cpu` / `gpu` / `gpu-cu128`) selects the correct PyTorch index via `[tool.uv.sources]` in `pyproject.toml`. +``` + +## Install with pip (Alternative) + +Alternatively, you can install the dependencies using pip. Make sure to install the correct version of PyTorch for your system before installing the package. + +```{code-block} bash +# Install PyTorch (example for CUDA 11.8) +pip install torch --index-url https://download.pytorch.org/whl/cu118 + +# Install neural-lam +pip install -e .[dev] +``` + +## Verify Installation + +```{code-block} bash +# Activate the environment +source .venv/bin/activate + +# Verify torch is installed +python -c "import torch; print(f'PyTorch {torch.__version__}')" + +# Verify neural-lam is importable +python -c "import neural_lam; print('Neural-LAM OK')" +``` + +## Building Documentation Locally + +```{code-block} bash +# Install docs dependencies +uv sync --extra cpu --extra docs --group dev + +# Build the documentation +jupyter-book build docs/ + +# Open in browser +open docs/_build/html/index.html # macOS +# xdg-open docs/_build/html/index.html # Linux +``` + +```{seealso} +See the {doc}`quickstart` guide to run your first training. +``` diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md new file mode 100644 index 000000000..60b002917 --- /dev/null +++ b/docs/getting-started/quickstart.md @@ -0,0 +1,57 @@ +# Quickstart + +## Overview + +This guide walks through the minimum steps to get started with Neural-LAM: +1. Set up example data +2. Create a graph +3. Train a model +4. Evaluate the model + +## Step 1: Download Example Data + +When running tests for the first time, example data is automatically downloaded from S3. You can trigger this download by running a minimal test. + +```{code-block} bash +# Run a minimal test to trigger data download +pytest tests/test_training.py -vv -s -k "test_training" --co +``` + +Alternatively, you can use the `DummyDatastore` for quick testing without downloading real data. + +## Step 2: Create a Graph + +Before training, you must construct a graph mesh for your data. + +```{code-block} bash +python -m neural_lam.create_graph --config_path --name +``` + +This script builds the mesh graph required by the GNN models. For more details, see {doc}`../architecture/graph-construction`. + +## Step 3: Train a Model + +Now you can train a model using the graph and configuration. + +```{code-block} bash +python -m neural_lam.train_model --config_path --model graph_lam --graph +``` + +Neural-LAM supports several models like `graph_lam`, `hilam`, and `hilam_parallel`. For more information on the available models, see {doc}`../architecture/models`. + +## Step 4: Evaluate + +After training, you can evaluate the model on the test set by loading the saved checkpoint. + +```{code-block} bash +python -m neural_lam.train_model --eval test --config_path --load +``` + +## Configuration + +Neural-LAM uses a YAML configuration system powered by `dataclass-wizard`. The configuration defines the dataset paths, training parameters, and model hyperparameters. For complete details, see the API reference for {py:class}`neural_lam.config.NeuralLAMConfig`. + +## Next Steps + +- {doc}`../architecture/overview` to understand the system design +- {doc}`../api/index` for complete API reference diff --git a/docs/guides/configuration.md b/docs/guides/configuration.md new file mode 100644 index 000000000..c7b9856f6 --- /dev/null +++ b/docs/guides/configuration.md @@ -0,0 +1,58 @@ +# Configuration Reference + +Neural-LAM uses `dataclass-wizard` to automatically parse and enforce type-safety for YAML configuration files. The configuration encapsulates both the **Datastore** configuration and the **Model/Training** configuration. + +Below are common templates you can use for your own runs. + +## 1. Quick CPU Test Run + +This minimal configuration uses a dummy datastore, perfect for quickly testing changes on a laptop without a GPU. + +```yaml +# tests/test_config.yaml +datastore: + _target_: "neural_lam.datastore.DummyDatastore" + +architecture: "graph_lam" +epochs: 2 +batch_size: 2 +lr: 1e-3 +hidden_dim: 32 +hidden_layers: 1 +``` + +## 2. MDP (Meteorological Data Processing) Full Training + +This is a production-level configuration for training on a real Zarr dataset produced by `mllam-data-prep` on a GPU cluster. + +```yaml +# config/mdp_training.yaml +datastore: + _target_: "neural_lam.datastore.MDPDatastore" + dataset_path: "/path/to/my/zarr_dataset.zarr" + subset_name: "meps" # Optional subset + +# Model architecture settings +architecture: "hi_lam_parallel" +hidden_dim: 128 +hidden_layers: 4 +mesh_aggr: "sum" + +# GNN Types for the different edges +g2m_gnn_type: "interaction" +m2g_gnn_type: "interaction" +mesh_up_gnn_type: "propagation" +mesh_down_gnn_type: "propagation" + +# Training parameters +epochs: 100 +batch_size: 16 +lr: 5e-4 +loss: "mse" + +# Logging and reproducibility +seed: 42 +``` + +## API Reference +For an exhaustive list of every configurable field and its default value, refer to the auto-generated documentation for the `NeuralLAMConfig` dataclass in the {py:mod}`neural_lam.config` module. diff --git a/docs/intro.md b/docs/intro.md index b7ae1d75b..fee7572f3 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -1,14 +1,73 @@ # Neural-LAM -**Graph-based neural weather prediction for Limited Area Modeling.** -Built with PyTorch, PyTorch Lightning, and PyG. +**Graph-based neural weather prediction for Limited Area Modeling** -```{admonition} Quick install +Neural-LAM is a PyTorch and PyTorch Lightning framework for high-resolution weather prediction using Graph Neural Networks. It provides a modular approach to Limited Area Modeling, supporting multiple graph-based architectures such as GraphLAM, HiLAM, and HiLAMParallel to process and predict meteorological data efficiently. + +```{admonition} Get Started in 5 Minutes :class: tip -pip install neural_lam +Install Neural-LAM and run your first prediction. +See the {doc}`Getting Started Guide `. +``` + +::::{grid} 2 +:gutter: 3 + +:::{grid-item-card} 🚀 Getting Started +:link: getting-started/installation +:link-type: doc +Installation guide and quickstart tutorial to get you up and running. +::: + + +:::{grid-item-card} 🏗️ Architecture +:link: architecture/overview +:link-type: doc +Understand the data flow, model structure, and design decisions. +::: + +:::{grid-item-card} 📚 API Reference +:link: api/index +:link-type: doc +Auto-generated reference for all modules, classes, and functions. +::: + +:::: + +## Key Features + +- **Modular design**: Swap datastores, models, and graph structures independently +- **Multiple model architectures**: GraphLAM (flat), HiLAM (hierarchical), HiLAMParallel (parallel hierarchical) +- **Flexible data handling**: Abstract datastore interface supporting zarr, numpy, and custom formats via mllam-data-prep +- **Production-ready**: PyTorch Lightning for training, W&B/MLflow logging, checkpoint management + +## Publications + +If you use Neural-LAM in your research, please cite the relevant papers: + +**NeurIPS 2024 Paper** ([Probabilistic Weather Forecasting with Hierarchical Graph Neural Networks](https://arxiv.org/abs/2406.04759)) +```bibtex +@inproceedings{oskarsson2024probabilistic, + title = {Probabilistic Weather Forecasting with Hierarchical Graph Neural Networks}, + author = {Oskarsson, Joel and Landelius, Tomas and Deisenroth, Marc Peter and Lindsten, Fredrik}, + booktitle = {Advances in Neural Information Processing Systems}, + volume = {37}, + year = {2024}, +} +``` + +**NeurIPS 2023 Workshop Paper** ([Graph-based Neural Weather Prediction for Limited Area Modeling](https://arxiv.org/abs/2309.17370)) +```bibtex +@inproceedings{oskarsson2023graphbased, + title={Graph-based Neural Weather Prediction for Limited Area Modeling}, + author={Oskarsson, Joel and Landelius, Tomas and Lindsten, Fredrik}, + booktitle={NeurIPS 2023 Workshop on Tackling Climate Change with Machine Learning}, + year={2023} +} ``` -- **[API Reference](autoapi/index)** — full auto-generated docs from source -- **[Slack](https://kutt.it/mllam)** — join the mllam community -- **[Issues](https://github.com/mllam/neural-lam/issues)** — bug reports & feature requests -- **[GitHub](https://github.com/mllam/neural-lam)** — source code +## Quick Links + +- [GitHub Repository](https://github.com/mllam/neural-lam) +- [Issue Tracker](https://github.com/mllam/neural-lam/issues) +- [MLLAM Community Slack](https://kutt.it/mllam) diff --git a/docs/notebooks/create_reduced_meps_dataset.ipynb b/docs/notebooks/create_reduced_meps_dataset.ipynb index daba23c44..00cfa247f 100644 --- a/docs/notebooks/create_reduced_meps_dataset.ipynb +++ b/docs/notebooks/create_reduced_meps_dataset.ipynb @@ -5,7 +5,7 @@ "metadata": {}, "source": [ "# Creating meps_example_reduced\n", - "This notebook outlines how the small-size test dataset ```meps_example_reduced``` was created based on the slightly larger dataset ```meps_example```. The zipped up datasets are 263 MB and 2.6 GB, respectively. See [README.md](../../README.md) for info on how to download ```meps_example```.\n", + "This notebook outlines how the small-size test dataset ```meps_example_reduced``` was created based on the slightly larger dataset ```meps_example```. The zipped up datasets are 263 MB and 2.6 GB, respectively. See [README](https://github.com/mllam/neural-lam#data) for info on how to download ```meps_example```.\n", "\n", "The dataset was reduced in size by reducing the number of grid points and variables.\n" ] diff --git a/docs/scripts/autoapi_astroid_patch.py b/docs/scripts/autoapi_astroid_patch.py index 66e31b600..8b647afe9 100644 --- a/docs/scripts/autoapi_astroid_patch.py +++ b/docs/scripts/autoapi_astroid_patch.py @@ -1,16 +1,31 @@ -"""Patch AutoAPI for astroid>=4.""" # codespell:ignore astroid +""" +Patch AutoAPI for astroid >= 4 compatibility. + +This module patches the `AstroidBuilder` from `astroid` to pass a `manager` +argument to the parent class `__init__`, which is strictly required for +astroid >= 4. AutoAPI does not natively pass this argument in its current +versions, so this workaround prevents documentation build errors. +""" # codespell:ignore astroid from __future__ import annotations # Standard library import inspect -# Third-party -from astroid import builder as astroid_builder # codespell:ignore astroid -from astroid.manager import AstroidManager # codespell:ignore astroid +try: + # Third-party + from astroid import builder as astroid_builder # codespell:ignore astroid + from astroid.manager import AstroidManager # codespell:ignore astroid + + ASTROID_AVAILABLE = True +except ImportError: + ASTROID_AVAILABLE = False def setup(app): + if not ASTROID_AVAILABLE: + return {"version": "0.1"} + builder_init = astroid_builder.AstroidBuilder.__init__ if "manager" not in inspect.signature(builder_init).parameters: return {"version": "0.1"} diff --git a/docs/scripts/generate_docs.sh b/docs/scripts/generate_docs.sh index 56eae0bbb..9f5abb969 100755 --- a/docs/scripts/generate_docs.sh +++ b/docs/scripts/generate_docs.sh @@ -3,55 +3,44 @@ set -euo pipefail -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -BOLD='\033[1m' -NC='\033[0m' -info() { echo -e "${BLUE}[INFO]${NC} $*"; } -success() { echo -e "${GREEN}[OK]${NC} $*"; } -warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } -error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } - SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" -NEURAL_LAM_DIR="$REPO_ROOT/neural_lam" DOCS_DIR="$REPO_ROOT/docs" -for tool in interrogate pydocstyle jupyter-book; do - command -v "$tool" &>/dev/null || { - error "$tool not found. Run: pdm install --group docs" - exit 1 - } -done - -# ── 1. interrogate ── -info "interrogate: docstring coverage audit (fail-under 50)" -interrogate "$NEURAL_LAM_DIR" \ - --fail-under 50 \ - --verbose \ - --generate-badge "$DOCS_DIR/" \ - 2>&1 | tee "$DOCS_DIR/interrogate_report.txt" && - success "Coverage ≥ 50% ✓" || - { - error "Coverage below 50%! See docs/interrogate_report.txt" - exit 1 - } - -# ── 2. pydocstyle ── -info "pydocstyle: style check (non-blocking)" -pydocstyle "$NEURAL_LAM_DIR" --convention=numpy --add-ignore=D100,D104,D105 && - success "pydocstyle passed ✓" || - warn "pydocstyle issues (non-blocking)" - -# ── 3. jupyter-book build ── -info "jupyter-book: building site" -jupyter-book build "$DOCS_DIR/" && - success "Build succeeded ✓" || - { - error "Build FAILED" - exit 1 - } - -echo -e "\n${BOLD} Done — open: $DOCS_DIR/_build/html/index.html${NC}" +if ! command -v uv &>/dev/null; then + echo "[ERROR] uv not found. Please install uv first." >&2 + exit 1 +fi + +echo "[INFO] Syncing dependencies..." +uv pip install -e ".[cpu,docs]" + +echo "[INFO] Removing old build at docs/_build..." +rm -rf "$DOCS_DIR/_build" + +echo "[INFO] Generating UML architecture diagrams with pyreverse..." +mkdir -p "$DOCS_DIR/_static/uml" +if [ -f "$REPO_ROOT/.venv/bin/pyreverse" ]; then + (cd "$REPO_ROOT" && "$REPO_ROOT/.venv/bin/pyreverse" -o mmd -p models neural_lam.models -d "$DOCS_DIR/_static/uml/" || echo "[WARN] pyreverse failed, continuing...") +else + (cd "$REPO_ROOT" && pyreverse -o mmd -p models neural_lam.models -d "$DOCS_DIR/_static/uml/" || echo "[WARN] pyreverse failed, continuing...") +fi + +echo "[INFO] Building site with jupyter-book..." +if [ -f "$REPO_ROOT/.venv/bin/jupyter-book" ]; then + "$REPO_ROOT/.venv/bin/jupyter-book" build docs/ --keep-going +else + # fallback to uv run if .venv structure differs + uv run jupyter-book build docs/ --keep-going +fi + +INDEX_HTML="$DOCS_DIR/_build/html/index.html" +echo "[OK] Build succeeded! Open: $INDEX_HTML" + +if command -v open &>/dev/null; then + open "$INDEX_HTML" +elif command -v xdg-open &>/dev/null; then + xdg-open "$INDEX_HTML" +else + echo "Please open $INDEX_HTML in your browser manually." +fi diff --git a/pyproject.toml b/pyproject.toml index 372a33bc2..d0340a5b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,19 @@ requires-python = ">=3.10" cpu = ["torch>=2.12,<2.13"] gpu = ["torch>=2.12,<2.13"] # CUDA 13.0, default GPU build gpu-cu128 = ["torch>=2.11,<2.12"] # CUDA 12.8, last torch series with cu128 wheels +docs = [ + "jupyter-book>=1.0.0,<2.0.0", + "sphinx-autoapi>=3.0.0", + "sphinx-copybutton>=0.5.2", + "sphinx-book-theme>=1.1.0", + "sphinxcontrib-mermaid>=0.9.2", + "sphinx>=7.2.6", + "myst-nb>=1.0.0", + "myst-parser>=2.0.0", + "sphinxext-opengraph>=0.9.0", + "pylint>=3.0.0", + "linkify-it-py>=2.0.0", +] [dependency-groups] dev = ["pre-commit>=3.8.0", "pytest>=8.3.2", "pooch>=1.8.2"] @@ -136,6 +149,7 @@ exclude = ["tests", "docs", "build"] markers = [ "slow: marks tests as slow (deselected by default, run with -m slow)", ] +norecursedirs = ["docs"] [build-system] requires = ["hatchling>=1.27.0", "hatch-vcs"] @@ -148,8 +162,6 @@ core-metadata-version = "2.4" source = "vcs" fallback-version = "0.0.0" -[tool.pytest.ini_options] -norecursedirs = ["docs"] [tool.hatch.build.targets.sdist] exclude = [ ".venv/", @@ -161,16 +173,3 @@ exclude = [ ".venv/", "venv/", ] - -[project.optional-dependencies] -docs = [ - "jupyter-book<2.0.0", - "sphinx-autoapi>=3.0.0", - "myst-nb>=1.0.0", - "sphinx-copybutton>=0.5.2", - "pydata-sphinx-theme>=0.15.2", - "interrogate>=1.7.0", - "pydocstyle>=6.3.0", - "sphinx>=7.2.6", - "sphinxcontrib-mermaid>=0.9.2", -] From 880dc48104a5addbcec81e089bf6625f3b43799d Mon Sep 17 00:00:00 2001 From: Mohit-Lakra Date: Fri, 19 Jun 2026 22:50:11 +0530 Subject: [PATCH 05/15] Resolve pyproject.toml uv extra conflicts for CI --- pyproject.toml | 29 + uv.lock | 1549 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 1566 insertions(+), 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d0340a5b1..e9da32eee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,6 +66,35 @@ docs = [ [dependency-groups] dev = ["pre-commit>=3.8.0", "pytest>=8.3.2", "pooch>=1.8.2"] +[tool.uv] +# uv-specific configuration for PyTorch CPU/GPU variant selection. +# NOTE: --torch-backend is only supported in `uv pip`, not `uv sync`. +# Tracking issue for project-interface support: https://github.com/astral-sh/uv/issues/12994 +# Until then, we use extras + explicit index routing for `uv sync`. +conflicts = [[{ extra = "cpu" }, { extra = "gpu" }, { extra = "gpu-cu128" }]] + +[[tool.uv.index]] +name = "pytorch-cpu" +url = "https://download.pytorch.org/whl/cpu" +explicit = true + +[[tool.uv.index]] +name = "pytorch-cu130" +url = "https://download.pytorch.org/whl/cu130" +explicit = true + +[[tool.uv.index]] +name = "pytorch-cu128" +url = "https://download.pytorch.org/whl/cu128" +explicit = true + +[tool.uv.sources] +torch = [ + { index = "pytorch-cpu", extra = "cpu" }, + { index = "pytorch-cu130", extra = "gpu" }, + { index = "pytorch-cu128", extra = "gpu-cu128" }, +] + [tool.setuptools] py-modules = ["neural_lam"] diff --git a/uv.lock b/uv.lock index b9d5a7ed5..786dad4d1 100644 --- a/uv.lock +++ b/uv.lock @@ -34,6 +34,18 @@ conflicts = [[ { package = "neural-lam", extra = "gpu-cu128" }, ]] +[[package]] +name = "accessible-pygments" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z" }, +] + [[package]] name = "aiohappyeyeballs" version = "2.6.1" @@ -176,6 +188,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, ] +[[package]] +name = "alabaster" +version = "0.7.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/3e/13dd8e5ed9094e734ac430b5d0eb4f2bb001708a8b7856cbf8e084e001ba/alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", size = 23776, upload-time = "2024-01-10T00:56:10.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92", size = 13511, upload-time = "2024-01-10T00:56:08.388Z" }, +] + [[package]] name = "alembic" version = "1.18.4" @@ -223,12 +244,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, ] +[[package]] +name = "appnope" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" }, +] + [[package]] name = "asciitree" version = "0.3.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/2d/6a/885bc91484e1aa8f618f6f0228d76d0e67000b0fdd6090673b777e311913/asciitree-0.3.3.tar.gz", hash = "sha256:4aa4b9b649f85e3fcb343363d97564aa1fb62e249677f2e18a96765145cc0f6e", size = 3951, upload-time = "2016-09-05T19:10:42.681Z" } +[[package]] +name = "astroid" +version = "4.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/63/0adf26577da5eff6eb7a177876c1cfa213856be9926a000f65c4add9692b/astroid-4.0.4.tar.gz", hash = "sha256:986fed8bcf79fb82c78b18a53352a0b287a73817d6dbcfba3162da36667c49a0", size = 406358, upload-time = "2026-02-07T23:35:07.509Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/cf/1c5f42b110e57bc5502eb80dbc3b03d256926062519224835ef08134f1f9/astroid-4.0.4-py3-none-any.whl", hash = "sha256:52f39653876c7dec3e3afd4c2696920e05c83832b9737afc21928f2d2eb7a753", size = 276445, upload-time = "2026-02-07T23:35:05.344Z" }, +] + [[package]] name = "astropy" version = "6.1.7" @@ -281,6 +323,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fb/2b/a88e0bca1a46a902854122e008d2f5fa67e42e011f5ed36a040587cc1c9d/astropy_iers_data-0.2026.3.30.0.54.34-py3-none-any.whl", hash = "sha256:b6784cbd4d1f28f5767af03702af4db33480ce4d159d64e0c65d33183b192973", size = 1985859, upload-time = "2026-03-30T00:55:13.807Z" }, ] +[[package]] +name = "asttokens" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, +] + [[package]] name = "async-timeout" version = "5.0.1" @@ -299,6 +350,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/65/318323f98dbee45d42dff61d8f047181bc6f2268a9068cfad035a46be5af/beautifulsoup4-4.15.0.tar.gz", hash = "sha256:288e3ca7d54b06f2ac191970bc275c1939cb46d450b255bf6718b04aa37ab4f7", size = 632571, upload-time = "2026-06-07T16:44:20.453Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/c6/92fcd42f1ba33e1184263f25bfabf3d27c383410470f169e4b8163bf9c17/beautifulsoup4-4.15.0-py3-none-any.whl", hash = "sha256:d6f88de62e1d4e38ecb1077eb9724cd0eff29d2a08ca16a401e9b9e93f117cf9", size = 109924, upload-time = "2026-06-07T16:44:21.566Z" }, +] + [[package]] name = "blinker" version = "1.9.0" @@ -614,6 +687,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "comm" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, +] + [[package]] name = "contourpy" version = "1.3.2" @@ -1084,6 +1166,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/21/c4/1698067427e6fd5bff407cafe5ba1b3cf109b92e2b064d2e4b4f73379bc3/dataclass_wizard-0.30.1-py2.py3-none-any.whl", hash = "sha256:bf4af012d4fc04511efcc2be52024589faec150795bec5517b56d4ab131e2d1d", size = 114331, upload-time = "2024-11-26T03:33:06.79Z" }, ] +[[package]] +name = "debugpy" +version = "1.8.21" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/aa/12037145b7a56eaa5b29b41872f7a21b538e807e13f32c4d3c46e59be084/debugpy-1.8.21.tar.gz", hash = "sha256:a3c53278e84c94e11bd87c53970ec391d1a67396c8b22609fcac576520e611a6", size = 1697577, upload-time = "2026-06-01T19:30:35.156Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/f3/6b1d4c71f4cbb5360009f928934a03b42906f28fc7b3f7f35f04e58acead/debugpy-1.8.21-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:8eeab7b5462f683452c57c0126aaa5ec4e974ddb705f39ba87dff8818c8e08f9", size = 2113873, upload-time = "2026-06-01T19:30:37.148Z" }, + { url = "https://files.pythonhosted.org/packages/1c/f2/17c3bf91cebc173bfbf5734cd2669723d0a35c0cf9d2fd2124546efeae83/debugpy-1.8.21-cp310-cp310-manylinux_2_34_x86_64.whl", hash = "sha256:0fddfdc130ac6d8bfc0415b0409822fa901c8f310e5c945ac5653a0352532344", size = 3004715, upload-time = "2026-06-01T19:30:38.888Z" }, + { url = "https://files.pythonhosted.org/packages/5a/22/1f8efd80c7b5909e760f9cfd0c9e8681d2d35d532f7c0a40760cd4da4a19/debugpy-1.8.21-cp310-cp310-win32.whl", hash = "sha256:72b5d676c4cbfac3bac5bb01c138a4656e843f93f03ce2a5f4e394ad49fbee73", size = 5303455, upload-time = "2026-06-01T19:30:40.52Z" }, + { url = "https://files.pythonhosted.org/packages/da/ce/54c79abd6cccef92fa7b43d97e3acafedf4d645557267ece05e948b5e4b8/debugpy-1.8.21-cp310-cp310-win_amd64.whl", hash = "sha256:a7fe47fd23da57b9e0bec3f4a8ee65a2dc55782455ed7f2141d75ab5d2eaeef5", size = 5331751, upload-time = "2026-06-01T19:30:42.146Z" }, + { url = "https://files.pythonhosted.org/packages/89/fb/cbf306d6e07a313a91e7171a98669054502840931432c227cfd505ee367f/debugpy-1.8.21-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:da456226c7b4c69e35dbe35dcee6623d912000a77816db7856a41af1c72a0264", size = 2203120, upload-time = "2026-06-01T19:30:43.964Z" }, + { url = "https://files.pythonhosted.org/packages/aa/57/aa739bd4ad2cbf96aeb1b20b56918ddd5ae4c28b68709bfcd327f02123ee/debugpy-1.8.21-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:f68b891688e61bdc08b8d364d919ff0051e0b94657b39dcd027bc3173edb7cdc", size = 3059958, upload-time = "2026-06-01T19:30:45.622Z" }, + { url = "https://files.pythonhosted.org/packages/a8/31/453d2c9a23d133fe2c8ec7ca1d816ded52a913487fe3ffef7c01b4b706af/debugpy-1.8.21-cp311-cp311-win32.whl", hash = "sha256:f843a8b08c2edeaf9b1582eed4f25441af21a297c22ff16bf76a662557aa9c9e", size = 5236515, upload-time = "2026-06-01T19:30:47.461Z" }, + { url = "https://files.pythonhosted.org/packages/60/94/6660de2f2d7bf388f229335ba4637646eebabdbf38564cb439a95a9193c9/debugpy-1.8.21-cp311-cp311-win_amd64.whl", hash = "sha256:84c564d8cc701d41843b29a92814c1f1bef6798724ca9d675c284ad9f6a547d7", size = 5256138, upload-time = "2026-06-01T19:30:49.113Z" }, + { url = "https://files.pythonhosted.org/packages/a2/df/bf625547431a9cadc9f4cbfeda38866e2b17f6aed147b625377e87834449/debugpy-1.8.21-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:9f96713896f39c3dff0ee841f47320c3f2983d33c341e009361bb0ebc79adc4e", size = 2483609, upload-time = "2026-06-01T19:30:50.794Z" }, + { url = "https://files.pythonhosted.org/packages/bf/09/59324b903599031ff9faaec1758292409f6561a0ec2492fe4b703327705a/debugpy-1.8.21-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:c193d474f0a211191f2b4449d2d06157c689013035bd952f3b617e0ef422b176", size = 3968900, upload-time = "2026-06-01T19:30:52.341Z" }, + { url = "https://files.pythonhosted.org/packages/14/cd/27f65b805d7fe005c44e1a36b9183ecdfbcdbf9d3e721a5115d461ecc7ee/debugpy-1.8.21-cp312-cp312-win32.whl", hash = "sha256:4743373c1cac7f9e74a1b9915bf1dbe0e900eca657ffb170ae07ac8363205ae9", size = 5336340, upload-time = "2026-06-01T19:30:54.047Z" }, + { url = "https://files.pythonhosted.org/packages/77/1d/c84e30c0c674184948b66f076ab271c01d940618a2824c23cd035a27bc20/debugpy-1.8.21-cp312-cp312-win_amd64.whl", hash = "sha256:bd7ba9dd3daa7c2f942c6ca8d4695a16bf9ac16b63615261c7982bc74f7ed20c", size = 5374751, upload-time = "2026-06-01T19:30:55.891Z" }, + { url = "https://files.pythonhosted.org/packages/77/6b/d817e1f8cc77aa055d37fba092e0febfdff40fe652d8d53d4cd7a86ad98d/debugpy-1.8.21-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:13678151fc401e2d68c9880b91e28714f797d40422994572b24560ef80910a88", size = 2477398, upload-time = "2026-06-01T19:30:57.644Z" }, + { url = "https://files.pythonhosted.org/packages/48/57/412421516afc3055fa577516f00beec3d663f9b0ab330639547ae6c57720/debugpy-1.8.21-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:ecbd158386c31ffe71d46f72d44d56e66331ab9b16cad649156d514368f23ab2", size = 3962096, upload-time = "2026-06-01T19:30:59.235Z" }, + { url = "https://files.pythonhosted.org/packages/c1/62/2c616337cf6ba7b07ebbc97f02c6c945a8e2f76b365e33ee809c32ee36d1/debugpy-1.8.21-cp313-cp313-win32.whl", hash = "sha256:2c2ae706dec41d99a9ca1f7ebc987a83e65578363be6f6b3ac9067504917fae1", size = 5336288, upload-time = "2026-06-01T19:31:00.79Z" }, + { url = "https://files.pythonhosted.org/packages/f8/99/9175103392f84c4b1bf7622888cdc68da07f0ff7d9e581266428f6776033/debugpy-1.8.21-cp313-cp313-win_amd64.whl", hash = "sha256:aa648733047443eb1d07682c4ef287d36a54507b643ffdf38b09a3ef002c72a0", size = 5376567, upload-time = "2026-06-01T19:31:02.56Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3d/f4bbb323a548bfab2af3d6b4ffd9bf22636e55956a1285d317a1de643aad/debugpy-1.8.21-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9bb2a685287a2ac9b181cde89edcec64845cb51de7faaa75badb9a698bc24782", size = 2477209, upload-time = "2026-06-01T19:31:04.157Z" }, + { url = "https://files.pythonhosted.org/packages/8c/2d/6e7ec524984a1702777868de49a4c53202bddac2a432a76a093469587750/debugpy-1.8.21-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:3d6922439bf33fd38a3e2c447869ebc7b97da5cd3d329ff1ef9bc06c4903437e", size = 3927115, upload-time = "2026-06-01T19:31:05.863Z" }, + { url = "https://files.pythonhosted.org/packages/97/47/d1aa6d64005a98a9144647d99306b419396f9ad7bf1d73c119e17a81fb4d/debugpy-1.8.21-cp314-cp314-win32.whl", hash = "sha256:15d4963bd5ffa48f0da0947fd06757fa7621945048a14ad7705431566d3c0e7c", size = 5336724, upload-time = "2026-06-01T19:31:07.711Z" }, + { url = "https://files.pythonhosted.org/packages/5f/67/b905b90d163af11878c1af8abafa4a25206335e112e284e413454543a6da/debugpy-1.8.21-cp314-cp314-win_amd64.whl", hash = "sha256:fe0744a12353406de0ae8ccff0d0a4a666f00801a3db8fd04e7a5f761cd520e8", size = 5373803, upload-time = "2026-06-01T19:31:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/95/51/67e7cf11a53e40694f720457d5b3a1cdaaa3d5a9a633e482f225456b93ff/debugpy-1.8.21-py2.py3-none-any.whl", hash = "sha256:b1e37d333663c8851516a47364ef473da127f9caebe4417e6df6f5825a7e9a92", size = 5352888, upload-time = "2026-06-01T19:31:25.186Z" }, +] + [[package]] name = "decorator" version = "5.2.1" @@ -1105,6 +1216,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dc/c4/da7089cd7aa4ab554f56e18a7fb08dcfed8fd2ae91fa528f5b1be207a148/deepdiff-9.0.0-py3-none-any.whl", hash = "sha256:b1ae0dd86290d86a03de5fbee728fde43095c1472ae4974bdab23ab4656305bd", size = 170540, upload-time = "2026-03-30T05:52:22.008Z" }, ] +[[package]] +name = "dill" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/e1/56027a71e31b02ddc53c7d65b01e68edf64dea2932122fe7746a516f75d5/dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa", size = 187315, upload-time = "2026-01-19T02:36:56.85Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019, upload-time = "2026-01-19T02:36:55.663Z" }, +] + [[package]] name = "distlib" version = "0.4.0" @@ -1128,6 +1248,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" }, ] +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, +] + [[package]] name = "donfig" version = "0.8.1.post1" @@ -1152,6 +1281,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, ] +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + [[package]] name = "fastapi" version = "0.135.2" @@ -1177,6 +1315,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/51/ac/e5d886f892666d2d1e5cb8c1a41146e1d79ae8896477b1153a21711d3b44/fasteners-0.20-py3-none-any.whl", hash = "sha256:9422c40d1e350e4259f509fb2e608d6bc43c0136f79a00db1b49046029d0b3b7", size = 18702, upload-time = "2025-08-11T10:19:35.716Z" }, ] +[[package]] +name = "fastjsonschema" +version = "2.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/b5/23b216d9d985a956623b6bd12d4086b60f0059b27799f23016af04a74ea1/fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de", size = 374130, upload-time = "2025-08-14T18:49:36.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463", size = 24024, upload-time = "2025-08-14T18:49:34.776Z" }, +] + [[package]] name = "filelock" version = "3.25.2" @@ -1843,6 +1990,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] +[[package]] +name = "imagesize" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" }, +] + [[package]] name = "importlib-metadata" version = "8.7.1" @@ -1864,6 +2020,117 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] +[[package]] +name = "ipykernel" +version = "7.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appnope", marker = "sys_platform == 'darwin' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "comm" }, + { name = "debugpy" }, + { name = "ipython", version = "8.39.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "ipython", version = "9.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "matplotlib-inline" }, + { name = "nest-asyncio2" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/c4/e4a38f579de4225a561305666f7541cdabb30075def2aa1ac17bd73c1fb5/ipykernel-7.3.0.tar.gz", hash = "sha256:9acaaaf97d16355166e4085afe9d225bfbdf2b7ef520f9df3be8f2b248275e09", size = 184899, upload-time = "2026-06-10T08:41:25.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/02/77b271f5dc58bfbc0b577c877b2365d1ffea2afe66a80c13f2312820348c/ipykernel-7.3.0-py3-none-any.whl", hash = "sha256:897eb64da762549ef610698fca5e9675195ec6ac8ec7f19d81ce1ca20c876057", size = 120583, upload-time = "2026-06-10T08:41:23.648Z" }, +] + +[[package]] +name = "ipython" +version = "8.39.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +] +dependencies = [ + { name = "colorama", marker = "(python_full_version < '3.11' and sys_platform == 'win32') or (python_full_version >= '3.11' and extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (python_full_version >= '3.11' and extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (python_full_version >= '3.11' and extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (sys_platform != 'win32' and extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (sys_platform != 'win32' and extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (sys_platform != 'win32' and extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "decorator", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "jedi", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "matplotlib-inline", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "pexpect", marker = "(python_full_version < '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32') or (python_full_version >= '3.11' and extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (python_full_version >= '3.11' and extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (python_full_version >= '3.11' and extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (sys_platform == 'emscripten' and extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (sys_platform == 'emscripten' and extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (sys_platform == 'emscripten' and extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (sys_platform == 'win32' and extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (sys_platform == 'win32' and extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (sys_platform == 'win32' and extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "prompt-toolkit", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "pygments", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "stack-data", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "traitlets", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "typing-extensions", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/18/f8598d287006885e7136451fdea0755af4ebcbfe342836f24deefaed1164/ipython-8.39.0.tar.gz", hash = "sha256:4110ae96012c379b8b6db898a07e186c40a2a1ef5d57a7fa83166047d9da7624", size = 5513971, upload-time = "2026-03-27T10:02:13.94Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/56/4cc7fc9e9e3f38fd324f24f8afe0ad8bb5fa41283f37f1aaf9de0612c968/ipython-8.39.0-py3-none-any.whl", hash = "sha256:bb3c51c4fa8148ab1dea07a79584d1c854e234ea44aa1283bcb37bc75054651f", size = 831849, upload-time = "2026-03-27T10:02:07.846Z" }, +] + +[[package]] +name = "ipython" +version = "9.14.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version >= '3.14' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version >= '3.14' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version >= '3.14' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version >= '3.14' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +] +dependencies = [ + { name = "colorama", marker = "(python_full_version >= '3.11' and sys_platform == 'win32') or (python_full_version < '3.11' and extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (python_full_version < '3.11' and extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (python_full_version < '3.11' and extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (sys_platform != 'win32' and extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (sys_platform != 'win32' and extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (sys_platform != 'win32' and extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "decorator", marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "jedi", marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "matplotlib-inline", marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "pexpect", marker = "(python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32') or (python_full_version < '3.11' and extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (python_full_version < '3.11' and extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (python_full_version < '3.11' and extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (sys_platform == 'emscripten' and extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (sys_platform == 'emscripten' and extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (sys_platform == 'emscripten' and extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (sys_platform == 'win32' and extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (sys_platform == 'win32' and extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (sys_platform == 'win32' and extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "prompt-toolkit", marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "psutil", marker = "(python_full_version >= '3.11' and sys_platform != 'emscripten') or (python_full_version < '3.11' and extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (python_full_version < '3.11' and extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (python_full_version < '3.11' and extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (sys_platform == 'emscripten' and extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (sys_platform == 'emscripten' and extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (sys_platform == 'emscripten' and extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "pygments", marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "stack-data", marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "traitlets", marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "typing-extensions", marker = "python_full_version == '3.11.*' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/23/3a27530575643c8bb7bfc757a28e2e7ef80092afbf59a2bc5716320b6602/ipython-9.14.1.tar.gz", hash = "sha256:f913bf74df06d458e46ced84ca506c23797590d594b236fe60b14df213291e7b", size = 4433457, upload-time = "2026-06-05T08:12:34.921Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/22/58818a63eaf8982b67632b1bc20585c811611b15a8da19d6012323dc76a5/ipython-9.14.1-py3-none-any.whl", hash = "sha256:5d4a9ecaa3b10e6e5f269dd0948bdb58ca9cb851899cd23e07c320d3eb11613c", size = 627770, upload-time = "2026-06-05T08:12:33.045Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments", marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + [[package]] name = "isodate" version = "0.7.2" @@ -1873,6 +2140,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, ] +[[package]] +name = "isort" +version = "8.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/7c/ec4ab396d31b3b395e2e999c8f46dec78c5e29209fac49d1f4dace04041d/isort-8.0.1.tar.gz", hash = "sha256:171ac4ff559cdc060bcfff550bc8404a486fee0caab245679c2abe7cb253c78d", size = 769592, upload-time = "2026-02-28T10:08:20.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/95/c7c34aa53c16353c56d0b802fba48d5f5caa2cdee7958acbcb795c830416/isort-8.0.1-py3-none-any.whl", hash = "sha256:28b89bc70f751b559aeca209e6120393d43fbe2490de0559662be7a9787e3d75", size = 89733, upload-time = "2026-02-28T10:08:19.466Z" }, +] + [[package]] name = "itsdangerous" version = "2.2.0" @@ -1882,6 +2158,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, ] +[[package]] +name = "jedi" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/b7/a3635f6a2d7cf5b5dd98064fc1d5fbbafcb25477bcea204a3a92145d158b/jedi-0.20.0.tar.gz", hash = "sha256:c3f4ccbd276696f4b19c54618d4fb18f9fc24b0aef02acf704b23f487daa1011", size = 3119416, upload-time = "2026-05-01T23:38:47.814Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/93/242e2eab5fe682ffcb8b0084bde703a41d51e17ee0f3a31ff0d9d813620a/jedi-0.20.0-py2.py3-none-any.whl", hash = "sha256:7bdd9c2634f56713299976f4cbd59cb3fa92165cc5e05ea811fb253480728b67", size = 4884812, upload-time = "2026-05-01T23:38:43.919Z" }, +] + [[package]] name = "jinja2" version = "3.1.6" @@ -1912,6 +2200,114 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, ] +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "rpds-py", version = "2026.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "jupyter-book" +version = "1.0.4.post1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "jinja2" }, + { name = "jsonschema" }, + { name = "linkify-it-py" }, + { name = "myst-nb" }, + { name = "myst-parser" }, + { name = "pyyaml" }, + { name = "sphinx" }, + { name = "sphinx-book-theme", version = "1.1.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "sphinx-book-theme", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "sphinx-comments" }, + { name = "sphinx-copybutton" }, + { name = "sphinx-design", version = "0.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "sphinx-design", version = "0.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "sphinx-external-toc" }, + { name = "sphinx-jupyterbook-latex" }, + { name = "sphinx-multitoc-numbering" }, + { name = "sphinx-thebe" }, + { name = "sphinx-togglebutton" }, + { name = "sphinxcontrib-bibtex" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/ee/5d10ce5b161764ad44219853f386e98b535cb3879bcb0d7376961a1e3897/jupyter_book-1.0.4.post1.tar.gz", hash = "sha256:2fe92c49ff74840edc0a86bb034eafdd0f645fca6e48266be367ce4d808b9601", size = 67412, upload-time = "2025-02-28T14:55:48.637Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/86/d45756beaeb4b9b06125599b429451f8640b5db6f019d606f33c85743fd4/jupyter_book-1.0.4.post1-py3-none-any.whl", hash = "sha256:3a27a6b2581f1894ffe8f347d1a3432f06fc616997547919c42cd41c54db625d", size = 45005, upload-time = "2025-02-28T14:55:46.561Z" }, +] + +[[package]] +name = "jupyter-cache" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "click" }, + { name = "importlib-metadata" }, + { name = "nbclient" }, + { name = "nbformat" }, + { name = "pyyaml" }, + { name = "sqlalchemy" }, + { name = "tabulate" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/f7/3627358075f183956e8c4974603232b03afd4ddc7baf72c2bc9fff522291/jupyter_cache-1.0.1.tar.gz", hash = "sha256:16e808eb19e3fb67a223db906e131ea6e01f03aa27f49a7214ce6a5fec186fb9", size = 32048, upload-time = "2024-11-15T16:03:55.322Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/6b/67b87da9d36bff9df7d0efbd1a325fa372a43be7158effaf43ed7b22341d/jupyter_cache-1.0.1-py3-none-any.whl", hash = "sha256:9c3cafd825ba7da8b5830485343091143dff903e4d8c69db9349b728b140abf6", size = 33907, upload-time = "2024-11-15T16:03:54.021Z" }, +] + +[[package]] +name = "jupyter-client" +version = "8.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "python-dateutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/dc/5512503b088997c2250b8bf18258fba9d9ce5ead641183700960d3c9d342/jupyter_client-8.9.1.tar.gz", hash = "sha256:a58f730dd9e728ba16ba1d62ebccf7ffe1ebbdbce4e95cfae941b7321ae1f4fa", size = 359256, upload-time = "2026-06-09T13:15:01.033Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/6f/56d39bf385c5c27988aebaf0c18a2a17e960575740100973511018bd904e/jupyter_client-8.9.1-py3-none-any.whl", hash = "sha256:0b7a295bc46e8751e9adae84781f726c851c1d911bd793edc4a3bde942e3da81", size = 109828, upload-time = "2026-06-09T13:14:58.835Z" }, +] + +[[package]] +name = "jupyter-core" +version = "5.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" }, +] + [[package]] name = "kiwisolver" version = "1.5.0" @@ -2036,6 +2432,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0a/dd/8050c947d435c8d4bc94e3252f4d8bb8a76cfb424f043a8680be637a57f1/kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1", size = 73558, upload-time = "2026-03-09T13:15:52.112Z" }, ] +[[package]] +name = "latexcodec" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/dd/4270b2c5e2ee49316c3859e62293bd2ea8e382339d63ab7bbe9f39c0ec3b/latexcodec-3.0.1.tar.gz", hash = "sha256:e78a6911cd72f9dec35031c6ec23584de6842bfbc4610a9678868d14cdfb0357", size = 31222, upload-time = "2025-06-17T18:47:34.051Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/40/23569737873cc9637fd488606347e9dd92b9fa37ba4fcda1f98ee5219a97/latexcodec-3.0.1-py3-none-any.whl", hash = "sha256:a9eb8200bff693f0437a69581f7579eb6bca25c4193515c09900ce76451e452e", size = 18532, upload-time = "2025-06-17T18:47:30.726Z" }, +] + [[package]] name = "lightning-utilities" version = "0.15.3" @@ -2049,6 +2454,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/25/f4/ead6e0e37209b07c9baa3e984ccdb0348ca370b77cea3aaea8ddbb097e00/lightning_utilities-0.15.3-py3-none-any.whl", hash = "sha256:6c55f1bee70084a1cbeaa41ada96e4b3a0fea5909e844dd335bd80f5a73c5f91", size = 31906, upload-time = "2026-02-22T14:48:52.488Z" }, ] +[[package]] +name = "linkify-it-py" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "uc-micro-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/c9/06ea13676ef354f0af6169587ae292d3e2406e212876a413bf9eece4eb23/linkify_it_py-2.1.0.tar.gz", hash = "sha256:43360231720999c10e9328dc3691160e27a718e280673d444c38d7d3aaa3b98b", size = 29158, upload-time = "2026-03-01T07:48:47.683Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/de/88b3be5c31b22333b3ca2f6ff1de4e863d8fe45aaea7485f591970ec1d3e/linkify_it_py-2.1.0-py3-none-any.whl", hash = "sha256:0d252c1594ecba2ecedc444053db5d3a9b7ec1b0dd929c8f1d74dce89f86c05e", size = 19878, upload-time = "2026-03-01T07:48:46.098Z" }, +] + [[package]] name = "locket" version = "1.0.0" @@ -2085,14 +2502,14 @@ wheels = [ [[package]] name = "markdown-it-py" -version = "4.0.0" +version = "3.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, ] [[package]] @@ -2255,6 +2672,39 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867, upload-time = "2025-12-10T22:56:48.954Z" }, ] +[[package]] +name = "matplotlib-inline" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/c0/9f7c9a46090390368a4d7bcb76bb87a4a36c421e4c0792cdb53486ffac7a/matplotlib_inline-0.2.2.tar.gz", hash = "sha256:72f3fe8fce36b70d4a5b612f899090cd0401deddc4ea90e1572b9f4bfb058c79", size = 8150, upload-time = "2026-05-08T17:33:33.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/09/5b161152e2d90f7b87f781c2e1267494aef9c32498df793f73ad0a0a494a/matplotlib_inline-0.2.2-py3-none-any.whl", hash = "sha256:3c821cf1c209f59fb2d2d64abbf5b23b67bcb2210d663f9918dd851c6da1fcf6", size = 9534, upload-time = "2026-05-08T17:33:32.055Z" }, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/59/fc/f8d0863f8862f25602c0404d75568e89fb6b4109804645e5cdfb1be5cf56/mdit_py_plugins-0.6.1.tar.gz", hash = "sha256:a2bca0f039f39dbd35fb74ae1b5f998608c437463371f0ff7f49a19a17a114d0", size = 56114, upload-time = "2026-05-13T09:03:38.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.1-py3-none-any.whl", hash = "sha256:214c82fb2ac524472ab6a5bcab1de80f73b50443e187f401bfd77efbc7c6481d", size = 66663, upload-time = "2026-05-13T09:03:37.76Z" }, +] + [[package]] name = "mdurl" version = "0.1.2" @@ -2525,20 +2975,98 @@ wheels = [ ] [[package]] -name = "narwhals" -version = "2.18.1" +name = "myst-nb" +version = "1.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/59/96/45218c2fdec4c9f22178f905086e85ef1a6d63862dcc3cd68eb60f1867f5/narwhals-2.18.1.tar.gz", hash = "sha256:652a1fcc9d432bbf114846688884c215f17eb118aa640b7419295d2f910d2a8b", size = 620578, upload-time = "2026-03-24T15:11:25.456Z" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "ipykernel" }, + { name = "ipython", version = "8.39.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "ipython", version = "9.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "jupyter-cache" }, + { name = "myst-parser" }, + { name = "nbclient" }, + { name = "nbformat" }, + { name = "pyyaml" }, + { name = "sphinx" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/b4/ff1abeea67e8cfe0a8c033389f6d1d8b0bfecfd611befb5cbdeab884fce6/myst_nb-1.4.0.tar.gz", hash = "sha256:c145598de62446a6fd009773dd071a40d3b76106ace780de1abdfc6961f614c2", size = 82285, upload-time = "2026-03-02T21:14:56.95Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl", hash = "sha256:a0a8bb80205323851338888ba3a12b4f65d352362c8a94be591244faf36504ad", size = 444952, upload-time = "2026-03-24T15:11:23.801Z" }, + { url = "https://files.pythonhosted.org/packages/94/93/0a378b48488879a1d925b42a804edfc6e0cd0ef854220f2dce738a46e7e9/myst_nb-1.4.0-py3-none-any.whl", hash = "sha256:0e2c86e7d3b82c3aa51383f82d6268f7714f3b772c23a796ab09538a8e68b4e4", size = 82555, upload-time = "2026-03-02T21:14:55.652Z" }, ] [[package]] -name = "networkx" -version = "3.4.2" +name = "myst-parser" +version = "3.0.1" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", +dependencies = [ + { name = "docutils" }, + { name = "jinja2" }, + { name = "markdown-it-py" }, + { name = "mdit-py-plugins" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/64/e2f13dac02f599980798c01156393b781aec983b52a6e4057ee58f07c43a/myst_parser-3.0.1.tar.gz", hash = "sha256:88f0cb406cb363b077d176b51c476f62d60604d68a8dcdf4832e080441301a87", size = 92392, upload-time = "2024-04-28T20:22:42.116Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/de/21aa8394f16add8f7427f0a1326ccd2b3a2a8a3245c9252bc5ac034c6155/myst_parser-3.0.1-py3-none-any.whl", hash = "sha256:6457aaa33a5d474aca678b8ead9b3dc298e89c68e67012e73146ea6fd54babf1", size = 83163, upload-time = "2024-04-28T20:22:39.985Z" }, +] + +[[package]] +name = "narwhals" +version = "2.18.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/96/45218c2fdec4c9f22178f905086e85ef1a6d63862dcc3cd68eb60f1867f5/narwhals-2.18.1.tar.gz", hash = "sha256:652a1fcc9d432bbf114846688884c215f17eb118aa640b7419295d2f910d2a8b", size = 620578, upload-time = "2026-03-24T15:11:25.456Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl", hash = "sha256:a0a8bb80205323851338888ba3a12b4f65d352362c8a94be591244faf36504ad", size = 444952, upload-time = "2026-03-24T15:11:23.801Z" }, +] + +[[package]] +name = "nbclient" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "nbformat" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/a5/b3bae4b590c0cbcada2c63a34f7580024e834a8ba213e949a2f906705787/nbclient-0.11.0.tar.gz", hash = "sha256:04a134a5b087f2c5887f228aca155db50169b8cd9334dee6942c8e927e56081a", size = 62535, upload-time = "2026-06-05T07:52:41.746Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/c9/94d73e5a01c5b926c3fa2496e97d7a8dc28ed5a77c0b2ed712f1a62e6694/nbclient-0.11.0-py3-none-any.whl", hash = "sha256:ef7fa0d59d6e1d41103933d8a445a18d5de860ca6b613b87b8574accdb3c2895", size = 25288, upload-time = "2026-06-05T07:52:40.115Z" }, +] + +[[package]] +name = "nbformat" +version = "5.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastjsonschema" }, + { name = "jsonschema" }, + { name = "jupyter-core" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749, upload-time = "2024-04-04T11:20:37.371Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454, upload-time = "2024-04-04T11:20:34.895Z" }, +] + +[[package]] +name = "nest-asyncio2" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/73/731debf26e27e0a0323d7bda270dc2f634b398e38f040a09da1f4351d0aa/nest_asyncio2-1.7.2.tar.gz", hash = "sha256:1921d70b92cc4612c374928d081552efb59b83d91b2b789d935c665fa01729a8", size = 14743, upload-time = "2026-02-13T00:34:04.386Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/3c/3179b85b0e1c3659f0369940200cd6d0fa900e6cefcc7ea0bc6dd0e29ffb/nest_asyncio2-1.7.2-py3-none-any.whl", hash = "sha256:f5dfa702f3f81f6a03857e9a19e2ba578c0946a4ad417b4c50a24d7ba641fe01", size = 7843, upload-time = "2026-02-13T00:34:02.691Z" }, +] + +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", @@ -2619,6 +3147,20 @@ cpu = [ { name = "torch", version = "2.12.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, { name = "torch", version = "2.12.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, ] +docs = [ + { name = "jupyter-book" }, + { name = "linkify-it-py" }, + { name = "myst-nb" }, + { name = "myst-parser" }, + { name = "pylint" }, + { name = "sphinx" }, + { name = "sphinx-autoapi" }, + { name = "sphinx-book-theme", version = "1.1.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "sphinx-book-theme", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "sphinx-copybutton" }, + { name = "sphinxcontrib-mermaid" }, + { name = "sphinxext-opengraph" }, +] gpu = [ { name = "torch", version = "2.12.0+cu130", source = { registry = "https://download.pytorch.org/whl/cu130" } }, ] @@ -2638,19 +3180,30 @@ requires-dist = [ { name = "boto3", specifier = ">=1.35.32" }, { name = "cartopy", specifier = ">=0.22.0" }, { name = "dataclass-wizard", specifier = "<0.31.0" }, + { name = "jupyter-book", marker = "extra == 'docs'", specifier = ">=1.0.0,<2.0.0" }, + { name = "linkify-it-py", marker = "extra == 'docs'", specifier = ">=2.0.0" }, { name = "matplotlib", specifier = ">=3.7.0" }, { name = "mlflow", specifier = ">=2.16.2" }, { name = "mllam-data-prep", specifier = ">=0.5.0" }, + { name = "myst-nb", marker = "extra == 'docs'", specifier = ">=1.0.0" }, + { name = "myst-parser", marker = "extra == 'docs'", specifier = ">=2.0.0" }, { name = "networkx", specifier = ">=3.0" }, { name = "numpy", specifier = ">=1.24.2" }, { name = "nvidia-ml-py", specifier = ">=13.580.82" }, { name = "parse", specifier = ">=1.20.2" }, { name = "pillow", specifier = ">=9.0.0" }, { name = "plotly", specifier = ">=5.15.0" }, + { name = "pylint", marker = "extra == 'docs'", specifier = ">=3.0.0" }, { name = "pyproj", specifier = ">=3.4.1" }, { name = "pytorch-lightning", specifier = ">=2.0.3" }, { name = "scipy", specifier = ">=1.10.0" }, { name = "shapely", specifier = ">=2.0.1" }, + { name = "sphinx", marker = "extra == 'docs'", specifier = ">=7.2.6" }, + { name = "sphinx-autoapi", marker = "extra == 'docs'", specifier = ">=3.0.0" }, + { name = "sphinx-book-theme", marker = "extra == 'docs'", specifier = ">=1.1.0" }, + { name = "sphinx-copybutton", marker = "extra == 'docs'", specifier = ">=0.5.2" }, + { name = "sphinxcontrib-mermaid", marker = "extra == 'docs'", specifier = ">=0.9.2" }, + { name = "sphinxext-opengraph", marker = "extra == 'docs'", specifier = ">=0.9.0" }, { name = "torch", specifier = ">=2.3.0" }, { name = "torch", marker = "extra == 'cpu'", specifier = ">=2.12,<2.13", index = "https://download.pytorch.org/whl/cpu", conflict = { package = "neural-lam", extra = "cpu" } }, { name = "torch", marker = "extra == 'gpu'", specifier = ">=2.12,<2.13", index = "https://download.pytorch.org/whl/cu130", conflict = { package = "neural-lam", extra = "gpu" } }, @@ -2659,7 +3212,7 @@ requires-dist = [ { name = "tueplots", specifier = ">=0.0.8" }, { name = "wandb", specifier = ">=0.13.10" }, ] -provides-extras = ["cpu", "gpu", "gpu-cu128"] +provides-extras = ["cpu", "docs", "gpu", "gpu-cu128"] [package.metadata.requires-dev] dev = [ @@ -3419,6 +3972,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c3/13/114daf766c33aec6c5a3954e7ea653f8a7ade9602c5c5a2228281698c490/parse-1.21.1-py2.py3-none-any.whl", hash = "sha256:55339ca698019815df3b8e8b550e5933933527e623b0cdf1ca2f404da35ffb47", size = 19693, upload-time = "2026-02-19T02:20:06.575Z" }, ] +[[package]] +name = "parso" +version = "0.8.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/4b/90c937815137d43ce71ba043cd3566221e9df6b9c805f24b5d138c9d40a7/parso-0.8.7.tar.gz", hash = "sha256:eaaac4c9fdd5e9e8852dc778d2d7405897ec510f2a298071453e5e3a07914bb1", size = 401824, upload-time = "2026-05-01T23:13:02.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025, upload-time = "2026-05-01T23:12:58.867Z" }, +] + [[package]] name = "partd" version = "1.4.2" @@ -3432,6 +3994,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl", hash = "sha256:978e4ac767ec4ba5b86c6eaa52e5a2a3bc748a2ca839e8cc798f1cc6ce6efb0f", size = 18905, upload-time = "2024-05-06T19:51:39.271Z" }, ] +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + [[package]] name = "pillow" version = "12.1.1" @@ -3603,6 +4177,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl", hash = "sha256:aad69b294ddbe3e1f95ef8886a060ed1666a0b83018bbf56295f6f226c43d287", size = 34433, upload-time = "2025-11-14T17:33:19.093Z" }, ] +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + [[package]] name = "propcache" version = "0.4.1" @@ -3772,6 +4358,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, ] +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + [[package]] name = "pyarrow" version = "23.0.1" @@ -3850,6 +4454,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, ] +[[package]] +name = "pybtex" +version = "0.26.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "latexcodec" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/f5/f30da9c93f0fa6d619332b2f69597219b625f35780473a05164a9981fd9a/pybtex-0.26.1.tar.gz", hash = "sha256:2e5543bea424e60e9e42eef70bff597be48649d8f68ba061a7a092b2477d5464", size = 692991, upload-time = "2026-04-03T13:05:39.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/f6/775eb92e865b28cdb4ad1f2bed7a5446197516f76b58a950faa3be3fd08d/pybtex-0.26.1-py3-none-any.whl", hash = "sha256:e26c0412cc54f5f21b2a6d9d175762a2d2af9ccf3a8f651cdb89ec035db77aa1", size = 126134, upload-time = "2026-04-03T13:05:40.623Z" }, +] + +[[package]] +name = "pybtex-docutils" +version = "1.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "pybtex" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7e/84/796ea94d26188a853660f81bded39f8de4cfe595130aef0dea1088705a11/pybtex-docutils-1.0.3.tar.gz", hash = "sha256:3a7ebdf92b593e00e8c1c538aa9a20bca5d92d84231124715acc964d51d93c6b", size = 18348, upload-time = "2023-08-22T18:47:54.833Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/b1/ce1f4596211efb5410e178a803f08e59b20bedb66837dcf41e21c54f9ec1/pybtex_docutils-1.0.3-py3-none-any.whl", hash = "sha256:8fd290d2ae48e32fcb54d86b0efb8d573198653c7e2447d5bec5847095f430b9", size = 6385, upload-time = "2023-08-22T06:43:20.513Z" }, +] + [[package]] name = "pycparser" version = "3.0" @@ -3992,6 +4622,72 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, ] +[[package]] +name = "pydata-sphinx-theme" +version = "0.15.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +] +dependencies = [ + { name = "accessible-pygments", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "babel", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "beautifulsoup4", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "docutils", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "packaging", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "pygments", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "sphinx", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "typing-extensions", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/ea/3ab478cccacc2e8ef69892c42c44ae547bae089f356c4b47caf61730958d/pydata_sphinx_theme-0.15.4.tar.gz", hash = "sha256:7762ec0ac59df3acecf49fd2f889e1b4565dbce8b88b2e29ee06fdd90645a06d", size = 2400673, upload-time = "2024-06-25T19:28:45.041Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/d3/c622950d87a2ffd1654208733b5bd1c5645930014abed8f4c0d74863988b/pydata_sphinx_theme-0.15.4-py3-none-any.whl", hash = "sha256:2136ad0e9500d0949f96167e63f3e298620040aea8f9c74621959eda5d4cf8e6", size = 4640157, upload-time = "2024-06-25T19:28:42.383Z" }, +] + +[[package]] +name = "pydata-sphinx-theme" +version = "0.16.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version >= '3.14' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version >= '3.14' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version >= '3.14' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version >= '3.14' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +] +dependencies = [ + { name = "accessible-pygments", marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "babel", marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "beautifulsoup4", marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "docutils", marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "pygments", marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "sphinx", marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "typing-extensions", marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/20/bb50f9de3a6de69e6abd6b087b52fa2418a0418b19597601605f855ad044/pydata_sphinx_theme-0.16.1.tar.gz", hash = "sha256:a08b7f0b7f70387219dc659bff0893a7554d5eb39b59d3b8ef37b8401b7642d7", size = 2412693, upload-time = "2024-12-17T10:53:39.537Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/0d/8ba33fa83a7dcde13eb3c1c2a0c1cc29950a048bfed6d9b0d8b6bd710b4c/pydata_sphinx_theme-0.16.1-py3-none-any.whl", hash = "sha256:225331e8ac4b32682c18fcac5a57a6f717c4e632cea5dd0e247b55155faeccde", size = 6723264, upload-time = "2024-12-17T10:53:35.645Z" }, +] + [[package]] name = "pyerfa" version = "2.0.1.5" @@ -4019,6 +4715,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] +[[package]] +name = "pylint" +version = "4.0.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "astroid" }, + { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "dill" }, + { name = "isort" }, + { name = "mccabe" }, + { name = "platformdirs" }, + { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "tomlkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/1d/3bb57f303701549550d74bf7ced2b07412be97125c167a0c9d216aa9f762/pylint-4.0.6.tar.gz", hash = "sha256:52f19191bee08bf103f9705ad1a0ece4aa5a0a4ef2bdcbd969375a1e6f6579d5", size = 1585588, upload-time = "2026-06-14T14:43:26.772Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/da/acb2e7d4dbd2dfb792d38c0d850481f29ad7049b356d23f56c687d35203b/pylint-4.0.6-py3-none-any.whl", hash = "sha256:d11a0e1fdb7b1cd46ec5d6fc78fee8b95f28695b2d6140e5809925f61e32ea54", size = 538389, upload-time = "2026-06-14T14:43:24.873Z" }, +] + [[package]] name = "pyparsing" version = "3.3.2" @@ -4344,6 +5059,94 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, ] +[[package]] +name = "pyzmq" +version = "27.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/b9/52aa9ec2867528b54f1e60846728d8b4d84726630874fee3a91e66c7df81/pyzmq-27.1.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:508e23ec9bc44c0005c4946ea013d9317ae00ac67778bd47519fdf5a0e930ff4", size = 1329850, upload-time = "2025-09-08T23:07:26.274Z" }, + { url = "https://files.pythonhosted.org/packages/99/64/5653e7b7425b169f994835a2b2abf9486264401fdef18df91ddae47ce2cc/pyzmq-27.1.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:507b6f430bdcf0ee48c0d30e734ea89ce5567fd7b8a0f0044a369c176aa44556", size = 906380, upload-time = "2025-09-08T23:07:29.78Z" }, + { url = "https://files.pythonhosted.org/packages/73/78/7d713284dbe022f6440e391bd1f3c48d9185673878034cfb3939cdf333b2/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf7b38f9fd7b81cb6d9391b2946382c8237fd814075c6aa9c3b746d53076023b", size = 666421, upload-time = "2025-09-08T23:07:31.263Z" }, + { url = "https://files.pythonhosted.org/packages/30/76/8f099f9d6482450428b17c4d6b241281af7ce6a9de8149ca8c1c649f6792/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03ff0b279b40d687691a6217c12242ee71f0fba28bf8626ff50e3ef0f4410e1e", size = 854149, upload-time = "2025-09-08T23:07:33.17Z" }, + { url = "https://files.pythonhosted.org/packages/59/f0/37fbfff06c68016019043897e4c969ceab18bde46cd2aca89821fcf4fb2e/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:677e744fee605753eac48198b15a2124016c009a11056f93807000ab11ce6526", size = 1655070, upload-time = "2025-09-08T23:07:35.205Z" }, + { url = "https://files.pythonhosted.org/packages/47/14/7254be73f7a8edc3587609554fcaa7bfd30649bf89cd260e4487ca70fdaa/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd2fec2b13137416a1c5648b7009499bcc8fea78154cd888855fa32514f3dad1", size = 2033441, upload-time = "2025-09-08T23:07:37.432Z" }, + { url = "https://files.pythonhosted.org/packages/22/dc/49f2be26c6f86f347e796a4d99b19167fc94503f0af3fd010ad262158822/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:08e90bb4b57603b84eab1d0ca05b3bbb10f60c1839dc471fc1c9e1507bef3386", size = 1891529, upload-time = "2025-09-08T23:07:39.047Z" }, + { url = "https://files.pythonhosted.org/packages/a3/3e/154fb963ae25be70c0064ce97776c937ecc7d8b0259f22858154a9999769/pyzmq-27.1.0-cp310-cp310-win32.whl", hash = "sha256:a5b42d7a0658b515319148875fcb782bbf118dd41c671b62dae33666c2213bda", size = 567276, upload-time = "2025-09-08T23:07:40.695Z" }, + { url = "https://files.pythonhosted.org/packages/62/b2/f4ab56c8c595abcb26b2be5fd9fa9e6899c1e5ad54964e93ae8bb35482be/pyzmq-27.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0bb87227430ee3aefcc0ade2088100e528d5d3298a0a715a64f3d04c60ba02f", size = 632208, upload-time = "2025-09-08T23:07:42.298Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e3/be2cc7ab8332bdac0522fdb64c17b1b6241a795bee02e0196636ec5beb79/pyzmq-27.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:9a916f76c2ab8d045b19f2286851a38e9ac94ea91faf65bd64735924522a8b32", size = 559766, upload-time = "2025-09-08T23:07:43.869Z" }, + { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" }, + { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038, upload-time = "2025-09-08T23:07:51.234Z" }, + { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531, upload-time = "2025-09-08T23:07:52.795Z" }, + { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786, upload-time = "2025-09-08T23:07:55.047Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220, upload-time = "2025-09-08T23:07:57.172Z" }, + { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155, upload-time = "2025-09-08T23:07:59.05Z" }, + { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428, upload-time = "2025-09-08T23:08:00.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497, upload-time = "2025-09-08T23:08:02.15Z" }, + { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, + { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, + { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" }, + { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" }, + { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" }, + { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" }, + { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" }, + { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" }, + { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" }, + { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" }, + { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" }, + { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" }, + { url = "https://files.pythonhosted.org/packages/f3/81/a65e71c1552f74dec9dff91d95bafb6e0d33338a8dfefbc88aa562a20c92/pyzmq-27.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c17e03cbc9312bee223864f1a2b13a99522e0dc9f7c5df0177cd45210ac286e6", size = 836266, upload-time = "2025-09-08T23:09:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/58/ed/0202ca350f4f2b69faa95c6d931e3c05c3a397c184cacb84cb4f8f42f287/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f328d01128373cb6763823b2b4e7f73bdf767834268c565151eacb3b7a392f90", size = 800206, upload-time = "2025-09-08T23:09:41.902Z" }, + { url = "https://files.pythonhosted.org/packages/47/42/1ff831fa87fe8f0a840ddb399054ca0009605d820e2b44ea43114f5459f4/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c1790386614232e1b3a40a958454bdd42c6d1811837b15ddbb052a032a43f62", size = 567747, upload-time = "2025-09-08T23:09:43.741Z" }, + { url = "https://files.pythonhosted.org/packages/d1/db/5c4d6807434751e3f21231bee98109aa57b9b9b55e058e450d0aef59b70f/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:448f9cb54eb0cee4732b46584f2710c8bc178b0e5371d9e4fc8125201e413a74", size = 747371, upload-time = "2025-09-08T23:09:45.575Z" }, + { url = "https://files.pythonhosted.org/packages/26/af/78ce193dbf03567eb8c0dc30e3df2b9e56f12a670bf7eb20f9fb532c7e8a/pyzmq-27.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:05b12f2d32112bf8c95ef2e74ec4f1d4beb01f8b5e703b38537f8849f92cb9ba", size = 544862, upload-time = "2025-09-08T23:09:47.448Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" }, + { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371, upload-time = "2025-09-08T23:09:54.563Z" }, + { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "rpds-py", version = "2026.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + [[package]] name = "requests" version = "2.33.0" @@ -4385,6 +5188,294 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" }, ] +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +] +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/0c/0c411a0ec64ccb6d104dcabe0e713e05e153a9a2c3c2bd2b32ce412166fe/rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288", size = 370490, upload-time = "2025-11-30T20:21:33.256Z" }, + { url = "https://files.pythonhosted.org/packages/19/6a/4ba3d0fb7297ebae71171822554abe48d7cab29c28b8f9f2c04b79988c05/rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00", size = 359751, upload-time = "2025-11-30T20:21:34.591Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7c/e4933565ef7f7a0818985d87c15d9d273f1a649afa6a52ea35ad011195ea/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6", size = 389696, upload-time = "2025-11-30T20:21:36.122Z" }, + { url = "https://files.pythonhosted.org/packages/5e/01/6271a2511ad0815f00f7ed4390cf2567bec1d4b1da39e2c27a41e6e3b4de/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7", size = 403136, upload-time = "2025-11-30T20:21:37.728Z" }, + { url = "https://files.pythonhosted.org/packages/55/64/c857eb7cd7541e9b4eee9d49c196e833128a55b89a9850a9c9ac33ccf897/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324", size = 524699, upload-time = "2025-11-30T20:21:38.92Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ed/94816543404078af9ab26159c44f9e98e20fe47e2126d5d32c9d9948d10a/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df", size = 412022, upload-time = "2025-11-30T20:21:40.407Z" }, + { url = "https://files.pythonhosted.org/packages/61/b5/707f6cf0066a6412aacc11d17920ea2e19e5b2f04081c64526eb35b5c6e7/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3", size = 390522, upload-time = "2025-11-30T20:21:42.17Z" }, + { url = "https://files.pythonhosted.org/packages/13/4e/57a85fda37a229ff4226f8cbcf09f2a455d1ed20e802ce5b2b4a7f5ed053/rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221", size = 404579, upload-time = "2025-11-30T20:21:43.769Z" }, + { url = "https://files.pythonhosted.org/packages/f9/da/c9339293513ec680a721e0e16bf2bac3db6e5d7e922488de471308349bba/rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7", size = 421305, upload-time = "2025-11-30T20:21:44.994Z" }, + { url = "https://files.pythonhosted.org/packages/f9/be/522cb84751114f4ad9d822ff5a1aa3c98006341895d5f084779b99596e5c/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff", size = 572503, upload-time = "2025-11-30T20:21:46.91Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9b/de879f7e7ceddc973ea6e4629e9b380213a6938a249e94b0cdbcc325bb66/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7", size = 598322, upload-time = "2025-11-30T20:21:48.709Z" }, + { url = "https://files.pythonhosted.org/packages/48/ac/f01fc22efec3f37d8a914fc1b2fb9bcafd56a299edbe96406f3053edea5a/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139", size = 560792, upload-time = "2025-11-30T20:21:50.024Z" }, + { url = "https://files.pythonhosted.org/packages/e2/da/4e2b19d0f131f35b6146425f846563d0ce036763e38913d917187307a671/rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464", size = 221901, upload-time = "2025-11-30T20:21:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/96/cb/156d7a5cf4f78a7cc571465d8aec7a3c447c94f6749c5123f08438bcf7bc/rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169", size = 235823, upload-time = "2025-11-30T20:21:52.505Z" }, + { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, + { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, + { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, + { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, + { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, + { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, + { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, + { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, + { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, + { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, + { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, + { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, + { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, + { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, + { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, + { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, + { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, + { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, + { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, +] + +[[package]] +name = "rpds-py" +version = "2026.5.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version >= '3.14' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version >= '3.14' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version >= '3.14' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version >= '3.14' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/43/25a8dcd3feedd735039a8f0b5b7e3b118232b5eae288c4fd9ab200d41094/rpds_py-2026.5.1.tar.gz", hash = "sha256:07b24fea40541e28570e5b795a4a38fbdcd12550c06bd0748005ecc8116ca256", size = 64459, upload-time = "2026-05-28T12:02:13.232Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/a0/acf8b6fc20bfdcd3a45bd3f57680fb198e157b7e997b9123b10763798bd2/rpds_py-2026.5.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3397a5ed7174dc2786bb214030232fc36fe8e5584fec43a9952cc542b1a12036", size = 355609, upload-time = "2026-05-28T11:58:50.78Z" }, + { url = "https://files.pythonhosted.org/packages/b6/95/f8203fd997484b1690a6869cd0e503b6c3c6be55b0ecc36d1a491fe742f0/rpds_py-2026.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:99ab6ba7bfa2cb0f96a04e3652355bf04e3f51aceb1e943b8541dab7ba4828cc", size = 348460, upload-time = "2026-05-28T11:58:52.374Z" }, + { url = "https://files.pythonhosted.org/packages/33/8c/b47326ad2f0be545a5e5c1a55937a12afaea7d392ba2837bb9680f57e6c9/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0efbe45632665e53e3db8fe1e5692db58fc5cb9bab4459d570b83efefe11164", size = 381031, upload-time = "2026-05-28T11:58:53.775Z" }, + { url = "https://files.pythonhosted.org/packages/22/0b/e83bbd97ffac6f6389b605cd4e1c8ac5761dc7e977769c9255d8c5adb7bd/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:01d17b29c0c23d82b1f4751147ec49cf451f1fc2554eb9ef5f957e55d2656ead", size = 387121, upload-time = "2026-05-28T11:58:55.243Z" }, + { url = "https://files.pythonhosted.org/packages/fd/0e/d285d1bc8864245919c61e1ca82263e4a66d337759c3a4cef72766ff9afc/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7559f72b94ae52659086c595dfa017cde03155f7832071d30959049052cb3ece", size = 501026, upload-time = "2026-05-28T11:58:56.788Z" }, + { url = "https://files.pythonhosted.org/packages/86/06/ccb2109a1e543437b5e43816f2b43b9554cc6783145528a4e3711e05c011/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e25b7088f9ccbfc0dfcaa52bf969300ca229e10ecf758974ebcbb080a4b37bb", size = 391865, upload-time = "2026-05-28T11:58:58.298Z" }, + { url = "https://files.pythonhosted.org/packages/3d/33/237173db1cfef10105b3839a24de00eb8d2a523711add4632447cdf0aedd/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613fc4ee9eaef26dc5840666214dd6fbcebcf32f46e76f4abc473059f4e13dda", size = 378012, upload-time = "2026-05-28T11:58:59.589Z" }, + { url = "https://files.pythonhosted.org/packages/97/64/1eae54e34d5161f9969295e80bd6b62a55f2b6ac5f2a5b60d02c2140e758/rpds_py-2026.5.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:85264a90ff4c05c1568dd65f5921c837614b67c60358fb4c17df3b7f2e90690a", size = 391111, upload-time = "2026-05-28T11:59:01.104Z" }, + { url = "https://files.pythonhosted.org/packages/d8/34/5bb334a5a0f65d77869217c4654f34c78a7d11b93938a3c076a2edeafc52/rpds_py-2026.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe71bca7d547acb17027c7fd1624ff8aae623499c498d3e7011182c4de5c25e0", size = 409225, upload-time = "2026-05-28T11:59:02.433Z" }, + { url = "https://files.pythonhosted.org/packages/16/0f/007ec21283b5b040b4ec3bd95e0402591e22bfa7d5c93dfe01c465c2d2d7/rpds_py-2026.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05fa4f41f37ec97c9c260441a940450a192f78d774d2b097eee1379f1e1246a", size = 556487, upload-time = "2026-05-28T11:59:04.012Z" }, + { url = "https://files.pythonhosted.org/packages/ff/10/5437c94508169b6b22d8418fef7a66e9ffb5f3b9e9c94460f2eedafe06ff/rpds_py-2026.5.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df1d2a1996755b24b9ecee92cb4d36c28f86f464a6a173349c26bab41e94b8c2", size = 620798, upload-time = "2026-05-28T11:59:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d5/9937dce4d6bda74157b954e7d1460db05a22f5929dccfeeba1ed27a93df0/rpds_py-2026.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8895840ac4809e5f60c88fd07617cd71326e73d6e5a8aa783c5c0f7c24985de2", size = 584053, upload-time = "2026-05-28T11:59:06.837Z" }, + { url = "https://files.pythonhosted.org/packages/6c/31/750617dd0ae1752471bf43f9e41d263398fae7cde7849d23b8574a70e617/rpds_py-2026.5.1-cp311-cp311-win32.whl", hash = "sha256:3684a59b158a7683aaeb8e25352e9a9dd2122cec78f2d8530266e4f91b4c7b3f", size = 214390, upload-time = "2026-05-28T11:59:08.402Z" }, + { url = "https://files.pythonhosted.org/packages/3c/bb/3dcab0e1d9516303f2eb672a5d6f62eca5a69e2886301e9c8c54b520c39b/rpds_py-2026.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:7bd530e6a530bb3ea892f194fafa455f3516ac25ecf7143fd33c09be62b0470a", size = 231097, upload-time = "2026-05-28T11:59:09.786Z" }, + { url = "https://files.pythonhosted.org/packages/49/d6/c6bbf5cb1cf12b9732df8074b57f6ef8341ba884c95d40632ae8bddb44e4/rpds_py-2026.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:0a5ae4dbe43c1076983b72616496919872ae7bbe7a1e21cc48336bc3154d130b", size = 226361, upload-time = "2026-05-28T11:59:11.079Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e7/a78582dc57caa592dcc7d4fb69b61390561e908eb3d2f5df5928a8e354c0/rpds_py-2026.5.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3abe24a66e57adcfa645d718063a5fa5103ecc71ddbf26d78af8f9368018ff1d", size = 353040, upload-time = "2026-05-28T11:59:12.531Z" }, + { url = "https://files.pythonhosted.org/packages/a3/43/35e3f136343aef451e545ce8c38d36c2f93c0ed88703db8b64ba2b205c68/rpds_py-2026.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58b1d94308ddf0b1982f61f2eb54bf92997c9ece8a8093ef014250f4a517906c", size = 345775, upload-time = "2026-05-28T11:59:13.827Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/0f2160c5982d3157734d5cb3ed63d8b2d583a73c9864f77b666449f32cf8/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa92420128dadce7f54bd73ba1825a273e9268fe9e35dbf7e6362890efa4e08", size = 376329, upload-time = "2026-05-28T11:59:15.271Z" }, + { url = "https://files.pythonhosted.org/packages/d0/11/ee0ba42aff83bf4effdbc576673c6be64c5e173978c3f6d537e94482f77d/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ca653c6546386227cd9800d1bef6a348099acf8db4250341da6d90f663d6dfcb", size = 383539, upload-time = "2026-05-28T11:59:16.665Z" }, + { url = "https://files.pythonhosted.org/packages/11/df/d94aa6a499d4ac40afe2d7620f2c597fd3c0f182e854ad7cf3f596a81cb6/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66c93681c4729e4e3ecba31b8179fae083ff3118841672835140338b4b9867c1", size = 494674, upload-time = "2026-05-28T11:59:17.991Z" }, + { url = "https://files.pythonhosted.org/packages/1f/75/33d30f43bb2f458de11979486a591b1bf6e5651765ed1704c6197c2dc773/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ff257542e04796880e011e15cd4dc21c2599975df2aaa8f2c8495ca574e1a5", size = 389268, upload-time = "2026-05-28T11:59:19.434Z" }, + { url = "https://files.pythonhosted.org/packages/f4/1e/2c9096fc19d5fd084b0184ca2b651e659aa0a37e6fdbecf6ece47f147fe1/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6825cc329b290e93c5f6a9be2393118a763f6ccf6abd83704e0c102ca583644", size = 376280, upload-time = "2026-05-28T11:59:21Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e5/61ec9f8be8211ea7f48448195549e4aaf02004083475493b0e137702ecb2/rpds_py-2026.5.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:de42116e69cb53b911cc34aee5ab98f36c597b822545045d49e938818b99e5e4", size = 387233, upload-time = "2026-05-28T11:59:22.454Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ca/bcec1005c4f4a234f92a29078631fee49206c7265ccae966f18fd332e80e/rpds_py-2026.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0f920015df2a504bebaba6d4c31ccf3fcf942f92655c086da30b671aad19aa6", size = 405009, upload-time = "2026-05-28T11:59:23.845Z" }, + { url = "https://files.pythonhosted.org/packages/72/e6/4d5718c5cf26c522dc7c9999e238da1e77380b81d0c5d1df11e271ddfeb1/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0408a24e44feb919423dc6d9da677cb5cddb894d2ca9e763967d156d9c60fab4", size = 553113, upload-time = "2026-05-28T11:59:25.184Z" }, + { url = "https://files.pythonhosted.org/packages/d4/25/2ee807bdb3e1f0b7eddf7782acd5665a8b5205a331a7d7244a52c4812fd9/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cea68bcd53467561ae2f96a6bdad1544299ba97b5b0ddcd5ac3d376e5c781c24", size = 618838, upload-time = "2026-05-28T11:59:26.749Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c1/7d4c26f167f8c41501cc073d30ee22082b16ce358cf5b00ec97cbc7804ea/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4be8b1d2a705cc37d08256004e1d07de143fa0075c8e85a3df020b776f62b732", size = 582436, upload-time = "2026-05-28T11:59:28.11Z" }, + { url = "https://files.pythonhosted.org/packages/04/1d/9d12b0a337bab46f4769f8857f4007e3b2d639e14f9a44a0efe157696e64/rpds_py-2026.5.1-cp312-cp312-win32.whl", hash = "sha256:6736718bd4fc49cbcb538ba30516fdbef161522acefb739657d48b97bd864fed", size = 212734, upload-time = "2026-05-28T11:59:29.689Z" }, + { url = "https://files.pythonhosted.org/packages/c5/93/e4116f2de7f56bc7406a76033dc501811ddeb22b7f056b92d632871ebb0c/rpds_py-2026.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:0a7d1eec967df0e9b22614a5e177622e0c89611d03727fa0cb48e45028907870", size = 229045, upload-time = "2026-05-28T11:59:31.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/53/6c3419d85eb2ec5938a37627c585b42d76a63bb731d6e42ed4b079ebf486/rpds_py-2026.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1841d067089e117142d79b98aa0df2f08b52f2ecc1819dd2700636c0db74a473", size = 223967, upload-time = "2026-05-28T11:59:32.318Z" }, + { url = "https://files.pythonhosted.org/packages/6c/32/14c961ad295f490eb0849ada8b79683e93a59b9de3afdd983eaf55fa6867/rpds_py-2026.5.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:efef4ac29c6ff495531eb17ee705b62841ecaa291b7c7077e848ea03e237164d", size = 352787, upload-time = "2026-05-28T11:59:33.655Z" }, + { url = "https://files.pythonhosted.org/packages/ca/bb/d1b85117967c11191441a7274ae616c65d93901d082c588f89a50a8da5ae/rpds_py-2026.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c39f5b67a8a2e67179ada2a954227d670fe65fa9098457f698f56ddf248709b3", size = 345179, upload-time = "2026-05-28T11:59:35Z" }, + { url = "https://files.pythonhosted.org/packages/7c/46/d84105f062e626a1b233f863907288a4708c2d833b8b4c6fb2764bc080c0/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5c30f3f04eef4fbd362226a6f31d7c8895ca4fbb6e0b790f6890a98d8da8559", size = 376173, upload-time = "2026-05-28T11:59:36.43Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ae/469d7959ce5b1201e1de135dc735b86db3b35dd0d1734f6a44246d5f061c/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:277f6c82f0580848796c7ecc8a7173aa3bfb928e4ff831261c2f60a81dc270db", size = 383162, upload-time = "2026-05-28T11:59:37.995Z" }, + { url = "https://files.pythonhosted.org/packages/dc/a2/57853d31a1116a561aa072794602ad3f6341e18d70a8523f1bd5b9fc1e5a/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63c2c4c213f1a4e3f3de28ecab029dbdee976324e729c0d7a55211be72576b02", size = 495093, upload-time = "2026-05-28T11:59:39.453Z" }, + { url = "https://files.pythonhosted.org/packages/99/63/3a8eabcad9314b7daf5c65f451d2c33d989235cd8a5762186cf2c3f5a4f8/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3350ec808fb538fe71a1f94dfaa0e29c598dfad805ce49f0caec5ae3183c652b", size = 389829, upload-time = "2026-05-28T11:59:40.896Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/05678d97fc25e2622df14dc530fb82023174ecfff6733991ed0d78f167bd/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1b964e3ab599e718dc46c018d104b1ebc007cbc6567d827c94a687fca56d77e", size = 374786, upload-time = "2026-05-28T11:59:42.626Z" }, + { url = "https://files.pythonhosted.org/packages/88/d1/8c90b6431e80a3b91b284a5c7c8c0c4f9c006444d90477a740d6e0f9c694/rpds_py-2026.5.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:19cb09fab7b7fc96b2a6e28f2e34b72a3705ff27b37edb77455316e5d3f3dc9b", size = 386920, upload-time = "2026-05-28T11:59:44.124Z" }, + { url = "https://files.pythonhosted.org/packages/ff/99/4638f672ab356682d633ee0da9255f5b67ce6efd0b85eb94ad3e255e65a5/rpds_py-2026.5.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abe76bcdba31e576cb83eeb8797aa0d882b738fef6dc65d0601fc753806a5b46", size = 405059, upload-time = "2026-05-28T11:59:47.177Z" }, + { url = "https://files.pythonhosted.org/packages/66/3f/3546524b6eb4cc2e1f363a3d638fa52f6c24faae3500c25fb488b02f1740/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bff7073db3899158fff55ebf57b113a67030af26f80a18978f9f0aa60250ddf", size = 553030, upload-time = "2026-05-28T11:59:48.603Z" }, + { url = "https://files.pythonhosted.org/packages/c6/c3/7b3388c796fcf471bd17194242d4dc1a7608567c0fa422bcc1c5e79f9c1e/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8ba264fa49be666cd9cc56bf34ec7002fb3d27a4aee5bcb4d43d0d18feb1bb6f", size = 618975, upload-time = "2026-05-28T11:59:50.314Z" }, + { url = "https://files.pythonhosted.org/packages/61/1e/a3cb07f2795075d1d88efddae2f541359fde5f08c81ee114c29c2949c90a/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4860b603ddda0475a8885499b3729e90229d480105b42651962a5397d995fa89", size = 581178, upload-time = "2026-05-28T11:59:51.673Z" }, + { url = "https://files.pythonhosted.org/packages/a1/74/e758c03a5ef46f04c37f2651a2893db846d569ba8a7bca469d4b58939bcd/rpds_py-2026.5.1-cp313-cp313-win32.whl", hash = "sha256:7944270ae71383f6e2657dd7d5ce4eeb4ac2d0059a6738f0510583d462ab4842", size = 212481, upload-time = "2026-05-28T11:59:53.148Z" }, + { url = "https://files.pythonhosted.org/packages/70/ec/a2aca432db9c7359b40fa393eeeaa0d166c2f70175be956e75fa24197c44/rpds_py-2026.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:88647f43a73c4e01be19b04ceef0c8d3a1958153604d13c773becd8016f2a0cf", size = 228519, upload-time = "2026-05-28T11:59:54.505Z" }, + { url = "https://files.pythonhosted.org/packages/29/60/a73bfdd45b096574556acf303bbd9fa9eed36ca8a818b514e2a5d5fe2b9d/rpds_py-2026.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:453895624ecf7db7063b1004e44037522bbaef9ff6a945e59bc71662d7a03abd", size = 223446, upload-time = "2026-05-28T11:59:56.081Z" }, + { url = "https://files.pythonhosted.org/packages/18/e2/408105fd611823f00882aea810f3989a30d26b1bab8b6beb20f98c724e0e/rpds_py-2026.5.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:b4e4bc98639ec915f512fde3aa7a95e0041d95d9c3cc86eea841fa63cb1e8600", size = 355287, upload-time = "2026-05-28T11:59:57.448Z" }, + { url = "https://files.pythonhosted.org/packages/8d/58/5c4a43436843c90d0f6d19f82c200c80e3843ca9fa07b237623327f6d384/rpds_py-2026.5.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cacedb7a6e167680acba45ad5716e89067d225dc80da0d7040cae8c81d4572fa", size = 347033, upload-time = "2026-05-28T11:59:58.881Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c2/1a71acdacaf4e259b10278fb87b039ded3cf80041bcd89dd8a3ea702ded6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68700371c5d7ae1412862ddfa719090925c93ecf351c566d66f09d04b136ea00", size = 376891, upload-time = "2026-05-28T12:00:00.516Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c8/535f3d9b65addd8e28aa87b83c6e526799c3717a88273db8ea795beeef7a/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:296c799becfa849c779c8725494fe9ed94959ed886787df4364b058465bad7f0", size = 385646, upload-time = "2026-05-28T12:00:02.394Z" }, + { url = "https://files.pythonhosted.org/packages/1c/91/dc033f313345c354ade914dbe73cdb90b615a4409ea02430d5356794f3d8/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3858b908218ee108d0bbfb2095ccc237648053c9bf98affad7cb079acaf1d97", size = 498830, upload-time = "2026-05-28T12:00:04.189Z" }, + { url = "https://files.pythonhosted.org/packages/27/fc/90fcbea459dbb8ddc18a2e0fd1de9412b48bc84ffff2db771cf714bacfd6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4fb8d2e7cb2f850b169806d61d1b991738acec96500a75c30f49caf064ce7cef", size = 392830, upload-time = "2026-05-28T12:00:05.797Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1d/46cd11a228c9750684a798d98f878be6f614aa762438da7378f035e79e35/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27b74c10ed6a8f190f4287f53bcfea348b92a84a9c9f70d30183d1e6172d580d", size = 379613, upload-time = "2026-05-28T12:00:07.433Z" }, + { url = "https://files.pythonhosted.org/packages/24/4a/d9b0c6af3a1de03eb93741bbe8be2bdce84d8fda8224f3005451d86df389/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:b9a6528956191c48c52294a592dbd4a8386d7048bdb25c0efcb6b966466c6d83", size = 388183, upload-time = "2026-05-28T12:00:09.227Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b4/db7aaabdda6d020afc87d981bcc2f57a434c7dec60ecfc2ab3dd50b20351/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:af03e34e860047bc7a352b842856fcf78798fbb81132cc98bd2f907ab4eb9cd2", size = 408578, upload-time = "2026-05-28T12:00:10.779Z" }, + { url = "https://files.pythonhosted.org/packages/08/d6/070f6a41cbb343e2ac4171859bf3f3623e0ab002f72619d6d505313ec2de/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fea6e836d10abbe191d557d33bd58bd5987725fe63aa1eefe557d230209855bd", size = 553573, upload-time = "2026-05-28T12:00:12.443Z" }, + { url = "https://files.pythonhosted.org/packages/75/ab/1a71ea3589c4345dac0a0518f0e6a031cb42689277851b683c46d27463a5/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:fc0c0f878ea770a0a8a462456c5ad36fc9fe6358e6b76fdadc7f17575e0b8bf1", size = 620861, upload-time = "2026-05-28T12:00:14.09Z" }, + { url = "https://files.pythonhosted.org/packages/8a/22/9bf80a56069c0c443fcfefac639a86a744550a2898817a6dfd3e26654924/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e0b360f316d966b048b085857630b3cc51f3db2f07b06f440eac8f695374d1e3", size = 585633, upload-time = "2026-05-28T12:00:15.66Z" }, + { url = "https://files.pythonhosted.org/packages/da/68/3b2c0a75c9e04125696f84ebdbbf304acf5a40b58ba4481cdb98a922c3ba/rpds_py-2026.5.1-cp313-cp313t-win32.whl", hash = "sha256:a2999883eedf72fdfb7520b92c7d4ec2572a71ff40239377aa604cc529eecafc", size = 210074, upload-time = "2026-05-28T12:00:17.291Z" }, + { url = "https://files.pythonhosted.org/packages/e7/8b/609157d5a25d37d4f29f92840ba531f416907c34ae5c5739dd21fc2bef98/rpds_py-2026.5.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e07be2a9d7122bd6e82dea89814ef8dc893feb1aae97fec1630f3263bbb30e55", size = 228635, upload-time = "2026-05-28T12:00:18.73Z" }, + { url = "https://files.pythonhosted.org/packages/d4/6f/19c1918a4b590d8de87e712e4abe4b3875771eff60216fb6153cf6665c68/rpds_py-2026.5.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1f2c391c3059798093b65df23aca2cac150460ae9c630d99dec83d703d9485b9", size = 349756, upload-time = "2026-05-28T12:00:20.217Z" }, + { url = "https://files.pythonhosted.org/packages/e5/60/a06fe7da34eca79dacbf958a2ba0c6eea85bc2b29de20080bf40f72f66fa/rpds_py-2026.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:413b424f7c4ee65ab5e5be91f5731be0f8b41a1ee2b12dfe810d716312e95a78", size = 343831, upload-time = "2026-05-28T12:00:21.711Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ec/b2333b97b90e2a6ef6ca8ad386ee284968e74bcfe113b3f1a8d9036429a9/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c595a1d9255dce0599e13130d1440ab2506654f2b50294226ee06402f8fef63", size = 375127, upload-time = "2026-05-28T12:00:23.326Z" }, + { url = "https://files.pythonhosted.org/packages/14/7f/e00aae54067f2b488c4637961d5f58204d470795fc791085fa3f15060d2e/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1c27c5f6102eac8c03e7595a00827a53b271ba40a53b59ff8709170e0855ea4a", size = 379034, upload-time = "2026-05-28T12:00:24.89Z" }, + { url = "https://files.pythonhosted.org/packages/be/cc/423999bbb8ae8dc93c77fc1d5e984ade5eb89d237d3bb884ccfa72ae2890/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c7fcf61d44cacecaf3aea542b0e053db77972a4573e7ceda16fb2b399161195", size = 490823, upload-time = "2026-05-28T12:00:26.676Z" }, + { url = "https://files.pythonhosted.org/packages/0f/aa/c671bf660f12e68d3c52ff86c7066ed1372df5a0f4f2ff584e419b8207e7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c817a189d4ee14290420e5ff051e4dd6baa13f3edf84685071dee07a6d538ee", size = 388144, upload-time = "2026-05-28T12:00:28.577Z" }, + { url = "https://files.pythonhosted.org/packages/19/c8/d63bb75b68afe77b229e3021c6031bcaf01da5db5b0e69d0d10f9ba679a7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21846aac0ed2e0589f38c12dc44e77bb64e494b771eadbcf169cba00566ba7ba", size = 371959, upload-time = "2026-05-28T12:00:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/82/35/c51122014d8274ff37dc606d60049c3db7d83da02b5b282511e5a906a9a6/rpds_py-2026.5.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b317c87a13f769a4e787819bd508aaa5d69aa09b0880de9af6d3a8a54571cdec", size = 383558, upload-time = "2026-05-28T12:00:31.764Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f9/2790cb99c136a5363acdeacf5c27c56f3de0d4118a1f48fca83404c99c89/rpds_py-2026.5.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce87129d9f2c14fa6c4a8601fb80eb4488c80d38a20cd13758ef11123e14995d", size = 402789, upload-time = "2026-05-28T12:00:33.247Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1b/e4fb584f8c75d35c38150ff6a332cda949e6f97acba1f4fd123b14ab56fe/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9cdddb6c1207d284d94fd1530adf57fbd797fe7c4b8704ba85f49414f2557e7d", size = 551405, upload-time = "2026-05-28T12:00:34.819Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f7/a6731b4216cb3793ea1af5391da240f5683dacc0d13e034fe5fc3503f240/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:4e237e139f94d3c036fd28eb9f564c99055476ff4ff05cd42be55ce349b5aa02", size = 616975, upload-time = "2026-05-28T12:00:36.268Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/2e051a81d95d8e63f4b35a1c463a87e8766bc3d083c067c5dfb6bf220747/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ed0954b524873214369184a9c82b0eaa45a3fbb9a798cd95b17e0d98499e7ea0", size = 578701, upload-time = "2026-05-28T12:00:37.82Z" }, + { url = "https://files.pythonhosted.org/packages/65/56/b5f6fdb2083e32bca8a8993d89e70db114b4756c9e2c38421328126689d2/rpds_py-2026.5.1-cp314-cp314-win32.whl", hash = "sha256:2d88621d6a7d4dfa633d21abe90f280bb205274e16b1d1e61c6ad4640b2453b7", size = 209806, upload-time = "2026-05-28T12:00:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/fb/80/65a5aa96c155e611d1ed844e4e1f57f3e36b021f396d9f8585d756e6b90d/rpds_py-2026.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:cef8ac28d26f4dda3533060c20fbf80a325458fa9fd23ea72a73cdfa8e978838", size = 225985, upload-time = "2026-05-28T12:00:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/27/7c/ad185212e87b05f196daef92bc5f3caf07298eb47c295b5585c3dd3093ac/rpds_py-2026.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:eaaea962c68cdc68d4a533ba985ab8e9484277910bbfaa2ab3ef7732667bfed8", size = 221219, upload-time = "2026-05-28T12:00:43.15Z" }, + { url = "https://files.pythonhosted.org/packages/23/58/e14ae18759020334646b031e708ab4158d653a938822bfb7b95ef2e93aa3/rpds_py-2026.5.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:21942f52dbbd5f8758bf021213d28bd45c39e873e65e2407faf5f1846f5761ad", size = 352148, upload-time = "2026-05-28T12:00:44.638Z" }, + { url = "https://files.pythonhosted.org/packages/31/9b/5f4a1e2f960bca3ac5d052b139dd31eed97b259f9d909173821760d542e8/rpds_py-2026.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f414556f6e3958300ff941e40c9f97e3dc9774ddd1b3434c475d73dd354bbed3", size = 345196, upload-time = "2026-05-28T12:00:46.14Z" }, + { url = "https://files.pythonhosted.org/packages/1a/71/1d9574d6a2fa20ab60eaa55c7467f5aa20cbc770f341a05f09c0876f59e2/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1013a8625c74043210190b246f5b1551e09757c1f356c6e4160ef96c5bc081", size = 374981, upload-time = "2026-05-28T12:00:47.531Z" }, + { url = "https://files.pythonhosted.org/packages/0c/9a/37e99f4915a80aa71670263c1267f7ae0af95f53a3f61e6c3bdc016d4515/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc68e231a77a5f0d774ae278a1f8e55c0456501820847c1e4efb3829f3441df6", size = 379961, upload-time = "2026-05-28T12:00:49.216Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ff/6e73f74b89d2e0715e0fc86b7dde893f9a61ae2f9b256ff3bdfe41ac4e94/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9baffb505aff33acc69b422a19f77806680f3c8632227d79f48de8a810d1c2c5", size = 495965, upload-time = "2026-05-28T12:00:51.111Z" }, + { url = "https://files.pythonhosted.org/packages/ea/e0/425faba25f59d74d4638b267f7c7a80e8649d2ef4db10a19b0c4a71e6e6f/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8d2f912928d426e8cfa396f7f3f8d29a59e6689c86dcca3c420730c1096322b", size = 389526, upload-time = "2026-05-28T12:00:52.77Z" }, + { url = "https://files.pythonhosted.org/packages/c6/76/7a41960e3fddae47fab43a28684d5da981401dffd88253de0944148654cb/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90f628283be835db980c941767d41c9a27b5239e54ba0a9c1335247e82406964", size = 376190, upload-time = "2026-05-28T12:00:54.215Z" }, + { url = "https://files.pythonhosted.org/packages/27/60/5f38dc70824fc6951b51d35377e577a3a3a4c81a6769cc5a2de25ebe0ad1/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:1ebb2f0ab7e16132995a72de805170e0203df0c3dd22e1ef1cd1fdd90bd7a131", size = 383921, upload-time = "2026-05-28T12:00:55.673Z" }, + { url = "https://files.pythonhosted.org/packages/60/1a/d60a38caa1505f4b9483c3fbbde12c94e1079154f4f401a6da96f7e77621/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f3df3d16ded76f1f8c9cdebd0e1ea55fdf4c23b812de189814da7cf229c22a81", size = 404766, upload-time = "2026-05-28T12:00:57.518Z" }, + { url = "https://files.pythonhosted.org/packages/87/ff/602fd3f174d6425f0bce05ad0dfbec0e96b38d0f7d08a79af5aa20083885/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9af8905b8f854990e40d5206aa5ac58d9b0fe0b7f351ff2bb086c20f6c8c6a47", size = 551343, upload-time = "2026-05-28T12:00:58.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c1/1be13327acdbead3eca1fde03b6a34dbb011f1e864e217f0d32cc1779a7f/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:036a36a87fb1cd3b214d11c4b3c4f7d2ddad933625dca1c900b56a057c07740a", size = 618502, upload-time = "2026-05-28T12:01:00.656Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d7/afb49b49d7f2be8b7ba1a9f0977fa5168003437b93086726f066544e8351/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:62ae3853454fe9ef283a03c96c2d835d39e84b14643a9d62c82ef0fb87d702ca", size = 581916, upload-time = "2026-05-28T12:01:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/25/d1/dbef8c1f8a10f07beb62b5f054e20099fd9924b3ec001b8f0b6ac7813a85/rpds_py-2026.5.1-cp314-cp314t-win32.whl", hash = "sha256:6c3d771a46ec18b12af06ce36243a9a80b07a5d0515236332d90863ca8bb326a", size = 207855, upload-time = "2026-05-28T12:01:03.821Z" }, + { url = "https://files.pythonhosted.org/packages/2a/72/bfa4e61ab8e7dc1c8adf397e05e6cbdd4239357bd72b248d3de662f23915/rpds_py-2026.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c93c629be4636cf54337bd5f06c104d55e42ced54d681f6fe21ae510a65116f6", size = 225422, upload-time = "2026-05-28T12:01:05.194Z" }, + { url = "https://files.pythonhosted.org/packages/27/3a/7b5da92b640f67b6717ccafc83cdd06bfa7ff2395c3685c68922bb54d703/rpds_py-2026.5.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:3574b55c604b8f75dacb007136508bbc0db406e626301778096a133327e7f2fb", size = 349576, upload-time = "2026-05-28T12:01:06.722Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8a/2aafd7ad355a1bd48ca76e2262b74b15e6432b5a1efe150efd4d779cd55d/rpds_py-2026.5.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:94068eb3ae6d43f5a786b7db96a406a34e6d5c24489feef32fd6e8946ea7b291", size = 343640, upload-time = "2026-05-28T12:01:08.441Z" }, + { url = "https://files.pythonhosted.org/packages/f7/7d/6c9523c1abbe840a1b7fba3c516d48e1d3487cc80fea4366c4071cf56784/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a5b10e8ce894825f380a8f1b6444cf73c294dfea62afbb2d13e3a9e630cec1", size = 375322, upload-time = "2026-05-28T12:01:09.934Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5d/0b7b03fb1dc509321f01de3149784ab773e34c8573022029af8076afcb9c/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fc09f82e63d4bcd58149572f857a431bae851dc747e313c3b5bdf7abb907fda8", size = 379066, upload-time = "2026-05-28T12:01:11.48Z" }, + { url = "https://files.pythonhosted.org/packages/d7/e2/8ef6012999ebf1cb1c22f876d9ce5e63d960fd4631d2af3202d3f480aa25/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e10464d17df3b582745c25cec695cb9558bca2cb6ddb631aee1787fc72c767b2", size = 494586, upload-time = "2026-05-28T12:01:13.051Z" }, + { url = "https://files.pythonhosted.org/packages/80/af/1eeb029bec67582c226b7809172207cd005073af4ebd906e65ff494f4983/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ba05adbf15d994c38ec0b7ab32e858e5110c21e9009a00a86545fd220f84e038", size = 388415, upload-time = "2026-05-28T12:01:14.631Z" }, + { url = "https://files.pythonhosted.org/packages/18/23/ffbe10711c4d766c1cab0557d6906c074f795814863c67b351355d29354a/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77c004fdc7b891967106f78ddfd7b076bfe6813c6139c6fff6aed3bcaa960b26", size = 372427, upload-time = "2026-05-28T12:01:16.153Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3a/30ba4a6ad457e5b070c18d742a33fb77d8d922b565cc881f8a5313d63bfe/rpds_py-2026.5.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:83bcf894486c9d78dd290d3c0124ff6dd8875d3025e2090a8ec49fcc37c55fdd", size = 383615, upload-time = "2026-05-28T12:01:17.809Z" }, + { url = "https://files.pythonhosted.org/packages/d3/69/62e242b53ce39c0814bd24e1a6e6eba6c92be716277745f317f9540a2e7b/rpds_py-2026.5.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3df104083952a0e0c6f10de33e440eabe98fb6317d23e1a58c68f6df08d01b9", size = 402786, upload-time = "2026-05-28T12:01:19.419Z" }, + { url = "https://files.pythonhosted.org/packages/38/c1/a770b9c186928a1ed0f7e6d7ae50e7f3950ed23e3f9e366dbc8e38cb55de/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:980450826cf22e133c57e0835070bdd0dd3f73b9b708c3ce223def2cb9469e14", size = 551583, upload-time = "2026-05-28T12:01:21.013Z" }, + { url = "https://files.pythonhosted.org/packages/21/7c/68e8579b95375b70d2a963103c42e705856cdb98569258bd807f4423891c/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:205dde846f24332ab0c1188699a043b8d165b79bb84529ce272c45048ff6be01", size = 616941, upload-time = "2026-05-28T12:01:22.548Z" }, + { url = "https://files.pythonhosted.org/packages/70/a1/a6135aed5730ff03ab957182259987ac11e55fb392a28dc6f0592048a280/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:3966b82dd563176396df030f3dd52a6e54cb69b718e95e78bd555ed3d1e0185d", size = 578349, upload-time = "2026-05-28T12:01:24.118Z" }, + { url = "https://files.pythonhosted.org/packages/09/6e/f24201a76a84e6c49d0bdfdfcb735210e21701e9b21c5bfc0ba497dd62f6/rpds_py-2026.5.1-cp315-cp315-win32.whl", hash = "sha256:7818f8d0a415be74d2be3590b0a1c1f463a642f4d0217e7d10602dceef5b79aa", size = 209922, upload-time = "2026-05-28T12:01:25.522Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e4/966bc240bb0485fc265278f6de44d05834bf0b3618886e0b22e33d54c49a/rpds_py-2026.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:b3cc20c0d800af78fd0fac68086e28c1856cec51ea528bb81ea851aa40d39325", size = 226003, upload-time = "2026-05-28T12:01:27.062Z" }, + { url = "https://files.pythonhosted.org/packages/5c/5c/a15a59269cd5e74472734516c73795c15eccfc841b3d4b0228c3f53f19d0/rpds_py-2026.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:3609e9939a8a76cd904cf98a3f1f13b5dc7e150adeaee89e0ea09652ea213e16", size = 221245, upload-time = "2026-05-28T12:01:28.51Z" }, + { url = "https://files.pythonhosted.org/packages/e0/22/135ce03804e179a71ceb13be095deda4a279bc88f7a6b8fa161c5ad44e12/rpds_py-2026.5.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:5d333a7127d4b307601ac37792bee01bb95c867cbfacf21b6375b804d6bbd723", size = 352015, upload-time = "2026-05-28T12:01:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/3b/5f/f1f6d2652eb9d848f6eb369d8db83a2da6249bb49ad2c2a48f45d54538d3/rpds_py-2026.5.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:b5f077b44a4f7808520f66dae234988d867deb9aed9be5da057ce9ba831b2a41", size = 345016, upload-time = "2026-05-28T12:01:31.656Z" }, + { url = "https://files.pythonhosted.org/packages/88/66/b74182775691ea2290c99e52ac8d5db844e56fbec90ce421f107658c8314/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d8f9b7b78c9538fc9e04e82ec0e888ff0c3cffcfad152c77e57cd09351a98a", size = 374775, upload-time = "2026-05-28T12:01:33.136Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8f/15e5a61d9f0a43902d36561d4f07cae6ae9f4716be825159fd72717f33af/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e3a8ae58895ac107ed934a6bf51e5846f95c53b9b940c2c6d310838fd5846358", size = 380270, upload-time = "2026-05-28T12:01:34.574Z" }, + { url = "https://files.pythonhosted.org/packages/02/c3/f859b12763a80540cdf2af0f15b19904cf756a71d7bdd3f82ff3e5b1bbf9/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0957cf3c2b8632ec7aaebffebea8005b353cc2a237b6e2ae3c2cac0820704cfb", size = 495285, upload-time = "2026-05-28T12:01:36.127Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c7/ff27c2ac8411d30b03b1829fd88cae8dad1a4d0da48dd25e57c4038042e6/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c396c1304de421050b3681ea70f371874b54d41b0151e96109758144c231e30b", size = 389581, upload-time = "2026-05-28T12:01:37.635Z" }, + { url = "https://files.pythonhosted.org/packages/6e/67/fe92ee32a6cc05c77228a2f8b1762e7124f386ec20ff83d0757b762d58d0/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad1bff7f666b9598e573815affd666aac6a13a585dde336f843e33350c7fadc", size = 376041, upload-time = "2026-05-28T12:01:39.307Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/b4d6685c27aba55bd82f25b278be8237038117d05f9659a6213ad3408130/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:656a042550878f12d45752452d47094b7cfe5ad1e9d7b87b5a22ad3ae5ff8015", size = 383946, upload-time = "2026-05-28T12:01:41.043Z" }, + { url = "https://files.pythonhosted.org/packages/bd/79/2c1d832a53c8e0f8e98fc970ec257b950fecd4f62be2ab7182b500a0cbc8/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c4bd4f70294737b5206a3e8e30ccadbf8a60301831c8ea23eec5dbeea1ecfa", size = 405526, upload-time = "2026-05-28T12:01:43.032Z" }, + { url = "https://files.pythonhosted.org/packages/78/c4/c98117b03c6a8581ab2c2dfccfe9a5ad82bd8128a3c28b46a6ad2d97c393/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:43bca78665423cabae77146f2fe7ce55272b6c8d55d82cca83effd42c7e13972", size = 551165, upload-time = "2026-05-28T12:01:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c1/bc479ca069200af730881b1bd525e3114b2b391a351509fcb1b772f28086/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:42d0f20e85e549c870749d0e247f0c10d318a45b7e9676d575d2dcb04a1b2e66", size = 618778, upload-time = "2026-05-28T12:01:46.337Z" }, + { url = "https://files.pythonhosted.org/packages/77/65/38ab2f90df44c2febfb63cc10ced40763d9b4bc94d173e734528663fe7f5/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b1be5c35683684d5331b93600c210e8367c254683d8a6df6bd21bd2da3a334fb", size = 581839, upload-time = "2026-05-28T12:01:48.109Z" }, + { url = "https://files.pythonhosted.org/packages/15/2d/ce1f605fe036aadd460e5822e578c6c7ec3a860936cca37d6e0f299daa77/rpds_py-2026.5.1-cp315-cp315t-win32.whl", hash = "sha256:75808f6c38ce7749bb68cc2770161aae5045e6c6f6781a9782e74b93304399df", size = 207866, upload-time = "2026-05-28T12:01:49.648Z" }, + { url = "https://files.pythonhosted.org/packages/79/cb/966040123eb102371559746908ef2c9471f4d43e17ec9a645a2258dab64b/rpds_py-2026.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:90bd6630002a1c7f09e7843dd79f0d24f3d2897cc25a753480917865d14f15b3", size = 225441, upload-time = "2026-05-28T12:01:51.408Z" }, + { url = "https://files.pythonhosted.org/packages/42/56/3fe0fb34820ff667be791b3a3c22b85e8bcba54e9c832f47438c191fa7be/rpds_py-2026.5.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:edf2765d84e42447f112ad877af8fe1db0089aaec5b28e88d6eab45e7fe99cea", size = 357151, upload-time = "2026-05-28T12:01:53.43Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f2/3eb9ccdb9f143b8c9b003978898cb497f942a324c077401e6b8834238e63/rpds_py-2026.5.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ad3773236e95f7f33991eb125224b7da66f206504d032a253a02da7e134519fb", size = 350195, upload-time = "2026-05-28T12:01:54.901Z" }, + { url = "https://files.pythonhosted.org/packages/a7/24/dbda232bc4f3ed732120692ab0d2c8402cb020516556d8bee622dcef2413/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a04df86b3f0fade39ec8fd0e0aab089b1da9fbd2b48df778a57ef96f5e7d38df", size = 381850, upload-time = "2026-05-28T12:01:56.601Z" }, + { url = "https://files.pythonhosted.org/packages/40/30/32e769839a358f78810c234f160f2cc21d1e4e47e1c0e0e0d535be5a0219/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6142dbd80c4df62a5d899f0d616d417f84e0bc8d32526c8e5589019d75d028a7", size = 387899, upload-time = "2026-05-28T12:01:58.212Z" }, + { url = "https://files.pythonhosted.org/packages/ab/86/ec84d243aadb3b34b71dd26a010d0930b2d284ff5fc9a69fec53810ee6fd/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b35217adefe87f2fe4db7e9766cabe84744bfe9616d9667be18988928c7f2dc", size = 501618, upload-time = "2026-05-28T12:01:59.888Z" }, + { url = "https://files.pythonhosted.org/packages/74/25/b60e52686bbff777a64f9e4f4d3dd57980dc846913777177a2c92e4937aa/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b95d5e11fc712b752081183a55a244c03cd00570489edd7014d8899f8ceb8162", size = 394003, upload-time = "2026-05-28T12:02:01.482Z" }, + { url = "https://files.pythonhosted.org/packages/9b/c7/b3a6a588cc2219510ef3f42e207483a93950bedd1e3a0fd4015c95cff9e5/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141c9498daf2ace9eda35d2b0e376f9ea8b058d84f2aef4f96fccfd449a2f251", size = 379778, upload-time = "2026-05-28T12:02:03.197Z" }, + { url = "https://files.pythonhosted.org/packages/31/00/c7dba3fc8a3da8cb3f6db1eb3386be4d79c2e97c6890d20eb9ac66ae8c43/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:6f249f8b860a200ad35193af961183ebe9132710484e6f6ce0cf89fd83c63a9a", size = 392359, upload-time = "2026-05-28T12:02:04.817Z" }, + { url = "https://files.pythonhosted.org/packages/93/dd/472ba494c70753f93745992c99855bee0636daf74e6984e5e003f150316f/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e4abbf391a70be864920858bf360f4fb380577c9a0f732438a1996726e2c195b", size = 412820, upload-time = "2026-05-28T12:02:06.401Z" }, + { url = "https://files.pythonhosted.org/packages/1d/6f/93831a3bfe789542ed0c1d0d74b78b440f055d6dc3ea4640eba2d95e6e23/rpds_py-2026.5.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:c74005a7bb87752acf351c93897ec63ad77a07a0da7ecad9c050e32e7286ba34", size = 557243, upload-time = "2026-05-28T12:02:08.013Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ff/0b3d604614ffc77522c6b288fdbce68957eb583da1002aa65ba38ac0ee40/rpds_py-2026.5.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:8213afbe8a3a906fb9acb2014423fe3359ee783d0bf90995f70623a3217bfa6c", size = 623541, upload-time = "2026-05-28T12:02:09.661Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ea/e7b0251441da9adfeaebcf29601d10f2a1455fcf0772fae9e7e19032bd96/rpds_py-2026.5.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8c43a8a973270fd173bf48cdf80bbe66312421cba68d40845034f174f2389049", size = 586326, upload-time = "2026-05-28T12:02:11.47Z" }, +] + [[package]] name = "s3transfer" version = "0.16.0" @@ -4812,6 +5903,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl", hash = "sha256:c106e05d5a61449cf6ba9a1e650227ecfb141590d2a98412103ff35d89fc7b2f", size = 24390, upload-time = "2026-03-09T03:43:24.361Z" }, ] +[[package]] +name = "snowballstemmer" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/f8/0a71edf031f03c40db17503cb8ca78a69a171254e568e7db241b0ab57ea1/snowballstemmer-3.1.1.tar.gz", hash = "sha256:e07bbc54a0d798fe6010a12398422e62a8bfbba95c394fd0956ef58cb4d3e260", size = 123314, upload-time = "2026-06-03T00:56:40.194Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/07/2ebca9b11fb9be7340a818d8d6f63feaebb146be2c4afbd6061701d6df6e/snowballstemmer-3.1.1-py3-none-any.whl", hash = "sha256:7e207fa178741da09cdee59d3ecec3827ad5f92b1fc5c9ff3755b639f71f5752", size = 104164, upload-time = "2026-06-03T00:56:38.614Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/2c/0a5f6f8ee0d5589e48c7640213ed5175d52cf540a06725b628cc1a45d6ce/soupsieve-2.8.4.tar.gz", hash = "sha256:e121fd02e975c695e4e9e8774a5ee35d74714b59307868dcc5319ad2d9e3328e", size = 121110, upload-time = "2026-05-24T13:55:57.154Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/f5/0c41cb68dcae6b7de4fac4188a3a9589e21fb31df21ea3a2e888db95e6c9/soupsieve-2.8.4-py3-none-any.whl", hash = "sha256:e7e6b0769c8f51ed59acab6e994b00621096cfb1c640a7509295987388fbaf65", size = 37304, upload-time = "2026-05-24T13:55:55.406Z" }, +] + [[package]] name = "spherical-geometry" version = "1.3.2" @@ -4912,6 +6021,343 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9d/e5/4c5bc0012820d42c05285a41d7acccb74897998018afc96f5e876443a7ca/spherical_geometry-1.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8b0ecf30745d9064bfa7017a10a8c468c9a44be02dae02c7a1785f9818fd4eee", size = 7688143, upload-time = "2026-03-11T16:58:48.316Z" }, ] +[[package]] +name = "sphinx" +version = "7.4.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, + { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/be/50e50cb4f2eff47df05673d361095cafd95521d2a22521b920c67a372dcb/sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe", size = 8067911, upload-time = "2024-07-20T14:46:56.059Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/ef/153f6803c5d5f8917dbb7f7fcf6d34a871ede3296fa89c2c703f5f8a6c8e/sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239", size = 3401624, upload-time = "2024-07-20T14:46:52.142Z" }, +] + +[[package]] +name = "sphinx-autoapi" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "astroid" }, + { name = "jinja2" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/25/386d851320bc306e9d7d41b8cecf113f638e205a99595c481c146622de94/sphinx_autoapi-3.8.0.tar.gz", hash = "sha256:9f8ac7d43baf28a0831ac0e392fab6a095b875af07e52d135a5f716cc3cf1142", size = 58418, upload-time = "2026-03-08T03:49:52.931Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/3a/8923a543fa2422d32be4c5311f488e4f275acde263c811e4d5d22bb544cb/sphinx_autoapi-3.8.0-py3-none-any.whl", hash = "sha256:245aefdeab85609ae4aa3576b0d99f69676aa6333dda438761bd125755b3c42d", size = 35994, upload-time = "2026-03-08T03:49:51.383Z" }, +] + +[[package]] +name = "sphinx-book-theme" +version = "1.1.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +] +dependencies = [ + { name = "pydata-sphinx-theme", version = "0.15.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "sphinx", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/19/d002ed96bdc7738c15847c730e1e88282d738263deac705d5713b4d8fa94/sphinx_book_theme-1.1.4.tar.gz", hash = "sha256:73efe28af871d0a89bd05856d300e61edce0d5b2fbb7984e84454be0fedfe9ed", size = 439188, upload-time = "2025-02-20T16:32:32.581Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/9e/c41d68be04eef5b6202b468e0f90faf0c469f3a03353f2a218fd78279710/sphinx_book_theme-1.1.4-py3-none-any.whl", hash = "sha256:843b3f5c8684640f4a2d01abd298beb66452d1b2394cd9ef5be5ebd5640ea0e1", size = 433952, upload-time = "2025-02-20T16:32:31.009Z" }, +] + +[[package]] +name = "sphinx-book-theme" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version >= '3.14' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version >= '3.14' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version >= '3.14' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version >= '3.14' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +] +dependencies = [ + { name = "pydata-sphinx-theme", version = "0.16.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + { name = "sphinx", marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/f7/154786f3cfb7692cd7acc24b6dfe4dcd1146b66f376b17df9e47125555e9/sphinx_book_theme-1.2.0.tar.gz", hash = "sha256:4a7ebfc7da4395309ac942ddfc38fbec5c5254c3be22195e99ad12586fbda9e3", size = 443962, upload-time = "2026-03-09T23:20:30.442Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/bf/6f506a37c7f8ecc4576caf9486e303c7af249f6d70447bb51dde9d78cb99/sphinx_book_theme-1.2.0-py3-none-any.whl", hash = "sha256:709605d308e1991c5ef0cf19c481dbe9084b62852e317fafab74382a0ee7ccfa", size = 455936, upload-time = "2026-03-09T23:20:28.788Z" }, +] + +[[package]] +name = "sphinx-comments" +version = "0.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/75/5bbf29e83eaf79843180cf424d0d550bda14a1792ca51dcf79daa065ba93/sphinx-comments-0.0.3.tar.gz", hash = "sha256:00170afff27019fad08e421da1ae49c681831fb2759786f07c826e89ac94cf21", size = 7960, upload-time = "2020-08-12T00:07:31.183Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/97/a5c39f619375d4f81d5422377fb027075898efa6b6202c1ccf1e5bb38a32/sphinx_comments-0.0.3-py3-none-any.whl", hash = "sha256:1e879b4e9bfa641467f83e3441ac4629225fc57c29995177d043252530c21d00", size = 4591, upload-time = "2020-08-12T00:07:30.297Z" }, +] + +[[package]] +name = "sphinx-copybutton" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/2b/a964715e7f5295f77509e59309959f4125122d648f86b4fe7d70ca1d882c/sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd", size = 23039, upload-time = "2023-04-14T08:10:22.998Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/48/1ea60e74949eecb12cdd6ac43987f9fd331156388dcc2319b45e2ebb81bf/sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e", size = 13343, upload-time = "2023-04-14T08:10:20.844Z" }, +] + +[[package]] +name = "sphinx-design" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +] +dependencies = [ + { name = "sphinx", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2b/69/b34e0cb5336f09c6866d53b4a19d76c227cdec1bbc7ac4de63ca7d58c9c7/sphinx_design-0.6.1.tar.gz", hash = "sha256:b44eea3719386d04d765c1a8257caca2b3e6f8421d7b3a5e742c0fd45f84e632", size = 2193689, upload-time = "2024-08-02T13:48:44.277Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/43/65c0acbd8cc6f50195a3a1fc195c404988b15c67090e73c7a41a9f57d6bd/sphinx_design-0.6.1-py3-none-any.whl", hash = "sha256:b11f37db1a802a183d61b159d9a202314d4d2fe29c163437001324fe2f19549c", size = 2215338, upload-time = "2024-08-02T13:48:42.106Z" }, +] + +[[package]] +name = "sphinx-design" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", + "python_full_version >= '3.14' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version >= '3.14' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version >= '3.14' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version >= '3.14' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.13.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.12.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", + "python_full_version == '3.11.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +] +dependencies = [ + { name = "sphinx", marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/7b/804f311da4663a4aecc6cf7abd83443f3d4ded970826d0c958edc77d4527/sphinx_design-0.7.0.tar.gz", hash = "sha256:d2a3f5b19c24b916adb52f97c5f00efab4009ca337812001109084a740ec9b7a", size = 2203582, upload-time = "2026-01-19T13:12:53.297Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/cf/45dd359f6ca0c3762ce0490f681da242f0530c49c81050c035c016bfdd3a/sphinx_design-0.7.0-py3-none-any.whl", hash = "sha256:f82bf179951d58f55dca78ab3706aeafa496b741a91b1911d371441127d64282", size = 2220350, upload-time = "2026-01-19T13:12:51.077Z" }, +] + +[[package]] +name = "sphinx-external-toc" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "pyyaml" }, + { name = "sphinx" }, + { name = "sphinx-multitoc-numbering" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/f8/85bcd2f1c142e580a1394c18920506d9399b8e8e97e4899bbee9c74a896e/sphinx_external_toc-1.1.0.tar.gz", hash = "sha256:f81833865006f6b4a9b2550a2474a6e3d7e7f2cb23ba23309260577ea65552f6", size = 37194, upload-time = "2026-01-16T13:15:59.03Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/80/1704c9179012e289dee2178354e385277ea51f4fa827c4bf7e36c77b0f4b/sphinx_external_toc-1.1.0-py3-none-any.whl", hash = "sha256:26c390b8d85aa641366fed2d3674910ec6820f48b91027affef485a2655ad7d0", size = 30609, upload-time = "2026-01-16T13:15:57.926Z" }, +] + +[[package]] +name = "sphinx-jupyterbook-latex" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/29/18a1fc30e9315e72f068637079169525069a7c0b2fbe51cf689af0576214/sphinx_jupyterbook_latex-1.0.0.tar.gz", hash = "sha256:f54c6674c13f1616f9a93443e98b9b5353f9fdda8e39b6ec552ccf0b3e5ffb62", size = 11945, upload-time = "2023-12-11T15:37:25.034Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/1f/1d4ecaf58b17fe61497644655f40b04d84a88348e41a6f0c6392394d95e4/sphinx_jupyterbook_latex-1.0.0-py3-none-any.whl", hash = "sha256:e0cd3e9e1c5af69136434e21a533343fdf013475c410a414d5b7b4922b4f3891", size = 13319, upload-time = "2023-12-11T15:37:23.25Z" }, +] + +[[package]] +name = "sphinx-multitoc-numbering" +version = "0.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/37/1e/577bae038372885ebc34bd8c0f290295785a0250cac6528eb6d50e4b92d5/sphinx-multitoc-numbering-0.1.3.tar.gz", hash = "sha256:c9607671ac511236fa5d61a7491c1031e700e8d498c9d2418e6c61d1251209ae", size = 4542, upload-time = "2021-03-15T12:01:43.758Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/9f/902f2030674cd9473fdbe5a2c2dec2618c27ec853484c35f82cf8df40ece/sphinx_multitoc_numbering-0.1.3-py3-none-any.whl", hash = "sha256:33d2e707a9b2b8ad636b3d4302e658a008025106fe0474046c651144c26d8514", size = 4616, upload-time = "2021-03-15T12:01:42.419Z" }, +] + +[[package]] +name = "sphinx-thebe" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/fd/926ba4af1eb2708b1ac0fa4376e4bfb11d9a32b2a00e3614137a569c1ddf/sphinx_thebe-0.3.1.tar.gz", hash = "sha256:576047f45560e82f64aa5f15200b1eb094dcfe1c5b8f531a8a65bd208e25a493", size = 20789, upload-time = "2024-02-07T13:31:57.002Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/7c/a53bdb465fd364bc3d255d96d5d70e6ba5183cfb4e45b8aa91c59b099124/sphinx_thebe-0.3.1-py3-none-any.whl", hash = "sha256:e7e7edee9f0d601c76bc70156c471e114939484b111dd8e74fe47ac88baffc52", size = 9030, upload-time = "2024-02-07T13:31:55.286Z" }, +] + +[[package]] +name = "sphinx-togglebutton" +version = "0.4.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "setuptools" }, + { name = "sphinx" }, + { name = "wheel" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/be/169a0b0a8ad9588e8697c85e1d489aaaca7416073c2fc0267c360af5aae9/sphinx_togglebutton-0.4.5.tar.gz", hash = "sha256:c870dfbd3bc6e119b50ff9a37a64f8991902269e856728931c7d89877e8d4b3d", size = 18101, upload-time = "2026-03-27T13:50:41.984Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/2e/3dd55564928c5d61f92827d4b91307dde7911a40fbe0000645d73202eea9/sphinx_togglebutton-0.4.5-py3-none-any.whl", hash = "sha256:74eac6d2426110c3e1e6f989a98e07d7823141a335df1ad8a9d637bdf6a7af62", size = 44907, upload-time = "2026-03-27T13:50:40.94Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-bibtex" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "pybtex" }, + { name = "pybtex-docutils" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/15/6a/8e0b2c2420286389e7fed78ff361ec30e2f1d58c8560af8d64df5e7b61e0/sphinxcontrib_bibtex-2.7.0.tar.gz", hash = "sha256:fee700f7aae29bb8f654c62913f00d34ac44fc0b8ca0fa67ac922ff4453addee", size = 120669, upload-time = "2026-05-06T09:29:24.935Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/c0/d28e62407f4733bbe0169287bc012f0ac3b4a2021066b285570654119c8b/sphinxcontrib_bibtex-2.7.0-py3-none-any.whl", hash = "sha256:28cf0ec7a957d1c7548d5749317ed472ce877e1b629f430f88e3789aa51f87b1", size = 40287, upload-time = "2026-05-06T09:29:23.253Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-mermaid" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/75/3a1cc926da8c563c58ddc124a7b3fe5ccadcae96c96e3a6f8ac3653a210a/sphinxcontrib_mermaid-2.0.2.tar.gz", hash = "sha256:f09576c78ca93fa0e3034fd9c45aaffa7c44ab449de9c43b8b8d262afe52bc66", size = 19265, upload-time = "2026-05-05T13:59:02.959Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/8d/93be7e0f7fa915a576859b3bfac7a7baa3303181c44d7db7eefbd3e8a69f/sphinxcontrib_mermaid-2.0.2-py3-none-any.whl", hash = "sha256:d862e514991279fb4816302c5cfe167d2557bf3ce7125ae0cb47dac80a0f46ce", size = 14094, upload-time = "2026-05-05T13:59:01.585Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + +[[package]] +name = "sphinxext-opengraph" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/c0/eb6838e3bae624ce6c8b90b245d17e84252863150e95efdb88f92c8aa3fb/sphinxext_opengraph-0.13.0.tar.gz", hash = "sha256:103335d08567ad8468faf1425f575e3b698e9621f9323949a6c8b96d9793e80b", size = 1026875, upload-time = "2025-08-29T12:20:31.066Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/a4/66c1fd4f8fab88faf71cee04a945f9806ba0fef753f2cfc8be6353f64508/sphinxext_opengraph-0.13.0-py3-none-any.whl", hash = "sha256:936c07828edc9ad9a7b07908b29596dc84ed0b3ceaa77acdf51282d232d4d80e", size = 1004152, upload-time = "2025-08-29T12:20:29.072Z" }, +] + [[package]] name = "sqlalchemy" version = "2.0.48" @@ -4981,6 +6427,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" }, ] +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + [[package]] name = "starlette" version = "1.0.0" @@ -5006,6 +6466,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, ] +[[package]] +name = "tabulate" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/58/8c37dea7bbf769b20d58e7ace7e5edfe65b849442b00ffcdd56be88697c6/tabulate-0.10.0.tar.gz", hash = "sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d", size = 91754, upload-time = "2026-03-04T18:55:34.402Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl", hash = "sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3", size = 39814, upload-time = "2026-03-04T18:55:31.284Z" }, +] + [[package]] name = "threadpoolctl" version = "3.6.0" @@ -5069,6 +6538,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, ] +[[package]] +name = "tomlkit" +version = "0.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/db/03eaf4331631ef6b27d6e3c9b68c54dc6f0d63d87201fed600cc409307fd/tomlkit-0.15.0.tar.gz", hash = "sha256:7d1a9ecba3086638211b13814ea79c90dd54dd11993564376f3aa92271f5c7a3", size = 161875, upload-time = "2026-05-10T07:38:22.245Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/43/8bd850ee71a191bf072e31302c73a66be413fecdd98fdcd111ecbcce13ca/tomlkit-0.15.0-py3-none-any.whl", hash = "sha256:4dbc8f0fc024412b57ced8757ac7461305126a648ff8c2c807fcb8e133a78738", size = 41328, upload-time = "2026-05-10T07:38:23.517Z" }, +] + [[package]] name = "toolz" version = "1.1.0" @@ -5368,6 +6846,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c3/a2/c7f6ebf546f8f644edf0f999aa98ece106986a77a7b922316bf6414ff825/torchmetrics-1.9.0-py3-none-any.whl", hash = "sha256:bfdcbff3dd1d96b3374bb2496eb39f23c4b28b8a845b6a18c313688e0d2d9ca1", size = 983384, upload-time = "2026-03-09T17:41:19.756Z" }, ] +[[package]] +name = "tornado" +version = "6.5.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/64/24/95ec527ad67b76d59299e5465b3935d05e4294b7e0290a3924b7487df30b/tornado-6.5.7.tar.gz", hash = "sha256:66c513a76cda70d53907bc27cf1447557699c2e95aa48ba27a442ff61c3ddfc2", size = 519252, upload-time = "2026-06-08T17:34:51.232Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/dc/c7043cab6fed8ae159fc1923ce829ada35c4dbd797d408a43858ffaf9639/tornado-6.5.7-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:148b2eb15c2c765a50796172c1e499649b35f30d2e3c3d3e15913cfa56bfb163", size = 448543, upload-time = "2026-06-08T17:34:38.052Z" }, + { url = "https://files.pythonhosted.org/packages/92/4f/090b1431e5a43df696feceffc268c5383cc079ecb5f08ce58f917109aafe/tornado-6.5.7-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9da38de27f1da3b78a966f0dae12b5a1ea9afe72ca805d84ff06508272ddf100", size = 446707, upload-time = "2026-06-08T17:34:39.594Z" }, + { url = "https://files.pythonhosted.org/packages/37/d8/ef374952fd5da67d4463122c2b8e5a96536ec10b4b339254c6dcde81d01c/tornado-6.5.7-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8d759e71906ee783f8867b93bf26a265743da4c1e2f4a018464c1ba019862972", size = 449774, upload-time = "2026-06-08T17:34:41.204Z" }, + { url = "https://files.pythonhosted.org/packages/35/37/d434c73f4c6e014b745b9b37085f34f40c022f007efff3d7fe65991899f3/tornado-6.5.7-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a46347a18f23fb92b396beebe0fb78f61dda0cc302445202c16203d8a18848b", size = 450745, upload-time = "2026-06-08T17:34:42.531Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2b/56b9aff361d7f1ab728a805ec7d7ea835f8807afa9f5cc690ea0e630efb9/tornado-6.5.7-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7778b30bef919231265e91c69963ce0f49a1e9c07ac900bbe75b19ce2575ba92", size = 450578, upload-time = "2026-06-08T17:34:43.787Z" }, + { url = "https://files.pythonhosted.org/packages/02/30/a7444fb23aa76860a14198fab96ac79f1866b0a6e19e26c4381b0938e50f/tornado-6.5.7-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e726f0c75da7726eec023aa62751ff8878bd2737e34fbdd33b1ae5897d2200f5", size = 449985, upload-time = "2026-06-08T17:34:45.326Z" }, + { url = "https://files.pythonhosted.org/packages/5c/42/5f0e56c01e8d9d36f4e23f367b85ae6cae0c1ecddd5e6977d8388ad27488/tornado-6.5.7-cp39-abi3-win32.whl", hash = "sha256:f8de3bf12d3efdd0cbe7c8887868198f8a91415e3f29fcf258d9b8eb7b1d9ae4", size = 451047, upload-time = "2026-06-08T17:34:46.784Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a4/b393076ffb21b469eec5b328a0534cf03a3b90bfc6b1f09507cdd075d938/tornado-6.5.7-cp39-abi3-win_amd64.whl", hash = "sha256:de942f843533a039ef9fa3d9c88c7cd8a7c94553fb5ad0154270989b3d99a2c4", size = 451485, upload-time = "2026-06-08T17:34:48.248Z" }, + { url = "https://files.pythonhosted.org/packages/71/2e/7b1c769803121b809112cf9a00681c472eae1d80e32d7ec0e0bd61d0d0e1/tornado-6.5.7-cp39-abi3-win_arm64.whl", hash = "sha256:ff934fce95643af5f11efdae618eaa73d469dc588641e5c8d19295a0c65c4796", size = 450506, upload-time = "2026-06-08T17:34:49.702Z" }, +] + [[package]] name = "tqdm" version = "4.67.3" @@ -5380,6 +6875,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, ] +[[package]] +name = "traitlets" +version = "5.15.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/a9/a2584b8313b89f94869ddb3c4074617a691de1812a614d2d50e32ca5a7a6/traitlets-5.15.1.tar.gz", hash = "sha256:7b1c07854fe25acb39e009bae49f11b79ff6cbb2f27999104e9110e7a6b53722", size = 163344, upload-time = "2026-06-03T12:26:06.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/8d/1080ee4c231f361b6ce4470d556c8c435b67c7e0753aaa641497ee92f88b/traitlets-5.15.1-py3-none-any.whl", hash = "sha256:770a53705f84b81ac107e83a1b3328ff2dae16094d8fc3cfc004e4b22dfd8e92", size = 85858, upload-time = "2026-06-03T12:26:04.395Z" }, +] + [[package]] name = "triton" version = "3.6.0" @@ -5479,6 +6983,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, ] +[[package]] +name = "uc-micro-py" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/78/67/9a363818028526e2d4579334460df777115bdec1bb77c08f9db88f6389f2/uc_micro_py-2.0.0.tar.gz", hash = "sha256:c53691e495c8db60e16ffc4861a35469b0ba0821fe409a8a7a0a71864d33a811", size = 6611, upload-time = "2026-03-01T06:31:27.526Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/73/d21edf5b204d1467e06500080a50f79d49ef2b997c79123a536d4a17d97c/uc_micro_py-2.0.0-py3-none-any.whl", hash = "sha256:3603a3859af53e5a39bc7677713c78ea6589ff188d70f4fee165db88e22b242c", size = 6383, upload-time = "2026-03-01T06:31:26.257Z" }, +] + [[package]] name = "urllib3" version = "2.6.3" @@ -5577,6 +7090,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7f/b2/0bba9bbb4596d2d2f285a16c2ab04118f6b957d8441566e1abb892e6a6b2/werkzeug-3.1.7-py3-none-any.whl", hash = "sha256:4b314d81163a3e1a169b6a0be2a000a0e204e8873c5de6586f453c55688d422f", size = 226295, upload-time = "2026-03-24T01:08:06.133Z" }, ] +[[package]] +name = "wheel" +version = "0.47.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/62/75f18a0f03b4219c456652c7780e4d749b929eb605c098ce3a5b6b6bc081/wheel-0.47.0.tar.gz", hash = "sha256:cc72bd1009ba0cf63922e28f94d9d83b920aa2bb28f798a31d0691b02fa3c9b3", size = 63854, upload-time = "2026-04-22T15:51:27.727Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/1b/9e33c09813d65e248f7f773119148a612516a4bea93e9c6f545f78455b7c/wheel-0.47.0-py3-none-any.whl", hash = "sha256:212281cab4dff978f6cedd499cd893e1f620791ca6ff7107cf270781e587eced", size = 32218, upload-time = "2026-04-22T15:51:26.296Z" }, +] + [[package]] name = "win32-setctime" version = "1.2.0" From 3702b9dbef6341983b613821ae4261289a4475d4 Mon Sep 17 00:00:00 2001 From: Mohit-Lakra Date: Fri, 19 Jun 2026 22:55:05 +0530 Subject: [PATCH 06/15] fix: prevent browser auto-open from crashing headless GitHub Actions runner --- docs/_static/custom.css | 10 +++++----- docs/architecture/models.md | 2 +- docs/scripts/generate_docs.sh | 14 ++++++++------ 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/docs/_static/custom.css b/docs/_static/custom.css index e42ae7d0d..56b7b15ac 100644 --- a/docs/_static/custom.css +++ b/docs/_static/custom.css @@ -8,11 +8,11 @@ --pst-color-primary: #1d4ed8; /* Strong Blue */ --pst-color-primary-bg: #eff6ff; --pst-color-secondary: #0f172a; /* Slate 900 */ - + /* Text colors - Pure high contrast */ --pst-color-text-base: #0f172a; /* Nearly black */ --pst-color-text-muted: #475569; /* Slate 600 */ - + /* Backgrounds */ --pst-color-background: #ffffff; --pst-color-surface: #f8fafc; /* Slate 50 */ @@ -22,7 +22,7 @@ --pst-font-family-base: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; --pst-font-family-heading: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; --pst-font-family-monospace: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace; - + --pst-font-size-base: 17px; /* Increased for better visibility */ } @@ -30,10 +30,10 @@ html[data-theme="dark"] { --pst-color-primary: #60a5fa; /* Light Blue */ --pst-color-primary-bg: #1e3a8a; - + --pst-color-text-base: #f8fafc; /* Nearly white */ --pst-color-text-muted: #cbd5e1; - + --pst-color-background: #0f172a; /* Slate 900 */ --pst-color-surface: #1e293b; /* Slate 800 */ --pst-color-border: #334155; /* Slate 700 */ diff --git a/docs/architecture/models.md b/docs/architecture/models.md index 331a8f787..b82729e69 100644 --- a/docs/architecture/models.md +++ b/docs/architecture/models.md @@ -14,7 +14,7 @@ sequenceDiagram participant AR as ARModel participant F as Forecaster participant SP as StepPredictor - + Trainer->>AR: training_step(batch) loop for each timestep AR->>F: forward(prev_state) diff --git a/docs/scripts/generate_docs.sh b/docs/scripts/generate_docs.sh index 9f5abb969..18e5f45eb 100755 --- a/docs/scripts/generate_docs.sh +++ b/docs/scripts/generate_docs.sh @@ -37,10 +37,12 @@ fi INDEX_HTML="$DOCS_DIR/_build/html/index.html" echo "[OK] Build succeeded! Open: $INDEX_HTML" -if command -v open &>/dev/null; then - open "$INDEX_HTML" -elif command -v xdg-open &>/dev/null; then - xdg-open "$INDEX_HTML" -else - echo "Please open $INDEX_HTML in your browser manually." +if [ "${CI:-false}" != "true" ]; then + if command -v open &>/dev/null; then + open "$INDEX_HTML" || true + elif command -v xdg-open &>/dev/null; then + xdg-open "$INDEX_HTML" || true + else + echo "Please open $INDEX_HTML in your browser manually." + fi fi From d5b782013a938fc68f6e6883108db5afecd22417 Mon Sep 17 00:00:00 2001 From: Mohit-Lakra Date: Sun, 21 Jun 2026 21:41:01 +0530 Subject: [PATCH 07/15] docs: clean up prose, migrate to sphinx autoapi, and fix formatting --- .github/workflows/docs.yml | 44 +- .readthedocs.yaml | 4 +- diff.patch | 1921 +++++++++++++++++ docs/_config.yml | 124 -- docs/_toc.yml | 35 - docs/about/changelog.md | 1 - docs/about/glossary.md | 60 - docs/architecture/data-flow.md | 38 - docs/architecture/graph-construction.md | 66 - docs/architecture/models.md | 60 - docs/architecture/overview.md | 51 - docs/architecture/theory.md | 47 - docs/conf.py | 111 + docs/contributing/coding-standards.md | 41 - docs/contributing/development-setup.md | 102 - docs/getting-started/installation.md | 2 +- docs/getting-started/quickstart.md | 7 +- docs/guides/configuration.md | 58 - docs/index.md | 30 + docs/intro.md | 73 - .../create_reduced_meps_dataset.ipynb | 239 -- docs/scripts/__init__.py | 1 - docs/scripts/autoapi_astroid_patch.py | 41 - docs/scripts/generate_docs.sh | 48 - neural_lam/create_graph.py | 49 +- neural_lam/datastore/base.py | 17 +- neural_lam/datastore/mdp.py | 7 +- neural_lam/datastore/npyfilesmeps/store.py | 124 +- neural_lam/datastore/plot_example.py | 2 +- neural_lam/gnn_layers.py | 13 +- pyproject.toml | 2 - uv.lock | 284 --- 32 files changed, 2179 insertions(+), 1523 deletions(-) create mode 100644 diff.patch delete mode 100644 docs/_config.yml delete mode 100644 docs/_toc.yml delete mode 120000 docs/about/changelog.md delete mode 100644 docs/about/glossary.md delete mode 100644 docs/architecture/data-flow.md delete mode 100644 docs/architecture/graph-construction.md delete mode 100644 docs/architecture/models.md delete mode 100644 docs/architecture/overview.md delete mode 100644 docs/architecture/theory.md create mode 100644 docs/conf.py delete mode 100644 docs/contributing/coding-standards.md delete mode 100644 docs/contributing/development-setup.md delete mode 100644 docs/guides/configuration.md create mode 100644 docs/index.md delete mode 100644 docs/intro.md delete mode 100644 docs/notebooks/create_reduced_meps_dataset.ipynb delete mode 100644 docs/scripts/__init__.py delete mode 100644 docs/scripts/autoapi_astroid_patch.py delete mode 100755 docs/scripts/generate_docs.sh diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 272f4ac0c..74537fdc0 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -42,8 +42,10 @@ jobs: - name: Install docs dependencies run: uv sync --extra cpu --extra docs --group dev --no-cache - - name: Build docs with pyreverse and jupyter-book - run: bash docs/scripts/generate_docs.sh + + + - name: Build docs with sphinx + run: uv run sphinx-build -W --keep-going -b html docs/ docs/_build/html/ - name: Upload docs artifact uses: actions/upload-artifact@v4 @@ -72,41 +74,7 @@ jobs: - name: Install docs dependencies run: uv sync --extra cpu --extra docs --group dev --no-cache - - name: Generate UML diagrams - run: | - mkdir -p docs/_static/uml - uv run pyreverse -o mmd -p models neural_lam.models -d docs/_static/uml/ || true - - - name: Run linkcheck - run: uv run jupyter-book build docs/ --builder linkcheck || true - - deploy-docs: - name: Deploy to GitHub Pages - runs-on: ubuntu-latest - needs: build-docs - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - permissions: - contents: read - pages: write - id-token: write - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - steps: - - name: Setup Pages - uses: actions/configure-pages@v4 - - - name: Download artifact - uses: actions/download-artifact@v4 - with: - name: docs-html - path: docs/_build/html/ - - name: Upload pages artifact - uses: actions/upload-pages-artifact@v3 - with: - path: docs/_build/html/ - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 + - name: Run linkcheck + run: uv run sphinx-build -W --keep-going -b linkcheck docs/ docs/_build/linkcheck/ diff --git a/.readthedocs.yaml b/.readthedocs.yaml index a56ede2d6..22c12b446 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -7,7 +7,5 @@ build: commands: - pip install uv - uv pip install --system -e ".[cpu,docs]" - - mkdir -p docs/_static/uml - - pyreverse -o mmd -p models neural_lam.models -d docs/_static/uml/ || true - - jupyter-book build docs/ + - sphinx-build -W --keep-going -b html docs/ docs/_build/html/ - cp -r docs/_build/html/* $READTHEDOCS_OUTPUT/html/ diff --git a/diff.patch b/diff.patch new file mode 100644 index 000000000..20e7a61ad --- /dev/null +++ b/diff.patch @@ -0,0 +1,1921 @@ +diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml +index 272f4ac..80e8c45 100644 +--- a/.github/workflows/docs.yml ++++ b/.github/workflows/docs.yml +@@ -42,8 +42,13 @@ jobs: + - name: Install docs dependencies + run: uv sync --extra cpu --extra docs --group dev --no-cache + +- - name: Build docs with pyreverse and jupyter-book +- run: bash docs/scripts/generate_docs.sh ++ - name: Generate UML diagrams ++ run: | ++ mkdir -p docs/_static/uml ++ uv run pyreverse -o mmd -p models neural_lam.models -d docs/_static/uml/ ++ ++ - name: Build docs with sphinx ++ run: uv run sphinx-build -W --keep-going -b html docs/ docs/_build/html/ + + - name: Upload docs artifact + uses: actions/upload-artifact@v4 +@@ -75,38 +80,7 @@ jobs: + - name: Generate UML diagrams + run: | + mkdir -p docs/_static/uml +- uv run pyreverse -o mmd -p models neural_lam.models -d docs/_static/uml/ || true ++ uv run pyreverse -o mmd -p models neural_lam.models -d docs/_static/uml/ + + - name: Run linkcheck +- run: uv run jupyter-book build docs/ --builder linkcheck || true +- +- deploy-docs: +- name: Deploy to GitHub Pages +- runs-on: ubuntu-latest +- needs: build-docs +- if: github.event_name == 'push' && github.ref == 'refs/heads/main' +- permissions: +- contents: read +- pages: write +- id-token: write +- environment: +- name: github-pages +- url: ${{ steps.deployment.outputs.page_url }} +- steps: +- - name: Setup Pages +- uses: actions/configure-pages@v4 +- +- - name: Download artifact +- uses: actions/download-artifact@v4 +- with: +- name: docs-html +- path: docs/_build/html/ +- +- - name: Upload pages artifact +- uses: actions/upload-pages-artifact@v3 +- with: +- path: docs/_build/html/ +- +- - name: Deploy to GitHub Pages +- id: deployment +- uses: actions/deploy-pages@v4 ++ run: uv run sphinx-build -W --keep-going -b linkcheck docs/ docs/_build/linkcheck/ +diff --git a/.readthedocs.yaml b/.readthedocs.yaml +index a56ede2..463c52a 100644 +--- a/.readthedocs.yaml ++++ b/.readthedocs.yaml +@@ -8,6 +8,6 @@ build: + - pip install uv + - uv pip install --system -e ".[cpu,docs]" + - mkdir -p docs/_static/uml +- - pyreverse -o mmd -p models neural_lam.models -d docs/_static/uml/ || true +- - jupyter-book build docs/ ++ - pyreverse -o mmd -p models neural_lam.models -d docs/_static/uml/ ++ - sphinx-build -W --keep-going -b html docs/ docs/_build/html/ + - cp -r docs/_build/html/* $READTHEDOCS_OUTPUT/html/ +diff --git a/docs/_config.yml b/docs/_config.yml +deleted file mode 100644 +index 88fa2c0..0000000 +--- a/docs/_config.yml ++++ /dev/null +@@ -1,124 +0,0 @@ +-# docs/_config.yml +- +-title: "Neural-LAM" +-author: "MLLAM Community" +-copyright: "2024–2026" +- +-logo: "_static/logo.png" +-favicon: "_static/favicon.ico" +- +-repository: +- url: "https://github.com/mllam/neural-lam" +- branch: "main" +- path_to_book: "docs" +- +-launch_buttons: +- binderhub_url: "https://mybinder.org" +- colab_url: "https://colab.research.google.com" +- +-execute: +- execute_notebooks: "off" +- +-exclude_patterns: +- - _build +- - Thumbs.db +- - .DS_Store +- - "**.ipynb_checkpoints" +- - _static +- - scripts +- +-parse: +- myst_enable_extensions: +- - colon_fence +- - dollarmath +- - linkify +- - substitution +- - tasklist +- - deflist +- - fieldlist +- - html_admonition +- - html_image +- - smartquotes +- - attrs_inline +- myst_url_schemes: [mailto, http, https] +- +-sphinx: +- extra_extensions: +- - "scripts.autoapi_astroid_patch" +- - "autoapi.extension" +- - "sphinx.ext.napoleon" +- - "sphinx.ext.viewcode" +- - "sphinx.ext.intersphinx" +- - "sphinx_copybutton" +- - "sphinxcontrib.mermaid" +- - "sphinxext.opengraph" +- +- local_extensions: +- scripts.autoapi_astroid_patch: "." +- +- config: +- html_theme: "sphinx_book_theme" +- html_theme_options: +- repository_url: "https://github.com/mllam/neural-lam" +- use_repository_button: true +- use_issues_button: true +- use_edit_page_button: true +- repository_branch: "main" +- path_to_docs: "docs" +- show_navbar_depth: 2 +- navigation_with_keys: false +- show_toc_level: 2 +- logo: +- text: "Neural-LAM" +- announcement: "" +- extra_footer: | +-

Built with Jupyter Book | +- Source

+- +- html_static_path: ["_static"] +- html_css_files: ["custom.css"] +- +- # ── AutoAPI ── +- autoapi_dirs: ["../neural_lam"] +- autoapi_root: "api" +- autoapi_type: "python" +- autoapi_options: +- - "members" +- - "undoc-members" +- - "show-inheritance" +- - "show-module-summary" +- autoapi_python_class_content: "both" +- autoapi_member_order: "groupwise" +- autoapi_python_use_implicit_namespaces: false +- autoapi_keep_files: true +- autoapi_add_toctree_entry: true +- autoapi_ignore: ["**/tests/**", "**/conftest.py"] +- +- # ── Napoleon ── +- napoleon_numpy_docstring: true +- napoleon_google_docstring: false +- napoleon_include_init_with_doc: true +- napoleon_include_private_with_doc: false +- napoleon_include_special_with_doc: false +- napoleon_use_param: true +- napoleon_use_rtype: true +- +- # ── Intersphinx ── +- intersphinx_mapping: +- python: ["https://docs.python.org/3", null] +- numpy: ["https://numpy.org/doc/stable", null] +- torch: ["https://pytorch.org/docs/stable", null] +- pytorch_lightning: ["https://lightning.ai/docs/pytorch/stable/", null] +- torch_geometric: ["https://pytorch-geometric.readthedocs.io/en/latest/", null] +- +- # ── Mermaid ── +- mermaid_d3_zoom: false +- mermaid_version: "11" +- +- # ── Suppress Warnings ── +- suppress_warnings: ["autoapi.python_import_resolution"] +- +- # ── OpenGraph ── +- ogp_site_url: "https://mllam.github.io/neural-lam/" +- ogp_image: "_static/logo.png" +- ogp_use_first_image: true +diff --git a/docs/_toc.yml b/docs/_toc.yml +deleted file mode 100644 +index 2ef150f..0000000 +--- a/docs/_toc.yml ++++ /dev/null +@@ -1,35 +0,0 @@ +-format: jb-book +-root: intro +- +-parts: +- - caption: Getting Started +- chapters: +- - file: getting-started/installation +- - file: getting-started/quickstart +- +- - caption: Tutorials +- chapters: +- - file: notebooks/create_reduced_meps_dataset +- +- - caption: Architecture +- chapters: +- - file: architecture/overview +- - file: architecture/theory +- - file: architecture/data-flow +- - file: architecture/models +- - file: architecture/graph-construction +- +- - caption: Resources +- chapters: +- - file: guides/configuration +- - file: api/index +- +- - caption: Contributing +- chapters: +- - file: contributing/development-setup +- - file: contributing/coding-standards +- +- - caption: About +- chapters: +- - file: about/changelog +- - file: about/glossary +diff --git a/docs/about/changelog.md b/docs/about/changelog.md +deleted file mode 120000 +index 699cc9e..0000000 +--- a/docs/about/changelog.md ++++ /dev/null +@@ -1 +0,0 @@ +-../../CHANGELOG.md +\ No newline at end of file +diff --git a/docs/about/glossary.md b/docs/about/glossary.md +deleted file mode 100644 +index 13f8b8a..0000000 +--- a/docs/about/glossary.md ++++ /dev/null +@@ -1,60 +0,0 @@ +-# Glossary +- +-```{glossary} +-ARModel +- The autoregressive base model implemented in PyTorch Lightning. +- +-BaseDatastore +- The abstract base class for data loaders and datastores. +- +-Datastore +- The overarching data handler component for loading weather data. +- +-Encode-Process-Decode +- An architecture style often used in graph neural networks involving encoding inputs into latent graphs, processing them with message passing, and decoding back to the target space. +- +-Forcing Variables +- External variables providing boundary or context conditions to the {term}`Forecaster`. +- +-Forecaster +- The core component or model responsible for generating predictions over time. +- +-GNN +- Graph Neural Network. A type of neural network designed to operate on graph structures. +- +-GraphLAM +- The main Graph-based Limited Area Model implementation. +- +-HiLAM +- Hierarchical Limited Area Model. +- +-HiLAMParallel +- A parallelized version of {term}`HiLAM`. +- +-LAM +- Limited Area Model. A weather prediction model focused on a specific geographic region rather than global scope. +- +-MDPDatastore +- Datastore designed to read Zarr formats via the {term}`mllam-data-prep` module. +- +-Mesh +- The structured or unstructured graph grid onto which the weather data is projected and processed. +- +-mllam-data-prep +- The tool/module responsible for preparing and formatting raw weather data into a format ingestible by Neural-LAM. +- +-PyG +- PyTorch Geometric, a library for deep learning on irregular input data such as graphs. +- +-State Variables +- The set of variables that describe the current internal state of the weather system in the model. +- +-StepPredictor +- A model component designed to predict the next single time step given a current state. +- +-WeatherDataModule +- The PyTorch Lightning data module encapsulating the {term}`WeatherDataset`. +- +-WeatherDataset +- The PyTorch dataset class representing the prepared weather data. +-``` +diff --git a/docs/architecture/data-flow.md b/docs/architecture/data-flow.md +deleted file mode 100644 +index 9cdfe7a..0000000 +--- a/docs/architecture/data-flow.md ++++ /dev/null +@@ -1,38 +0,0 @@ +-# Data Flow +- +-## Overview +- +-The Neural-LAM data pipeline is designed to efficiently load raw meteorological data and feed it into the models for training and inference. It abstracts data sources, normalizes variables, and prepares batches using PyTorch Lightning. +- +-## Datastore Abstraction +- +-The {py:class}`neural_lam.datastore.BaseDatastore` provides an abstract interface for data access. It defines methods for fetching input and target tensors (`get_xy`), as well as metadata like variable names and units. Subclasses like `MDPDatastore` implement this interface to support specific formats like Zarr. +- +-## WeatherDataset +- +-The {py:class}`neural_lam.weather_dataset.WeatherDataset` acts as a PyTorch wrapper around a `BaseDatastore`. It handles indexing, temporal batching, and any necessary on-the-fly transformations required before the data reaches the Lightning module. +- +-## WeatherDataModule +- +-The {py:class}`neural_lam.weather_dataset.WeatherDataModule` is a PyTorch Lightning DataModule that encapsulates the `WeatherDataset`s for training, validation, and testing splits. It manages the dataloaders and ensures data is correctly distributed across devices during distributed training. +- +-## Configuration +- +-The entire data pipeline is driven by type-safe YAML configurations defined in {py:mod}`neural_lam.config`. Dataclasses define expected paths, batch sizes, and data normalization statistics, allowing easy experimentation without code changes. +- +-## Data Format Requirements +- +-| Component | Format/Shape Expected | +-|-----------|-----------------------| +-| Model Input | `(batch, sequence_length, num_grid_nodes, num_features)` | +-| Target Output | `(batch, sequence_length, num_grid_nodes, num_features)` | +-| Static Features | `(num_grid_nodes, num_static_features)` | +- +-```{mermaid} +-flowchart TD +- A["Raw Data Files"] -->|Read by| B["BaseDatastore"] +- B -->|get_xy| C["WeatherDataset"] +- C -->|__getitem__| D["DataLoader"] +- D -->|B, T, N, F| E["WeatherDataModule"] +- E -->|Batch| F["Model Forward Pass"] +-``` +diff --git a/docs/architecture/graph-construction.md b/docs/architecture/graph-construction.md +deleted file mode 100644 +index d50daad..0000000 +--- a/docs/architecture/graph-construction.md ++++ /dev/null +@@ -1,66 +0,0 @@ +-# Graph Construction +- +-## Why Graphs? +- +-Graph Neural Networks (GNNs) leverage graph-based message passing, which is highly suited for weather prediction. Unlike standard CNNs on rigid grids, graphs can naturally represent irregular spatial distributions and complex geometries, common in Limited Area Modeling (LAM). +- +-The graph structure dictates how information flows across spatial regions. A well-designed mesh ensures that localized weather phenomena correctly influence neighboring regions, while hierarchical structures allow long-range interactions (like large-scale pressure systems) to propagate efficiently across the domain without requiring hundreds of standard grid steps. +- +-## The create_graph Module +- +-The `create_graph` script is used to pre-compute and build the mesh graphs required before training any models. This ensures that the complex spatial structures are generated once and loaded efficiently during training. +- +-Reference: {py:mod}`neural_lam.create_graph` +- +-## Graph Types +- +-### Flat Mesh +-- Single level of mesh nodes +-- Used by GraphLAM +- +-### Hierarchical Mesh +-- Multiple levels of mesh nodes at increasing spatial scales +-- Used by HiLAM and HiLAMParallel +- +-```{mermaid} +-graph TD +- subgraph grid ["Grid Level"] +- G1["Grid Node"] --- G2["Grid Node"] --- G3["Grid Node"] +- end +- subgraph mesh1 ["Mesh Level 1"] +- M1["Mesh Node"] --- M2["Mesh Node"] +- end +- subgraph mesh2 ["Mesh Level 2"] +- M3["Mesh Node"] +- end +- G1 -.-> M1 +- G2 -.-> M1 +- G2 -.-> M2 +- G3 -.-> M2 +- M1 -.-> M3 +- M2 -.-> M3 +-``` +- +-## Edge Features +- +-For each edge type in the constructed graph, specific features are computed to assist message passing: +-- **Spatial Distance**: The physical distance between connected nodes. +-- **Directional Vectors**: Vector representations of the direction between nodes, enabling the GNN to understand flow and gradients (e.g., wind direction). +-- **Elevation Differences**: Changes in altitude between nodes, which is critical for orographic effects in weather. +- +-## Usage +- +-```bash +-python -m neural_lam.create_graph \ +- --config_path \ +- --name +-``` +- +-- `--config_path`: Path to the YAML configuration file which dictates the dataset properties and graph parameters. +-- `--name`: The name assigned to the generated graph, which is used to load it during training. +- +-## GNN Layers +- +-The `InteractionNet` represents the core GNN layer implementation in Neural-LAM. It utilizes PyTorch Geometric's `MessagePassing` interface to aggregate features from neighboring nodes and update node states, incorporating edge features natively to refine the spatial interactions. +- +-Reference: {py:mod}`neural_lam.gnn_layers` +diff --git a/docs/architecture/models.md b/docs/architecture/models.md +deleted file mode 100644 +index b82729e..0000000 +--- a/docs/architecture/models.md ++++ /dev/null +@@ -1,60 +0,0 @@ +-# Model Architectures +- +-## Overview +- +-Neural-LAM employs a modular model hierarchy based on the **encode-process-decode** paradigm. This paradigm allows the models to encode grid-based weather data into a graph structure, perform spatial and temporal message passing to process the data, and finally decode the updated graph states back into the original grid representation. +- +-## Autoregressive Framework +- +-The core training and evaluation loop is handled by `ARModel`, a PyTorch Lightning module defined in `neural_lam/models/module.py`. This autoregressive framework is responsible for unrolling predictions over multiple timesteps. +- +-```{mermaid} +-sequenceDiagram +- participant Trainer as Lightning Trainer +- participant AR as ARModel +- participant F as Forecaster +- participant SP as StepPredictor +- +- Trainer->>AR: training_step(batch) +- loop for each timestep +- AR->>F: forward(prev_state) +- F->>SP: predict_step(state, graph) +- SP-->>F: next_state_delta +- F-->>AR: next_state +- end +- AR->>AR: compute_loss() +- AR-->>Trainer: loss +-``` +- +-## Encode-Process-Decode +- +-The base graph model, defined in `neural_lam/models/step_predictors/base.py`, implements the standard encode-process-decode steps: +-1. **Encode**: Features from the weather grid are mapped onto the nodes and edges of the mesh graph. +-2. **Process**: A Graph Neural Network (GNN) performs multiple rounds of message passing to propagate information across the spatial domain. +-3. **Decode**: The updated mesh features are mapped back to the grid to produce the next state prediction. +- +-## GraphLAM +- +-GraphLAM is the fundamental model architecture utilizing a single-level flat graph. It is effective for standard resolution forecasting without multi-scale processing. +- +-Reference: {py:class}`neural_lam.models.step_predictors.graph.GraphLAM` +- +-## HiLAM +- +-HiLAM introduces a hierarchical model design, utilizing multiple levels of mesh nodes at increasing spatial scales. This allows the network to efficiently capture both local, fine-grained interactions and long-range, global atmospheric patterns. +- +-Reference: {py:class}`neural_lam.models.step_predictors.graph.HiLAM` +- +-## HiLAMParallel +- +-HiLAMParallel is a parallel hierarchical variant of HiLAM. It processes multi-scale information simultaneously across different hierarchy levels, rather than sequentially, potentially improving computational efficiency and long-range interaction modeling. +- +-Reference: {py:class}`neural_lam.models.step_predictors.graph.HiLAMParallel` +- +-## Choosing a Model +- +-| Model | Graph Type | Complexity | Best For | +-|---|---|---|---| +-| **GraphLAM** | Flat Mesh | Low | Baseline forecasting, single-scale dynamics | +-| **HiLAM** | Hierarchical Mesh | Medium | Capturing both local and global dependencies efficiently | +-| **HiLAMParallel** | Hierarchical Mesh | High | Highly parallel environments, very large spatial domains | +diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md +deleted file mode 100644 +index 7d5c8ab..0000000 +--- a/docs/architecture/overview.md ++++ /dev/null +@@ -1,51 +0,0 @@ +-# Architecture Overview +- +-## System Design +- +-Neural-LAM is designed with a modular architecture that separates data handling, model architecture, and training logic. This separation of concerns allows for easy experimentation with different graph structures, model components, and datasets without requiring extensive changes to the core system. +- +-The core components include a robust data pipeline utilizing an abstract datastore interface, a flexible set of graph-based neural network models (such as `GraphLAM`, `HiLAM`, and `HiLAMParallel`), and a training module built on top of PyTorch Lightning. This design ensures scalability and ease of use for both researchers and practitioners. +- +-## Data Flow +- +-```{mermaid} +-flowchart LR +- A["Raw Data
(zarr / numpy)"] --> B["Datastore
(BaseDatastore)"] +- B --> C["WeatherDataset"] +- C --> D["WeatherDataModule
(Lightning)"] +- D --> E["ARModel
(Autoregressive)"] +- E --> F["StepPredictor
(GNN)"] +- F --> G["Predictions"] +- G --> H["Loss & Metrics"] +-``` +- +-## Module Map +- +-| Module | Responsibility | +-|--------|----------------| +-| `datastore` | Handles reading from diverse data sources (e.g., Zarr, NetCDF) via the `BaseDatastore` interface. | +-| `weather_dataset` | Wraps the datastore in a PyTorch `Dataset` and Lightning `DataModule` for training. | +-| `models` | Contains the core neural network architectures (`ARModel`, `BaseGraphModel`, `GraphLAM`, etc.). | +-| `create_graph` | Utility to build the hierarchical mesh graphs used by the GNN models. | +-| `config` | Manages the YAML-based configuration via dataclasses. | +- +-## Component Interaction +- +-This diagram is **automatically generated** from the Python source code using `pyreverse`, ensuring it never goes stale! +- +-```{eval-rst} +-.. mermaid:: ../_static/uml/classes_models.mmd +-``` +- +-## Key Design Decisions +- +-- **Modular Datastores**: `BaseDatastore` abstracts away data loading intricacies. +-- **PyTorch Lightning**: Used to reduce boilerplate and scale training easily. +-- **Hierarchical Graphs**: `create_graph` decouples graph structure generation from model logic. +-- **Dataclass Configurations**: Type-safe YAML configurations managed by `dataclass-wizard`. +- +-## See Also +- +-- {doc}`data-flow` for detailed data pipeline documentation +-- {doc}`models` for model architecture details +-- {doc}`graph-construction` for graph creation details +diff --git a/docs/architecture/theory.md b/docs/architecture/theory.md +deleted file mode 100644 +index a636c53..0000000 +--- a/docs/architecture/theory.md ++++ /dev/null +@@ -1,47 +0,0 @@ +-# Theory & Methods +- +-This section describes the mathematical formulations and algorithms underlying the Neural-LAM graph models. +- +-## The Encode-Process-Decode Paradigm +- +-All graph-based models in Neural-LAM (`GraphLAM`, `HiLAM`, and `HiLAMParallel`) follow the classic "Encode-Process-Decode" paradigm for graph neural networks. +- +-1. **Encode**: The input grid state \( X^t \) at timestep \( t \) is mapped to latent node features on the graph: +- \[ H_{grid} = \text{Encoder}(X^t) \] +- +-2. **Process**: A series of message passing steps updates the latent node representations based on the graph connectivity: +- \[ H'_{grid} = \text{Processor}(H_{grid}, \mathcal{G}) \] +- +-3. **Decode**: The updated latent features are mapped back to predict the residual change for the next timestep: +- \[ \Delta X^{t+1} = \text{Decoder}(H'_{grid}) \] +- \[ X^{t+1} = X^t + \Delta X^{t+1} \] +- +-## Message Passing Functions +- +-Neural-LAM supports several GNN layers for message passing along different edge types (e.g., Grid-to-Mesh, Mesh-to-Mesh). +- +-### InteractionNet +- +-The default layer is the `InteractionNet` (based on Interaction Networks). Given a sender node \( v_s \), a receiver node \( v_r \), and an edge feature \( e_{s,r} \), the message passing works as follows: +- +-1. **Edge Update (Message Formulation)**: +- \[ m_{s,r} = \text{MLP}_{edge}\left([h_s, h_r, e_{s,r}]\right) \] +- +-2. **Node Update (Aggregation)**: +- \[ h'_r = \text{MLP}_{node}\left([h_r, \sum_{s \in \mathcal{N}(r)} m_{s,r}]\right) \] +- +-### PropagationNet +- +-`PropagationNet` modifies the Interaction Network to strongly incentivize directional information flow from senders to receivers, which is crucial for moving information up and down the hierarchical mesh levels: +- +-\[ h'_r = h_r + \text{MLP}_{prop}\left([h_r, \sum_{s \in \mathcal{N}(r)} m_{s,r}]\right) \] +- +-This residual connection ensures that nodes maintain their internal state while selectively incorporating new information from neighbors. +- +-## Loss Weighting +- +-To balance predictions across variables with different physical scales and variances, the loss function uses a weighted Mean Squared Error (MSE): +- +-\[ \mathcal{L} = \frac{1}{V \cdot N} \sum_{v=1}^V \sum_{i=1}^N w_v \cdot \left( \hat{X}^{t+1}_{i,v} - X^{t+1}_{i,v} \right)^2 \] +- +-Where \( w_v \) are variable-specific weights defined in the `loss_weighting` module, and the errors are computed on standardized variables. +diff --git a/docs/contributing/coding-standards.md b/docs/contributing/coding-standards.md +deleted file mode 100644 +index c1655d7..0000000 +--- a/docs/contributing/coding-standards.md ++++ /dev/null +@@ -1,41 +0,0 @@ +-# Coding Standards +- +-## Code Style +- +-Our codebase enforces strict styling guidelines using the following tools: +-- **Formatter**: `black` +-- **Import Sorting**: `isort` +-- **Linting**: `flake8` +-- **Type Checking**: `mypy` +-- **Docstring Coverage**: `interrogate` +-- **Spell Checking**: `codespell` +- +-## Pull Request Process +- +-1. **Search before creating**: Search existing issues or PRs to avoid duplicates. +-2. **Every PR requires an issue**: Open an issue first if none exists. +-3. **Link the issue**: Include `closes #` or `refs #` in the PR body. +-4. **Use the PR template**: Fill out every section of the provided template. +-5. **Run pre-commit hooks**: Ensure code passes locally via `uvx pre-commit run --all-files`. +-6. **Run tests**: Run `pytest tests/` and fix any failures before opening the PR. +-7. **Update CHANGELOG**: Add a line to `CHANGELOG.md` in the appropriate section. +- +-## Docstring Standard +- +-- We use **NumPy-style** docstrings. +-- We require **100% docstring coverage** for public functions, methods, and classes. +-- Always include `Parameters`, `Returns`, and `Raises` sections if applicable. +-- Make sure to specify **tensor shapes** in the docstrings when passing or returning tensors. +- +-## Commit Messages +- +-- Must be in the **imperative form** (e.g., "Add test for feature X" instead of "Added test..."). +-- Keep **one concern per PR** to ensure unrelated changes are not mixed. +-- AI attribution of tool names is mandatory if used and should be mentioned in the commit message trailer as `Co-authored-by `. +- +-## Adding Documentation +- +-When adding new modules or classes: +-1. Include detailed docstrings directly in the code. +-2. If appropriate, create a new Markdown page under `docs/architecture/` (or update an existing one). +-3. Ensure the new page is added to the `_toc.yml` so it appears in the documentation navigation structure. +diff --git a/docs/contributing/development-setup.md b/docs/contributing/development-setup.md +deleted file mode 100644 +index 93f3a2c..0000000 +--- a/docs/contributing/development-setup.md ++++ /dev/null +@@ -1,102 +0,0 @@ +-# Development Setup +- +-## Prerequisites +- +-- Python >=3.10 +-- Git +-- uv (recommended) or pip +- +-## Clone and Install +- +-```bash +-git clone https://github.com/mllam/neural-lam.git +-cd neural-lam +-uv sync --extra cpu --group dev --locked +-source .venv/bin/activate +-``` +- +-## Pre-commit Hooks +- +-We use `pre-commit` to ensure code formatting and quality before commits. The hooks include: +-- `black` for code formatting. +-- `isort` for import sorting. +-- `flake8` for linting. +-- `mypy` for static type checking. +-- `codespell` for spell checking. +-- `interrogate` for docstring coverage. +- +-To install and run the pre-commit hooks: +- +-```bash +-pre-commit install +-uvx pre-commit run --all-files +-``` +- +-## Running Tests +- +-We use `pytest` for running our test suite. Note that Weights & Biases (W&B) is automatically disabled during tests. +- +-Run all tests: +-```bash +-pytest -vv -s --doctest-modules +-``` +- +-Run tests in a single file: +-```bash +-pytest tests/test_training.py -vv -s +-``` +- +-Run a single function test: +-```bash +-pytest tests/test_training.py::test_fn -vv +-``` +- +-## Building Documentation +- +-We use `jupyter-book` to build the documentation. You can build it using `uv`: +- +-```bash +-uv run jb build docs +-``` +- +-## Project Structure +- +-- `docs/` - Project documentation. +-- `neural_lam/` - Main source code directory containing core modules, models, and data logic. +- - `datastore/` - Datastore classes for loading data. +- - `models/` - Core neural network models. +-- `tests/` - Unit tests and test data examples. +- +-## Writing Docstrings +- +-We follow the NumPy-style format for docstrings. All functions and classes must have docstrings including Parameters, Returns, Raises, and Tensor shapes where applicable. +- +-Example: +-```python +-import torch +- +-def process_state(state: torch.Tensor, threshold: float = 0.5) -> torch.Tensor: +- """ +- Process the given state tensor by applying a threshold. +- +- Parameters +- ---------- +- state : torch.Tensor +- The input state tensor of shape (batch_size, num_features). +- threshold : float, optional +- The threshold value to apply, by default 0.5. +- +- Returns +- ------- +- torch.Tensor +- The processed state tensor of shape (batch_size, num_features). +- +- Raises +- ------ +- ValueError +- If the state tensor is empty. +- """ +- if state.numel() == 0: +- raise ValueError("State tensor cannot be empty.") +- return torch.where(state > threshold, state, torch.zeros_like(state)) +-``` +diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md +index 60b0029..96fbd11 100644 +--- a/docs/getting-started/quickstart.md ++++ b/docs/getting-started/quickstart.md +@@ -27,7 +27,7 @@ Before training, you must construct a graph mesh for your data. + python -m neural_lam.create_graph --config_path --name + ``` + +-This script builds the mesh graph required by the GNN models. For more details, see {doc}`../architecture/graph-construction`. ++This script builds the mesh graph required by the GNN models. + + ## Step 3: Train a Model + +@@ -37,7 +37,7 @@ Now you can train a model using the graph and configuration. + python -m neural_lam.train_model --config_path --model graph_lam --graph + ``` + +-Neural-LAM supports several models like `graph_lam`, `hilam`, and `hilam_parallel`. For more information on the available models, see {doc}`../architecture/models`. ++Neural-LAM supports several models like `graph_lam`, `hilam`, and `hilam_parallel`. + + ## Step 4: Evaluate + +@@ -51,7 +51,4 @@ python -m neural_lam.train_model --eval test --config_path --lo + + Neural-LAM uses a YAML configuration system powered by `dataclass-wizard`. The configuration defines the dataset paths, training parameters, and model hyperparameters. For complete details, see the API reference for {py:class}`neural_lam.config.NeuralLAMConfig`. + +-## Next Steps +- +-- {doc}`../architecture/overview` to understand the system design + - {doc}`../api/index` for complete API reference +diff --git a/docs/guides/configuration.md b/docs/guides/configuration.md +deleted file mode 100644 +index c7b9856..0000000 +--- a/docs/guides/configuration.md ++++ /dev/null +@@ -1,58 +0,0 @@ +-# Configuration Reference +- +-Neural-LAM uses `dataclass-wizard` to automatically parse and enforce type-safety for YAML configuration files. The configuration encapsulates both the **Datastore** configuration and the **Model/Training** configuration. +- +-Below are common templates you can use for your own runs. +- +-## 1. Quick CPU Test Run +- +-This minimal configuration uses a dummy datastore, perfect for quickly testing changes on a laptop without a GPU. +- +-```yaml +-# tests/test_config.yaml +-datastore: +- _target_: "neural_lam.datastore.DummyDatastore" +- +-architecture: "graph_lam" +-epochs: 2 +-batch_size: 2 +-lr: 1e-3 +-hidden_dim: 32 +-hidden_layers: 1 +-``` +- +-## 2. MDP (Meteorological Data Processing) Full Training +- +-This is a production-level configuration for training on a real Zarr dataset produced by `mllam-data-prep` on a GPU cluster. +- +-```yaml +-# config/mdp_training.yaml +-datastore: +- _target_: "neural_lam.datastore.MDPDatastore" +- dataset_path: "/path/to/my/zarr_dataset.zarr" +- subset_name: "meps" # Optional subset +- +-# Model architecture settings +-architecture: "hi_lam_parallel" +-hidden_dim: 128 +-hidden_layers: 4 +-mesh_aggr: "sum" +- +-# GNN Types for the different edges +-g2m_gnn_type: "interaction" +-m2g_gnn_type: "interaction" +-mesh_up_gnn_type: "propagation" +-mesh_down_gnn_type: "propagation" +- +-# Training parameters +-epochs: 100 +-batch_size: 16 +-lr: 5e-4 +-loss: "mse" +- +-# Logging and reproducibility +-seed: 42 +-``` +- +-## API Reference +-For an exhaustive list of every configurable field and its default value, refer to the auto-generated documentation for the `NeuralLAMConfig` dataclass in the {py:mod}`neural_lam.config` module. +diff --git a/docs/intro.md b/docs/intro.md +deleted file mode 100644 +index fee7572..0000000 +--- a/docs/intro.md ++++ /dev/null +@@ -1,73 +0,0 @@ +-# Neural-LAM +- +-**Graph-based neural weather prediction for Limited Area Modeling** +- +-Neural-LAM is a PyTorch and PyTorch Lightning framework for high-resolution weather prediction using Graph Neural Networks. It provides a modular approach to Limited Area Modeling, supporting multiple graph-based architectures such as GraphLAM, HiLAM, and HiLAMParallel to process and predict meteorological data efficiently. +- +-```{admonition} Get Started in 5 Minutes +-:class: tip +-Install Neural-LAM and run your first prediction. +-See the {doc}`Getting Started Guide `. +-``` +- +-::::{grid} 2 +-:gutter: 3 +- +-:::{grid-item-card} 🚀 Getting Started +-:link: getting-started/installation +-:link-type: doc +-Installation guide and quickstart tutorial to get you up and running. +-::: +- +- +-:::{grid-item-card} 🏗️ Architecture +-:link: architecture/overview +-:link-type: doc +-Understand the data flow, model structure, and design decisions. +-::: +- +-:::{grid-item-card} 📚 API Reference +-:link: api/index +-:link-type: doc +-Auto-generated reference for all modules, classes, and functions. +-::: +- +-:::: +- +-## Key Features +- +-- **Modular design**: Swap datastores, models, and graph structures independently +-- **Multiple model architectures**: GraphLAM (flat), HiLAM (hierarchical), HiLAMParallel (parallel hierarchical) +-- **Flexible data handling**: Abstract datastore interface supporting zarr, numpy, and custom formats via mllam-data-prep +-- **Production-ready**: PyTorch Lightning for training, W&B/MLflow logging, checkpoint management +- +-## Publications +- +-If you use Neural-LAM in your research, please cite the relevant papers: +- +-**NeurIPS 2024 Paper** ([Probabilistic Weather Forecasting with Hierarchical Graph Neural Networks](https://arxiv.org/abs/2406.04759)) +-```bibtex +-@inproceedings{oskarsson2024probabilistic, +- title = {Probabilistic Weather Forecasting with Hierarchical Graph Neural Networks}, +- author = {Oskarsson, Joel and Landelius, Tomas and Deisenroth, Marc Peter and Lindsten, Fredrik}, +- booktitle = {Advances in Neural Information Processing Systems}, +- volume = {37}, +- year = {2024}, +-} +-``` +- +-**NeurIPS 2023 Workshop Paper** ([Graph-based Neural Weather Prediction for Limited Area Modeling](https://arxiv.org/abs/2309.17370)) +-```bibtex +-@inproceedings{oskarsson2023graphbased, +- title={Graph-based Neural Weather Prediction for Limited Area Modeling}, +- author={Oskarsson, Joel and Landelius, Tomas and Lindsten, Fredrik}, +- booktitle={NeurIPS 2023 Workshop on Tackling Climate Change with Machine Learning}, +- year={2023} +-} +-``` +- +-## Quick Links +- +-- [GitHub Repository](https://github.com/mllam/neural-lam) +-- [Issue Tracker](https://github.com/mllam/neural-lam/issues) +-- [MLLAM Community Slack](https://kutt.it/mllam) +diff --git a/docs/notebooks/create_reduced_meps_dataset.ipynb b/docs/notebooks/create_reduced_meps_dataset.ipynb +deleted file mode 100644 +index 00cfa24..0000000 +--- a/docs/notebooks/create_reduced_meps_dataset.ipynb ++++ /dev/null +@@ -1,239 +0,0 @@ +-{ +- "cells": [ +- { +- "cell_type": "markdown", +- "metadata": {}, +- "source": [ +- "# Creating meps_example_reduced\n", +- "This notebook outlines how the small-size test dataset ```meps_example_reduced``` was created based on the slightly larger dataset ```meps_example```. The zipped up datasets are 263 MB and 2.6 GB, respectively. See [README](https://github.com/mllam/neural-lam#data) for info on how to download ```meps_example```.\n", +- "\n", +- "The dataset was reduced in size by reducing the number of grid points and variables.\n" +- ] +- }, +- { +- "cell_type": "code", +- "execution_count": 2, +- "metadata": {}, +- "outputs": [], +- "source": [ +- "# Standard library\n", +- "import os\n", +- "\n", +- "# Third-party\n", +- "import numpy as np\n", +- "import torch" +- ] +- }, +- { +- "cell_type": "markdown", +- "metadata": {}, +- "source": [ +- "\n", +- "The number of grid points was reduced to 1/4 by halving the number of coordinates in both the x and y direction. This was done by removing a quarter of the grid points along each outer edge, so the center grid points would stay centered in the new set.\n", +- "\n" +- ] +- }, +- { +- "cell_type": "code", +- "execution_count": null, +- "metadata": {}, +- "outputs": [], +- "source": [ +- "# Load existing grid\n", +- "grid_xy = np.load('data/meps_example/static/nwp_xy.npy')\n", +- "# Get slices in each dimension by cutting off a quarter along each edge\n", +- "num_x, num_y = grid_xy.shape[1:]\n", +- "x_slice = slice(num_x//4, 3*num_x//4)\n", +- "y_slice = slice(num_y//4, 3*num_y//4)\n", +- "# Index and save reduced grid\n", +- "grid_xy_reduced = grid_xy[:, x_slice, y_slice]\n", +- "np.save('data/meps_example_reduced/static/nwp_xy.npy', grid_xy_reduced)" +- ] +- }, +- { +- "cell_type": "markdown", +- "metadata": {}, +- "source": [ +- "\n", +- "This cut out the border, so a new perimeter of 10 grid points was established as border (10 was also the border size in the original \"meps_example\").\n" +- ] +- }, +- { +- "cell_type": "code", +- "execution_count": 6, +- "metadata": {}, +- "outputs": [], +- "source": [ +- "# Outer 10 grid points are border\n", +- "old_border_mask = np.load('data/meps_example/static/border_mask.npy')\n", +- "assert np.all(old_border_mask[10:-10, 10:-10] == False)\n", +- "assert np.all(old_border_mask[:10, :] == True)\n", +- "assert np.all(old_border_mask[:, :10] == True)\n", +- "assert np.all(old_border_mask[-10:,:] == True)\n", +- "assert np.all(old_border_mask[:,-10:] == True)\n", +- "\n", +- "# Create new array with False everywhere but the outer 10 grid points\n", +- "border_mask = np.zeros_like(grid_xy_reduced[0,:,:], dtype=bool)\n", +- "border_mask[:10] = True\n", +- "border_mask[:,:10] = True\n", +- "border_mask[-10:] = True\n", +- "border_mask[:,-10:] = True\n", +- "np.save('data/meps_example_reduced/static/border_mask.npy', border_mask)" +- ] +- }, +- { +- "cell_type": "markdown", +- "metadata": {}, +- "source": [ +- "A few other files also needed to be copied using only the new reduced grid" +- ] +- }, +- { +- "cell_type": "code", +- "execution_count": null, +- "metadata": {}, +- "outputs": [], +- "source": [ +- "# Load surface_geopotential.npy, index only values from the reduced grid, and save to new file\n", +- "surface_geopotential = np.load('data/meps_example/static/surface_geopotential.npy')\n", +- "surface_geopotential_reduced = surface_geopotential[x_slice, y_slice]\n", +- "np.save('data/meps_example_reduced/static/surface_geopotential.npy', surface_geopotential_reduced)\n", +- "\n", +- "# Load pytorch file grid_features.pt\n", +- "grid_features = torch.load('data/meps_example/static/grid_features.pt')\n", +- "# Index only values from the reduced grid. \n", +- "# First reshape from (num_grid_points_total, 4) to (num_grid_points_x, num_grid_points_y, 4), \n", +- "# then index, then reshape back to new total number of grid points\n", +- "print(grid_features.shape)\n", +- "grid_features_new = grid_features.reshape(num_x, num_y, 4)[x_slice,y_slice,:].reshape((-1, 4))\n", +- "# Save to new file\n", +- "torch.save(grid_features_new, 'data/meps_example_reduced/static/grid_features.pt')\n", +- "\n", +- "# flux_stats.pt is just a vector of length 2, so the grid shape and variable changes does not change this file\n", +- "torch.save(torch.load('data/meps_example/static/flux_stats.pt'), 'data/meps_example_reduced/static/flux_stats.pt')" +- ] +- }, +- { +- "cell_type": "markdown", +- "metadata": {}, +- "source": [ +- "\n", +- "The number of variables was reduced by truncating the variable list to the first 8." +- ] +- }, +- { +- "cell_type": "code", +- "execution_count": null, +- "metadata": {}, +- "outputs": [], +- "source": [ +- "num_vars = 8\n", +- "\n", +- "# Load parameter_weights.npy, truncate to first 8 variables, and save to new file\n", +- "parameter_weights = np.load('data/meps_example/static/parameter_weights.npy')\n", +- "parameter_weights_reduced = parameter_weights[:num_vars]\n", +- "np.save('data/meps_example_reduced/static/parameter_weights.npy', parameter_weights_reduced)\n", +- "\n", +- "# Do the same for following 4 pytorch files\n", +- "for file in ['diff_mean', 'diff_std', 'parameter_mean', 'parameter_std']:\n", +- " old_file = torch.load(f'data/meps_example/static/{file}.pt')\n", +- " new_file = old_file[:num_vars]\n", +- " torch.save(new_file, f'data/meps_example_reduced/static/{file}.pt')" +- ] +- }, +- { +- "cell_type": "markdown", +- "metadata": {}, +- "source": [ +- "Lastly the files in each of the directories train, test, and val have to be reduced. The folders all have the same structure with files of the following types:\n", +- "```\n", +- "nwp_YYYYMMDDHH_mbrXXX.npy\n", +- "wtr_YYYYMMDDHH.npy\n", +- "nwp_toa_downwelling_shortwave_flux_YYYYMMDDHH.npy\n", +- "```\n", +- "with ```YYYYMMDDHH``` being some date with hours, and ```XXX``` being some 3-digit integer.\n", +- "\n", +- "The first type of file has x and y in dimensions 1 and 2, and variable index in dimension 3. Dimension 0 is unchanged.\n", +- "The second type has has x and y in dimensions 1 and 2. Dimension 0 is unchanged.\n", +- "The last type has just x and y as the only 2 dimensions.\n", +- "\n" +- ] +- }, +- { +- "cell_type": "code", +- "execution_count": 12, +- "metadata": {}, +- "outputs": [ +- { +- "name": "stdout", +- "output_type": "stream", +- "text": [ +- "(65, 268, 238, 18)\n", +- "(65, 268, 238)\n" +- ] +- } +- ], +- "source": [ +- "print(np.load('data/meps_example/samples/train/nwp_2022040100_mbr000.npy').shape)\n", +- "print(np.load('data/meps_example/samples/train/nwp_toa_downwelling_shortwave_flux_2022040112.npy').shape)" +- ] +- }, +- { +- "cell_type": "markdown", +- "metadata": {}, +- "source": [ +- "The following loop goes through each file in each sample folder and indexes them according to the dimensions given by the file name." +- ] +- }, +- { +- "cell_type": "code", +- "execution_count": null, +- "metadata": {}, +- "outputs": [], +- "source": [ +- "for sample in ['train', 'test', 'val']:\n", +- " files = os.listdir(f'data/meps_example/samples/{sample}')\n", +- "\n", +- " for f in files:\n", +- " data = np.load(f'data/meps_example/samples/{sample}/{f}')\n", +- " if 'mbr' in f:\n", +- " data = data[:,x_slice,y_slice,:num_vars]\n", +- " elif 'wtr' in f:\n", +- " data = data[x_slice, y_slice]\n", +- " else:\n", +- " data = data[:,x_slice,y_slice]\n", +- " np.save(f'data/meps_example_reduced/samples/{sample}/{f}', data)" +- ] +- }, +- { +- "cell_type": "markdown", +- "metadata": {}, +- "source": [ +- "Lastly, the file ```data_config.yaml``` is modified manually by truncating the variable units, long and short names, and setting the new grid shape. Also the unit descriptions containing ```^``` was automatically parsed using latex, and to avoid having to install latex in the GitHub CI/CD pipeline, this was changed to ```**```. \n", +- "\n", +- "This new config file was placed in ```data/meps_example_reduced```, and that directory was then zipped and placed in a European Weather Cloud S3 bucket." +- ] +- } +- ], +- "metadata": { +- "kernelspec": { +- "display_name": "Python 3", +- "language": "python", +- "name": "python3" +- }, +- "language_info": { +- "codemirror_mode": { +- "name": "ipython", +- "version": 3 +- }, +- "file_extension": ".py", +- "mimetype": "text/x-python", +- "name": "python", +- "nbconvert_exporter": "python", +- "pygments_lexer": "ipython3", +- "version": "3.10.14" +- } +- }, +- "nbformat": 4, +- "nbformat_minor": 2 +-} +diff --git a/docs/scripts/__init__.py b/docs/scripts/__init__.py +deleted file mode 100644 +index 359aa93..0000000 +--- a/docs/scripts/__init__.py ++++ /dev/null +@@ -1 +0,0 @@ +-"""Helper scripts used during documentation builds.""" +diff --git a/docs/scripts/autoapi_astroid_patch.py b/docs/scripts/autoapi_astroid_patch.py +deleted file mode 100644 +index 8b647af..0000000 +--- a/docs/scripts/autoapi_astroid_patch.py ++++ /dev/null +@@ -1,41 +0,0 @@ +-""" +-Patch AutoAPI for astroid >= 4 compatibility. +- +-This module patches the `AstroidBuilder` from `astroid` to pass a `manager` +-argument to the parent class `__init__`, which is strictly required for +-astroid >= 4. AutoAPI does not natively pass this argument in its current +-versions, so this workaround prevents documentation build errors. +-""" # codespell:ignore astroid +- +-from __future__ import annotations +- +-# Standard library +-import inspect +- +-try: +- # Third-party +- from astroid import builder as astroid_builder # codespell:ignore astroid +- from astroid.manager import AstroidManager # codespell:ignore astroid +- +- ASTROID_AVAILABLE = True +-except ImportError: +- ASTROID_AVAILABLE = False +- +- +-def setup(app): +- if not ASTROID_AVAILABLE: +- return {"version": "0.1"} +- +- builder_init = astroid_builder.AstroidBuilder.__init__ +- if "manager" not in inspect.signature(builder_init).parameters: +- return {"version": "0.1"} +- original_builder = astroid_builder.AstroidBuilder +- +- class AutoapiAstroidBuilder(original_builder): +- def __init__(self, *args, **kwargs): +- if not args and "manager" not in kwargs: +- kwargs["manager"] = AstroidManager() +- super().__init__(*args, **kwargs) +- +- astroid_builder.AstroidBuilder = AutoapiAstroidBuilder +- return {"version": "0.1"} +diff --git a/docs/scripts/generate_docs.sh b/docs/scripts/generate_docs.sh +deleted file mode 100755 +index 18e5f45..0000000 +--- a/docs/scripts/generate_docs.sh ++++ /dev/null +@@ -1,48 +0,0 @@ +-#!/usr/bin/env bash +-# docs/scripts/generate_docs.sh +- +-set -euo pipefail +- +-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +-REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +-DOCS_DIR="$REPO_ROOT/docs" +- +-if ! command -v uv &>/dev/null; then +- echo "[ERROR] uv not found. Please install uv first." >&2 +- exit 1 +-fi +- +-echo "[INFO] Syncing dependencies..." +-uv pip install -e ".[cpu,docs]" +- +-echo "[INFO] Removing old build at docs/_build..." +-rm -rf "$DOCS_DIR/_build" +- +-echo "[INFO] Generating UML architecture diagrams with pyreverse..." +-mkdir -p "$DOCS_DIR/_static/uml" +-if [ -f "$REPO_ROOT/.venv/bin/pyreverse" ]; then +- (cd "$REPO_ROOT" && "$REPO_ROOT/.venv/bin/pyreverse" -o mmd -p models neural_lam.models -d "$DOCS_DIR/_static/uml/" || echo "[WARN] pyreverse failed, continuing...") +-else +- (cd "$REPO_ROOT" && pyreverse -o mmd -p models neural_lam.models -d "$DOCS_DIR/_static/uml/" || echo "[WARN] pyreverse failed, continuing...") +-fi +- +-echo "[INFO] Building site with jupyter-book..." +-if [ -f "$REPO_ROOT/.venv/bin/jupyter-book" ]; then +- "$REPO_ROOT/.venv/bin/jupyter-book" build docs/ --keep-going +-else +- # fallback to uv run if .venv structure differs +- uv run jupyter-book build docs/ --keep-going +-fi +- +-INDEX_HTML="$DOCS_DIR/_build/html/index.html" +-echo "[OK] Build succeeded! Open: $INDEX_HTML" +- +-if [ "${CI:-false}" != "true" ]; then +- if command -v open &>/dev/null; then +- open "$INDEX_HTML" || true +- elif command -v xdg-open &>/dev/null; then +- xdg-open "$INDEX_HTML" || true +- else +- echo "Please open $INDEX_HTML in your browser manually." +- fi +-fi +diff --git a/neural_lam/create_graph.py b/neural_lam/create_graph.py +index c67e439..3f6fb5f 100644 +--- a/neural_lam/create_graph.py ++++ b/neural_lam/create_graph.py +@@ -275,42 +275,33 @@ def create_graph( + `graph_dir_path`. + + Creates the following files for all graphs: ++ + - g2m_edge_index.pt [2, N_g2m_edges] + - g2m_features.pt [N_g2m_edges, d_features] + - m2g_edge_index.pt [2, N_m2m_edges] + - m2g_features.pt [N_m2m_edges, d_features] + - m2m_edge_index.pt list of [2, N_m2m_edges_level], length==n_levels +- - m2m_features.pt list of [N_m2m_edges_level, d_features], +- length==n_levels +- - mesh_features.pt list of [N_mesh_nodes_level, d_mesh_static], +- length==n_levels +- +- where +- d_features: +- number of features per edge (currently d_features==3, for +- edge-length, x and y) +- N_g2m_edges: +- number of edges in the graph from grid-to-mesh +- N_m2g_edges: +- number of edges in the graph from mesh-to-grid +- N_m2m_edges_level: +- number of edges in the graph from mesh-to-mesh at a given level +- (list index corresponds to the level) +- d_mesh_static: +- number of static features per mesh node (currently +- d_mesh_static==2, for x and y) +- N_mesh_nodes_level: +- number of nodes in the mesh at a given level ++ - m2m_features.pt list of [N_m2m_edges_level, d_features], length==n_levels ++ - mesh_features.pt list of [N_mesh_nodes_level, d_mesh_static], length==n_levels ++ ++ where: ++ ++ * d_features: number of features per edge (currently d_features==3, for ++ edge-length, x and y) ++ * N_g2m_edges: number of edges in the graph from grid-to-mesh ++ * N_m2g_edges: number of edges in the graph from mesh-to-grid ++ * N_m2m_edges_level: number of edges in the graph from mesh-to-mesh at a given level ++ (list index corresponds to the level) ++ * d_mesh_static: number of static features per mesh node (currently ++ d_mesh_static==2, for x and y) ++ * N_mesh_nodes_level: number of nodes in the mesh at a given level + + And in addition for hierarchical graphs: +- - mesh_up_edge_index.pt +- list of [2, N_mesh_updown_edges_level], length==n_levels-1 +- - mesh_up_features.pt +- list of [N_mesh_updown_edges_level, d_features], length==n_levels-1 +- - mesh_down_edge_index.pt +- list of [2, N_mesh_updown_edges_level], length==n_levels-1 +- - mesh_down_features.pt +- list of [N_mesh_updown_edges_level, d_features], length==n_levels-1 ++ ++ - mesh_up_edge_index.pt: list of [2, N_mesh_updown_edges_level], length==n_levels-1 ++ - mesh_up_features.pt: list of [N_mesh_updown_edges_level, d_features], length==n_levels-1 ++ - mesh_down_edge_index.pt: list of [2, N_mesh_updown_edges_level], length==n_levels-1 ++ - mesh_down_features.pt: list of [N_mesh_updown_edges_level, d_features], length==n_levels-1 + + where N_mesh_updown_edges_level is the number of edges in the graph from + mesh-to-mesh between two consecutive levels (list index corresponds index +diff --git a/neural_lam/datastore/base.py b/neural_lam/datastore/base.py +index 8376d38..8c17282 100644 +--- a/neural_lam/datastore/base.py ++++ b/neural_lam/datastore/base.py +@@ -421,6 +421,7 @@ class BaseDatastore(abc.ABC): + ---------- + category : str + The category of the dataset (state/forcing/static). ++ + Returns + ------- + List[str] +@@ -481,12 +482,13 @@ class BaseRegularGridDatastore(BaseDatastore): + + The following methods and attributes must be implemented for datastore that + represents regular-gridded data: +- - `grid_shape_state` (property): 2D shape of the grid for the state ++ ++ * `grid_shape_state` (property): 2D shape of the grid for the state + variables. +- - `get_xy` (method): Return the x, y coordinates of the dataset, with the ++ * `get_xy` (method): Return the x, y coordinates of the dataset, with the + option to not stack the coordinates (so that they are returned as a 2D + grid). +- - `get_lat_lon` (method): Return the latitude/longitude coordinates of ++ * `get_lat_lon` (method): Return the latitude/longitude coordinates of + the dataset for convenience when plotting. + + The operation of going from (x,y)-indexed regular grid +@@ -526,10 +528,11 @@ class BaseRegularGridDatastore(BaseDatastore): + ------- + np.ndarray + The x, y coordinates of the dataset, returned differently based on +- the value of `stacked`: - `stacked==True`: shape `(n_grid_points, +- 2)` where +- n_grid_points=N_x*N_y. +- - `stacked==False`: shape `(N_x, N_y, 2)` ++ the value of `stacked`: ++ ++ * `stacked==True`: shape `(n_grid_points, 2)` where ++ n_grid_points=N_x*N_y. ++ * `stacked==False`: shape `(N_x, N_y, 2)` + """ + + def unstack_grid_coords( +diff --git a/neural_lam/datastore/mdp.py b/neural_lam/datastore/mdp.py +index 7cad45d..cceacc5 100644 +--- a/neural_lam/datastore/mdp.py ++++ b/neural_lam/datastore/mdp.py +@@ -500,9 +500,10 @@ class MDPDatastore(BaseRegularGridDatastore): + np.ndarray + The x, y coordinates of the dataset, returned differently based on + the value of `stacked`: +- - `stacked==True`: shape `(n_grid_points, 2)` where +- n_grid_points=N_x*N_y. +- - `stacked==False`: shape `(N_x, N_y, 2)` ++ ++ * `stacked==True`: shape `(n_grid_points, 2)` where ++ n_grid_points=N_x*N_y. ++ * `stacked==False`: shape `(N_x, N_y, 2)` + + """ + # assume variables are stored in dimensions [grid_index, ...] +diff --git a/neural_lam/datastore/npyfilesmeps/store.py b/neural_lam/datastore/npyfilesmeps/store.py +index e14bbc7..e86c5e7 100644 +--- a/neural_lam/datastore/npyfilesmeps/store.py ++++ b/neural_lam/datastore/npyfilesmeps/store.py +@@ -84,60 +84,60 @@ class NpyFilesDatastoreMEPS(BaseRegularGridDatastore): + `[y, x]`. + + +- Folder structure: +- +- meps_example_reduced +- ├── data_config.yaml +- ├── samples +- │ ├── test +- │ │ ├── nwp_2022090100_mbr000.npy +- │ │ ├── nwp_2022090100_mbr001.npy +- │ │ ├── nwp_2022090112_mbr000.npy +- │ │ ├── nwp_2022090112_mbr001.npy +- │ │ ├── ... +- │ │ ├── nwp_toa_downwelling_shortwave_flux_2022090100.npy +- │ │ ├── nwp_toa_downwelling_shortwave_flux_2022090112.npy +- │ │ ├── ... +- │ │ ├── wtr_2022090100.npy +- │ │ ├── wtr_2022090112.npy +- │ │ └── ... +- │ ├── train +- │ │ ├── nwp_2022040100_mbr000.npy +- │ │ ├── nwp_2022040100_mbr001.npy +- │ │ ├── ... +- │ │ ├── nwp_2022040112_mbr000.npy +- │ │ ├── nwp_2022040112_mbr001.npy +- │ │ ├── ... +- │ │ ├── nwp_toa_downwelling_shortwave_flux_2022040100.npy +- │ │ ├── nwp_toa_downwelling_shortwave_flux_2022040112.npy +- │ │ ├── ... +- │ │ ├── wtr_2022040100.npy +- │ │ ├── wtr_2022040112.npy +- │ │ └── ... +- │ └── val +- │ ├── nwp_2022060500_mbr000.npy +- │ ├── nwp_2022060500_mbr001.npy +- │ ├── ... +- │ ├── nwp_2022060512_mbr000.npy +- │ ├── nwp_2022060512_mbr001.npy +- │ ├── ... +- │ ├── nwp_toa_downwelling_shortwave_flux_2022060500.npy +- │ ├── nwp_toa_downwelling_shortwave_flux_2022060512.npy +- │ ├── ... +- │ ├── wtr_2022060500.npy +- │ ├── wtr_2022060512.npy +- │ └── ... +- └── static +- ├── border_mask.npy +- ├── diff_mean.pt +- ├── diff_std.pt +- ├── flux_stats.pt +- ├── grid_features.pt +- ├── nwp_xy.npy +- ├── parameter_mean.pt +- ├── parameter_std.pt +- ├── parameter_weights.npy +- └── surface_geopotential.npy ++ Folder structure:: ++ ++ meps_example_reduced ++ ├── data_config.yaml ++ ├── samples ++ │ ├── test ++ │ │ ├── nwp_2022090100_mbr000.npy ++ │ │ ├── nwp_2022090100_mbr001.npy ++ │ │ ├── nwp_2022090112_mbr000.npy ++ │ │ ├── nwp_2022090112_mbr001.npy ++ │ │ ├── ... ++ │ │ ├── nwp_toa_downwelling_shortwave_flux_2022090100.npy ++ │ │ ├── nwp_toa_downwelling_shortwave_flux_2022090112.npy ++ │ │ ├── ... ++ │ │ ├── wtr_2022090100.npy ++ │ │ ├── wtr_2022090112.npy ++ │ │ └── ... ++ │ ├── train ++ │ │ ├── nwp_2022040100_mbr000.npy ++ │ │ ├── nwp_2022040100_mbr001.npy ++ │ │ ├── ... ++ │ │ ├── nwp_2022040112_mbr000.npy ++ │ │ ├── nwp_2022040112_mbr001.npy ++ │ │ ├── ... ++ │ │ ├── nwp_toa_downwelling_shortwave_flux_2022040100.npy ++ │ │ ├── nwp_toa_downwelling_shortwave_flux_2022040112.npy ++ │ │ ├── ... ++ │ │ ├── wtr_2022040100.npy ++ │ │ ├── wtr_2022040112.npy ++ │ │ └── ... ++ │ └── val ++ │ ├── nwp_2022060500_mbr000.npy ++ │ ├── nwp_2022060500_mbr001.npy ++ │ ├── ... ++ │ ├── nwp_2022060512_mbr000.npy ++ │ ├── nwp_2022060512_mbr001.npy ++ │ ├── ... ++ │ ├── nwp_toa_downwelling_shortwave_flux_2022060500.npy ++ │ ├── nwp_toa_downwelling_shortwave_flux_2022060512.npy ++ │ ├── ... ++ │ ├── wtr_2022060500.npy ++ │ ├── wtr_2022060512.npy ++ │ └── ... ++ └── static ++ ├── border_mask.npy ++ ├── diff_mean.pt ++ ├── diff_std.pt ++ ├── flux_stats.pt ++ ├── grid_features.pt ++ ├── nwp_xy.npy ++ ├── parameter_mean.pt ++ ├── parameter_std.pt ++ ├── parameter_weights.npy ++ └── surface_geopotential.npy + + For the MEPS dataset: + N_t' = 65 +@@ -244,11 +244,10 @@ class NpyFilesDatastoreMEPS(BaseRegularGridDatastore): + xr.DataArray + The data array for the given category and split, with dimensions + per category: +- state: `[elapsed_forecast_duration, analysis_time, grid_index, +- feature, ensemble_member]` +- forcing: `[elapsed_forecast_duration, analysis_time, grid_index, +- feature]` +- static: `[grid_index, feature]` ++ ++ * state: ``[elapsed_forecast_duration, analysis_time, grid_index, feature, ensemble_member]`` ++ * forcing: ``[elapsed_forecast_duration, analysis_time, grid_index, feature]`` ++ * static: ``[grid_index, feature]`` + + """ + if category == "state": +@@ -669,9 +668,10 @@ class NpyFilesDatastoreMEPS(BaseRegularGridDatastore): + np.ndarray + The x, y coordinates of the dataset (with x first then y second), + returned differently based on the value of `stacked`: +- - `stacked==True`: shape `(n_grid_points, 2)` where +- n_grid_points=N_x*N_y. +- - `stacked==False`: shape `(N_x, N_y, 2)` ++ ++ * `stacked==True`: shape `(n_grid_points, 2)` where ++ n_grid_points=N_x*N_y. ++ * `stacked==False`: shape `(N_x, N_y, 2)` + + """ + +diff --git a/neural_lam/datastore/plot_example.py b/neural_lam/datastore/plot_example.py +index 13f32e5..789282b 100644 +--- a/neural_lam/datastore/plot_example.py ++++ b/neural_lam/datastore/plot_example.py +@@ -34,7 +34,7 @@ def plot_example_from_datastore( + Whether to standardize the data before plotting, by default True. + selection : dict, optional + Selections to apply to the dataarray, for example +- `time="1990-09-03T0:00" would select this single timestep, by default ++ `time="1990-09-03T0:00"` would select this single timestep, by default + {}. + index_selection: dict, optional + Index-based selection to apply to the dataarray, for example +diff --git a/neural_lam/gnn_layers.py b/neural_lam/gnn_layers.py +index 7a92ea7..99a0014 100644 +--- a/neural_lam/gnn_layers.py ++++ b/neural_lam/gnn_layers.py +@@ -182,6 +182,7 @@ class InteractionNet(pyg.nn.MessagePassing): + ) -> tuple[torch.Tensor, torch.Tensor]: + """ + Overridden aggregation function to: ++ + * return both aggregated and per-edge messages, + * only aggregate to the number of receiver nodes (``self.num_rec``) + rather than to ``dim_size``. +@@ -260,9 +261,15 @@ def get_gnn_class(gnn_type: str) -> Type[pyg.nn.MessagePassing]: + """ + Look up a GNN class by name. + +- gnn_type: One of the keys in GNN_TYPES +- (currently "InteractionNet" or "PropagationNet") +- Returns the corresponding GNN class. ++ Parameters ++ ---------- ++ gnn_type : str ++ One of the keys in GNN_TYPES (currently "InteractionNet" or "PropagationNet") ++ ++ Returns ++ ------- ++ Type[pyg.nn.MessagePassing] ++ The corresponding GNN class. + """ + if gnn_type not in GNN_TYPES: + raise ValueError( +diff --git a/pyproject.toml b/pyproject.toml +index e9da32e..6e31531 100644 +--- a/pyproject.toml ++++ b/pyproject.toml +@@ -50,7 +50,6 @@ cpu = ["torch>=2.12,<2.13"] + gpu = ["torch>=2.12,<2.13"] # CUDA 13.0, default GPU build + gpu-cu128 = ["torch>=2.11,<2.12"] # CUDA 12.8, last torch series with cu128 wheels + docs = [ +- "jupyter-book>=1.0.0,<2.0.0", + "sphinx-autoapi>=3.0.0", + "sphinx-copybutton>=0.5.2", + "sphinx-book-theme>=1.1.0", +diff --git a/uv.lock b/uv.lock +index 786dad4..00f853f 100644 +--- a/uv.lock ++++ b/uv.lock +@@ -2228,37 +2228,6 @@ wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, + ] + +-[[package]] +-name = "jupyter-book" +-version = "1.0.4.post1" +-source = { registry = "https://pypi.org/simple" } +-dependencies = [ +- { name = "click" }, +- { name = "jinja2" }, +- { name = "jsonschema" }, +- { name = "linkify-it-py" }, +- { name = "myst-nb" }, +- { name = "myst-parser" }, +- { name = "pyyaml" }, +- { name = "sphinx" }, +- { name = "sphinx-book-theme", version = "1.1.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, +- { name = "sphinx-book-theme", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, +- { name = "sphinx-comments" }, +- { name = "sphinx-copybutton" }, +- { name = "sphinx-design", version = "0.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, +- { name = "sphinx-design", version = "0.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, +- { name = "sphinx-external-toc" }, +- { name = "sphinx-jupyterbook-latex" }, +- { name = "sphinx-multitoc-numbering" }, +- { name = "sphinx-thebe" }, +- { name = "sphinx-togglebutton" }, +- { name = "sphinxcontrib-bibtex" }, +-] +-sdist = { url = "https://files.pythonhosted.org/packages/cf/ee/5d10ce5b161764ad44219853f386e98b535cb3879bcb0d7376961a1e3897/jupyter_book-1.0.4.post1.tar.gz", hash = "sha256:2fe92c49ff74840edc0a86bb034eafdd0f645fca6e48266be367ce4d808b9601", size = 67412, upload-time = "2025-02-28T14:55:48.637Z" } +-wheels = [ +- { url = "https://files.pythonhosted.org/packages/37/86/d45756beaeb4b9b06125599b429451f8640b5db6f019d606f33c85743fd4/jupyter_book-1.0.4.post1-py3-none-any.whl", hash = "sha256:3a27a6b2581f1894ffe8f347d1a3432f06fc616997547919c42cd41c54db625d", size = 45005, upload-time = "2025-02-28T14:55:46.561Z" }, +-] +- + [[package]] + name = "jupyter-cache" + version = "1.0.1" +@@ -2432,15 +2401,6 @@ wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/dd/8050c947d435c8d4bc94e3252f4d8bb8a76cfb424f043a8680be637a57f1/kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1", size = 73558, upload-time = "2026-03-09T13:15:52.112Z" }, + ] + +-[[package]] +-name = "latexcodec" +-version = "3.0.1" +-source = { registry = "https://pypi.org/simple" } +-sdist = { url = "https://files.pythonhosted.org/packages/27/dd/4270b2c5e2ee49316c3859e62293bd2ea8e382339d63ab7bbe9f39c0ec3b/latexcodec-3.0.1.tar.gz", hash = "sha256:e78a6911cd72f9dec35031c6ec23584de6842bfbc4610a9678868d14cdfb0357", size = 31222, upload-time = "2025-06-17T18:47:34.051Z" } +-wheels = [ +- { url = "https://files.pythonhosted.org/packages/b5/40/23569737873cc9637fd488606347e9dd92b9fa37ba4fcda1f98ee5219a97/latexcodec-3.0.1-py3-none-any.whl", hash = "sha256:a9eb8200bff693f0437a69581f7579eb6bca25c4193515c09900ce76451e452e", size = 18532, upload-time = "2025-06-17T18:47:30.726Z" }, +-] +- + [[package]] + name = "lightning-utilities" + version = "0.15.3" +@@ -3148,7 +3108,6 @@ cpu = [ + { name = "torch", version = "2.12.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, + ] + docs = [ +- { name = "jupyter-book" }, + { name = "linkify-it-py" }, + { name = "myst-nb" }, + { name = "myst-parser" }, +@@ -3180,7 +3139,6 @@ requires-dist = [ + { name = "boto3", specifier = ">=1.35.32" }, + { name = "cartopy", specifier = ">=0.22.0" }, + { name = "dataclass-wizard", specifier = "<0.31.0" }, +- { name = "jupyter-book", marker = "extra == 'docs'", specifier = ">=1.0.0,<2.0.0" }, + { name = "linkify-it-py", marker = "extra == 'docs'", specifier = ">=2.0.0" }, + { name = "matplotlib", specifier = ">=3.7.0" }, + { name = "mlflow", specifier = ">=2.16.2" }, +@@ -4454,32 +4412,6 @@ wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, + ] + +-[[package]] +-name = "pybtex" +-version = "0.26.1" +-source = { registry = "https://pypi.org/simple" } +-dependencies = [ +- { name = "latexcodec" }, +- { name = "pyyaml" }, +-] +-sdist = { url = "https://files.pythonhosted.org/packages/4d/f5/f30da9c93f0fa6d619332b2f69597219b625f35780473a05164a9981fd9a/pybtex-0.26.1.tar.gz", hash = "sha256:2e5543bea424e60e9e42eef70bff597be48649d8f68ba061a7a092b2477d5464", size = 692991, upload-time = "2026-04-03T13:05:39.014Z" } +-wheels = [ +- { url = "https://files.pythonhosted.org/packages/44/f6/775eb92e865b28cdb4ad1f2bed7a5446197516f76b58a950faa3be3fd08d/pybtex-0.26.1-py3-none-any.whl", hash = "sha256:e26c0412cc54f5f21b2a6d9d175762a2d2af9ccf3a8f651cdb89ec035db77aa1", size = 126134, upload-time = "2026-04-03T13:05:40.623Z" }, +-] +- +-[[package]] +-name = "pybtex-docutils" +-version = "1.0.3" +-source = { registry = "https://pypi.org/simple" } +-dependencies = [ +- { name = "docutils" }, +- { name = "pybtex" }, +-] +-sdist = { url = "https://files.pythonhosted.org/packages/7e/84/796ea94d26188a853660f81bded39f8de4cfe595130aef0dea1088705a11/pybtex-docutils-1.0.3.tar.gz", hash = "sha256:3a7ebdf92b593e00e8c1c538aa9a20bca5d92d84231124715acc964d51d93c6b", size = 18348, upload-time = "2023-08-22T18:47:54.833Z" } +-wheels = [ +- { url = "https://files.pythonhosted.org/packages/11/b1/ce1f4596211efb5410e178a803f08e59b20bedb66837dcf41e21c54f9ec1/pybtex_docutils-1.0.3-py3-none-any.whl", hash = "sha256:8fd290d2ae48e32fcb54d86b0efb8d573198653c7e2447d5bec5847095f430b9", size = 6385, upload-time = "2023-08-22T06:43:20.513Z" }, +-] +- + [[package]] + name = "pycparser" + version = "3.0" +@@ -6119,18 +6051,6 @@ wheels = [ + { url = "https://files.pythonhosted.org/packages/02/bf/6f506a37c7f8ecc4576caf9486e303c7af249f6d70447bb51dde9d78cb99/sphinx_book_theme-1.2.0-py3-none-any.whl", hash = "sha256:709605d308e1991c5ef0cf19c481dbe9084b62852e317fafab74382a0ee7ccfa", size = 455936, upload-time = "2026-03-09T23:20:28.788Z" }, + ] + +-[[package]] +-name = "sphinx-comments" +-version = "0.0.3" +-source = { registry = "https://pypi.org/simple" } +-dependencies = [ +- { name = "sphinx" }, +-] +-sdist = { url = "https://files.pythonhosted.org/packages/c0/75/5bbf29e83eaf79843180cf424d0d550bda14a1792ca51dcf79daa065ba93/sphinx-comments-0.0.3.tar.gz", hash = "sha256:00170afff27019fad08e421da1ae49c681831fb2759786f07c826e89ac94cf21", size = 7960, upload-time = "2020-08-12T00:07:31.183Z" } +-wheels = [ +- { url = "https://files.pythonhosted.org/packages/26/97/a5c39f619375d4f81d5422377fb027075898efa6b6202c1ccf1e5bb38a32/sphinx_comments-0.0.3-py3-none-any.whl", hash = "sha256:1e879b4e9bfa641467f83e3441ac4629225fc57c29995177d043252530c21d00", size = 4591, upload-time = "2020-08-12T00:07:30.297Z" }, +-] +- + [[package]] + name = "sphinx-copybutton" + version = "0.5.2" +@@ -6143,126 +6063,6 @@ wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/48/1ea60e74949eecb12cdd6ac43987f9fd331156388dcc2319b45e2ebb81bf/sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e", size = 13343, upload-time = "2023-04-14T08:10:20.844Z" }, + ] + +-[[package]] +-name = "sphinx-design" +-version = "0.6.1" +-source = { registry = "https://pypi.org/simple" } +-resolution-markers = [ +- "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", +- "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +- "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +- "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +- "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +-] +-dependencies = [ +- { name = "sphinx", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, +-] +-sdist = { url = "https://files.pythonhosted.org/packages/2b/69/b34e0cb5336f09c6866d53b4a19d76c227cdec1bbc7ac4de63ca7d58c9c7/sphinx_design-0.6.1.tar.gz", hash = "sha256:b44eea3719386d04d765c1a8257caca2b3e6f8421d7b3a5e742c0fd45f84e632", size = 2193689, upload-time = "2024-08-02T13:48:44.277Z" } +-wheels = [ +- { url = "https://files.pythonhosted.org/packages/c6/43/65c0acbd8cc6f50195a3a1fc195c404988b15c67090e73c7a41a9f57d6bd/sphinx_design-0.6.1-py3-none-any.whl", hash = "sha256:b11f37db1a802a183d61b159d9a202314d4d2fe29c163437001324fe2f19549c", size = 2215338, upload-time = "2024-08-02T13:48:42.106Z" }, +-] +- +-[[package]] +-name = "sphinx-design" +-version = "0.7.0" +-source = { registry = "https://pypi.org/simple" } +-resolution-markers = [ +- "python_full_version >= '3.14' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", +- "python_full_version == '3.13.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", +- "python_full_version == '3.12.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", +- "python_full_version == '3.11.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", +- "python_full_version >= '3.14' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +- "python_full_version == '3.13.*' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +- "python_full_version == '3.12.*' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +- "python_full_version == '3.11.*' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +- "python_full_version >= '3.14' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +- "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +- "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +- "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +- "python_full_version >= '3.14' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +- "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +- "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +- "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +- "python_full_version >= '3.14' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +- "python_full_version == '3.13.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +- "python_full_version == '3.12.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +- "python_full_version == '3.11.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", +-] +-dependencies = [ +- { name = "sphinx", marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, +-] +-sdist = { url = "https://files.pythonhosted.org/packages/13/7b/804f311da4663a4aecc6cf7abd83443f3d4ded970826d0c958edc77d4527/sphinx_design-0.7.0.tar.gz", hash = "sha256:d2a3f5b19c24b916adb52f97c5f00efab4009ca337812001109084a740ec9b7a", size = 2203582, upload-time = "2026-01-19T13:12:53.297Z" } +-wheels = [ +- { url = "https://files.pythonhosted.org/packages/30/cf/45dd359f6ca0c3762ce0490f681da242f0530c49c81050c035c016bfdd3a/sphinx_design-0.7.0-py3-none-any.whl", hash = "sha256:f82bf179951d58f55dca78ab3706aeafa496b741a91b1911d371441127d64282", size = 2220350, upload-time = "2026-01-19T13:12:51.077Z" }, +-] +- +-[[package]] +-name = "sphinx-external-toc" +-version = "1.1.0" +-source = { registry = "https://pypi.org/simple" } +-dependencies = [ +- { name = "click" }, +- { name = "pyyaml" }, +- { name = "sphinx" }, +- { name = "sphinx-multitoc-numbering" }, +-] +-sdist = { url = "https://files.pythonhosted.org/packages/c5/f8/85bcd2f1c142e580a1394c18920506d9399b8e8e97e4899bbee9c74a896e/sphinx_external_toc-1.1.0.tar.gz", hash = "sha256:f81833865006f6b4a9b2550a2474a6e3d7e7f2cb23ba23309260577ea65552f6", size = 37194, upload-time = "2026-01-16T13:15:59.03Z" } +-wheels = [ +- { url = "https://files.pythonhosted.org/packages/9f/80/1704c9179012e289dee2178354e385277ea51f4fa827c4bf7e36c77b0f4b/sphinx_external_toc-1.1.0-py3-none-any.whl", hash = "sha256:26c390b8d85aa641366fed2d3674910ec6820f48b91027affef485a2655ad7d0", size = 30609, upload-time = "2026-01-16T13:15:57.926Z" }, +-] +- +-[[package]] +-name = "sphinx-jupyterbook-latex" +-version = "1.0.0" +-source = { registry = "https://pypi.org/simple" } +-dependencies = [ +- { name = "packaging" }, +- { name = "sphinx" }, +-] +-sdist = { url = "https://files.pythonhosted.org/packages/80/29/18a1fc30e9315e72f068637079169525069a7c0b2fbe51cf689af0576214/sphinx_jupyterbook_latex-1.0.0.tar.gz", hash = "sha256:f54c6674c13f1616f9a93443e98b9b5353f9fdda8e39b6ec552ccf0b3e5ffb62", size = 11945, upload-time = "2023-12-11T15:37:25.034Z" } +-wheels = [ +- { url = "https://files.pythonhosted.org/packages/70/1f/1d4ecaf58b17fe61497644655f40b04d84a88348e41a6f0c6392394d95e4/sphinx_jupyterbook_latex-1.0.0-py3-none-any.whl", hash = "sha256:e0cd3e9e1c5af69136434e21a533343fdf013475c410a414d5b7b4922b4f3891", size = 13319, upload-time = "2023-12-11T15:37:23.25Z" }, +-] +- +-[[package]] +-name = "sphinx-multitoc-numbering" +-version = "0.1.3" +-source = { registry = "https://pypi.org/simple" } +-dependencies = [ +- { name = "sphinx" }, +-] +-sdist = { url = "https://files.pythonhosted.org/packages/37/1e/577bae038372885ebc34bd8c0f290295785a0250cac6528eb6d50e4b92d5/sphinx-multitoc-numbering-0.1.3.tar.gz", hash = "sha256:c9607671ac511236fa5d61a7491c1031e700e8d498c9d2418e6c61d1251209ae", size = 4542, upload-time = "2021-03-15T12:01:43.758Z" } +-wheels = [ +- { url = "https://files.pythonhosted.org/packages/ec/9f/902f2030674cd9473fdbe5a2c2dec2618c27ec853484c35f82cf8df40ece/sphinx_multitoc_numbering-0.1.3-py3-none-any.whl", hash = "sha256:33d2e707a9b2b8ad636b3d4302e658a008025106fe0474046c651144c26d8514", size = 4616, upload-time = "2021-03-15T12:01:42.419Z" }, +-] +- +-[[package]] +-name = "sphinx-thebe" +-version = "0.3.1" +-source = { registry = "https://pypi.org/simple" } +-dependencies = [ +- { name = "sphinx" }, +-] +-sdist = { url = "https://files.pythonhosted.org/packages/da/fd/926ba4af1eb2708b1ac0fa4376e4bfb11d9a32b2a00e3614137a569c1ddf/sphinx_thebe-0.3.1.tar.gz", hash = "sha256:576047f45560e82f64aa5f15200b1eb094dcfe1c5b8f531a8a65bd208e25a493", size = 20789, upload-time = "2024-02-07T13:31:57.002Z" } +-wheels = [ +- { url = "https://files.pythonhosted.org/packages/ca/7c/a53bdb465fd364bc3d255d96d5d70e6ba5183cfb4e45b8aa91c59b099124/sphinx_thebe-0.3.1-py3-none-any.whl", hash = "sha256:e7e7edee9f0d601c76bc70156c471e114939484b111dd8e74fe47ac88baffc52", size = 9030, upload-time = "2024-02-07T13:31:55.286Z" }, +-] +- +-[[package]] +-name = "sphinx-togglebutton" +-version = "0.4.5" +-source = { registry = "https://pypi.org/simple" } +-dependencies = [ +- { name = "docutils" }, +- { name = "setuptools" }, +- { name = "sphinx" }, +- { name = "wheel" }, +-] +-sdist = { url = "https://files.pythonhosted.org/packages/cc/be/169a0b0a8ad9588e8697c85e1d489aaaca7416073c2fc0267c360af5aae9/sphinx_togglebutton-0.4.5.tar.gz", hash = "sha256:c870dfbd3bc6e119b50ff9a37a64f8991902269e856728931c7d89877e8d4b3d", size = 18101, upload-time = "2026-03-27T13:50:41.984Z" } +-wheels = [ +- { url = "https://files.pythonhosted.org/packages/d8/2e/3dd55564928c5d61f92827d4b91307dde7911a40fbe0000645d73202eea9/sphinx_togglebutton-0.4.5-py3-none-any.whl", hash = "sha256:74eac6d2426110c3e1e6f989a98e07d7823141a335df1ad8a9d637bdf6a7af62", size = 44907, upload-time = "2026-03-27T13:50:40.94Z" }, +-] +- + [[package]] + name = "sphinxcontrib-applehelp" + version = "2.0.0" +@@ -6272,21 +6072,6 @@ wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, + ] + +-[[package]] +-name = "sphinxcontrib-bibtex" +-version = "2.7.0" +-source = { registry = "https://pypi.org/simple" } +-dependencies = [ +- { name = "docutils" }, +- { name = "pybtex" }, +- { name = "pybtex-docutils" }, +- { name = "sphinx" }, +-] +-sdist = { url = "https://files.pythonhosted.org/packages/15/6a/8e0b2c2420286389e7fed78ff361ec30e2f1d58c8560af8d64df5e7b61e0/sphinxcontrib_bibtex-2.7.0.tar.gz", hash = "sha256:fee700f7aae29bb8f654c62913f00d34ac44fc0b8ca0fa67ac922ff4453addee", size = 120669, upload-time = "2026-05-06T09:29:24.935Z" } +-wheels = [ +- { url = "https://files.pythonhosted.org/packages/52/c0/d28e62407f4733bbe0169287bc012f0ac3b4a2021066b285570654119c8b/sphinxcontrib_bibtex-2.7.0-py3-none-any.whl", hash = "sha256:28cf0ec7a957d1c7548d5749317ed472ce877e1b629f430f88e3789aa51f87b1", size = 40287, upload-time = "2026-05-06T09:29:23.253Z" }, +-] +- + [[package]] + name = "sphinxcontrib-devhelp" + version = "2.0.0" +@@ -7090,18 +6875,6 @@ wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/b2/0bba9bbb4596d2d2f285a16c2ab04118f6b957d8441566e1abb892e6a6b2/werkzeug-3.1.7-py3-none-any.whl", hash = "sha256:4b314d81163a3e1a169b6a0be2a000a0e204e8873c5de6586f453c55688d422f", size = 226295, upload-time = "2026-03-24T01:08:06.133Z" }, + ] + +-[[package]] +-name = "wheel" +-version = "0.47.0" +-source = { registry = "https://pypi.org/simple" } +-dependencies = [ +- { name = "packaging" }, +-] +-sdist = { url = "https://files.pythonhosted.org/packages/39/62/75f18a0f03b4219c456652c7780e4d749b929eb605c098ce3a5b6b6bc081/wheel-0.47.0.tar.gz", hash = "sha256:cc72bd1009ba0cf63922e28f94d9d83b920aa2bb28f798a31d0691b02fa3c9b3", size = 63854, upload-time = "2026-04-22T15:51:27.727Z" } +-wheels = [ +- { url = "https://files.pythonhosted.org/packages/87/1b/9e33c09813d65e248f7f773119148a612516a4bea93e9c6f545f78455b7c/wheel-0.47.0-py3-none-any.whl", hash = "sha256:212281cab4dff978f6cedd499cd893e1f620791ca6ff7107cf270781e587eced", size = 32218, upload-time = "2026-04-22T15:51:26.296Z" }, +-] +- + [[package]] + name = "win32-setctime" + version = "1.2.0" diff --git a/docs/_config.yml b/docs/_config.yml deleted file mode 100644 index 88fa2c021..000000000 --- a/docs/_config.yml +++ /dev/null @@ -1,124 +0,0 @@ -# docs/_config.yml - -title: "Neural-LAM" -author: "MLLAM Community" -copyright: "2024–2026" - -logo: "_static/logo.png" -favicon: "_static/favicon.ico" - -repository: - url: "https://github.com/mllam/neural-lam" - branch: "main" - path_to_book: "docs" - -launch_buttons: - binderhub_url: "https://mybinder.org" - colab_url: "https://colab.research.google.com" - -execute: - execute_notebooks: "off" - -exclude_patterns: - - _build - - Thumbs.db - - .DS_Store - - "**.ipynb_checkpoints" - - _static - - scripts - -parse: - myst_enable_extensions: - - colon_fence - - dollarmath - - linkify - - substitution - - tasklist - - deflist - - fieldlist - - html_admonition - - html_image - - smartquotes - - attrs_inline - myst_url_schemes: [mailto, http, https] - -sphinx: - extra_extensions: - - "scripts.autoapi_astroid_patch" - - "autoapi.extension" - - "sphinx.ext.napoleon" - - "sphinx.ext.viewcode" - - "sphinx.ext.intersphinx" - - "sphinx_copybutton" - - "sphinxcontrib.mermaid" - - "sphinxext.opengraph" - - local_extensions: - scripts.autoapi_astroid_patch: "." - - config: - html_theme: "sphinx_book_theme" - html_theme_options: - repository_url: "https://github.com/mllam/neural-lam" - use_repository_button: true - use_issues_button: true - use_edit_page_button: true - repository_branch: "main" - path_to_docs: "docs" - show_navbar_depth: 2 - navigation_with_keys: false - show_toc_level: 2 - logo: - text: "Neural-LAM" - announcement: "" - extra_footer: | -

Built with Jupyter Book | - Source

- - html_static_path: ["_static"] - html_css_files: ["custom.css"] - - # ── AutoAPI ── - autoapi_dirs: ["../neural_lam"] - autoapi_root: "api" - autoapi_type: "python" - autoapi_options: - - "members" - - "undoc-members" - - "show-inheritance" - - "show-module-summary" - autoapi_python_class_content: "both" - autoapi_member_order: "groupwise" - autoapi_python_use_implicit_namespaces: false - autoapi_keep_files: true - autoapi_add_toctree_entry: true - autoapi_ignore: ["**/tests/**", "**/conftest.py"] - - # ── Napoleon ── - napoleon_numpy_docstring: true - napoleon_google_docstring: false - napoleon_include_init_with_doc: true - napoleon_include_private_with_doc: false - napoleon_include_special_with_doc: false - napoleon_use_param: true - napoleon_use_rtype: true - - # ── Intersphinx ── - intersphinx_mapping: - python: ["https://docs.python.org/3", null] - numpy: ["https://numpy.org/doc/stable", null] - torch: ["https://pytorch.org/docs/stable", null] - pytorch_lightning: ["https://lightning.ai/docs/pytorch/stable/", null] - torch_geometric: ["https://pytorch-geometric.readthedocs.io/en/latest/", null] - - # ── Mermaid ── - mermaid_d3_zoom: false - mermaid_version: "11" - - # ── Suppress Warnings ── - suppress_warnings: ["autoapi.python_import_resolution"] - - # ── OpenGraph ── - ogp_site_url: "https://mllam.github.io/neural-lam/" - ogp_image: "_static/logo.png" - ogp_use_first_image: true diff --git a/docs/_toc.yml b/docs/_toc.yml deleted file mode 100644 index 2ef150f02..000000000 --- a/docs/_toc.yml +++ /dev/null @@ -1,35 +0,0 @@ -format: jb-book -root: intro - -parts: - - caption: Getting Started - chapters: - - file: getting-started/installation - - file: getting-started/quickstart - - - caption: Tutorials - chapters: - - file: notebooks/create_reduced_meps_dataset - - - caption: Architecture - chapters: - - file: architecture/overview - - file: architecture/theory - - file: architecture/data-flow - - file: architecture/models - - file: architecture/graph-construction - - - caption: Resources - chapters: - - file: guides/configuration - - file: api/index - - - caption: Contributing - chapters: - - file: contributing/development-setup - - file: contributing/coding-standards - - - caption: About - chapters: - - file: about/changelog - - file: about/glossary diff --git a/docs/about/changelog.md b/docs/about/changelog.md deleted file mode 120000 index 699cc9e7b..000000000 --- a/docs/about/changelog.md +++ /dev/null @@ -1 +0,0 @@ -../../CHANGELOG.md \ No newline at end of file diff --git a/docs/about/glossary.md b/docs/about/glossary.md deleted file mode 100644 index 13f8b8a66..000000000 --- a/docs/about/glossary.md +++ /dev/null @@ -1,60 +0,0 @@ -# Glossary - -```{glossary} -ARModel - The autoregressive base model implemented in PyTorch Lightning. - -BaseDatastore - The abstract base class for data loaders and datastores. - -Datastore - The overarching data handler component for loading weather data. - -Encode-Process-Decode - An architecture style often used in graph neural networks involving encoding inputs into latent graphs, processing them with message passing, and decoding back to the target space. - -Forcing Variables - External variables providing boundary or context conditions to the {term}`Forecaster`. - -Forecaster - The core component or model responsible for generating predictions over time. - -GNN - Graph Neural Network. A type of neural network designed to operate on graph structures. - -GraphLAM - The main Graph-based Limited Area Model implementation. - -HiLAM - Hierarchical Limited Area Model. - -HiLAMParallel - A parallelized version of {term}`HiLAM`. - -LAM - Limited Area Model. A weather prediction model focused on a specific geographic region rather than global scope. - -MDPDatastore - Datastore designed to read Zarr formats via the {term}`mllam-data-prep` module. - -Mesh - The structured or unstructured graph grid onto which the weather data is projected and processed. - -mllam-data-prep - The tool/module responsible for preparing and formatting raw weather data into a format ingestible by Neural-LAM. - -PyG - PyTorch Geometric, a library for deep learning on irregular input data such as graphs. - -State Variables - The set of variables that describe the current internal state of the weather system in the model. - -StepPredictor - A model component designed to predict the next single time step given a current state. - -WeatherDataModule - The PyTorch Lightning data module encapsulating the {term}`WeatherDataset`. - -WeatherDataset - The PyTorch dataset class representing the prepared weather data. -``` diff --git a/docs/architecture/data-flow.md b/docs/architecture/data-flow.md deleted file mode 100644 index 9cdfe7acd..000000000 --- a/docs/architecture/data-flow.md +++ /dev/null @@ -1,38 +0,0 @@ -# Data Flow - -## Overview - -The Neural-LAM data pipeline is designed to efficiently load raw meteorological data and feed it into the models for training and inference. It abstracts data sources, normalizes variables, and prepares batches using PyTorch Lightning. - -## Datastore Abstraction - -The {py:class}`neural_lam.datastore.BaseDatastore` provides an abstract interface for data access. It defines methods for fetching input and target tensors (`get_xy`), as well as metadata like variable names and units. Subclasses like `MDPDatastore` implement this interface to support specific formats like Zarr. - -## WeatherDataset - -The {py:class}`neural_lam.weather_dataset.WeatherDataset` acts as a PyTorch wrapper around a `BaseDatastore`. It handles indexing, temporal batching, and any necessary on-the-fly transformations required before the data reaches the Lightning module. - -## WeatherDataModule - -The {py:class}`neural_lam.weather_dataset.WeatherDataModule` is a PyTorch Lightning DataModule that encapsulates the `WeatherDataset`s for training, validation, and testing splits. It manages the dataloaders and ensures data is correctly distributed across devices during distributed training. - -## Configuration - -The entire data pipeline is driven by type-safe YAML configurations defined in {py:mod}`neural_lam.config`. Dataclasses define expected paths, batch sizes, and data normalization statistics, allowing easy experimentation without code changes. - -## Data Format Requirements - -| Component | Format/Shape Expected | -|-----------|-----------------------| -| Model Input | `(batch, sequence_length, num_grid_nodes, num_features)` | -| Target Output | `(batch, sequence_length, num_grid_nodes, num_features)` | -| Static Features | `(num_grid_nodes, num_static_features)` | - -```{mermaid} -flowchart TD - A["Raw Data Files"] -->|Read by| B["BaseDatastore"] - B -->|get_xy| C["WeatherDataset"] - C -->|__getitem__| D["DataLoader"] - D -->|B, T, N, F| E["WeatherDataModule"] - E -->|Batch| F["Model Forward Pass"] -``` diff --git a/docs/architecture/graph-construction.md b/docs/architecture/graph-construction.md deleted file mode 100644 index d50daad0e..000000000 --- a/docs/architecture/graph-construction.md +++ /dev/null @@ -1,66 +0,0 @@ -# Graph Construction - -## Why Graphs? - -Graph Neural Networks (GNNs) leverage graph-based message passing, which is highly suited for weather prediction. Unlike standard CNNs on rigid grids, graphs can naturally represent irregular spatial distributions and complex geometries, common in Limited Area Modeling (LAM). - -The graph structure dictates how information flows across spatial regions. A well-designed mesh ensures that localized weather phenomena correctly influence neighboring regions, while hierarchical structures allow long-range interactions (like large-scale pressure systems) to propagate efficiently across the domain without requiring hundreds of standard grid steps. - -## The create_graph Module - -The `create_graph` script is used to pre-compute and build the mesh graphs required before training any models. This ensures that the complex spatial structures are generated once and loaded efficiently during training. - -Reference: {py:mod}`neural_lam.create_graph` - -## Graph Types - -### Flat Mesh -- Single level of mesh nodes -- Used by GraphLAM - -### Hierarchical Mesh -- Multiple levels of mesh nodes at increasing spatial scales -- Used by HiLAM and HiLAMParallel - -```{mermaid} -graph TD - subgraph grid ["Grid Level"] - G1["Grid Node"] --- G2["Grid Node"] --- G3["Grid Node"] - end - subgraph mesh1 ["Mesh Level 1"] - M1["Mesh Node"] --- M2["Mesh Node"] - end - subgraph mesh2 ["Mesh Level 2"] - M3["Mesh Node"] - end - G1 -.-> M1 - G2 -.-> M1 - G2 -.-> M2 - G3 -.-> M2 - M1 -.-> M3 - M2 -.-> M3 -``` - -## Edge Features - -For each edge type in the constructed graph, specific features are computed to assist message passing: -- **Spatial Distance**: The physical distance between connected nodes. -- **Directional Vectors**: Vector representations of the direction between nodes, enabling the GNN to understand flow and gradients (e.g., wind direction). -- **Elevation Differences**: Changes in altitude between nodes, which is critical for orographic effects in weather. - -## Usage - -```bash -python -m neural_lam.create_graph \ - --config_path \ - --name -``` - -- `--config_path`: Path to the YAML configuration file which dictates the dataset properties and graph parameters. -- `--name`: The name assigned to the generated graph, which is used to load it during training. - -## GNN Layers - -The `InteractionNet` represents the core GNN layer implementation in Neural-LAM. It utilizes PyTorch Geometric's `MessagePassing` interface to aggregate features from neighboring nodes and update node states, incorporating edge features natively to refine the spatial interactions. - -Reference: {py:mod}`neural_lam.gnn_layers` diff --git a/docs/architecture/models.md b/docs/architecture/models.md deleted file mode 100644 index b82729e69..000000000 --- a/docs/architecture/models.md +++ /dev/null @@ -1,60 +0,0 @@ -# Model Architectures - -## Overview - -Neural-LAM employs a modular model hierarchy based on the **encode-process-decode** paradigm. This paradigm allows the models to encode grid-based weather data into a graph structure, perform spatial and temporal message passing to process the data, and finally decode the updated graph states back into the original grid representation. - -## Autoregressive Framework - -The core training and evaluation loop is handled by `ARModel`, a PyTorch Lightning module defined in `neural_lam/models/module.py`. This autoregressive framework is responsible for unrolling predictions over multiple timesteps. - -```{mermaid} -sequenceDiagram - participant Trainer as Lightning Trainer - participant AR as ARModel - participant F as Forecaster - participant SP as StepPredictor - - Trainer->>AR: training_step(batch) - loop for each timestep - AR->>F: forward(prev_state) - F->>SP: predict_step(state, graph) - SP-->>F: next_state_delta - F-->>AR: next_state - end - AR->>AR: compute_loss() - AR-->>Trainer: loss -``` - -## Encode-Process-Decode - -The base graph model, defined in `neural_lam/models/step_predictors/base.py`, implements the standard encode-process-decode steps: -1. **Encode**: Features from the weather grid are mapped onto the nodes and edges of the mesh graph. -2. **Process**: A Graph Neural Network (GNN) performs multiple rounds of message passing to propagate information across the spatial domain. -3. **Decode**: The updated mesh features are mapped back to the grid to produce the next state prediction. - -## GraphLAM - -GraphLAM is the fundamental model architecture utilizing a single-level flat graph. It is effective for standard resolution forecasting without multi-scale processing. - -Reference: {py:class}`neural_lam.models.step_predictors.graph.GraphLAM` - -## HiLAM - -HiLAM introduces a hierarchical model design, utilizing multiple levels of mesh nodes at increasing spatial scales. This allows the network to efficiently capture both local, fine-grained interactions and long-range, global atmospheric patterns. - -Reference: {py:class}`neural_lam.models.step_predictors.graph.HiLAM` - -## HiLAMParallel - -HiLAMParallel is a parallel hierarchical variant of HiLAM. It processes multi-scale information simultaneously across different hierarchy levels, rather than sequentially, potentially improving computational efficiency and long-range interaction modeling. - -Reference: {py:class}`neural_lam.models.step_predictors.graph.HiLAMParallel` - -## Choosing a Model - -| Model | Graph Type | Complexity | Best For | -|---|---|---|---| -| **GraphLAM** | Flat Mesh | Low | Baseline forecasting, single-scale dynamics | -| **HiLAM** | Hierarchical Mesh | Medium | Capturing both local and global dependencies efficiently | -| **HiLAMParallel** | Hierarchical Mesh | High | Highly parallel environments, very large spatial domains | diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md deleted file mode 100644 index 7d5c8abcb..000000000 --- a/docs/architecture/overview.md +++ /dev/null @@ -1,51 +0,0 @@ -# Architecture Overview - -## System Design - -Neural-LAM is designed with a modular architecture that separates data handling, model architecture, and training logic. This separation of concerns allows for easy experimentation with different graph structures, model components, and datasets without requiring extensive changes to the core system. - -The core components include a robust data pipeline utilizing an abstract datastore interface, a flexible set of graph-based neural network models (such as `GraphLAM`, `HiLAM`, and `HiLAMParallel`), and a training module built on top of PyTorch Lightning. This design ensures scalability and ease of use for both researchers and practitioners. - -## Data Flow - -```{mermaid} -flowchart LR - A["Raw Data
(zarr / numpy)"] --> B["Datastore
(BaseDatastore)"] - B --> C["WeatherDataset"] - C --> D["WeatherDataModule
(Lightning)"] - D --> E["ARModel
(Autoregressive)"] - E --> F["StepPredictor
(GNN)"] - F --> G["Predictions"] - G --> H["Loss & Metrics"] -``` - -## Module Map - -| Module | Responsibility | -|--------|----------------| -| `datastore` | Handles reading from diverse data sources (e.g., Zarr, NetCDF) via the `BaseDatastore` interface. | -| `weather_dataset` | Wraps the datastore in a PyTorch `Dataset` and Lightning `DataModule` for training. | -| `models` | Contains the core neural network architectures (`ARModel`, `BaseGraphModel`, `GraphLAM`, etc.). | -| `create_graph` | Utility to build the hierarchical mesh graphs used by the GNN models. | -| `config` | Manages the YAML-based configuration via dataclasses. | - -## Component Interaction - -This diagram is **automatically generated** from the Python source code using `pyreverse`, ensuring it never goes stale! - -```{eval-rst} -.. mermaid:: ../_static/uml/classes_models.mmd -``` - -## Key Design Decisions - -- **Modular Datastores**: `BaseDatastore` abstracts away data loading intricacies. -- **PyTorch Lightning**: Used to reduce boilerplate and scale training easily. -- **Hierarchical Graphs**: `create_graph` decouples graph structure generation from model logic. -- **Dataclass Configurations**: Type-safe YAML configurations managed by `dataclass-wizard`. - -## See Also - -- {doc}`data-flow` for detailed data pipeline documentation -- {doc}`models` for model architecture details -- {doc}`graph-construction` for graph creation details diff --git a/docs/architecture/theory.md b/docs/architecture/theory.md deleted file mode 100644 index a636c5374..000000000 --- a/docs/architecture/theory.md +++ /dev/null @@ -1,47 +0,0 @@ -# Theory & Methods - -This section describes the mathematical formulations and algorithms underlying the Neural-LAM graph models. - -## The Encode-Process-Decode Paradigm - -All graph-based models in Neural-LAM (`GraphLAM`, `HiLAM`, and `HiLAMParallel`) follow the classic "Encode-Process-Decode" paradigm for graph neural networks. - -1. **Encode**: The input grid state \( X^t \) at timestep \( t \) is mapped to latent node features on the graph: - \[ H_{grid} = \text{Encoder}(X^t) \] - -2. **Process**: A series of message passing steps updates the latent node representations based on the graph connectivity: - \[ H'_{grid} = \text{Processor}(H_{grid}, \mathcal{G}) \] - -3. **Decode**: The updated latent features are mapped back to predict the residual change for the next timestep: - \[ \Delta X^{t+1} = \text{Decoder}(H'_{grid}) \] - \[ X^{t+1} = X^t + \Delta X^{t+1} \] - -## Message Passing Functions - -Neural-LAM supports several GNN layers for message passing along different edge types (e.g., Grid-to-Mesh, Mesh-to-Mesh). - -### InteractionNet - -The default layer is the `InteractionNet` (based on Interaction Networks). Given a sender node \( v_s \), a receiver node \( v_r \), and an edge feature \( e_{s,r} \), the message passing works as follows: - -1. **Edge Update (Message Formulation)**: - \[ m_{s,r} = \text{MLP}_{edge}\left([h_s, h_r, e_{s,r}]\right) \] - -2. **Node Update (Aggregation)**: - \[ h'_r = \text{MLP}_{node}\left([h_r, \sum_{s \in \mathcal{N}(r)} m_{s,r}]\right) \] - -### PropagationNet - -`PropagationNet` modifies the Interaction Network to strongly incentivize directional information flow from senders to receivers, which is crucial for moving information up and down the hierarchical mesh levels: - -\[ h'_r = h_r + \text{MLP}_{prop}\left([h_r, \sum_{s \in \mathcal{N}(r)} m_{s,r}]\right) \] - -This residual connection ensures that nodes maintain their internal state while selectively incorporating new information from neighbors. - -## Loss Weighting - -To balance predictions across variables with different physical scales and variances, the loss function uses a weighted Mean Squared Error (MSE): - -\[ \mathcal{L} = \frac{1}{V \cdot N} \sum_{v=1}^V \sum_{i=1}^N w_v \cdot \left( \hat{X}^{t+1}_{i,v} - X^{t+1}_{i,v} \right)^2 \] - -Where \( w_v \) are variable-specific weights defined in the `loss_weighting` module, and the errors are computed on standardized variables. diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 000000000..8f2001101 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,111 @@ +import os +import sys + +# Add the project root to sys.path so sphinx can find neural_lam +sys.path.insert(0, os.path.abspath('..')) + +project = 'Neural-LAM' +copyright = '2024–2026, MLLAM Community' +author = 'MLLAM Community' + +extensions = [ + 'autoapi.extension', + 'myst_nb', + 'sphinx.ext.napoleon', + 'sphinx.ext.viewcode', + 'sphinx.ext.intersphinx', + 'sphinx_copybutton', + 'sphinxcontrib.mermaid', + 'sphinxext.opengraph', +] + +# AutoAPI settings +autoapi_dirs = ['../neural_lam'] +autoapi_root = 'api' +autoapi_type = 'python' +autoapi_options = [ + 'members', + 'undoc-members', + 'show-inheritance', + 'show-module-summary', +] +autoapi_python_class_content = 'both' +autoapi_member_order = 'groupwise' +autoapi_python_use_implicit_namespaces = False +autoapi_keep_files = True +autoapi_add_toctree_entry = True +autoapi_ignore = ['**/tests/**', '**/conftest.py'] + +# Napoleon settings +napoleon_numpy_docstring = True +napoleon_google_docstring = False +napoleon_include_init_with_doc = True +napoleon_include_private_with_doc = False +napoleon_include_special_with_doc = False +napoleon_use_param = True +napoleon_use_rtype = True +napoleon_use_ivar = True + +# Intersphinx settings +intersphinx_mapping = { + 'python': ('https://docs.python.org/3', None), + 'numpy': ('https://numpy.org/doc/stable', None), + 'torch': ('https://pytorch.org/docs/stable', None), + 'pytorch_lightning': ('https://lightning.ai/docs/pytorch/stable/', None), + 'torch_geometric': ('https://pytorch-geometric.readthedocs.io/en/latest/', None), +} + +# MyST / Notebook settings +myst_enable_extensions = [ + 'colon_fence', + 'dollarmath', + 'linkify', + 'substitution', + 'tasklist', + 'deflist', + 'fieldlist', + 'html_admonition', + 'html_image', + 'smartquotes', + 'attrs_inline', +] +nb_execution_mode = 'off' + +# HTML Theme +html_theme = 'sphinx_book_theme' +html_logo = '_static/logo.png' +html_favicon = '_static/favicon.ico' +html_static_path = ['_static'] +html_css_files = ['custom.css'] + +html_theme_options = { + 'repository_url': 'https://github.com/mllam/neural-lam', + 'use_repository_button': True, + 'use_issues_button': True, + 'use_edit_page_button': True, + 'repository_branch': 'main', + 'path_to_docs': 'docs', + 'show_navbar_depth': 2, + 'show_toc_level': 2, + 'logo': { + 'text': 'Neural-LAM' + }, + 'extra_footer': '

Built with Sphinx | Source

', +} + +# OpenGraph settings +ogp_site_url = 'https://mllam.github.io/neural-lam/' +ogp_image = '_static/logo.png' +ogp_use_first_image = True + +# Mermaid settings +mermaid_d3_zoom = False +mermaid_version = '11' + +suppress_warnings = ['autoapi.python_import_resolution'] + +# Linkcheck settings +linkcheck_ignore = [ + r'https://kutt\.it/mllam', # Returns 403 Forbidden for bots + r'https://docs\.pytorch\.org/.*', # Flaky anchors in intersphinx +] diff --git a/docs/contributing/coding-standards.md b/docs/contributing/coding-standards.md deleted file mode 100644 index c1655d728..000000000 --- a/docs/contributing/coding-standards.md +++ /dev/null @@ -1,41 +0,0 @@ -# Coding Standards - -## Code Style - -Our codebase enforces strict styling guidelines using the following tools: -- **Formatter**: `black` -- **Import Sorting**: `isort` -- **Linting**: `flake8` -- **Type Checking**: `mypy` -- **Docstring Coverage**: `interrogate` -- **Spell Checking**: `codespell` - -## Pull Request Process - -1. **Search before creating**: Search existing issues or PRs to avoid duplicates. -2. **Every PR requires an issue**: Open an issue first if none exists. -3. **Link the issue**: Include `closes #` or `refs #` in the PR body. -4. **Use the PR template**: Fill out every section of the provided template. -5. **Run pre-commit hooks**: Ensure code passes locally via `uvx pre-commit run --all-files`. -6. **Run tests**: Run `pytest tests/` and fix any failures before opening the PR. -7. **Update CHANGELOG**: Add a line to `CHANGELOG.md` in the appropriate section. - -## Docstring Standard - -- We use **NumPy-style** docstrings. -- We require **100% docstring coverage** for public functions, methods, and classes. -- Always include `Parameters`, `Returns`, and `Raises` sections if applicable. -- Make sure to specify **tensor shapes** in the docstrings when passing or returning tensors. - -## Commit Messages - -- Must be in the **imperative form** (e.g., "Add test for feature X" instead of "Added test..."). -- Keep **one concern per PR** to ensure unrelated changes are not mixed. -- AI attribution of tool names is mandatory if used and should be mentioned in the commit message trailer as `Co-authored-by `. - -## Adding Documentation - -When adding new modules or classes: -1. Include detailed docstrings directly in the code. -2. If appropriate, create a new Markdown page under `docs/architecture/` (or update an existing one). -3. Ensure the new page is added to the `_toc.yml` so it appears in the documentation navigation structure. diff --git a/docs/contributing/development-setup.md b/docs/contributing/development-setup.md deleted file mode 100644 index 93f3a2c03..000000000 --- a/docs/contributing/development-setup.md +++ /dev/null @@ -1,102 +0,0 @@ -# Development Setup - -## Prerequisites - -- Python >=3.10 -- Git -- uv (recommended) or pip - -## Clone and Install - -```bash -git clone https://github.com/mllam/neural-lam.git -cd neural-lam -uv sync --extra cpu --group dev --locked -source .venv/bin/activate -``` - -## Pre-commit Hooks - -We use `pre-commit` to ensure code formatting and quality before commits. The hooks include: -- `black` for code formatting. -- `isort` for import sorting. -- `flake8` for linting. -- `mypy` for static type checking. -- `codespell` for spell checking. -- `interrogate` for docstring coverage. - -To install and run the pre-commit hooks: - -```bash -pre-commit install -uvx pre-commit run --all-files -``` - -## Running Tests - -We use `pytest` for running our test suite. Note that Weights & Biases (W&B) is automatically disabled during tests. - -Run all tests: -```bash -pytest -vv -s --doctest-modules -``` - -Run tests in a single file: -```bash -pytest tests/test_training.py -vv -s -``` - -Run a single function test: -```bash -pytest tests/test_training.py::test_fn -vv -``` - -## Building Documentation - -We use `jupyter-book` to build the documentation. You can build it using `uv`: - -```bash -uv run jb build docs -``` - -## Project Structure - -- `docs/` - Project documentation. -- `neural_lam/` - Main source code directory containing core modules, models, and data logic. - - `datastore/` - Datastore classes for loading data. - - `models/` - Core neural network models. -- `tests/` - Unit tests and test data examples. - -## Writing Docstrings - -We follow the NumPy-style format for docstrings. All functions and classes must have docstrings including Parameters, Returns, Raises, and Tensor shapes where applicable. - -Example: -```python -import torch - -def process_state(state: torch.Tensor, threshold: float = 0.5) -> torch.Tensor: - """ - Process the given state tensor by applying a threshold. - - Parameters - ---------- - state : torch.Tensor - The input state tensor of shape (batch_size, num_features). - threshold : float, optional - The threshold value to apply, by default 0.5. - - Returns - ------- - torch.Tensor - The processed state tensor of shape (batch_size, num_features). - - Raises - ------ - ValueError - If the state tensor is empty. - """ - if state.numel() == 0: - raise ValueError("State tensor cannot be empty.") - return torch.where(state > threshold, state, torch.zeros_like(state)) -``` diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index d55e0259e..802032f24 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -60,7 +60,7 @@ python -c "import neural_lam; print('Neural-LAM OK')" uv sync --extra cpu --extra docs --group dev # Build the documentation -jupyter-book build docs/ +sphinx-build -b html docs/ docs/_build/html/ # Open in browser open docs/_build/html/index.html # macOS diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index 60b002917..96fbd115d 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -27,7 +27,7 @@ Before training, you must construct a graph mesh for your data. python -m neural_lam.create_graph --config_path --name ``` -This script builds the mesh graph required by the GNN models. For more details, see {doc}`../architecture/graph-construction`. +This script builds the mesh graph required by the GNN models. ## Step 3: Train a Model @@ -37,7 +37,7 @@ Now you can train a model using the graph and configuration. python -m neural_lam.train_model --config_path --model graph_lam --graph ``` -Neural-LAM supports several models like `graph_lam`, `hilam`, and `hilam_parallel`. For more information on the available models, see {doc}`../architecture/models`. +Neural-LAM supports several models like `graph_lam`, `hilam`, and `hilam_parallel`. ## Step 4: Evaluate @@ -51,7 +51,4 @@ python -m neural_lam.train_model --eval test --config_path --lo Neural-LAM uses a YAML configuration system powered by `dataclass-wizard`. The configuration defines the dataset paths, training parameters, and model hyperparameters. For complete details, see the API reference for {py:class}`neural_lam.config.NeuralLAMConfig`. -## Next Steps - -- {doc}`../architecture/overview` to understand the system design - {doc}`../api/index` for complete API reference diff --git a/docs/guides/configuration.md b/docs/guides/configuration.md deleted file mode 100644 index c7b9856f6..000000000 --- a/docs/guides/configuration.md +++ /dev/null @@ -1,58 +0,0 @@ -# Configuration Reference - -Neural-LAM uses `dataclass-wizard` to automatically parse and enforce type-safety for YAML configuration files. The configuration encapsulates both the **Datastore** configuration and the **Model/Training** configuration. - -Below are common templates you can use for your own runs. - -## 1. Quick CPU Test Run - -This minimal configuration uses a dummy datastore, perfect for quickly testing changes on a laptop without a GPU. - -```yaml -# tests/test_config.yaml -datastore: - _target_: "neural_lam.datastore.DummyDatastore" - -architecture: "graph_lam" -epochs: 2 -batch_size: 2 -lr: 1e-3 -hidden_dim: 32 -hidden_layers: 1 -``` - -## 2. MDP (Meteorological Data Processing) Full Training - -This is a production-level configuration for training on a real Zarr dataset produced by `mllam-data-prep` on a GPU cluster. - -```yaml -# config/mdp_training.yaml -datastore: - _target_: "neural_lam.datastore.MDPDatastore" - dataset_path: "/path/to/my/zarr_dataset.zarr" - subset_name: "meps" # Optional subset - -# Model architecture settings -architecture: "hi_lam_parallel" -hidden_dim: 128 -hidden_layers: 4 -mesh_aggr: "sum" - -# GNN Types for the different edges -g2m_gnn_type: "interaction" -m2g_gnn_type: "interaction" -mesh_up_gnn_type: "propagation" -mesh_down_gnn_type: "propagation" - -# Training parameters -epochs: 100 -batch_size: 16 -lr: 5e-4 -loss: "mse" - -# Logging and reproducibility -seed: 42 -``` - -## API Reference -For an exhaustive list of every configurable field and its default value, refer to the auto-generated documentation for the `NeuralLAMConfig` dataclass in the {py:mod}`neural_lam.config` module. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000..2bafde6a2 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,30 @@ +# Neural-LAM + +**Graph-based neural weather prediction for Limited Area Modeling** + +Neural-LAM is a PyTorch and PyTorch Lightning framework for high-resolution weather prediction using Graph Neural Networks. It provides a modular approach to Limited Area Modeling, supporting multiple graph-based architectures such as GraphLAM, HiLAM, and HiLAMParallel to process and predict meteorological data efficiently. + +```{toctree} +:maxdepth: 2 +:hidden: + +getting-started/installation +getting-started/quickstart +api/index +``` + +- **[🚀 Getting Started](getting-started/installation.md)**: Installation guide and quickstart tutorial to get you up and running. +- **[📚 API Reference](api/index)**: Auto-generated reference for all modules, classes, and functions. + +## Key Features + +- **Modular design**: Swap datastores, models, and graph structures independently +- **Multiple model architectures**: GraphLAM (flat), HiLAM (hierarchical), HiLAMParallel (parallel hierarchical) +- **Flexible data handling**: Abstract datastore interface supporting zarr, numpy, and custom formats via mllam-data-prep +- **Production-ready**: PyTorch Lightning for training, W&B/MLflow logging, checkpoint management + +## Quick Links + +- [GitHub Repository](https://github.com/mllam/neural-lam) +- [Issue Tracker](https://github.com/mllam/neural-lam/issues) +- [MLLAM Community Slack](https://kutt.it/mllam) diff --git a/docs/intro.md b/docs/intro.md deleted file mode 100644 index fee7572f3..000000000 --- a/docs/intro.md +++ /dev/null @@ -1,73 +0,0 @@ -# Neural-LAM - -**Graph-based neural weather prediction for Limited Area Modeling** - -Neural-LAM is a PyTorch and PyTorch Lightning framework for high-resolution weather prediction using Graph Neural Networks. It provides a modular approach to Limited Area Modeling, supporting multiple graph-based architectures such as GraphLAM, HiLAM, and HiLAMParallel to process and predict meteorological data efficiently. - -```{admonition} Get Started in 5 Minutes -:class: tip -Install Neural-LAM and run your first prediction. -See the {doc}`Getting Started Guide `. -``` - -::::{grid} 2 -:gutter: 3 - -:::{grid-item-card} 🚀 Getting Started -:link: getting-started/installation -:link-type: doc -Installation guide and quickstart tutorial to get you up and running. -::: - - -:::{grid-item-card} 🏗️ Architecture -:link: architecture/overview -:link-type: doc -Understand the data flow, model structure, and design decisions. -::: - -:::{grid-item-card} 📚 API Reference -:link: api/index -:link-type: doc -Auto-generated reference for all modules, classes, and functions. -::: - -:::: - -## Key Features - -- **Modular design**: Swap datastores, models, and graph structures independently -- **Multiple model architectures**: GraphLAM (flat), HiLAM (hierarchical), HiLAMParallel (parallel hierarchical) -- **Flexible data handling**: Abstract datastore interface supporting zarr, numpy, and custom formats via mllam-data-prep -- **Production-ready**: PyTorch Lightning for training, W&B/MLflow logging, checkpoint management - -## Publications - -If you use Neural-LAM in your research, please cite the relevant papers: - -**NeurIPS 2024 Paper** ([Probabilistic Weather Forecasting with Hierarchical Graph Neural Networks](https://arxiv.org/abs/2406.04759)) -```bibtex -@inproceedings{oskarsson2024probabilistic, - title = {Probabilistic Weather Forecasting with Hierarchical Graph Neural Networks}, - author = {Oskarsson, Joel and Landelius, Tomas and Deisenroth, Marc Peter and Lindsten, Fredrik}, - booktitle = {Advances in Neural Information Processing Systems}, - volume = {37}, - year = {2024}, -} -``` - -**NeurIPS 2023 Workshop Paper** ([Graph-based Neural Weather Prediction for Limited Area Modeling](https://arxiv.org/abs/2309.17370)) -```bibtex -@inproceedings{oskarsson2023graphbased, - title={Graph-based Neural Weather Prediction for Limited Area Modeling}, - author={Oskarsson, Joel and Landelius, Tomas and Lindsten, Fredrik}, - booktitle={NeurIPS 2023 Workshop on Tackling Climate Change with Machine Learning}, - year={2023} -} -``` - -## Quick Links - -- [GitHub Repository](https://github.com/mllam/neural-lam) -- [Issue Tracker](https://github.com/mllam/neural-lam/issues) -- [MLLAM Community Slack](https://kutt.it/mllam) diff --git a/docs/notebooks/create_reduced_meps_dataset.ipynb b/docs/notebooks/create_reduced_meps_dataset.ipynb deleted file mode 100644 index 00cfa247f..000000000 --- a/docs/notebooks/create_reduced_meps_dataset.ipynb +++ /dev/null @@ -1,239 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Creating meps_example_reduced\n", - "This notebook outlines how the small-size test dataset ```meps_example_reduced``` was created based on the slightly larger dataset ```meps_example```. The zipped up datasets are 263 MB and 2.6 GB, respectively. See [README](https://github.com/mllam/neural-lam#data) for info on how to download ```meps_example```.\n", - "\n", - "The dataset was reduced in size by reducing the number of grid points and variables.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "# Standard library\n", - "import os\n", - "\n", - "# Third-party\n", - "import numpy as np\n", - "import torch" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "The number of grid points was reduced to 1/4 by halving the number of coordinates in both the x and y direction. This was done by removing a quarter of the grid points along each outer edge, so the center grid points would stay centered in the new set.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Load existing grid\n", - "grid_xy = np.load('data/meps_example/static/nwp_xy.npy')\n", - "# Get slices in each dimension by cutting off a quarter along each edge\n", - "num_x, num_y = grid_xy.shape[1:]\n", - "x_slice = slice(num_x//4, 3*num_x//4)\n", - "y_slice = slice(num_y//4, 3*num_y//4)\n", - "# Index and save reduced grid\n", - "grid_xy_reduced = grid_xy[:, x_slice, y_slice]\n", - "np.save('data/meps_example_reduced/static/nwp_xy.npy', grid_xy_reduced)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "This cut out the border, so a new perimeter of 10 grid points was established as border (10 was also the border size in the original \"meps_example\").\n" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "# Outer 10 grid points are border\n", - "old_border_mask = np.load('data/meps_example/static/border_mask.npy')\n", - "assert np.all(old_border_mask[10:-10, 10:-10] == False)\n", - "assert np.all(old_border_mask[:10, :] == True)\n", - "assert np.all(old_border_mask[:, :10] == True)\n", - "assert np.all(old_border_mask[-10:,:] == True)\n", - "assert np.all(old_border_mask[:,-10:] == True)\n", - "\n", - "# Create new array with False everywhere but the outer 10 grid points\n", - "border_mask = np.zeros_like(grid_xy_reduced[0,:,:], dtype=bool)\n", - "border_mask[:10] = True\n", - "border_mask[:,:10] = True\n", - "border_mask[-10:] = True\n", - "border_mask[:,-10:] = True\n", - "np.save('data/meps_example_reduced/static/border_mask.npy', border_mask)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A few other files also needed to be copied using only the new reduced grid" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Load surface_geopotential.npy, index only values from the reduced grid, and save to new file\n", - "surface_geopotential = np.load('data/meps_example/static/surface_geopotential.npy')\n", - "surface_geopotential_reduced = surface_geopotential[x_slice, y_slice]\n", - "np.save('data/meps_example_reduced/static/surface_geopotential.npy', surface_geopotential_reduced)\n", - "\n", - "# Load pytorch file grid_features.pt\n", - "grid_features = torch.load('data/meps_example/static/grid_features.pt')\n", - "# Index only values from the reduced grid. \n", - "# First reshape from (num_grid_points_total, 4) to (num_grid_points_x, num_grid_points_y, 4), \n", - "# then index, then reshape back to new total number of grid points\n", - "print(grid_features.shape)\n", - "grid_features_new = grid_features.reshape(num_x, num_y, 4)[x_slice,y_slice,:].reshape((-1, 4))\n", - "# Save to new file\n", - "torch.save(grid_features_new, 'data/meps_example_reduced/static/grid_features.pt')\n", - "\n", - "# flux_stats.pt is just a vector of length 2, so the grid shape and variable changes does not change this file\n", - "torch.save(torch.load('data/meps_example/static/flux_stats.pt'), 'data/meps_example_reduced/static/flux_stats.pt')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "The number of variables was reduced by truncating the variable list to the first 8." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "num_vars = 8\n", - "\n", - "# Load parameter_weights.npy, truncate to first 8 variables, and save to new file\n", - "parameter_weights = np.load('data/meps_example/static/parameter_weights.npy')\n", - "parameter_weights_reduced = parameter_weights[:num_vars]\n", - "np.save('data/meps_example_reduced/static/parameter_weights.npy', parameter_weights_reduced)\n", - "\n", - "# Do the same for following 4 pytorch files\n", - "for file in ['diff_mean', 'diff_std', 'parameter_mean', 'parameter_std']:\n", - " old_file = torch.load(f'data/meps_example/static/{file}.pt')\n", - " new_file = old_file[:num_vars]\n", - " torch.save(new_file, f'data/meps_example_reduced/static/{file}.pt')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Lastly the files in each of the directories train, test, and val have to be reduced. The folders all have the same structure with files of the following types:\n", - "```\n", - "nwp_YYYYMMDDHH_mbrXXX.npy\n", - "wtr_YYYYMMDDHH.npy\n", - "nwp_toa_downwelling_shortwave_flux_YYYYMMDDHH.npy\n", - "```\n", - "with ```YYYYMMDDHH``` being some date with hours, and ```XXX``` being some 3-digit integer.\n", - "\n", - "The first type of file has x and y in dimensions 1 and 2, and variable index in dimension 3. Dimension 0 is unchanged.\n", - "The second type has has x and y in dimensions 1 and 2. Dimension 0 is unchanged.\n", - "The last type has just x and y as the only 2 dimensions.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(65, 268, 238, 18)\n", - "(65, 268, 238)\n" - ] - } - ], - "source": [ - "print(np.load('data/meps_example/samples/train/nwp_2022040100_mbr000.npy').shape)\n", - "print(np.load('data/meps_example/samples/train/nwp_toa_downwelling_shortwave_flux_2022040112.npy').shape)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The following loop goes through each file in each sample folder and indexes them according to the dimensions given by the file name." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for sample in ['train', 'test', 'val']:\n", - " files = os.listdir(f'data/meps_example/samples/{sample}')\n", - "\n", - " for f in files:\n", - " data = np.load(f'data/meps_example/samples/{sample}/{f}')\n", - " if 'mbr' in f:\n", - " data = data[:,x_slice,y_slice,:num_vars]\n", - " elif 'wtr' in f:\n", - " data = data[x_slice, y_slice]\n", - " else:\n", - " data = data[:,x_slice,y_slice]\n", - " np.save(f'data/meps_example_reduced/samples/{sample}/{f}', data)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Lastly, the file ```data_config.yaml``` is modified manually by truncating the variable units, long and short names, and setting the new grid shape. Also the unit descriptions containing ```^``` was automatically parsed using latex, and to avoid having to install latex in the GitHub CI/CD pipeline, this was changed to ```**```. \n", - "\n", - "This new config file was placed in ```data/meps_example_reduced```, and that directory was then zipped and placed in a European Weather Cloud S3 bucket." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.14" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/docs/scripts/__init__.py b/docs/scripts/__init__.py deleted file mode 100644 index 359aa9312..000000000 --- a/docs/scripts/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Helper scripts used during documentation builds.""" diff --git a/docs/scripts/autoapi_astroid_patch.py b/docs/scripts/autoapi_astroid_patch.py deleted file mode 100644 index 8b647afe9..000000000 --- a/docs/scripts/autoapi_astroid_patch.py +++ /dev/null @@ -1,41 +0,0 @@ -""" -Patch AutoAPI for astroid >= 4 compatibility. - -This module patches the `AstroidBuilder` from `astroid` to pass a `manager` -argument to the parent class `__init__`, which is strictly required for -astroid >= 4. AutoAPI does not natively pass this argument in its current -versions, so this workaround prevents documentation build errors. -""" # codespell:ignore astroid - -from __future__ import annotations - -# Standard library -import inspect - -try: - # Third-party - from astroid import builder as astroid_builder # codespell:ignore astroid - from astroid.manager import AstroidManager # codespell:ignore astroid - - ASTROID_AVAILABLE = True -except ImportError: - ASTROID_AVAILABLE = False - - -def setup(app): - if not ASTROID_AVAILABLE: - return {"version": "0.1"} - - builder_init = astroid_builder.AstroidBuilder.__init__ - if "manager" not in inspect.signature(builder_init).parameters: - return {"version": "0.1"} - original_builder = astroid_builder.AstroidBuilder - - class AutoapiAstroidBuilder(original_builder): - def __init__(self, *args, **kwargs): - if not args and "manager" not in kwargs: - kwargs["manager"] = AstroidManager() - super().__init__(*args, **kwargs) - - astroid_builder.AstroidBuilder = AutoapiAstroidBuilder - return {"version": "0.1"} diff --git a/docs/scripts/generate_docs.sh b/docs/scripts/generate_docs.sh deleted file mode 100755 index 18e5f45eb..000000000 --- a/docs/scripts/generate_docs.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash -# docs/scripts/generate_docs.sh - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" -DOCS_DIR="$REPO_ROOT/docs" - -if ! command -v uv &>/dev/null; then - echo "[ERROR] uv not found. Please install uv first." >&2 - exit 1 -fi - -echo "[INFO] Syncing dependencies..." -uv pip install -e ".[cpu,docs]" - -echo "[INFO] Removing old build at docs/_build..." -rm -rf "$DOCS_DIR/_build" - -echo "[INFO] Generating UML architecture diagrams with pyreverse..." -mkdir -p "$DOCS_DIR/_static/uml" -if [ -f "$REPO_ROOT/.venv/bin/pyreverse" ]; then - (cd "$REPO_ROOT" && "$REPO_ROOT/.venv/bin/pyreverse" -o mmd -p models neural_lam.models -d "$DOCS_DIR/_static/uml/" || echo "[WARN] pyreverse failed, continuing...") -else - (cd "$REPO_ROOT" && pyreverse -o mmd -p models neural_lam.models -d "$DOCS_DIR/_static/uml/" || echo "[WARN] pyreverse failed, continuing...") -fi - -echo "[INFO] Building site with jupyter-book..." -if [ -f "$REPO_ROOT/.venv/bin/jupyter-book" ]; then - "$REPO_ROOT/.venv/bin/jupyter-book" build docs/ --keep-going -else - # fallback to uv run if .venv structure differs - uv run jupyter-book build docs/ --keep-going -fi - -INDEX_HTML="$DOCS_DIR/_build/html/index.html" -echo "[OK] Build succeeded! Open: $INDEX_HTML" - -if [ "${CI:-false}" != "true" ]; then - if command -v open &>/dev/null; then - open "$INDEX_HTML" || true - elif command -v xdg-open &>/dev/null; then - xdg-open "$INDEX_HTML" || true - else - echo "Please open $INDEX_HTML in your browser manually." - fi -fi diff --git a/neural_lam/create_graph.py b/neural_lam/create_graph.py index c67e43975..3f6fb5f3c 100644 --- a/neural_lam/create_graph.py +++ b/neural_lam/create_graph.py @@ -275,42 +275,33 @@ def create_graph( `graph_dir_path`. Creates the following files for all graphs: + - g2m_edge_index.pt [2, N_g2m_edges] - g2m_features.pt [N_g2m_edges, d_features] - m2g_edge_index.pt [2, N_m2m_edges] - m2g_features.pt [N_m2m_edges, d_features] - m2m_edge_index.pt list of [2, N_m2m_edges_level], length==n_levels - - m2m_features.pt list of [N_m2m_edges_level, d_features], - length==n_levels - - mesh_features.pt list of [N_mesh_nodes_level, d_mesh_static], - length==n_levels - - where - d_features: - number of features per edge (currently d_features==3, for - edge-length, x and y) - N_g2m_edges: - number of edges in the graph from grid-to-mesh - N_m2g_edges: - number of edges in the graph from mesh-to-grid - N_m2m_edges_level: - number of edges in the graph from mesh-to-mesh at a given level - (list index corresponds to the level) - d_mesh_static: - number of static features per mesh node (currently - d_mesh_static==2, for x and y) - N_mesh_nodes_level: - number of nodes in the mesh at a given level + - m2m_features.pt list of [N_m2m_edges_level, d_features], length==n_levels + - mesh_features.pt list of [N_mesh_nodes_level, d_mesh_static], length==n_levels + + where: + + * d_features: number of features per edge (currently d_features==3, for + edge-length, x and y) + * N_g2m_edges: number of edges in the graph from grid-to-mesh + * N_m2g_edges: number of edges in the graph from mesh-to-grid + * N_m2m_edges_level: number of edges in the graph from mesh-to-mesh at a given level + (list index corresponds to the level) + * d_mesh_static: number of static features per mesh node (currently + d_mesh_static==2, for x and y) + * N_mesh_nodes_level: number of nodes in the mesh at a given level And in addition for hierarchical graphs: - - mesh_up_edge_index.pt - list of [2, N_mesh_updown_edges_level], length==n_levels-1 - - mesh_up_features.pt - list of [N_mesh_updown_edges_level, d_features], length==n_levels-1 - - mesh_down_edge_index.pt - list of [2, N_mesh_updown_edges_level], length==n_levels-1 - - mesh_down_features.pt - list of [N_mesh_updown_edges_level, d_features], length==n_levels-1 + + - mesh_up_edge_index.pt: list of [2, N_mesh_updown_edges_level], length==n_levels-1 + - mesh_up_features.pt: list of [N_mesh_updown_edges_level, d_features], length==n_levels-1 + - mesh_down_edge_index.pt: list of [2, N_mesh_updown_edges_level], length==n_levels-1 + - mesh_down_features.pt: list of [N_mesh_updown_edges_level, d_features], length==n_levels-1 where N_mesh_updown_edges_level is the number of edges in the graph from mesh-to-mesh between two consecutive levels (list index corresponds index diff --git a/neural_lam/datastore/base.py b/neural_lam/datastore/base.py index 8376d38de..8c17282f5 100644 --- a/neural_lam/datastore/base.py +++ b/neural_lam/datastore/base.py @@ -421,6 +421,7 @@ def expected_dim_order( ---------- category : str The category of the dataset (state/forcing/static). + Returns ------- List[str] @@ -481,12 +482,13 @@ class BaseRegularGridDatastore(BaseDatastore): The following methods and attributes must be implemented for datastore that represents regular-gridded data: - - `grid_shape_state` (property): 2D shape of the grid for the state + + * `grid_shape_state` (property): 2D shape of the grid for the state variables. - - `get_xy` (method): Return the x, y coordinates of the dataset, with the + * `get_xy` (method): Return the x, y coordinates of the dataset, with the option to not stack the coordinates (so that they are returned as a 2D grid). - - `get_lat_lon` (method): Return the latitude/longitude coordinates of + * `get_lat_lon` (method): Return the latitude/longitude coordinates of the dataset for convenience when plotting. The operation of going from (x,y)-indexed regular grid @@ -526,10 +528,11 @@ def get_xy(self, category: str, stacked: bool) -> np.ndarray: ------- np.ndarray The x, y coordinates of the dataset, returned differently based on - the value of `stacked`: - `stacked==True`: shape `(n_grid_points, - 2)` where - n_grid_points=N_x*N_y. - - `stacked==False`: shape `(N_x, N_y, 2)` + the value of `stacked`: + + * `stacked==True`: shape `(n_grid_points, 2)` where + n_grid_points=N_x*N_y. + * `stacked==False`: shape `(N_x, N_y, 2)` """ def unstack_grid_coords( diff --git a/neural_lam/datastore/mdp.py b/neural_lam/datastore/mdp.py index 7cad45d7b..cceacc5fe 100644 --- a/neural_lam/datastore/mdp.py +++ b/neural_lam/datastore/mdp.py @@ -500,9 +500,10 @@ def get_xy(self, category: str, stacked: bool) -> ndarray: np.ndarray The x, y coordinates of the dataset, returned differently based on the value of `stacked`: - - `stacked==True`: shape `(n_grid_points, 2)` where - n_grid_points=N_x*N_y. - - `stacked==False`: shape `(N_x, N_y, 2)` + + * `stacked==True`: shape `(n_grid_points, 2)` where + n_grid_points=N_x*N_y. + * `stacked==False`: shape `(N_x, N_y, 2)` """ # assume variables are stored in dimensions [grid_index, ...] diff --git a/neural_lam/datastore/npyfilesmeps/store.py b/neural_lam/datastore/npyfilesmeps/store.py index e14bbc740..e86c5e70e 100644 --- a/neural_lam/datastore/npyfilesmeps/store.py +++ b/neural_lam/datastore/npyfilesmeps/store.py @@ -84,60 +84,60 @@ class NpyFilesDatastoreMEPS(BaseRegularGridDatastore): `[y, x]`. - Folder structure: - - meps_example_reduced - ├── data_config.yaml - ├── samples - │ ├── test - │ │ ├── nwp_2022090100_mbr000.npy - │ │ ├── nwp_2022090100_mbr001.npy - │ │ ├── nwp_2022090112_mbr000.npy - │ │ ├── nwp_2022090112_mbr001.npy - │ │ ├── ... - │ │ ├── nwp_toa_downwelling_shortwave_flux_2022090100.npy - │ │ ├── nwp_toa_downwelling_shortwave_flux_2022090112.npy - │ │ ├── ... - │ │ ├── wtr_2022090100.npy - │ │ ├── wtr_2022090112.npy - │ │ └── ... - │ ├── train - │ │ ├── nwp_2022040100_mbr000.npy - │ │ ├── nwp_2022040100_mbr001.npy - │ │ ├── ... - │ │ ├── nwp_2022040112_mbr000.npy - │ │ ├── nwp_2022040112_mbr001.npy - │ │ ├── ... - │ │ ├── nwp_toa_downwelling_shortwave_flux_2022040100.npy - │ │ ├── nwp_toa_downwelling_shortwave_flux_2022040112.npy - │ │ ├── ... - │ │ ├── wtr_2022040100.npy - │ │ ├── wtr_2022040112.npy - │ │ └── ... - │ └── val - │ ├── nwp_2022060500_mbr000.npy - │ ├── nwp_2022060500_mbr001.npy - │ ├── ... - │ ├── nwp_2022060512_mbr000.npy - │ ├── nwp_2022060512_mbr001.npy - │ ├── ... - │ ├── nwp_toa_downwelling_shortwave_flux_2022060500.npy - │ ├── nwp_toa_downwelling_shortwave_flux_2022060512.npy - │ ├── ... - │ ├── wtr_2022060500.npy - │ ├── wtr_2022060512.npy - │ └── ... - └── static - ├── border_mask.npy - ├── diff_mean.pt - ├── diff_std.pt - ├── flux_stats.pt - ├── grid_features.pt - ├── nwp_xy.npy - ├── parameter_mean.pt - ├── parameter_std.pt - ├── parameter_weights.npy - └── surface_geopotential.npy + Folder structure:: + + meps_example_reduced + ├── data_config.yaml + ├── samples + │ ├── test + │ │ ├── nwp_2022090100_mbr000.npy + │ │ ├── nwp_2022090100_mbr001.npy + │ │ ├── nwp_2022090112_mbr000.npy + │ │ ├── nwp_2022090112_mbr001.npy + │ │ ├── ... + │ │ ├── nwp_toa_downwelling_shortwave_flux_2022090100.npy + │ │ ├── nwp_toa_downwelling_shortwave_flux_2022090112.npy + │ │ ├── ... + │ │ ├── wtr_2022090100.npy + │ │ ├── wtr_2022090112.npy + │ │ └── ... + │ ├── train + │ │ ├── nwp_2022040100_mbr000.npy + │ │ ├── nwp_2022040100_mbr001.npy + │ │ ├── ... + │ │ ├── nwp_2022040112_mbr000.npy + │ │ ├── nwp_2022040112_mbr001.npy + │ │ ├── ... + │ │ ├── nwp_toa_downwelling_shortwave_flux_2022040100.npy + │ │ ├── nwp_toa_downwelling_shortwave_flux_2022040112.npy + │ │ ├── ... + │ │ ├── wtr_2022040100.npy + │ │ ├── wtr_2022040112.npy + │ │ └── ... + │ └── val + │ ├── nwp_2022060500_mbr000.npy + │ ├── nwp_2022060500_mbr001.npy + │ ├── ... + │ ├── nwp_2022060512_mbr000.npy + │ ├── nwp_2022060512_mbr001.npy + │ ├── ... + │ ├── nwp_toa_downwelling_shortwave_flux_2022060500.npy + │ ├── nwp_toa_downwelling_shortwave_flux_2022060512.npy + │ ├── ... + │ ├── wtr_2022060500.npy + │ ├── wtr_2022060512.npy + │ └── ... + └── static + ├── border_mask.npy + ├── diff_mean.pt + ├── diff_std.pt + ├── flux_stats.pt + ├── grid_features.pt + ├── nwp_xy.npy + ├── parameter_mean.pt + ├── parameter_std.pt + ├── parameter_weights.npy + └── surface_geopotential.npy For the MEPS dataset: N_t' = 65 @@ -244,11 +244,10 @@ def get_dataarray( xr.DataArray The data array for the given category and split, with dimensions per category: - state: `[elapsed_forecast_duration, analysis_time, grid_index, - feature, ensemble_member]` - forcing: `[elapsed_forecast_duration, analysis_time, grid_index, - feature]` - static: `[grid_index, feature]` + + * state: ``[elapsed_forecast_duration, analysis_time, grid_index, feature, ensemble_member]`` + * forcing: ``[elapsed_forecast_duration, analysis_time, grid_index, feature]`` + * static: ``[grid_index, feature]`` """ if category == "state": @@ -669,9 +668,10 @@ def get_xy(self, category: str, stacked: bool) -> np.ndarray: np.ndarray The x, y coordinates of the dataset (with x first then y second), returned differently based on the value of `stacked`: - - `stacked==True`: shape `(n_grid_points, 2)` where - n_grid_points=N_x*N_y. - - `stacked==False`: shape `(N_x, N_y, 2)` + + * `stacked==True`: shape `(n_grid_points, 2)` where + n_grid_points=N_x*N_y. + * `stacked==False`: shape `(N_x, N_y, 2)` """ diff --git a/neural_lam/datastore/plot_example.py b/neural_lam/datastore/plot_example.py index 13f32e578..789282bff 100644 --- a/neural_lam/datastore/plot_example.py +++ b/neural_lam/datastore/plot_example.py @@ -34,7 +34,7 @@ def plot_example_from_datastore( Whether to standardize the data before plotting, by default True. selection : dict, optional Selections to apply to the dataarray, for example - `time="1990-09-03T0:00" would select this single timestep, by default + `time="1990-09-03T0:00"` would select this single timestep, by default {}. index_selection: dict, optional Index-based selection to apply to the dataarray, for example diff --git a/neural_lam/gnn_layers.py b/neural_lam/gnn_layers.py index 7a92ea76a..99a00144d 100644 --- a/neural_lam/gnn_layers.py +++ b/neural_lam/gnn_layers.py @@ -182,6 +182,7 @@ def aggregate( ) -> tuple[torch.Tensor, torch.Tensor]: """ Overridden aggregation function to: + * return both aggregated and per-edge messages, * only aggregate to the number of receiver nodes (``self.num_rec``) rather than to ``dim_size``. @@ -260,9 +261,15 @@ def get_gnn_class(gnn_type: str) -> Type[pyg.nn.MessagePassing]: """ Look up a GNN class by name. - gnn_type: One of the keys in GNN_TYPES - (currently "InteractionNet" or "PropagationNet") - Returns the corresponding GNN class. + Parameters + ---------- + gnn_type : str + One of the keys in GNN_TYPES (currently "InteractionNet" or "PropagationNet") + + Returns + ------- + Type[pyg.nn.MessagePassing] + The corresponding GNN class. """ if gnn_type not in GNN_TYPES: raise ValueError( diff --git a/pyproject.toml b/pyproject.toml index e9da32eee..38ada844e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,6 @@ cpu = ["torch>=2.12,<2.13"] gpu = ["torch>=2.12,<2.13"] # CUDA 13.0, default GPU build gpu-cu128 = ["torch>=2.11,<2.12"] # CUDA 12.8, last torch series with cu128 wheels docs = [ - "jupyter-book>=1.0.0,<2.0.0", "sphinx-autoapi>=3.0.0", "sphinx-copybutton>=0.5.2", "sphinx-book-theme>=1.1.0", @@ -59,7 +58,6 @@ docs = [ "myst-nb>=1.0.0", "myst-parser>=2.0.0", "sphinxext-opengraph>=0.9.0", - "pylint>=3.0.0", "linkify-it-py>=2.0.0", ] diff --git a/uv.lock b/uv.lock index 786dad4d1..5d125a729 100644 --- a/uv.lock +++ b/uv.lock @@ -1216,15 +1216,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dc/c4/da7089cd7aa4ab554f56e18a7fb08dcfed8fd2ae91fa528f5b1be207a148/deepdiff-9.0.0-py3-none-any.whl", hash = "sha256:b1ae0dd86290d86a03de5fbee728fde43095c1472ae4974bdab23ab4656305bd", size = 170540, upload-time = "2026-03-30T05:52:22.008Z" }, ] -[[package]] -name = "dill" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/81/e1/56027a71e31b02ddc53c7d65b01e68edf64dea2932122fe7746a516f75d5/dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa", size = 187315, upload-time = "2026-01-19T02:36:56.85Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019, upload-time = "2026-01-19T02:36:55.663Z" }, -] - [[package]] name = "distlib" version = "0.4.0" @@ -2140,15 +2131,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, ] -[[package]] -name = "isort" -version = "8.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ef/7c/ec4ab396d31b3b395e2e999c8f46dec78c5e29209fac49d1f4dace04041d/isort-8.0.1.tar.gz", hash = "sha256:171ac4ff559cdc060bcfff550bc8404a486fee0caab245679c2abe7cb253c78d", size = 769592, upload-time = "2026-02-28T10:08:20.685Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/95/c7c34aa53c16353c56d0b802fba48d5f5caa2cdee7958acbcb795c830416/isort-8.0.1-py3-none-any.whl", hash = "sha256:28b89bc70f751b559aeca209e6120393d43fbe2490de0559662be7a9787e3d75", size = 89733, upload-time = "2026-02-28T10:08:19.466Z" }, -] - [[package]] name = "itsdangerous" version = "2.2.0" @@ -2228,37 +2210,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, ] -[[package]] -name = "jupyter-book" -version = "1.0.4.post1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "jinja2" }, - { name = "jsonschema" }, - { name = "linkify-it-py" }, - { name = "myst-nb" }, - { name = "myst-parser" }, - { name = "pyyaml" }, - { name = "sphinx" }, - { name = "sphinx-book-theme", version = "1.1.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, - { name = "sphinx-book-theme", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, - { name = "sphinx-comments" }, - { name = "sphinx-copybutton" }, - { name = "sphinx-design", version = "0.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, - { name = "sphinx-design", version = "0.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, - { name = "sphinx-external-toc" }, - { name = "sphinx-jupyterbook-latex" }, - { name = "sphinx-multitoc-numbering" }, - { name = "sphinx-thebe" }, - { name = "sphinx-togglebutton" }, - { name = "sphinxcontrib-bibtex" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cf/ee/5d10ce5b161764ad44219853f386e98b535cb3879bcb0d7376961a1e3897/jupyter_book-1.0.4.post1.tar.gz", hash = "sha256:2fe92c49ff74840edc0a86bb034eafdd0f645fca6e48266be367ce4d808b9601", size = 67412, upload-time = "2025-02-28T14:55:48.637Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/37/86/d45756beaeb4b9b06125599b429451f8640b5db6f019d606f33c85743fd4/jupyter_book-1.0.4.post1-py3-none-any.whl", hash = "sha256:3a27a6b2581f1894ffe8f347d1a3432f06fc616997547919c42cd41c54db625d", size = 45005, upload-time = "2025-02-28T14:55:46.561Z" }, -] - [[package]] name = "jupyter-cache" version = "1.0.1" @@ -2432,15 +2383,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0a/dd/8050c947d435c8d4bc94e3252f4d8bb8a76cfb424f043a8680be637a57f1/kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1", size = 73558, upload-time = "2026-03-09T13:15:52.112Z" }, ] -[[package]] -name = "latexcodec" -version = "3.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/27/dd/4270b2c5e2ee49316c3859e62293bd2ea8e382339d63ab7bbe9f39c0ec3b/latexcodec-3.0.1.tar.gz", hash = "sha256:e78a6911cd72f9dec35031c6ec23584de6842bfbc4610a9678868d14cdfb0357", size = 31222, upload-time = "2025-06-17T18:47:34.051Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/40/23569737873cc9637fd488606347e9dd92b9fa37ba4fcda1f98ee5219a97/latexcodec-3.0.1-py3-none-any.whl", hash = "sha256:a9eb8200bff693f0437a69581f7579eb6bca25c4193515c09900ce76451e452e", size = 18532, upload-time = "2025-06-17T18:47:30.726Z" }, -] - [[package]] name = "lightning-utilities" version = "0.15.3" @@ -2684,15 +2626,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/09/5b161152e2d90f7b87f781c2e1267494aef9c32498df793f73ad0a0a494a/matplotlib_inline-0.2.2-py3-none-any.whl", hash = "sha256:3c821cf1c209f59fb2d2d64abbf5b23b67bcb2210d663f9918dd851c6da1fcf6", size = 9534, upload-time = "2026-05-08T17:33:32.055Z" }, ] -[[package]] -name = "mccabe" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, -] - [[package]] name = "mdit-py-plugins" version = "0.6.1" @@ -3148,11 +3081,9 @@ cpu = [ { name = "torch", version = "2.12.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, ] docs = [ - { name = "jupyter-book" }, { name = "linkify-it-py" }, { name = "myst-nb" }, { name = "myst-parser" }, - { name = "pylint" }, { name = "sphinx" }, { name = "sphinx-autoapi" }, { name = "sphinx-book-theme", version = "1.1.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, @@ -3180,7 +3111,6 @@ requires-dist = [ { name = "boto3", specifier = ">=1.35.32" }, { name = "cartopy", specifier = ">=0.22.0" }, { name = "dataclass-wizard", specifier = "<0.31.0" }, - { name = "jupyter-book", marker = "extra == 'docs'", specifier = ">=1.0.0,<2.0.0" }, { name = "linkify-it-py", marker = "extra == 'docs'", specifier = ">=2.0.0" }, { name = "matplotlib", specifier = ">=3.7.0" }, { name = "mlflow", specifier = ">=2.16.2" }, @@ -3193,7 +3123,6 @@ requires-dist = [ { name = "parse", specifier = ">=1.20.2" }, { name = "pillow", specifier = ">=9.0.0" }, { name = "plotly", specifier = ">=5.15.0" }, - { name = "pylint", marker = "extra == 'docs'", specifier = ">=3.0.0" }, { name = "pyproj", specifier = ">=3.4.1" }, { name = "pytorch-lightning", specifier = ">=2.0.3" }, { name = "scipy", specifier = ">=1.10.0" }, @@ -4454,32 +4383,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, ] -[[package]] -name = "pybtex" -version = "0.26.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "latexcodec" }, - { name = "pyyaml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4d/f5/f30da9c93f0fa6d619332b2f69597219b625f35780473a05164a9981fd9a/pybtex-0.26.1.tar.gz", hash = "sha256:2e5543bea424e60e9e42eef70bff597be48649d8f68ba061a7a092b2477d5464", size = 692991, upload-time = "2026-04-03T13:05:39.014Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/f6/775eb92e865b28cdb4ad1f2bed7a5446197516f76b58a950faa3be3fd08d/pybtex-0.26.1-py3-none-any.whl", hash = "sha256:e26c0412cc54f5f21b2a6d9d175762a2d2af9ccf3a8f651cdb89ec035db77aa1", size = 126134, upload-time = "2026-04-03T13:05:40.623Z" }, -] - -[[package]] -name = "pybtex-docutils" -version = "1.0.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "docutils" }, - { name = "pybtex" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7e/84/796ea94d26188a853660f81bded39f8de4cfe595130aef0dea1088705a11/pybtex-docutils-1.0.3.tar.gz", hash = "sha256:3a7ebdf92b593e00e8c1c538aa9a20bca5d92d84231124715acc964d51d93c6b", size = 18348, upload-time = "2023-08-22T18:47:54.833Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/11/b1/ce1f4596211efb5410e178a803f08e59b20bedb66837dcf41e21c54f9ec1/pybtex_docutils-1.0.3-py3-none-any.whl", hash = "sha256:8fd290d2ae48e32fcb54d86b0efb8d573198653c7e2447d5bec5847095f430b9", size = 6385, upload-time = "2023-08-22T06:43:20.513Z" }, -] - [[package]] name = "pycparser" version = "3.0" @@ -4715,25 +4618,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] -[[package]] -name = "pylint" -version = "4.0.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "astroid" }, - { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, - { name = "dill" }, - { name = "isort" }, - { name = "mccabe" }, - { name = "platformdirs" }, - { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, - { name = "tomlkit" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7d/1d/3bb57f303701549550d74bf7ced2b07412be97125c167a0c9d216aa9f762/pylint-4.0.6.tar.gz", hash = "sha256:52f19191bee08bf103f9705ad1a0ece4aa5a0a4ef2bdcbd969375a1e6f6579d5", size = 1585588, upload-time = "2026-06-14T14:43:26.772Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/da/acb2e7d4dbd2dfb792d38c0d850481f29ad7049b356d23f56c687d35203b/pylint-4.0.6-py3-none-any.whl", hash = "sha256:d11a0e1fdb7b1cd46ec5d6fc78fee8b95f28695b2d6140e5809925f61e32ea54", size = 538389, upload-time = "2026-06-14T14:43:24.873Z" }, -] - [[package]] name = "pyparsing" version = "3.3.2" @@ -6119,18 +6003,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/bf/6f506a37c7f8ecc4576caf9486e303c7af249f6d70447bb51dde9d78cb99/sphinx_book_theme-1.2.0-py3-none-any.whl", hash = "sha256:709605d308e1991c5ef0cf19c481dbe9084b62852e317fafab74382a0ee7ccfa", size = 455936, upload-time = "2026-03-09T23:20:28.788Z" }, ] -[[package]] -name = "sphinx-comments" -version = "0.0.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c0/75/5bbf29e83eaf79843180cf424d0d550bda14a1792ca51dcf79daa065ba93/sphinx-comments-0.0.3.tar.gz", hash = "sha256:00170afff27019fad08e421da1ae49c681831fb2759786f07c826e89ac94cf21", size = 7960, upload-time = "2020-08-12T00:07:31.183Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/26/97/a5c39f619375d4f81d5422377fb027075898efa6b6202c1ccf1e5bb38a32/sphinx_comments-0.0.3-py3-none-any.whl", hash = "sha256:1e879b4e9bfa641467f83e3441ac4629225fc57c29995177d043252530c21d00", size = 4591, upload-time = "2020-08-12T00:07:30.297Z" }, -] - [[package]] name = "sphinx-copybutton" version = "0.5.2" @@ -6143,126 +6015,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9e/48/1ea60e74949eecb12cdd6ac43987f9fd331156388dcc2319b45e2ebb81bf/sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e", size = 13343, upload-time = "2023-04-14T08:10:20.844Z" }, ] -[[package]] -name = "sphinx-design" -version = "0.6.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", - "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", - "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", - "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", - "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", -] -dependencies = [ - { name = "sphinx", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2b/69/b34e0cb5336f09c6866d53b4a19d76c227cdec1bbc7ac4de63ca7d58c9c7/sphinx_design-0.6.1.tar.gz", hash = "sha256:b44eea3719386d04d765c1a8257caca2b3e6f8421d7b3a5e742c0fd45f84e632", size = 2193689, upload-time = "2024-08-02T13:48:44.277Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/43/65c0acbd8cc6f50195a3a1fc195c404988b15c67090e73c7a41a9f57d6bd/sphinx_design-0.6.1-py3-none-any.whl", hash = "sha256:b11f37db1a802a183d61b159d9a202314d4d2fe29c163437001324fe2f19549c", size = 2215338, upload-time = "2024-08-02T13:48:42.106Z" }, -] - -[[package]] -name = "sphinx-design" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", - "python_full_version == '3.13.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", - "python_full_version == '3.12.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", - "python_full_version == '3.11.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", - "python_full_version >= '3.14' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", - "python_full_version == '3.13.*' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", - "python_full_version == '3.12.*' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", - "python_full_version == '3.11.*' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", - "python_full_version >= '3.14' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", - "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", - "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", - "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", - "python_full_version >= '3.14' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", - "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", - "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", - "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", - "python_full_version >= '3.14' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", - "python_full_version == '3.13.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", - "python_full_version == '3.12.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", - "python_full_version == '3.11.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", -] -dependencies = [ - { name = "sphinx", marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/13/7b/804f311da4663a4aecc6cf7abd83443f3d4ded970826d0c958edc77d4527/sphinx_design-0.7.0.tar.gz", hash = "sha256:d2a3f5b19c24b916adb52f97c5f00efab4009ca337812001109084a740ec9b7a", size = 2203582, upload-time = "2026-01-19T13:12:53.297Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/30/cf/45dd359f6ca0c3762ce0490f681da242f0530c49c81050c035c016bfdd3a/sphinx_design-0.7.0-py3-none-any.whl", hash = "sha256:f82bf179951d58f55dca78ab3706aeafa496b741a91b1911d371441127d64282", size = 2220350, upload-time = "2026-01-19T13:12:51.077Z" }, -] - -[[package]] -name = "sphinx-external-toc" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "pyyaml" }, - { name = "sphinx" }, - { name = "sphinx-multitoc-numbering" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c5/f8/85bcd2f1c142e580a1394c18920506d9399b8e8e97e4899bbee9c74a896e/sphinx_external_toc-1.1.0.tar.gz", hash = "sha256:f81833865006f6b4a9b2550a2474a6e3d7e7f2cb23ba23309260577ea65552f6", size = 37194, upload-time = "2026-01-16T13:15:59.03Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/80/1704c9179012e289dee2178354e385277ea51f4fa827c4bf7e36c77b0f4b/sphinx_external_toc-1.1.0-py3-none-any.whl", hash = "sha256:26c390b8d85aa641366fed2d3674910ec6820f48b91027affef485a2655ad7d0", size = 30609, upload-time = "2026-01-16T13:15:57.926Z" }, -] - -[[package]] -name = "sphinx-jupyterbook-latex" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/80/29/18a1fc30e9315e72f068637079169525069a7c0b2fbe51cf689af0576214/sphinx_jupyterbook_latex-1.0.0.tar.gz", hash = "sha256:f54c6674c13f1616f9a93443e98b9b5353f9fdda8e39b6ec552ccf0b3e5ffb62", size = 11945, upload-time = "2023-12-11T15:37:25.034Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/70/1f/1d4ecaf58b17fe61497644655f40b04d84a88348e41a6f0c6392394d95e4/sphinx_jupyterbook_latex-1.0.0-py3-none-any.whl", hash = "sha256:e0cd3e9e1c5af69136434e21a533343fdf013475c410a414d5b7b4922b4f3891", size = 13319, upload-time = "2023-12-11T15:37:23.25Z" }, -] - -[[package]] -name = "sphinx-multitoc-numbering" -version = "0.1.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/37/1e/577bae038372885ebc34bd8c0f290295785a0250cac6528eb6d50e4b92d5/sphinx-multitoc-numbering-0.1.3.tar.gz", hash = "sha256:c9607671ac511236fa5d61a7491c1031e700e8d498c9d2418e6c61d1251209ae", size = 4542, upload-time = "2021-03-15T12:01:43.758Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/9f/902f2030674cd9473fdbe5a2c2dec2618c27ec853484c35f82cf8df40ece/sphinx_multitoc_numbering-0.1.3-py3-none-any.whl", hash = "sha256:33d2e707a9b2b8ad636b3d4302e658a008025106fe0474046c651144c26d8514", size = 4616, upload-time = "2021-03-15T12:01:42.419Z" }, -] - -[[package]] -name = "sphinx-thebe" -version = "0.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/da/fd/926ba4af1eb2708b1ac0fa4376e4bfb11d9a32b2a00e3614137a569c1ddf/sphinx_thebe-0.3.1.tar.gz", hash = "sha256:576047f45560e82f64aa5f15200b1eb094dcfe1c5b8f531a8a65bd208e25a493", size = 20789, upload-time = "2024-02-07T13:31:57.002Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/7c/a53bdb465fd364bc3d255d96d5d70e6ba5183cfb4e45b8aa91c59b099124/sphinx_thebe-0.3.1-py3-none-any.whl", hash = "sha256:e7e7edee9f0d601c76bc70156c471e114939484b111dd8e74fe47ac88baffc52", size = 9030, upload-time = "2024-02-07T13:31:55.286Z" }, -] - -[[package]] -name = "sphinx-togglebutton" -version = "0.4.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "docutils" }, - { name = "setuptools" }, - { name = "sphinx" }, - { name = "wheel" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cc/be/169a0b0a8ad9588e8697c85e1d489aaaca7416073c2fc0267c360af5aae9/sphinx_togglebutton-0.4.5.tar.gz", hash = "sha256:c870dfbd3bc6e119b50ff9a37a64f8991902269e856728931c7d89877e8d4b3d", size = 18101, upload-time = "2026-03-27T13:50:41.984Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/2e/3dd55564928c5d61f92827d4b91307dde7911a40fbe0000645d73202eea9/sphinx_togglebutton-0.4.5-py3-none-any.whl", hash = "sha256:74eac6d2426110c3e1e6f989a98e07d7823141a335df1ad8a9d637bdf6a7af62", size = 44907, upload-time = "2026-03-27T13:50:40.94Z" }, -] - [[package]] name = "sphinxcontrib-applehelp" version = "2.0.0" @@ -6272,21 +6024,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, ] -[[package]] -name = "sphinxcontrib-bibtex" -version = "2.7.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "docutils" }, - { name = "pybtex" }, - { name = "pybtex-docutils" }, - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/15/6a/8e0b2c2420286389e7fed78ff361ec30e2f1d58c8560af8d64df5e7b61e0/sphinxcontrib_bibtex-2.7.0.tar.gz", hash = "sha256:fee700f7aae29bb8f654c62913f00d34ac44fc0b8ca0fa67ac922ff4453addee", size = 120669, upload-time = "2026-05-06T09:29:24.935Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/52/c0/d28e62407f4733bbe0169287bc012f0ac3b4a2021066b285570654119c8b/sphinxcontrib_bibtex-2.7.0-py3-none-any.whl", hash = "sha256:28cf0ec7a957d1c7548d5749317ed472ce877e1b629f430f88e3789aa51f87b1", size = 40287, upload-time = "2026-05-06T09:29:23.253Z" }, -] - [[package]] name = "sphinxcontrib-devhelp" version = "2.0.0" @@ -6538,15 +6275,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, ] -[[package]] -name = "tomlkit" -version = "0.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/51/db/03eaf4331631ef6b27d6e3c9b68c54dc6f0d63d87201fed600cc409307fd/tomlkit-0.15.0.tar.gz", hash = "sha256:7d1a9ecba3086638211b13814ea79c90dd54dd11993564376f3aa92271f5c7a3", size = 161875, upload-time = "2026-05-10T07:38:22.245Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/43/8bd850ee71a191bf072e31302c73a66be413fecdd98fdcd111ecbcce13ca/tomlkit-0.15.0-py3-none-any.whl", hash = "sha256:4dbc8f0fc024412b57ced8757ac7461305126a648ff8c2c807fcb8e133a78738", size = 41328, upload-time = "2026-05-10T07:38:23.517Z" }, -] - [[package]] name = "toolz" version = "1.1.0" @@ -7090,18 +6818,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7f/b2/0bba9bbb4596d2d2f285a16c2ab04118f6b957d8441566e1abb892e6a6b2/werkzeug-3.1.7-py3-none-any.whl", hash = "sha256:4b314d81163a3e1a169b6a0be2a000a0e204e8873c5de6586f453c55688d422f", size = 226295, upload-time = "2026-03-24T01:08:06.133Z" }, ] -[[package]] -name = "wheel" -version = "0.47.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/39/62/75f18a0f03b4219c456652c7780e4d749b929eb605c098ce3a5b6b6bc081/wheel-0.47.0.tar.gz", hash = "sha256:cc72bd1009ba0cf63922e28f94d9d83b920aa2bb28f798a31d0691b02fa3c9b3", size = 63854, upload-time = "2026-04-22T15:51:27.727Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/87/1b/9e33c09813d65e248f7f773119148a612516a4bea93e9c6f545f78455b7c/wheel-0.47.0-py3-none-any.whl", hash = "sha256:212281cab4dff978f6cedd499cd893e1f620791ca6ff7107cf270781e587eced", size = 32218, upload-time = "2026-04-22T15:51:26.296Z" }, -] - [[package]] name = "win32-setctime" version = "1.2.0" From b02bf7994ab8a046e3b97023e8d55252c3da8042 Mon Sep 17 00:00:00 2001 From: Mohit-Lakra Date: Sun, 21 Jun 2026 21:47:10 +0530 Subject: [PATCH 08/15] docs : Formatting Fix --- diff.patch | 1921 -------------------- docs/conf.py | 129 +- neural_lam/create_graph.py | 28 +- neural_lam/datastore/base.py | 4 +- neural_lam/datastore/mdp.py | 2 +- neural_lam/datastore/npyfilesmeps/store.py | 10 +- neural_lam/gnn_layers.py | 5 +- 7 files changed, 96 insertions(+), 2003 deletions(-) delete mode 100644 diff.patch diff --git a/diff.patch b/diff.patch deleted file mode 100644 index 20e7a61ad..000000000 --- a/diff.patch +++ /dev/null @@ -1,1921 +0,0 @@ -diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml -index 272f4ac..80e8c45 100644 ---- a/.github/workflows/docs.yml -+++ b/.github/workflows/docs.yml -@@ -42,8 +42,13 @@ jobs: - - name: Install docs dependencies - run: uv sync --extra cpu --extra docs --group dev --no-cache - -- - name: Build docs with pyreverse and jupyter-book -- run: bash docs/scripts/generate_docs.sh -+ - name: Generate UML diagrams -+ run: | -+ mkdir -p docs/_static/uml -+ uv run pyreverse -o mmd -p models neural_lam.models -d docs/_static/uml/ -+ -+ - name: Build docs with sphinx -+ run: uv run sphinx-build -W --keep-going -b html docs/ docs/_build/html/ - - - name: Upload docs artifact - uses: actions/upload-artifact@v4 -@@ -75,38 +80,7 @@ jobs: - - name: Generate UML diagrams - run: | - mkdir -p docs/_static/uml -- uv run pyreverse -o mmd -p models neural_lam.models -d docs/_static/uml/ || true -+ uv run pyreverse -o mmd -p models neural_lam.models -d docs/_static/uml/ - - - name: Run linkcheck -- run: uv run jupyter-book build docs/ --builder linkcheck || true -- -- deploy-docs: -- name: Deploy to GitHub Pages -- runs-on: ubuntu-latest -- needs: build-docs -- if: github.event_name == 'push' && github.ref == 'refs/heads/main' -- permissions: -- contents: read -- pages: write -- id-token: write -- environment: -- name: github-pages -- url: ${{ steps.deployment.outputs.page_url }} -- steps: -- - name: Setup Pages -- uses: actions/configure-pages@v4 -- -- - name: Download artifact -- uses: actions/download-artifact@v4 -- with: -- name: docs-html -- path: docs/_build/html/ -- -- - name: Upload pages artifact -- uses: actions/upload-pages-artifact@v3 -- with: -- path: docs/_build/html/ -- -- - name: Deploy to GitHub Pages -- id: deployment -- uses: actions/deploy-pages@v4 -+ run: uv run sphinx-build -W --keep-going -b linkcheck docs/ docs/_build/linkcheck/ -diff --git a/.readthedocs.yaml b/.readthedocs.yaml -index a56ede2..463c52a 100644 ---- a/.readthedocs.yaml -+++ b/.readthedocs.yaml -@@ -8,6 +8,6 @@ build: - - pip install uv - - uv pip install --system -e ".[cpu,docs]" - - mkdir -p docs/_static/uml -- - pyreverse -o mmd -p models neural_lam.models -d docs/_static/uml/ || true -- - jupyter-book build docs/ -+ - pyreverse -o mmd -p models neural_lam.models -d docs/_static/uml/ -+ - sphinx-build -W --keep-going -b html docs/ docs/_build/html/ - - cp -r docs/_build/html/* $READTHEDOCS_OUTPUT/html/ -diff --git a/docs/_config.yml b/docs/_config.yml -deleted file mode 100644 -index 88fa2c0..0000000 ---- a/docs/_config.yml -+++ /dev/null -@@ -1,124 +0,0 @@ --# docs/_config.yml -- --title: "Neural-LAM" --author: "MLLAM Community" --copyright: "2024–2026" -- --logo: "_static/logo.png" --favicon: "_static/favicon.ico" -- --repository: -- url: "https://github.com/mllam/neural-lam" -- branch: "main" -- path_to_book: "docs" -- --launch_buttons: -- binderhub_url: "https://mybinder.org" -- colab_url: "https://colab.research.google.com" -- --execute: -- execute_notebooks: "off" -- --exclude_patterns: -- - _build -- - Thumbs.db -- - .DS_Store -- - "**.ipynb_checkpoints" -- - _static -- - scripts -- --parse: -- myst_enable_extensions: -- - colon_fence -- - dollarmath -- - linkify -- - substitution -- - tasklist -- - deflist -- - fieldlist -- - html_admonition -- - html_image -- - smartquotes -- - attrs_inline -- myst_url_schemes: [mailto, http, https] -- --sphinx: -- extra_extensions: -- - "scripts.autoapi_astroid_patch" -- - "autoapi.extension" -- - "sphinx.ext.napoleon" -- - "sphinx.ext.viewcode" -- - "sphinx.ext.intersphinx" -- - "sphinx_copybutton" -- - "sphinxcontrib.mermaid" -- - "sphinxext.opengraph" -- -- local_extensions: -- scripts.autoapi_astroid_patch: "." -- -- config: -- html_theme: "sphinx_book_theme" -- html_theme_options: -- repository_url: "https://github.com/mllam/neural-lam" -- use_repository_button: true -- use_issues_button: true -- use_edit_page_button: true -- repository_branch: "main" -- path_to_docs: "docs" -- show_navbar_depth: 2 -- navigation_with_keys: false -- show_toc_level: 2 -- logo: -- text: "Neural-LAM" -- announcement: "" -- extra_footer: | --

Built with Jupyter Book | -- Source

-- -- html_static_path: ["_static"] -- html_css_files: ["custom.css"] -- -- # ── AutoAPI ── -- autoapi_dirs: ["../neural_lam"] -- autoapi_root: "api" -- autoapi_type: "python" -- autoapi_options: -- - "members" -- - "undoc-members" -- - "show-inheritance" -- - "show-module-summary" -- autoapi_python_class_content: "both" -- autoapi_member_order: "groupwise" -- autoapi_python_use_implicit_namespaces: false -- autoapi_keep_files: true -- autoapi_add_toctree_entry: true -- autoapi_ignore: ["**/tests/**", "**/conftest.py"] -- -- # ── Napoleon ── -- napoleon_numpy_docstring: true -- napoleon_google_docstring: false -- napoleon_include_init_with_doc: true -- napoleon_include_private_with_doc: false -- napoleon_include_special_with_doc: false -- napoleon_use_param: true -- napoleon_use_rtype: true -- -- # ── Intersphinx ── -- intersphinx_mapping: -- python: ["https://docs.python.org/3", null] -- numpy: ["https://numpy.org/doc/stable", null] -- torch: ["https://pytorch.org/docs/stable", null] -- pytorch_lightning: ["https://lightning.ai/docs/pytorch/stable/", null] -- torch_geometric: ["https://pytorch-geometric.readthedocs.io/en/latest/", null] -- -- # ── Mermaid ── -- mermaid_d3_zoom: false -- mermaid_version: "11" -- -- # ── Suppress Warnings ── -- suppress_warnings: ["autoapi.python_import_resolution"] -- -- # ── OpenGraph ── -- ogp_site_url: "https://mllam.github.io/neural-lam/" -- ogp_image: "_static/logo.png" -- ogp_use_first_image: true -diff --git a/docs/_toc.yml b/docs/_toc.yml -deleted file mode 100644 -index 2ef150f..0000000 ---- a/docs/_toc.yml -+++ /dev/null -@@ -1,35 +0,0 @@ --format: jb-book --root: intro -- --parts: -- - caption: Getting Started -- chapters: -- - file: getting-started/installation -- - file: getting-started/quickstart -- -- - caption: Tutorials -- chapters: -- - file: notebooks/create_reduced_meps_dataset -- -- - caption: Architecture -- chapters: -- - file: architecture/overview -- - file: architecture/theory -- - file: architecture/data-flow -- - file: architecture/models -- - file: architecture/graph-construction -- -- - caption: Resources -- chapters: -- - file: guides/configuration -- - file: api/index -- -- - caption: Contributing -- chapters: -- - file: contributing/development-setup -- - file: contributing/coding-standards -- -- - caption: About -- chapters: -- - file: about/changelog -- - file: about/glossary -diff --git a/docs/about/changelog.md b/docs/about/changelog.md -deleted file mode 120000 -index 699cc9e..0000000 ---- a/docs/about/changelog.md -+++ /dev/null -@@ -1 +0,0 @@ --../../CHANGELOG.md -\ No newline at end of file -diff --git a/docs/about/glossary.md b/docs/about/glossary.md -deleted file mode 100644 -index 13f8b8a..0000000 ---- a/docs/about/glossary.md -+++ /dev/null -@@ -1,60 +0,0 @@ --# Glossary -- --```{glossary} --ARModel -- The autoregressive base model implemented in PyTorch Lightning. -- --BaseDatastore -- The abstract base class for data loaders and datastores. -- --Datastore -- The overarching data handler component for loading weather data. -- --Encode-Process-Decode -- An architecture style often used in graph neural networks involving encoding inputs into latent graphs, processing them with message passing, and decoding back to the target space. -- --Forcing Variables -- External variables providing boundary or context conditions to the {term}`Forecaster`. -- --Forecaster -- The core component or model responsible for generating predictions over time. -- --GNN -- Graph Neural Network. A type of neural network designed to operate on graph structures. -- --GraphLAM -- The main Graph-based Limited Area Model implementation. -- --HiLAM -- Hierarchical Limited Area Model. -- --HiLAMParallel -- A parallelized version of {term}`HiLAM`. -- --LAM -- Limited Area Model. A weather prediction model focused on a specific geographic region rather than global scope. -- --MDPDatastore -- Datastore designed to read Zarr formats via the {term}`mllam-data-prep` module. -- --Mesh -- The structured or unstructured graph grid onto which the weather data is projected and processed. -- --mllam-data-prep -- The tool/module responsible for preparing and formatting raw weather data into a format ingestible by Neural-LAM. -- --PyG -- PyTorch Geometric, a library for deep learning on irregular input data such as graphs. -- --State Variables -- The set of variables that describe the current internal state of the weather system in the model. -- --StepPredictor -- A model component designed to predict the next single time step given a current state. -- --WeatherDataModule -- The PyTorch Lightning data module encapsulating the {term}`WeatherDataset`. -- --WeatherDataset -- The PyTorch dataset class representing the prepared weather data. --``` -diff --git a/docs/architecture/data-flow.md b/docs/architecture/data-flow.md -deleted file mode 100644 -index 9cdfe7a..0000000 ---- a/docs/architecture/data-flow.md -+++ /dev/null -@@ -1,38 +0,0 @@ --# Data Flow -- --## Overview -- --The Neural-LAM data pipeline is designed to efficiently load raw meteorological data and feed it into the models for training and inference. It abstracts data sources, normalizes variables, and prepares batches using PyTorch Lightning. -- --## Datastore Abstraction -- --The {py:class}`neural_lam.datastore.BaseDatastore` provides an abstract interface for data access. It defines methods for fetching input and target tensors (`get_xy`), as well as metadata like variable names and units. Subclasses like `MDPDatastore` implement this interface to support specific formats like Zarr. -- --## WeatherDataset -- --The {py:class}`neural_lam.weather_dataset.WeatherDataset` acts as a PyTorch wrapper around a `BaseDatastore`. It handles indexing, temporal batching, and any necessary on-the-fly transformations required before the data reaches the Lightning module. -- --## WeatherDataModule -- --The {py:class}`neural_lam.weather_dataset.WeatherDataModule` is a PyTorch Lightning DataModule that encapsulates the `WeatherDataset`s for training, validation, and testing splits. It manages the dataloaders and ensures data is correctly distributed across devices during distributed training. -- --## Configuration -- --The entire data pipeline is driven by type-safe YAML configurations defined in {py:mod}`neural_lam.config`. Dataclasses define expected paths, batch sizes, and data normalization statistics, allowing easy experimentation without code changes. -- --## Data Format Requirements -- --| Component | Format/Shape Expected | --|-----------|-----------------------| --| Model Input | `(batch, sequence_length, num_grid_nodes, num_features)` | --| Target Output | `(batch, sequence_length, num_grid_nodes, num_features)` | --| Static Features | `(num_grid_nodes, num_static_features)` | -- --```{mermaid} --flowchart TD -- A["Raw Data Files"] -->|Read by| B["BaseDatastore"] -- B -->|get_xy| C["WeatherDataset"] -- C -->|__getitem__| D["DataLoader"] -- D -->|B, T, N, F| E["WeatherDataModule"] -- E -->|Batch| F["Model Forward Pass"] --``` -diff --git a/docs/architecture/graph-construction.md b/docs/architecture/graph-construction.md -deleted file mode 100644 -index d50daad..0000000 ---- a/docs/architecture/graph-construction.md -+++ /dev/null -@@ -1,66 +0,0 @@ --# Graph Construction -- --## Why Graphs? -- --Graph Neural Networks (GNNs) leverage graph-based message passing, which is highly suited for weather prediction. Unlike standard CNNs on rigid grids, graphs can naturally represent irregular spatial distributions and complex geometries, common in Limited Area Modeling (LAM). -- --The graph structure dictates how information flows across spatial regions. A well-designed mesh ensures that localized weather phenomena correctly influence neighboring regions, while hierarchical structures allow long-range interactions (like large-scale pressure systems) to propagate efficiently across the domain without requiring hundreds of standard grid steps. -- --## The create_graph Module -- --The `create_graph` script is used to pre-compute and build the mesh graphs required before training any models. This ensures that the complex spatial structures are generated once and loaded efficiently during training. -- --Reference: {py:mod}`neural_lam.create_graph` -- --## Graph Types -- --### Flat Mesh --- Single level of mesh nodes --- Used by GraphLAM -- --### Hierarchical Mesh --- Multiple levels of mesh nodes at increasing spatial scales --- Used by HiLAM and HiLAMParallel -- --```{mermaid} --graph TD -- subgraph grid ["Grid Level"] -- G1["Grid Node"] --- G2["Grid Node"] --- G3["Grid Node"] -- end -- subgraph mesh1 ["Mesh Level 1"] -- M1["Mesh Node"] --- M2["Mesh Node"] -- end -- subgraph mesh2 ["Mesh Level 2"] -- M3["Mesh Node"] -- end -- G1 -.-> M1 -- G2 -.-> M1 -- G2 -.-> M2 -- G3 -.-> M2 -- M1 -.-> M3 -- M2 -.-> M3 --``` -- --## Edge Features -- --For each edge type in the constructed graph, specific features are computed to assist message passing: --- **Spatial Distance**: The physical distance between connected nodes. --- **Directional Vectors**: Vector representations of the direction between nodes, enabling the GNN to understand flow and gradients (e.g., wind direction). --- **Elevation Differences**: Changes in altitude between nodes, which is critical for orographic effects in weather. -- --## Usage -- --```bash --python -m neural_lam.create_graph \ -- --config_path \ -- --name --``` -- --- `--config_path`: Path to the YAML configuration file which dictates the dataset properties and graph parameters. --- `--name`: The name assigned to the generated graph, which is used to load it during training. -- --## GNN Layers -- --The `InteractionNet` represents the core GNN layer implementation in Neural-LAM. It utilizes PyTorch Geometric's `MessagePassing` interface to aggregate features from neighboring nodes and update node states, incorporating edge features natively to refine the spatial interactions. -- --Reference: {py:mod}`neural_lam.gnn_layers` -diff --git a/docs/architecture/models.md b/docs/architecture/models.md -deleted file mode 100644 -index b82729e..0000000 ---- a/docs/architecture/models.md -+++ /dev/null -@@ -1,60 +0,0 @@ --# Model Architectures -- --## Overview -- --Neural-LAM employs a modular model hierarchy based on the **encode-process-decode** paradigm. This paradigm allows the models to encode grid-based weather data into a graph structure, perform spatial and temporal message passing to process the data, and finally decode the updated graph states back into the original grid representation. -- --## Autoregressive Framework -- --The core training and evaluation loop is handled by `ARModel`, a PyTorch Lightning module defined in `neural_lam/models/module.py`. This autoregressive framework is responsible for unrolling predictions over multiple timesteps. -- --```{mermaid} --sequenceDiagram -- participant Trainer as Lightning Trainer -- participant AR as ARModel -- participant F as Forecaster -- participant SP as StepPredictor -- -- Trainer->>AR: training_step(batch) -- loop for each timestep -- AR->>F: forward(prev_state) -- F->>SP: predict_step(state, graph) -- SP-->>F: next_state_delta -- F-->>AR: next_state -- end -- AR->>AR: compute_loss() -- AR-->>Trainer: loss --``` -- --## Encode-Process-Decode -- --The base graph model, defined in `neural_lam/models/step_predictors/base.py`, implements the standard encode-process-decode steps: --1. **Encode**: Features from the weather grid are mapped onto the nodes and edges of the mesh graph. --2. **Process**: A Graph Neural Network (GNN) performs multiple rounds of message passing to propagate information across the spatial domain. --3. **Decode**: The updated mesh features are mapped back to the grid to produce the next state prediction. -- --## GraphLAM -- --GraphLAM is the fundamental model architecture utilizing a single-level flat graph. It is effective for standard resolution forecasting without multi-scale processing. -- --Reference: {py:class}`neural_lam.models.step_predictors.graph.GraphLAM` -- --## HiLAM -- --HiLAM introduces a hierarchical model design, utilizing multiple levels of mesh nodes at increasing spatial scales. This allows the network to efficiently capture both local, fine-grained interactions and long-range, global atmospheric patterns. -- --Reference: {py:class}`neural_lam.models.step_predictors.graph.HiLAM` -- --## HiLAMParallel -- --HiLAMParallel is a parallel hierarchical variant of HiLAM. It processes multi-scale information simultaneously across different hierarchy levels, rather than sequentially, potentially improving computational efficiency and long-range interaction modeling. -- --Reference: {py:class}`neural_lam.models.step_predictors.graph.HiLAMParallel` -- --## Choosing a Model -- --| Model | Graph Type | Complexity | Best For | --|---|---|---|---| --| **GraphLAM** | Flat Mesh | Low | Baseline forecasting, single-scale dynamics | --| **HiLAM** | Hierarchical Mesh | Medium | Capturing both local and global dependencies efficiently | --| **HiLAMParallel** | Hierarchical Mesh | High | Highly parallel environments, very large spatial domains | -diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md -deleted file mode 100644 -index 7d5c8ab..0000000 ---- a/docs/architecture/overview.md -+++ /dev/null -@@ -1,51 +0,0 @@ --# Architecture Overview -- --## System Design -- --Neural-LAM is designed with a modular architecture that separates data handling, model architecture, and training logic. This separation of concerns allows for easy experimentation with different graph structures, model components, and datasets without requiring extensive changes to the core system. -- --The core components include a robust data pipeline utilizing an abstract datastore interface, a flexible set of graph-based neural network models (such as `GraphLAM`, `HiLAM`, and `HiLAMParallel`), and a training module built on top of PyTorch Lightning. This design ensures scalability and ease of use for both researchers and practitioners. -- --## Data Flow -- --```{mermaid} --flowchart LR -- A["Raw Data
(zarr / numpy)"] --> B["Datastore
(BaseDatastore)"] -- B --> C["WeatherDataset"] -- C --> D["WeatherDataModule
(Lightning)"] -- D --> E["ARModel
(Autoregressive)"] -- E --> F["StepPredictor
(GNN)"] -- F --> G["Predictions"] -- G --> H["Loss & Metrics"] --``` -- --## Module Map -- --| Module | Responsibility | --|--------|----------------| --| `datastore` | Handles reading from diverse data sources (e.g., Zarr, NetCDF) via the `BaseDatastore` interface. | --| `weather_dataset` | Wraps the datastore in a PyTorch `Dataset` and Lightning `DataModule` for training. | --| `models` | Contains the core neural network architectures (`ARModel`, `BaseGraphModel`, `GraphLAM`, etc.). | --| `create_graph` | Utility to build the hierarchical mesh graphs used by the GNN models. | --| `config` | Manages the YAML-based configuration via dataclasses. | -- --## Component Interaction -- --This diagram is **automatically generated** from the Python source code using `pyreverse`, ensuring it never goes stale! -- --```{eval-rst} --.. mermaid:: ../_static/uml/classes_models.mmd --``` -- --## Key Design Decisions -- --- **Modular Datastores**: `BaseDatastore` abstracts away data loading intricacies. --- **PyTorch Lightning**: Used to reduce boilerplate and scale training easily. --- **Hierarchical Graphs**: `create_graph` decouples graph structure generation from model logic. --- **Dataclass Configurations**: Type-safe YAML configurations managed by `dataclass-wizard`. -- --## See Also -- --- {doc}`data-flow` for detailed data pipeline documentation --- {doc}`models` for model architecture details --- {doc}`graph-construction` for graph creation details -diff --git a/docs/architecture/theory.md b/docs/architecture/theory.md -deleted file mode 100644 -index a636c53..0000000 ---- a/docs/architecture/theory.md -+++ /dev/null -@@ -1,47 +0,0 @@ --# Theory & Methods -- --This section describes the mathematical formulations and algorithms underlying the Neural-LAM graph models. -- --## The Encode-Process-Decode Paradigm -- --All graph-based models in Neural-LAM (`GraphLAM`, `HiLAM`, and `HiLAMParallel`) follow the classic "Encode-Process-Decode" paradigm for graph neural networks. -- --1. **Encode**: The input grid state \( X^t \) at timestep \( t \) is mapped to latent node features on the graph: -- \[ H_{grid} = \text{Encoder}(X^t) \] -- --2. **Process**: A series of message passing steps updates the latent node representations based on the graph connectivity: -- \[ H'_{grid} = \text{Processor}(H_{grid}, \mathcal{G}) \] -- --3. **Decode**: The updated latent features are mapped back to predict the residual change for the next timestep: -- \[ \Delta X^{t+1} = \text{Decoder}(H'_{grid}) \] -- \[ X^{t+1} = X^t + \Delta X^{t+1} \] -- --## Message Passing Functions -- --Neural-LAM supports several GNN layers for message passing along different edge types (e.g., Grid-to-Mesh, Mesh-to-Mesh). -- --### InteractionNet -- --The default layer is the `InteractionNet` (based on Interaction Networks). Given a sender node \( v_s \), a receiver node \( v_r \), and an edge feature \( e_{s,r} \), the message passing works as follows: -- --1. **Edge Update (Message Formulation)**: -- \[ m_{s,r} = \text{MLP}_{edge}\left([h_s, h_r, e_{s,r}]\right) \] -- --2. **Node Update (Aggregation)**: -- \[ h'_r = \text{MLP}_{node}\left([h_r, \sum_{s \in \mathcal{N}(r)} m_{s,r}]\right) \] -- --### PropagationNet -- --`PropagationNet` modifies the Interaction Network to strongly incentivize directional information flow from senders to receivers, which is crucial for moving information up and down the hierarchical mesh levels: -- --\[ h'_r = h_r + \text{MLP}_{prop}\left([h_r, \sum_{s \in \mathcal{N}(r)} m_{s,r}]\right) \] -- --This residual connection ensures that nodes maintain their internal state while selectively incorporating new information from neighbors. -- --## Loss Weighting -- --To balance predictions across variables with different physical scales and variances, the loss function uses a weighted Mean Squared Error (MSE): -- --\[ \mathcal{L} = \frac{1}{V \cdot N} \sum_{v=1}^V \sum_{i=1}^N w_v \cdot \left( \hat{X}^{t+1}_{i,v} - X^{t+1}_{i,v} \right)^2 \] -- --Where \( w_v \) are variable-specific weights defined in the `loss_weighting` module, and the errors are computed on standardized variables. -diff --git a/docs/contributing/coding-standards.md b/docs/contributing/coding-standards.md -deleted file mode 100644 -index c1655d7..0000000 ---- a/docs/contributing/coding-standards.md -+++ /dev/null -@@ -1,41 +0,0 @@ --# Coding Standards -- --## Code Style -- --Our codebase enforces strict styling guidelines using the following tools: --- **Formatter**: `black` --- **Import Sorting**: `isort` --- **Linting**: `flake8` --- **Type Checking**: `mypy` --- **Docstring Coverage**: `interrogate` --- **Spell Checking**: `codespell` -- --## Pull Request Process -- --1. **Search before creating**: Search existing issues or PRs to avoid duplicates. --2. **Every PR requires an issue**: Open an issue first if none exists. --3. **Link the issue**: Include `closes #` or `refs #` in the PR body. --4. **Use the PR template**: Fill out every section of the provided template. --5. **Run pre-commit hooks**: Ensure code passes locally via `uvx pre-commit run --all-files`. --6. **Run tests**: Run `pytest tests/` and fix any failures before opening the PR. --7. **Update CHANGELOG**: Add a line to `CHANGELOG.md` in the appropriate section. -- --## Docstring Standard -- --- We use **NumPy-style** docstrings. --- We require **100% docstring coverage** for public functions, methods, and classes. --- Always include `Parameters`, `Returns`, and `Raises` sections if applicable. --- Make sure to specify **tensor shapes** in the docstrings when passing or returning tensors. -- --## Commit Messages -- --- Must be in the **imperative form** (e.g., "Add test for feature X" instead of "Added test..."). --- Keep **one concern per PR** to ensure unrelated changes are not mixed. --- AI attribution of tool names is mandatory if used and should be mentioned in the commit message trailer as `Co-authored-by `. -- --## Adding Documentation -- --When adding new modules or classes: --1. Include detailed docstrings directly in the code. --2. If appropriate, create a new Markdown page under `docs/architecture/` (or update an existing one). --3. Ensure the new page is added to the `_toc.yml` so it appears in the documentation navigation structure. -diff --git a/docs/contributing/development-setup.md b/docs/contributing/development-setup.md -deleted file mode 100644 -index 93f3a2c..0000000 ---- a/docs/contributing/development-setup.md -+++ /dev/null -@@ -1,102 +0,0 @@ --# Development Setup -- --## Prerequisites -- --- Python >=3.10 --- Git --- uv (recommended) or pip -- --## Clone and Install -- --```bash --git clone https://github.com/mllam/neural-lam.git --cd neural-lam --uv sync --extra cpu --group dev --locked --source .venv/bin/activate --``` -- --## Pre-commit Hooks -- --We use `pre-commit` to ensure code formatting and quality before commits. The hooks include: --- `black` for code formatting. --- `isort` for import sorting. --- `flake8` for linting. --- `mypy` for static type checking. --- `codespell` for spell checking. --- `interrogate` for docstring coverage. -- --To install and run the pre-commit hooks: -- --```bash --pre-commit install --uvx pre-commit run --all-files --``` -- --## Running Tests -- --We use `pytest` for running our test suite. Note that Weights & Biases (W&B) is automatically disabled during tests. -- --Run all tests: --```bash --pytest -vv -s --doctest-modules --``` -- --Run tests in a single file: --```bash --pytest tests/test_training.py -vv -s --``` -- --Run a single function test: --```bash --pytest tests/test_training.py::test_fn -vv --``` -- --## Building Documentation -- --We use `jupyter-book` to build the documentation. You can build it using `uv`: -- --```bash --uv run jb build docs --``` -- --## Project Structure -- --- `docs/` - Project documentation. --- `neural_lam/` - Main source code directory containing core modules, models, and data logic. -- - `datastore/` - Datastore classes for loading data. -- - `models/` - Core neural network models. --- `tests/` - Unit tests and test data examples. -- --## Writing Docstrings -- --We follow the NumPy-style format for docstrings. All functions and classes must have docstrings including Parameters, Returns, Raises, and Tensor shapes where applicable. -- --Example: --```python --import torch -- --def process_state(state: torch.Tensor, threshold: float = 0.5) -> torch.Tensor: -- """ -- Process the given state tensor by applying a threshold. -- -- Parameters -- ---------- -- state : torch.Tensor -- The input state tensor of shape (batch_size, num_features). -- threshold : float, optional -- The threshold value to apply, by default 0.5. -- -- Returns -- ------- -- torch.Tensor -- The processed state tensor of shape (batch_size, num_features). -- -- Raises -- ------ -- ValueError -- If the state tensor is empty. -- """ -- if state.numel() == 0: -- raise ValueError("State tensor cannot be empty.") -- return torch.where(state > threshold, state, torch.zeros_like(state)) --``` -diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md -index 60b0029..96fbd11 100644 ---- a/docs/getting-started/quickstart.md -+++ b/docs/getting-started/quickstart.md -@@ -27,7 +27,7 @@ Before training, you must construct a graph mesh for your data. - python -m neural_lam.create_graph --config_path --name - ``` - --This script builds the mesh graph required by the GNN models. For more details, see {doc}`../architecture/graph-construction`. -+This script builds the mesh graph required by the GNN models. - - ## Step 3: Train a Model - -@@ -37,7 +37,7 @@ Now you can train a model using the graph and configuration. - python -m neural_lam.train_model --config_path --model graph_lam --graph - ``` - --Neural-LAM supports several models like `graph_lam`, `hilam`, and `hilam_parallel`. For more information on the available models, see {doc}`../architecture/models`. -+Neural-LAM supports several models like `graph_lam`, `hilam`, and `hilam_parallel`. - - ## Step 4: Evaluate - -@@ -51,7 +51,4 @@ python -m neural_lam.train_model --eval test --config_path --lo - - Neural-LAM uses a YAML configuration system powered by `dataclass-wizard`. The configuration defines the dataset paths, training parameters, and model hyperparameters. For complete details, see the API reference for {py:class}`neural_lam.config.NeuralLAMConfig`. - --## Next Steps -- --- {doc}`../architecture/overview` to understand the system design - - {doc}`../api/index` for complete API reference -diff --git a/docs/guides/configuration.md b/docs/guides/configuration.md -deleted file mode 100644 -index c7b9856..0000000 ---- a/docs/guides/configuration.md -+++ /dev/null -@@ -1,58 +0,0 @@ --# Configuration Reference -- --Neural-LAM uses `dataclass-wizard` to automatically parse and enforce type-safety for YAML configuration files. The configuration encapsulates both the **Datastore** configuration and the **Model/Training** configuration. -- --Below are common templates you can use for your own runs. -- --## 1. Quick CPU Test Run -- --This minimal configuration uses a dummy datastore, perfect for quickly testing changes on a laptop without a GPU. -- --```yaml --# tests/test_config.yaml --datastore: -- _target_: "neural_lam.datastore.DummyDatastore" -- --architecture: "graph_lam" --epochs: 2 --batch_size: 2 --lr: 1e-3 --hidden_dim: 32 --hidden_layers: 1 --``` -- --## 2. MDP (Meteorological Data Processing) Full Training -- --This is a production-level configuration for training on a real Zarr dataset produced by `mllam-data-prep` on a GPU cluster. -- --```yaml --# config/mdp_training.yaml --datastore: -- _target_: "neural_lam.datastore.MDPDatastore" -- dataset_path: "/path/to/my/zarr_dataset.zarr" -- subset_name: "meps" # Optional subset -- --# Model architecture settings --architecture: "hi_lam_parallel" --hidden_dim: 128 --hidden_layers: 4 --mesh_aggr: "sum" -- --# GNN Types for the different edges --g2m_gnn_type: "interaction" --m2g_gnn_type: "interaction" --mesh_up_gnn_type: "propagation" --mesh_down_gnn_type: "propagation" -- --# Training parameters --epochs: 100 --batch_size: 16 --lr: 5e-4 --loss: "mse" -- --# Logging and reproducibility --seed: 42 --``` -- --## API Reference --For an exhaustive list of every configurable field and its default value, refer to the auto-generated documentation for the `NeuralLAMConfig` dataclass in the {py:mod}`neural_lam.config` module. -diff --git a/docs/intro.md b/docs/intro.md -deleted file mode 100644 -index fee7572..0000000 ---- a/docs/intro.md -+++ /dev/null -@@ -1,73 +0,0 @@ --# Neural-LAM -- --**Graph-based neural weather prediction for Limited Area Modeling** -- --Neural-LAM is a PyTorch and PyTorch Lightning framework for high-resolution weather prediction using Graph Neural Networks. It provides a modular approach to Limited Area Modeling, supporting multiple graph-based architectures such as GraphLAM, HiLAM, and HiLAMParallel to process and predict meteorological data efficiently. -- --```{admonition} Get Started in 5 Minutes --:class: tip --Install Neural-LAM and run your first prediction. --See the {doc}`Getting Started Guide `. --``` -- --::::{grid} 2 --:gutter: 3 -- --:::{grid-item-card} 🚀 Getting Started --:link: getting-started/installation --:link-type: doc --Installation guide and quickstart tutorial to get you up and running. --::: -- -- --:::{grid-item-card} 🏗️ Architecture --:link: architecture/overview --:link-type: doc --Understand the data flow, model structure, and design decisions. --::: -- --:::{grid-item-card} 📚 API Reference --:link: api/index --:link-type: doc --Auto-generated reference for all modules, classes, and functions. --::: -- --:::: -- --## Key Features -- --- **Modular design**: Swap datastores, models, and graph structures independently --- **Multiple model architectures**: GraphLAM (flat), HiLAM (hierarchical), HiLAMParallel (parallel hierarchical) --- **Flexible data handling**: Abstract datastore interface supporting zarr, numpy, and custom formats via mllam-data-prep --- **Production-ready**: PyTorch Lightning for training, W&B/MLflow logging, checkpoint management -- --## Publications -- --If you use Neural-LAM in your research, please cite the relevant papers: -- --**NeurIPS 2024 Paper** ([Probabilistic Weather Forecasting with Hierarchical Graph Neural Networks](https://arxiv.org/abs/2406.04759)) --```bibtex --@inproceedings{oskarsson2024probabilistic, -- title = {Probabilistic Weather Forecasting with Hierarchical Graph Neural Networks}, -- author = {Oskarsson, Joel and Landelius, Tomas and Deisenroth, Marc Peter and Lindsten, Fredrik}, -- booktitle = {Advances in Neural Information Processing Systems}, -- volume = {37}, -- year = {2024}, --} --``` -- --**NeurIPS 2023 Workshop Paper** ([Graph-based Neural Weather Prediction for Limited Area Modeling](https://arxiv.org/abs/2309.17370)) --```bibtex --@inproceedings{oskarsson2023graphbased, -- title={Graph-based Neural Weather Prediction for Limited Area Modeling}, -- author={Oskarsson, Joel and Landelius, Tomas and Lindsten, Fredrik}, -- booktitle={NeurIPS 2023 Workshop on Tackling Climate Change with Machine Learning}, -- year={2023} --} --``` -- --## Quick Links -- --- [GitHub Repository](https://github.com/mllam/neural-lam) --- [Issue Tracker](https://github.com/mllam/neural-lam/issues) --- [MLLAM Community Slack](https://kutt.it/mllam) -diff --git a/docs/notebooks/create_reduced_meps_dataset.ipynb b/docs/notebooks/create_reduced_meps_dataset.ipynb -deleted file mode 100644 -index 00cfa24..0000000 ---- a/docs/notebooks/create_reduced_meps_dataset.ipynb -+++ /dev/null -@@ -1,239 +0,0 @@ --{ -- "cells": [ -- { -- "cell_type": "markdown", -- "metadata": {}, -- "source": [ -- "# Creating meps_example_reduced\n", -- "This notebook outlines how the small-size test dataset ```meps_example_reduced``` was created based on the slightly larger dataset ```meps_example```. The zipped up datasets are 263 MB and 2.6 GB, respectively. See [README](https://github.com/mllam/neural-lam#data) for info on how to download ```meps_example```.\n", -- "\n", -- "The dataset was reduced in size by reducing the number of grid points and variables.\n" -- ] -- }, -- { -- "cell_type": "code", -- "execution_count": 2, -- "metadata": {}, -- "outputs": [], -- "source": [ -- "# Standard library\n", -- "import os\n", -- "\n", -- "# Third-party\n", -- "import numpy as np\n", -- "import torch" -- ] -- }, -- { -- "cell_type": "markdown", -- "metadata": {}, -- "source": [ -- "\n", -- "The number of grid points was reduced to 1/4 by halving the number of coordinates in both the x and y direction. This was done by removing a quarter of the grid points along each outer edge, so the center grid points would stay centered in the new set.\n", -- "\n" -- ] -- }, -- { -- "cell_type": "code", -- "execution_count": null, -- "metadata": {}, -- "outputs": [], -- "source": [ -- "# Load existing grid\n", -- "grid_xy = np.load('data/meps_example/static/nwp_xy.npy')\n", -- "# Get slices in each dimension by cutting off a quarter along each edge\n", -- "num_x, num_y = grid_xy.shape[1:]\n", -- "x_slice = slice(num_x//4, 3*num_x//4)\n", -- "y_slice = slice(num_y//4, 3*num_y//4)\n", -- "# Index and save reduced grid\n", -- "grid_xy_reduced = grid_xy[:, x_slice, y_slice]\n", -- "np.save('data/meps_example_reduced/static/nwp_xy.npy', grid_xy_reduced)" -- ] -- }, -- { -- "cell_type": "markdown", -- "metadata": {}, -- "source": [ -- "\n", -- "This cut out the border, so a new perimeter of 10 grid points was established as border (10 was also the border size in the original \"meps_example\").\n" -- ] -- }, -- { -- "cell_type": "code", -- "execution_count": 6, -- "metadata": {}, -- "outputs": [], -- "source": [ -- "# Outer 10 grid points are border\n", -- "old_border_mask = np.load('data/meps_example/static/border_mask.npy')\n", -- "assert np.all(old_border_mask[10:-10, 10:-10] == False)\n", -- "assert np.all(old_border_mask[:10, :] == True)\n", -- "assert np.all(old_border_mask[:, :10] == True)\n", -- "assert np.all(old_border_mask[-10:,:] == True)\n", -- "assert np.all(old_border_mask[:,-10:] == True)\n", -- "\n", -- "# Create new array with False everywhere but the outer 10 grid points\n", -- "border_mask = np.zeros_like(grid_xy_reduced[0,:,:], dtype=bool)\n", -- "border_mask[:10] = True\n", -- "border_mask[:,:10] = True\n", -- "border_mask[-10:] = True\n", -- "border_mask[:,-10:] = True\n", -- "np.save('data/meps_example_reduced/static/border_mask.npy', border_mask)" -- ] -- }, -- { -- "cell_type": "markdown", -- "metadata": {}, -- "source": [ -- "A few other files also needed to be copied using only the new reduced grid" -- ] -- }, -- { -- "cell_type": "code", -- "execution_count": null, -- "metadata": {}, -- "outputs": [], -- "source": [ -- "# Load surface_geopotential.npy, index only values from the reduced grid, and save to new file\n", -- "surface_geopotential = np.load('data/meps_example/static/surface_geopotential.npy')\n", -- "surface_geopotential_reduced = surface_geopotential[x_slice, y_slice]\n", -- "np.save('data/meps_example_reduced/static/surface_geopotential.npy', surface_geopotential_reduced)\n", -- "\n", -- "# Load pytorch file grid_features.pt\n", -- "grid_features = torch.load('data/meps_example/static/grid_features.pt')\n", -- "# Index only values from the reduced grid. \n", -- "# First reshape from (num_grid_points_total, 4) to (num_grid_points_x, num_grid_points_y, 4), \n", -- "# then index, then reshape back to new total number of grid points\n", -- "print(grid_features.shape)\n", -- "grid_features_new = grid_features.reshape(num_x, num_y, 4)[x_slice,y_slice,:].reshape((-1, 4))\n", -- "# Save to new file\n", -- "torch.save(grid_features_new, 'data/meps_example_reduced/static/grid_features.pt')\n", -- "\n", -- "# flux_stats.pt is just a vector of length 2, so the grid shape and variable changes does not change this file\n", -- "torch.save(torch.load('data/meps_example/static/flux_stats.pt'), 'data/meps_example_reduced/static/flux_stats.pt')" -- ] -- }, -- { -- "cell_type": "markdown", -- "metadata": {}, -- "source": [ -- "\n", -- "The number of variables was reduced by truncating the variable list to the first 8." -- ] -- }, -- { -- "cell_type": "code", -- "execution_count": null, -- "metadata": {}, -- "outputs": [], -- "source": [ -- "num_vars = 8\n", -- "\n", -- "# Load parameter_weights.npy, truncate to first 8 variables, and save to new file\n", -- "parameter_weights = np.load('data/meps_example/static/parameter_weights.npy')\n", -- "parameter_weights_reduced = parameter_weights[:num_vars]\n", -- "np.save('data/meps_example_reduced/static/parameter_weights.npy', parameter_weights_reduced)\n", -- "\n", -- "# Do the same for following 4 pytorch files\n", -- "for file in ['diff_mean', 'diff_std', 'parameter_mean', 'parameter_std']:\n", -- " old_file = torch.load(f'data/meps_example/static/{file}.pt')\n", -- " new_file = old_file[:num_vars]\n", -- " torch.save(new_file, f'data/meps_example_reduced/static/{file}.pt')" -- ] -- }, -- { -- "cell_type": "markdown", -- "metadata": {}, -- "source": [ -- "Lastly the files in each of the directories train, test, and val have to be reduced. The folders all have the same structure with files of the following types:\n", -- "```\n", -- "nwp_YYYYMMDDHH_mbrXXX.npy\n", -- "wtr_YYYYMMDDHH.npy\n", -- "nwp_toa_downwelling_shortwave_flux_YYYYMMDDHH.npy\n", -- "```\n", -- "with ```YYYYMMDDHH``` being some date with hours, and ```XXX``` being some 3-digit integer.\n", -- "\n", -- "The first type of file has x and y in dimensions 1 and 2, and variable index in dimension 3. Dimension 0 is unchanged.\n", -- "The second type has has x and y in dimensions 1 and 2. Dimension 0 is unchanged.\n", -- "The last type has just x and y as the only 2 dimensions.\n", -- "\n" -- ] -- }, -- { -- "cell_type": "code", -- "execution_count": 12, -- "metadata": {}, -- "outputs": [ -- { -- "name": "stdout", -- "output_type": "stream", -- "text": [ -- "(65, 268, 238, 18)\n", -- "(65, 268, 238)\n" -- ] -- } -- ], -- "source": [ -- "print(np.load('data/meps_example/samples/train/nwp_2022040100_mbr000.npy').shape)\n", -- "print(np.load('data/meps_example/samples/train/nwp_toa_downwelling_shortwave_flux_2022040112.npy').shape)" -- ] -- }, -- { -- "cell_type": "markdown", -- "metadata": {}, -- "source": [ -- "The following loop goes through each file in each sample folder and indexes them according to the dimensions given by the file name." -- ] -- }, -- { -- "cell_type": "code", -- "execution_count": null, -- "metadata": {}, -- "outputs": [], -- "source": [ -- "for sample in ['train', 'test', 'val']:\n", -- " files = os.listdir(f'data/meps_example/samples/{sample}')\n", -- "\n", -- " for f in files:\n", -- " data = np.load(f'data/meps_example/samples/{sample}/{f}')\n", -- " if 'mbr' in f:\n", -- " data = data[:,x_slice,y_slice,:num_vars]\n", -- " elif 'wtr' in f:\n", -- " data = data[x_slice, y_slice]\n", -- " else:\n", -- " data = data[:,x_slice,y_slice]\n", -- " np.save(f'data/meps_example_reduced/samples/{sample}/{f}', data)" -- ] -- }, -- { -- "cell_type": "markdown", -- "metadata": {}, -- "source": [ -- "Lastly, the file ```data_config.yaml``` is modified manually by truncating the variable units, long and short names, and setting the new grid shape. Also the unit descriptions containing ```^``` was automatically parsed using latex, and to avoid having to install latex in the GitHub CI/CD pipeline, this was changed to ```**```. \n", -- "\n", -- "This new config file was placed in ```data/meps_example_reduced```, and that directory was then zipped and placed in a European Weather Cloud S3 bucket." -- ] -- } -- ], -- "metadata": { -- "kernelspec": { -- "display_name": "Python 3", -- "language": "python", -- "name": "python3" -- }, -- "language_info": { -- "codemirror_mode": { -- "name": "ipython", -- "version": 3 -- }, -- "file_extension": ".py", -- "mimetype": "text/x-python", -- "name": "python", -- "nbconvert_exporter": "python", -- "pygments_lexer": "ipython3", -- "version": "3.10.14" -- } -- }, -- "nbformat": 4, -- "nbformat_minor": 2 --} -diff --git a/docs/scripts/__init__.py b/docs/scripts/__init__.py -deleted file mode 100644 -index 359aa93..0000000 ---- a/docs/scripts/__init__.py -+++ /dev/null -@@ -1 +0,0 @@ --"""Helper scripts used during documentation builds.""" -diff --git a/docs/scripts/autoapi_astroid_patch.py b/docs/scripts/autoapi_astroid_patch.py -deleted file mode 100644 -index 8b647af..0000000 ---- a/docs/scripts/autoapi_astroid_patch.py -+++ /dev/null -@@ -1,41 +0,0 @@ --""" --Patch AutoAPI for astroid >= 4 compatibility. -- --This module patches the `AstroidBuilder` from `astroid` to pass a `manager` --argument to the parent class `__init__`, which is strictly required for --astroid >= 4. AutoAPI does not natively pass this argument in its current --versions, so this workaround prevents documentation build errors. --""" # codespell:ignore astroid -- --from __future__ import annotations -- --# Standard library --import inspect -- --try: -- # Third-party -- from astroid import builder as astroid_builder # codespell:ignore astroid -- from astroid.manager import AstroidManager # codespell:ignore astroid -- -- ASTROID_AVAILABLE = True --except ImportError: -- ASTROID_AVAILABLE = False -- -- --def setup(app): -- if not ASTROID_AVAILABLE: -- return {"version": "0.1"} -- -- builder_init = astroid_builder.AstroidBuilder.__init__ -- if "manager" not in inspect.signature(builder_init).parameters: -- return {"version": "0.1"} -- original_builder = astroid_builder.AstroidBuilder -- -- class AutoapiAstroidBuilder(original_builder): -- def __init__(self, *args, **kwargs): -- if not args and "manager" not in kwargs: -- kwargs["manager"] = AstroidManager() -- super().__init__(*args, **kwargs) -- -- astroid_builder.AstroidBuilder = AutoapiAstroidBuilder -- return {"version": "0.1"} -diff --git a/docs/scripts/generate_docs.sh b/docs/scripts/generate_docs.sh -deleted file mode 100755 -index 18e5f45..0000000 ---- a/docs/scripts/generate_docs.sh -+++ /dev/null -@@ -1,48 +0,0 @@ --#!/usr/bin/env bash --# docs/scripts/generate_docs.sh -- --set -euo pipefail -- --SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" --REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" --DOCS_DIR="$REPO_ROOT/docs" -- --if ! command -v uv &>/dev/null; then -- echo "[ERROR] uv not found. Please install uv first." >&2 -- exit 1 --fi -- --echo "[INFO] Syncing dependencies..." --uv pip install -e ".[cpu,docs]" -- --echo "[INFO] Removing old build at docs/_build..." --rm -rf "$DOCS_DIR/_build" -- --echo "[INFO] Generating UML architecture diagrams with pyreverse..." --mkdir -p "$DOCS_DIR/_static/uml" --if [ -f "$REPO_ROOT/.venv/bin/pyreverse" ]; then -- (cd "$REPO_ROOT" && "$REPO_ROOT/.venv/bin/pyreverse" -o mmd -p models neural_lam.models -d "$DOCS_DIR/_static/uml/" || echo "[WARN] pyreverse failed, continuing...") --else -- (cd "$REPO_ROOT" && pyreverse -o mmd -p models neural_lam.models -d "$DOCS_DIR/_static/uml/" || echo "[WARN] pyreverse failed, continuing...") --fi -- --echo "[INFO] Building site with jupyter-book..." --if [ -f "$REPO_ROOT/.venv/bin/jupyter-book" ]; then -- "$REPO_ROOT/.venv/bin/jupyter-book" build docs/ --keep-going --else -- # fallback to uv run if .venv structure differs -- uv run jupyter-book build docs/ --keep-going --fi -- --INDEX_HTML="$DOCS_DIR/_build/html/index.html" --echo "[OK] Build succeeded! Open: $INDEX_HTML" -- --if [ "${CI:-false}" != "true" ]; then -- if command -v open &>/dev/null; then -- open "$INDEX_HTML" || true -- elif command -v xdg-open &>/dev/null; then -- xdg-open "$INDEX_HTML" || true -- else -- echo "Please open $INDEX_HTML in your browser manually." -- fi --fi -diff --git a/neural_lam/create_graph.py b/neural_lam/create_graph.py -index c67e439..3f6fb5f 100644 ---- a/neural_lam/create_graph.py -+++ b/neural_lam/create_graph.py -@@ -275,42 +275,33 @@ def create_graph( - `graph_dir_path`. - - Creates the following files for all graphs: -+ - - g2m_edge_index.pt [2, N_g2m_edges] - - g2m_features.pt [N_g2m_edges, d_features] - - m2g_edge_index.pt [2, N_m2m_edges] - - m2g_features.pt [N_m2m_edges, d_features] - - m2m_edge_index.pt list of [2, N_m2m_edges_level], length==n_levels -- - m2m_features.pt list of [N_m2m_edges_level, d_features], -- length==n_levels -- - mesh_features.pt list of [N_mesh_nodes_level, d_mesh_static], -- length==n_levels -- -- where -- d_features: -- number of features per edge (currently d_features==3, for -- edge-length, x and y) -- N_g2m_edges: -- number of edges in the graph from grid-to-mesh -- N_m2g_edges: -- number of edges in the graph from mesh-to-grid -- N_m2m_edges_level: -- number of edges in the graph from mesh-to-mesh at a given level -- (list index corresponds to the level) -- d_mesh_static: -- number of static features per mesh node (currently -- d_mesh_static==2, for x and y) -- N_mesh_nodes_level: -- number of nodes in the mesh at a given level -+ - m2m_features.pt list of [N_m2m_edges_level, d_features], length==n_levels -+ - mesh_features.pt list of [N_mesh_nodes_level, d_mesh_static], length==n_levels -+ -+ where: -+ -+ * d_features: number of features per edge (currently d_features==3, for -+ edge-length, x and y) -+ * N_g2m_edges: number of edges in the graph from grid-to-mesh -+ * N_m2g_edges: number of edges in the graph from mesh-to-grid -+ * N_m2m_edges_level: number of edges in the graph from mesh-to-mesh at a given level -+ (list index corresponds to the level) -+ * d_mesh_static: number of static features per mesh node (currently -+ d_mesh_static==2, for x and y) -+ * N_mesh_nodes_level: number of nodes in the mesh at a given level - - And in addition for hierarchical graphs: -- - mesh_up_edge_index.pt -- list of [2, N_mesh_updown_edges_level], length==n_levels-1 -- - mesh_up_features.pt -- list of [N_mesh_updown_edges_level, d_features], length==n_levels-1 -- - mesh_down_edge_index.pt -- list of [2, N_mesh_updown_edges_level], length==n_levels-1 -- - mesh_down_features.pt -- list of [N_mesh_updown_edges_level, d_features], length==n_levels-1 -+ -+ - mesh_up_edge_index.pt: list of [2, N_mesh_updown_edges_level], length==n_levels-1 -+ - mesh_up_features.pt: list of [N_mesh_updown_edges_level, d_features], length==n_levels-1 -+ - mesh_down_edge_index.pt: list of [2, N_mesh_updown_edges_level], length==n_levels-1 -+ - mesh_down_features.pt: list of [N_mesh_updown_edges_level, d_features], length==n_levels-1 - - where N_mesh_updown_edges_level is the number of edges in the graph from - mesh-to-mesh between two consecutive levels (list index corresponds index -diff --git a/neural_lam/datastore/base.py b/neural_lam/datastore/base.py -index 8376d38..8c17282 100644 ---- a/neural_lam/datastore/base.py -+++ b/neural_lam/datastore/base.py -@@ -421,6 +421,7 @@ class BaseDatastore(abc.ABC): - ---------- - category : str - The category of the dataset (state/forcing/static). -+ - Returns - ------- - List[str] -@@ -481,12 +482,13 @@ class BaseRegularGridDatastore(BaseDatastore): - - The following methods and attributes must be implemented for datastore that - represents regular-gridded data: -- - `grid_shape_state` (property): 2D shape of the grid for the state -+ -+ * `grid_shape_state` (property): 2D shape of the grid for the state - variables. -- - `get_xy` (method): Return the x, y coordinates of the dataset, with the -+ * `get_xy` (method): Return the x, y coordinates of the dataset, with the - option to not stack the coordinates (so that they are returned as a 2D - grid). -- - `get_lat_lon` (method): Return the latitude/longitude coordinates of -+ * `get_lat_lon` (method): Return the latitude/longitude coordinates of - the dataset for convenience when plotting. - - The operation of going from (x,y)-indexed regular grid -@@ -526,10 +528,11 @@ class BaseRegularGridDatastore(BaseDatastore): - ------- - np.ndarray - The x, y coordinates of the dataset, returned differently based on -- the value of `stacked`: - `stacked==True`: shape `(n_grid_points, -- 2)` where -- n_grid_points=N_x*N_y. -- - `stacked==False`: shape `(N_x, N_y, 2)` -+ the value of `stacked`: -+ -+ * `stacked==True`: shape `(n_grid_points, 2)` where -+ n_grid_points=N_x*N_y. -+ * `stacked==False`: shape `(N_x, N_y, 2)` - """ - - def unstack_grid_coords( -diff --git a/neural_lam/datastore/mdp.py b/neural_lam/datastore/mdp.py -index 7cad45d..cceacc5 100644 ---- a/neural_lam/datastore/mdp.py -+++ b/neural_lam/datastore/mdp.py -@@ -500,9 +500,10 @@ class MDPDatastore(BaseRegularGridDatastore): - np.ndarray - The x, y coordinates of the dataset, returned differently based on - the value of `stacked`: -- - `stacked==True`: shape `(n_grid_points, 2)` where -- n_grid_points=N_x*N_y. -- - `stacked==False`: shape `(N_x, N_y, 2)` -+ -+ * `stacked==True`: shape `(n_grid_points, 2)` where -+ n_grid_points=N_x*N_y. -+ * `stacked==False`: shape `(N_x, N_y, 2)` - - """ - # assume variables are stored in dimensions [grid_index, ...] -diff --git a/neural_lam/datastore/npyfilesmeps/store.py b/neural_lam/datastore/npyfilesmeps/store.py -index e14bbc7..e86c5e7 100644 ---- a/neural_lam/datastore/npyfilesmeps/store.py -+++ b/neural_lam/datastore/npyfilesmeps/store.py -@@ -84,60 +84,60 @@ class NpyFilesDatastoreMEPS(BaseRegularGridDatastore): - `[y, x]`. - - -- Folder structure: -- -- meps_example_reduced -- ├── data_config.yaml -- ├── samples -- │ ├── test -- │ │ ├── nwp_2022090100_mbr000.npy -- │ │ ├── nwp_2022090100_mbr001.npy -- │ │ ├── nwp_2022090112_mbr000.npy -- │ │ ├── nwp_2022090112_mbr001.npy -- │ │ ├── ... -- │ │ ├── nwp_toa_downwelling_shortwave_flux_2022090100.npy -- │ │ ├── nwp_toa_downwelling_shortwave_flux_2022090112.npy -- │ │ ├── ... -- │ │ ├── wtr_2022090100.npy -- │ │ ├── wtr_2022090112.npy -- │ │ └── ... -- │ ├── train -- │ │ ├── nwp_2022040100_mbr000.npy -- │ │ ├── nwp_2022040100_mbr001.npy -- │ │ ├── ... -- │ │ ├── nwp_2022040112_mbr000.npy -- │ │ ├── nwp_2022040112_mbr001.npy -- │ │ ├── ... -- │ │ ├── nwp_toa_downwelling_shortwave_flux_2022040100.npy -- │ │ ├── nwp_toa_downwelling_shortwave_flux_2022040112.npy -- │ │ ├── ... -- │ │ ├── wtr_2022040100.npy -- │ │ ├── wtr_2022040112.npy -- │ │ └── ... -- │ └── val -- │ ├── nwp_2022060500_mbr000.npy -- │ ├── nwp_2022060500_mbr001.npy -- │ ├── ... -- │ ├── nwp_2022060512_mbr000.npy -- │ ├── nwp_2022060512_mbr001.npy -- │ ├── ... -- │ ├── nwp_toa_downwelling_shortwave_flux_2022060500.npy -- │ ├── nwp_toa_downwelling_shortwave_flux_2022060512.npy -- │ ├── ... -- │ ├── wtr_2022060500.npy -- │ ├── wtr_2022060512.npy -- │ └── ... -- └── static -- ├── border_mask.npy -- ├── diff_mean.pt -- ├── diff_std.pt -- ├── flux_stats.pt -- ├── grid_features.pt -- ├── nwp_xy.npy -- ├── parameter_mean.pt -- ├── parameter_std.pt -- ├── parameter_weights.npy -- └── surface_geopotential.npy -+ Folder structure:: -+ -+ meps_example_reduced -+ ├── data_config.yaml -+ ├── samples -+ │ ├── test -+ │ │ ├── nwp_2022090100_mbr000.npy -+ │ │ ├── nwp_2022090100_mbr001.npy -+ │ │ ├── nwp_2022090112_mbr000.npy -+ │ │ ├── nwp_2022090112_mbr001.npy -+ │ │ ├── ... -+ │ │ ├── nwp_toa_downwelling_shortwave_flux_2022090100.npy -+ │ │ ├── nwp_toa_downwelling_shortwave_flux_2022090112.npy -+ │ │ ├── ... -+ │ │ ├── wtr_2022090100.npy -+ │ │ ├── wtr_2022090112.npy -+ │ │ └── ... -+ │ ├── train -+ │ │ ├── nwp_2022040100_mbr000.npy -+ │ │ ├── nwp_2022040100_mbr001.npy -+ │ │ ├── ... -+ │ │ ├── nwp_2022040112_mbr000.npy -+ │ │ ├── nwp_2022040112_mbr001.npy -+ │ │ ├── ... -+ │ │ ├── nwp_toa_downwelling_shortwave_flux_2022040100.npy -+ │ │ ├── nwp_toa_downwelling_shortwave_flux_2022040112.npy -+ │ │ ├── ... -+ │ │ ├── wtr_2022040100.npy -+ │ │ ├── wtr_2022040112.npy -+ │ │ └── ... -+ │ └── val -+ │ ├── nwp_2022060500_mbr000.npy -+ │ ├── nwp_2022060500_mbr001.npy -+ │ ├── ... -+ │ ├── nwp_2022060512_mbr000.npy -+ │ ├── nwp_2022060512_mbr001.npy -+ │ ├── ... -+ │ ├── nwp_toa_downwelling_shortwave_flux_2022060500.npy -+ │ ├── nwp_toa_downwelling_shortwave_flux_2022060512.npy -+ │ ├── ... -+ │ ├── wtr_2022060500.npy -+ │ ├── wtr_2022060512.npy -+ │ └── ... -+ └── static -+ ├── border_mask.npy -+ ├── diff_mean.pt -+ ├── diff_std.pt -+ ├── flux_stats.pt -+ ├── grid_features.pt -+ ├── nwp_xy.npy -+ ├── parameter_mean.pt -+ ├── parameter_std.pt -+ ├── parameter_weights.npy -+ └── surface_geopotential.npy - - For the MEPS dataset: - N_t' = 65 -@@ -244,11 +244,10 @@ class NpyFilesDatastoreMEPS(BaseRegularGridDatastore): - xr.DataArray - The data array for the given category and split, with dimensions - per category: -- state: `[elapsed_forecast_duration, analysis_time, grid_index, -- feature, ensemble_member]` -- forcing: `[elapsed_forecast_duration, analysis_time, grid_index, -- feature]` -- static: `[grid_index, feature]` -+ -+ * state: ``[elapsed_forecast_duration, analysis_time, grid_index, feature, ensemble_member]`` -+ * forcing: ``[elapsed_forecast_duration, analysis_time, grid_index, feature]`` -+ * static: ``[grid_index, feature]`` - - """ - if category == "state": -@@ -669,9 +668,10 @@ class NpyFilesDatastoreMEPS(BaseRegularGridDatastore): - np.ndarray - The x, y coordinates of the dataset (with x first then y second), - returned differently based on the value of `stacked`: -- - `stacked==True`: shape `(n_grid_points, 2)` where -- n_grid_points=N_x*N_y. -- - `stacked==False`: shape `(N_x, N_y, 2)` -+ -+ * `stacked==True`: shape `(n_grid_points, 2)` where -+ n_grid_points=N_x*N_y. -+ * `stacked==False`: shape `(N_x, N_y, 2)` - - """ - -diff --git a/neural_lam/datastore/plot_example.py b/neural_lam/datastore/plot_example.py -index 13f32e5..789282b 100644 ---- a/neural_lam/datastore/plot_example.py -+++ b/neural_lam/datastore/plot_example.py -@@ -34,7 +34,7 @@ def plot_example_from_datastore( - Whether to standardize the data before plotting, by default True. - selection : dict, optional - Selections to apply to the dataarray, for example -- `time="1990-09-03T0:00" would select this single timestep, by default -+ `time="1990-09-03T0:00"` would select this single timestep, by default - {}. - index_selection: dict, optional - Index-based selection to apply to the dataarray, for example -diff --git a/neural_lam/gnn_layers.py b/neural_lam/gnn_layers.py -index 7a92ea7..99a0014 100644 ---- a/neural_lam/gnn_layers.py -+++ b/neural_lam/gnn_layers.py -@@ -182,6 +182,7 @@ class InteractionNet(pyg.nn.MessagePassing): - ) -> tuple[torch.Tensor, torch.Tensor]: - """ - Overridden aggregation function to: -+ - * return both aggregated and per-edge messages, - * only aggregate to the number of receiver nodes (``self.num_rec``) - rather than to ``dim_size``. -@@ -260,9 +261,15 @@ def get_gnn_class(gnn_type: str) -> Type[pyg.nn.MessagePassing]: - """ - Look up a GNN class by name. - -- gnn_type: One of the keys in GNN_TYPES -- (currently "InteractionNet" or "PropagationNet") -- Returns the corresponding GNN class. -+ Parameters -+ ---------- -+ gnn_type : str -+ One of the keys in GNN_TYPES (currently "InteractionNet" or "PropagationNet") -+ -+ Returns -+ ------- -+ Type[pyg.nn.MessagePassing] -+ The corresponding GNN class. - """ - if gnn_type not in GNN_TYPES: - raise ValueError( -diff --git a/pyproject.toml b/pyproject.toml -index e9da32e..6e31531 100644 ---- a/pyproject.toml -+++ b/pyproject.toml -@@ -50,7 +50,6 @@ cpu = ["torch>=2.12,<2.13"] - gpu = ["torch>=2.12,<2.13"] # CUDA 13.0, default GPU build - gpu-cu128 = ["torch>=2.11,<2.12"] # CUDA 12.8, last torch series with cu128 wheels - docs = [ -- "jupyter-book>=1.0.0,<2.0.0", - "sphinx-autoapi>=3.0.0", - "sphinx-copybutton>=0.5.2", - "sphinx-book-theme>=1.1.0", -diff --git a/uv.lock b/uv.lock -index 786dad4..00f853f 100644 ---- a/uv.lock -+++ b/uv.lock -@@ -2228,37 +2228,6 @@ wheels = [ - { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, - ] - --[[package]] --name = "jupyter-book" --version = "1.0.4.post1" --source = { registry = "https://pypi.org/simple" } --dependencies = [ -- { name = "click" }, -- { name = "jinja2" }, -- { name = "jsonschema" }, -- { name = "linkify-it-py" }, -- { name = "myst-nb" }, -- { name = "myst-parser" }, -- { name = "pyyaml" }, -- { name = "sphinx" }, -- { name = "sphinx-book-theme", version = "1.1.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, -- { name = "sphinx-book-theme", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, -- { name = "sphinx-comments" }, -- { name = "sphinx-copybutton" }, -- { name = "sphinx-design", version = "0.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, -- { name = "sphinx-design", version = "0.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, -- { name = "sphinx-external-toc" }, -- { name = "sphinx-jupyterbook-latex" }, -- { name = "sphinx-multitoc-numbering" }, -- { name = "sphinx-thebe" }, -- { name = "sphinx-togglebutton" }, -- { name = "sphinxcontrib-bibtex" }, --] --sdist = { url = "https://files.pythonhosted.org/packages/cf/ee/5d10ce5b161764ad44219853f386e98b535cb3879bcb0d7376961a1e3897/jupyter_book-1.0.4.post1.tar.gz", hash = "sha256:2fe92c49ff74840edc0a86bb034eafdd0f645fca6e48266be367ce4d808b9601", size = 67412, upload-time = "2025-02-28T14:55:48.637Z" } --wheels = [ -- { url = "https://files.pythonhosted.org/packages/37/86/d45756beaeb4b9b06125599b429451f8640b5db6f019d606f33c85743fd4/jupyter_book-1.0.4.post1-py3-none-any.whl", hash = "sha256:3a27a6b2581f1894ffe8f347d1a3432f06fc616997547919c42cd41c54db625d", size = 45005, upload-time = "2025-02-28T14:55:46.561Z" }, --] -- - [[package]] - name = "jupyter-cache" - version = "1.0.1" -@@ -2432,15 +2401,6 @@ wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/dd/8050c947d435c8d4bc94e3252f4d8bb8a76cfb424f043a8680be637a57f1/kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1", size = 73558, upload-time = "2026-03-09T13:15:52.112Z" }, - ] - --[[package]] --name = "latexcodec" --version = "3.0.1" --source = { registry = "https://pypi.org/simple" } --sdist = { url = "https://files.pythonhosted.org/packages/27/dd/4270b2c5e2ee49316c3859e62293bd2ea8e382339d63ab7bbe9f39c0ec3b/latexcodec-3.0.1.tar.gz", hash = "sha256:e78a6911cd72f9dec35031c6ec23584de6842bfbc4610a9678868d14cdfb0357", size = 31222, upload-time = "2025-06-17T18:47:34.051Z" } --wheels = [ -- { url = "https://files.pythonhosted.org/packages/b5/40/23569737873cc9637fd488606347e9dd92b9fa37ba4fcda1f98ee5219a97/latexcodec-3.0.1-py3-none-any.whl", hash = "sha256:a9eb8200bff693f0437a69581f7579eb6bca25c4193515c09900ce76451e452e", size = 18532, upload-time = "2025-06-17T18:47:30.726Z" }, --] -- - [[package]] - name = "lightning-utilities" - version = "0.15.3" -@@ -3148,7 +3108,6 @@ cpu = [ - { name = "torch", version = "2.12.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, - ] - docs = [ -- { name = "jupyter-book" }, - { name = "linkify-it-py" }, - { name = "myst-nb" }, - { name = "myst-parser" }, -@@ -3180,7 +3139,6 @@ requires-dist = [ - { name = "boto3", specifier = ">=1.35.32" }, - { name = "cartopy", specifier = ">=0.22.0" }, - { name = "dataclass-wizard", specifier = "<0.31.0" }, -- { name = "jupyter-book", marker = "extra == 'docs'", specifier = ">=1.0.0,<2.0.0" }, - { name = "linkify-it-py", marker = "extra == 'docs'", specifier = ">=2.0.0" }, - { name = "matplotlib", specifier = ">=3.7.0" }, - { name = "mlflow", specifier = ">=2.16.2" }, -@@ -4454,32 +4412,6 @@ wheels = [ - { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, - ] - --[[package]] --name = "pybtex" --version = "0.26.1" --source = { registry = "https://pypi.org/simple" } --dependencies = [ -- { name = "latexcodec" }, -- { name = "pyyaml" }, --] --sdist = { url = "https://files.pythonhosted.org/packages/4d/f5/f30da9c93f0fa6d619332b2f69597219b625f35780473a05164a9981fd9a/pybtex-0.26.1.tar.gz", hash = "sha256:2e5543bea424e60e9e42eef70bff597be48649d8f68ba061a7a092b2477d5464", size = 692991, upload-time = "2026-04-03T13:05:39.014Z" } --wheels = [ -- { url = "https://files.pythonhosted.org/packages/44/f6/775eb92e865b28cdb4ad1f2bed7a5446197516f76b58a950faa3be3fd08d/pybtex-0.26.1-py3-none-any.whl", hash = "sha256:e26c0412cc54f5f21b2a6d9d175762a2d2af9ccf3a8f651cdb89ec035db77aa1", size = 126134, upload-time = "2026-04-03T13:05:40.623Z" }, --] -- --[[package]] --name = "pybtex-docutils" --version = "1.0.3" --source = { registry = "https://pypi.org/simple" } --dependencies = [ -- { name = "docutils" }, -- { name = "pybtex" }, --] --sdist = { url = "https://files.pythonhosted.org/packages/7e/84/796ea94d26188a853660f81bded39f8de4cfe595130aef0dea1088705a11/pybtex-docutils-1.0.3.tar.gz", hash = "sha256:3a7ebdf92b593e00e8c1c538aa9a20bca5d92d84231124715acc964d51d93c6b", size = 18348, upload-time = "2023-08-22T18:47:54.833Z" } --wheels = [ -- { url = "https://files.pythonhosted.org/packages/11/b1/ce1f4596211efb5410e178a803f08e59b20bedb66837dcf41e21c54f9ec1/pybtex_docutils-1.0.3-py3-none-any.whl", hash = "sha256:8fd290d2ae48e32fcb54d86b0efb8d573198653c7e2447d5bec5847095f430b9", size = 6385, upload-time = "2023-08-22T06:43:20.513Z" }, --] -- - [[package]] - name = "pycparser" - version = "3.0" -@@ -6119,18 +6051,6 @@ wheels = [ - { url = "https://files.pythonhosted.org/packages/02/bf/6f506a37c7f8ecc4576caf9486e303c7af249f6d70447bb51dde9d78cb99/sphinx_book_theme-1.2.0-py3-none-any.whl", hash = "sha256:709605d308e1991c5ef0cf19c481dbe9084b62852e317fafab74382a0ee7ccfa", size = 455936, upload-time = "2026-03-09T23:20:28.788Z" }, - ] - --[[package]] --name = "sphinx-comments" --version = "0.0.3" --source = { registry = "https://pypi.org/simple" } --dependencies = [ -- { name = "sphinx" }, --] --sdist = { url = "https://files.pythonhosted.org/packages/c0/75/5bbf29e83eaf79843180cf424d0d550bda14a1792ca51dcf79daa065ba93/sphinx-comments-0.0.3.tar.gz", hash = "sha256:00170afff27019fad08e421da1ae49c681831fb2759786f07c826e89ac94cf21", size = 7960, upload-time = "2020-08-12T00:07:31.183Z" } --wheels = [ -- { url = "https://files.pythonhosted.org/packages/26/97/a5c39f619375d4f81d5422377fb027075898efa6b6202c1ccf1e5bb38a32/sphinx_comments-0.0.3-py3-none-any.whl", hash = "sha256:1e879b4e9bfa641467f83e3441ac4629225fc57c29995177d043252530c21d00", size = 4591, upload-time = "2020-08-12T00:07:30.297Z" }, --] -- - [[package]] - name = "sphinx-copybutton" - version = "0.5.2" -@@ -6143,126 +6063,6 @@ wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/48/1ea60e74949eecb12cdd6ac43987f9fd331156388dcc2319b45e2ebb81bf/sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e", size = 13343, upload-time = "2023-04-14T08:10:20.844Z" }, - ] - --[[package]] --name = "sphinx-design" --version = "0.6.1" --source = { registry = "https://pypi.org/simple" } --resolution-markers = [ -- "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", -- "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", -- "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", -- "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", -- "python_full_version < '3.11' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", --] --dependencies = [ -- { name = "sphinx", marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, --] --sdist = { url = "https://files.pythonhosted.org/packages/2b/69/b34e0cb5336f09c6866d53b4a19d76c227cdec1bbc7ac4de63ca7d58c9c7/sphinx_design-0.6.1.tar.gz", hash = "sha256:b44eea3719386d04d765c1a8257caca2b3e6f8421d7b3a5e742c0fd45f84e632", size = 2193689, upload-time = "2024-08-02T13:48:44.277Z" } --wheels = [ -- { url = "https://files.pythonhosted.org/packages/c6/43/65c0acbd8cc6f50195a3a1fc195c404988b15c67090e73c7a41a9f57d6bd/sphinx_design-0.6.1-py3-none-any.whl", hash = "sha256:b11f37db1a802a183d61b159d9a202314d4d2fe29c163437001324fe2f19549c", size = 2215338, upload-time = "2024-08-02T13:48:42.106Z" }, --] -- --[[package]] --name = "sphinx-design" --version = "0.7.0" --source = { registry = "https://pypi.org/simple" } --resolution-markers = [ -- "python_full_version >= '3.14' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", -- "python_full_version == '3.13.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", -- "python_full_version == '3.12.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", -- "python_full_version == '3.11.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128'", -- "python_full_version >= '3.14' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", -- "python_full_version == '3.13.*' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", -- "python_full_version == '3.12.*' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", -- "python_full_version == '3.11.*' and extra != 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", -- "python_full_version >= '3.14' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", -- "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", -- "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", -- "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", -- "python_full_version >= '3.14' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", -- "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", -- "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", -- "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", -- "python_full_version >= '3.14' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", -- "python_full_version == '3.13.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", -- "python_full_version == '3.12.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", -- "python_full_version == '3.11.*' and extra != 'extra-10-neural-lam-cpu' and extra != 'extra-10-neural-lam-gpu' and extra != 'extra-10-neural-lam-gpu-cu128'", --] --dependencies = [ -- { name = "sphinx", marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, --] --sdist = { url = "https://files.pythonhosted.org/packages/13/7b/804f311da4663a4aecc6cf7abd83443f3d4ded970826d0c958edc77d4527/sphinx_design-0.7.0.tar.gz", hash = "sha256:d2a3f5b19c24b916adb52f97c5f00efab4009ca337812001109084a740ec9b7a", size = 2203582, upload-time = "2026-01-19T13:12:53.297Z" } --wheels = [ -- { url = "https://files.pythonhosted.org/packages/30/cf/45dd359f6ca0c3762ce0490f681da242f0530c49c81050c035c016bfdd3a/sphinx_design-0.7.0-py3-none-any.whl", hash = "sha256:f82bf179951d58f55dca78ab3706aeafa496b741a91b1911d371441127d64282", size = 2220350, upload-time = "2026-01-19T13:12:51.077Z" }, --] -- --[[package]] --name = "sphinx-external-toc" --version = "1.1.0" --source = { registry = "https://pypi.org/simple" } --dependencies = [ -- { name = "click" }, -- { name = "pyyaml" }, -- { name = "sphinx" }, -- { name = "sphinx-multitoc-numbering" }, --] --sdist = { url = "https://files.pythonhosted.org/packages/c5/f8/85bcd2f1c142e580a1394c18920506d9399b8e8e97e4899bbee9c74a896e/sphinx_external_toc-1.1.0.tar.gz", hash = "sha256:f81833865006f6b4a9b2550a2474a6e3d7e7f2cb23ba23309260577ea65552f6", size = 37194, upload-time = "2026-01-16T13:15:59.03Z" } --wheels = [ -- { url = "https://files.pythonhosted.org/packages/9f/80/1704c9179012e289dee2178354e385277ea51f4fa827c4bf7e36c77b0f4b/sphinx_external_toc-1.1.0-py3-none-any.whl", hash = "sha256:26c390b8d85aa641366fed2d3674910ec6820f48b91027affef485a2655ad7d0", size = 30609, upload-time = "2026-01-16T13:15:57.926Z" }, --] -- --[[package]] --name = "sphinx-jupyterbook-latex" --version = "1.0.0" --source = { registry = "https://pypi.org/simple" } --dependencies = [ -- { name = "packaging" }, -- { name = "sphinx" }, --] --sdist = { url = "https://files.pythonhosted.org/packages/80/29/18a1fc30e9315e72f068637079169525069a7c0b2fbe51cf689af0576214/sphinx_jupyterbook_latex-1.0.0.tar.gz", hash = "sha256:f54c6674c13f1616f9a93443e98b9b5353f9fdda8e39b6ec552ccf0b3e5ffb62", size = 11945, upload-time = "2023-12-11T15:37:25.034Z" } --wheels = [ -- { url = "https://files.pythonhosted.org/packages/70/1f/1d4ecaf58b17fe61497644655f40b04d84a88348e41a6f0c6392394d95e4/sphinx_jupyterbook_latex-1.0.0-py3-none-any.whl", hash = "sha256:e0cd3e9e1c5af69136434e21a533343fdf013475c410a414d5b7b4922b4f3891", size = 13319, upload-time = "2023-12-11T15:37:23.25Z" }, --] -- --[[package]] --name = "sphinx-multitoc-numbering" --version = "0.1.3" --source = { registry = "https://pypi.org/simple" } --dependencies = [ -- { name = "sphinx" }, --] --sdist = { url = "https://files.pythonhosted.org/packages/37/1e/577bae038372885ebc34bd8c0f290295785a0250cac6528eb6d50e4b92d5/sphinx-multitoc-numbering-0.1.3.tar.gz", hash = "sha256:c9607671ac511236fa5d61a7491c1031e700e8d498c9d2418e6c61d1251209ae", size = 4542, upload-time = "2021-03-15T12:01:43.758Z" } --wheels = [ -- { url = "https://files.pythonhosted.org/packages/ec/9f/902f2030674cd9473fdbe5a2c2dec2618c27ec853484c35f82cf8df40ece/sphinx_multitoc_numbering-0.1.3-py3-none-any.whl", hash = "sha256:33d2e707a9b2b8ad636b3d4302e658a008025106fe0474046c651144c26d8514", size = 4616, upload-time = "2021-03-15T12:01:42.419Z" }, --] -- --[[package]] --name = "sphinx-thebe" --version = "0.3.1" --source = { registry = "https://pypi.org/simple" } --dependencies = [ -- { name = "sphinx" }, --] --sdist = { url = "https://files.pythonhosted.org/packages/da/fd/926ba4af1eb2708b1ac0fa4376e4bfb11d9a32b2a00e3614137a569c1ddf/sphinx_thebe-0.3.1.tar.gz", hash = "sha256:576047f45560e82f64aa5f15200b1eb094dcfe1c5b8f531a8a65bd208e25a493", size = 20789, upload-time = "2024-02-07T13:31:57.002Z" } --wheels = [ -- { url = "https://files.pythonhosted.org/packages/ca/7c/a53bdb465fd364bc3d255d96d5d70e6ba5183cfb4e45b8aa91c59b099124/sphinx_thebe-0.3.1-py3-none-any.whl", hash = "sha256:e7e7edee9f0d601c76bc70156c471e114939484b111dd8e74fe47ac88baffc52", size = 9030, upload-time = "2024-02-07T13:31:55.286Z" }, --] -- --[[package]] --name = "sphinx-togglebutton" --version = "0.4.5" --source = { registry = "https://pypi.org/simple" } --dependencies = [ -- { name = "docutils" }, -- { name = "setuptools" }, -- { name = "sphinx" }, -- { name = "wheel" }, --] --sdist = { url = "https://files.pythonhosted.org/packages/cc/be/169a0b0a8ad9588e8697c85e1d489aaaca7416073c2fc0267c360af5aae9/sphinx_togglebutton-0.4.5.tar.gz", hash = "sha256:c870dfbd3bc6e119b50ff9a37a64f8991902269e856728931c7d89877e8d4b3d", size = 18101, upload-time = "2026-03-27T13:50:41.984Z" } --wheels = [ -- { url = "https://files.pythonhosted.org/packages/d8/2e/3dd55564928c5d61f92827d4b91307dde7911a40fbe0000645d73202eea9/sphinx_togglebutton-0.4.5-py3-none-any.whl", hash = "sha256:74eac6d2426110c3e1e6f989a98e07d7823141a335df1ad8a9d637bdf6a7af62", size = 44907, upload-time = "2026-03-27T13:50:40.94Z" }, --] -- - [[package]] - name = "sphinxcontrib-applehelp" - version = "2.0.0" -@@ -6272,21 +6072,6 @@ wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, - ] - --[[package]] --name = "sphinxcontrib-bibtex" --version = "2.7.0" --source = { registry = "https://pypi.org/simple" } --dependencies = [ -- { name = "docutils" }, -- { name = "pybtex" }, -- { name = "pybtex-docutils" }, -- { name = "sphinx" }, --] --sdist = { url = "https://files.pythonhosted.org/packages/15/6a/8e0b2c2420286389e7fed78ff361ec30e2f1d58c8560af8d64df5e7b61e0/sphinxcontrib_bibtex-2.7.0.tar.gz", hash = "sha256:fee700f7aae29bb8f654c62913f00d34ac44fc0b8ca0fa67ac922ff4453addee", size = 120669, upload-time = "2026-05-06T09:29:24.935Z" } --wheels = [ -- { url = "https://files.pythonhosted.org/packages/52/c0/d28e62407f4733bbe0169287bc012f0ac3b4a2021066b285570654119c8b/sphinxcontrib_bibtex-2.7.0-py3-none-any.whl", hash = "sha256:28cf0ec7a957d1c7548d5749317ed472ce877e1b629f430f88e3789aa51f87b1", size = 40287, upload-time = "2026-05-06T09:29:23.253Z" }, --] -- - [[package]] - name = "sphinxcontrib-devhelp" - version = "2.0.0" -@@ -7090,18 +6875,6 @@ wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/b2/0bba9bbb4596d2d2f285a16c2ab04118f6b957d8441566e1abb892e6a6b2/werkzeug-3.1.7-py3-none-any.whl", hash = "sha256:4b314d81163a3e1a169b6a0be2a000a0e204e8873c5de6586f453c55688d422f", size = 226295, upload-time = "2026-03-24T01:08:06.133Z" }, - ] - --[[package]] --name = "wheel" --version = "0.47.0" --source = { registry = "https://pypi.org/simple" } --dependencies = [ -- { name = "packaging" }, --] --sdist = { url = "https://files.pythonhosted.org/packages/39/62/75f18a0f03b4219c456652c7780e4d749b929eb605c098ce3a5b6b6bc081/wheel-0.47.0.tar.gz", hash = "sha256:cc72bd1009ba0cf63922e28f94d9d83b920aa2bb28f798a31d0691b02fa3c9b3", size = 63854, upload-time = "2026-04-22T15:51:27.727Z" } --wheels = [ -- { url = "https://files.pythonhosted.org/packages/87/1b/9e33c09813d65e248f7f773119148a612516a4bea93e9c6f545f78455b7c/wheel-0.47.0-py3-none-any.whl", hash = "sha256:212281cab4dff978f6cedd499cd893e1f620791ca6ff7107cf270781e587eced", size = 32218, upload-time = "2026-04-22T15:51:26.296Z" }, --] -- - [[package]] - name = "win32-setctime" - version = "1.2.0" diff --git a/docs/conf.py b/docs/conf.py index 8f2001101..21bd7110a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,40 +1,41 @@ +# Standard library import os import sys # Add the project root to sys.path so sphinx can find neural_lam -sys.path.insert(0, os.path.abspath('..')) +sys.path.insert(0, os.path.abspath("..")) -project = 'Neural-LAM' -copyright = '2024–2026, MLLAM Community' -author = 'MLLAM Community' +project = "Neural-LAM" +copyright = "2024–2026, MLLAM Community" +author = "MLLAM Community" extensions = [ - 'autoapi.extension', - 'myst_nb', - 'sphinx.ext.napoleon', - 'sphinx.ext.viewcode', - 'sphinx.ext.intersphinx', - 'sphinx_copybutton', - 'sphinxcontrib.mermaid', - 'sphinxext.opengraph', + "autoapi.extension", + "myst_nb", + "sphinx.ext.napoleon", + "sphinx.ext.viewcode", + "sphinx.ext.intersphinx", + "sphinx_copybutton", + "sphinxcontrib.mermaid", + "sphinxext.opengraph", ] # AutoAPI settings -autoapi_dirs = ['../neural_lam'] -autoapi_root = 'api' -autoapi_type = 'python' +autoapi_dirs = ["../neural_lam"] +autoapi_root = "api" +autoapi_type = "python" autoapi_options = [ - 'members', - 'undoc-members', - 'show-inheritance', - 'show-module-summary', + "members", + "undoc-members", + "show-inheritance", + "show-module-summary", ] -autoapi_python_class_content = 'both' -autoapi_member_order = 'groupwise' +autoapi_python_class_content = "both" +autoapi_member_order = "groupwise" autoapi_python_use_implicit_namespaces = False autoapi_keep_files = True autoapi_add_toctree_entry = True -autoapi_ignore = ['**/tests/**', '**/conftest.py'] +autoapi_ignore = ["**/tests/**", "**/conftest.py"] # Napoleon settings napoleon_numpy_docstring = True @@ -48,64 +49,68 @@ # Intersphinx settings intersphinx_mapping = { - 'python': ('https://docs.python.org/3', None), - 'numpy': ('https://numpy.org/doc/stable', None), - 'torch': ('https://pytorch.org/docs/stable', None), - 'pytorch_lightning': ('https://lightning.ai/docs/pytorch/stable/', None), - 'torch_geometric': ('https://pytorch-geometric.readthedocs.io/en/latest/', None), + "python": ("https://docs.python.org/3", None), + "numpy": ("https://numpy.org/doc/stable", None), + "torch": ("https://pytorch.org/docs/stable", None), + "pytorch_lightning": ("https://lightning.ai/docs/pytorch/stable/", None), + "torch_geometric": ( + "https://pytorch-geometric.readthedocs.io/en/latest/", + None, + ), } # MyST / Notebook settings myst_enable_extensions = [ - 'colon_fence', - 'dollarmath', - 'linkify', - 'substitution', - 'tasklist', - 'deflist', - 'fieldlist', - 'html_admonition', - 'html_image', - 'smartquotes', - 'attrs_inline', + "colon_fence", + "dollarmath", + "linkify", + "substitution", + "tasklist", + "deflist", + "fieldlist", + "html_admonition", + "html_image", + "smartquotes", + "attrs_inline", ] -nb_execution_mode = 'off' +nb_execution_mode = "off" # HTML Theme -html_theme = 'sphinx_book_theme' -html_logo = '_static/logo.png' -html_favicon = '_static/favicon.ico' -html_static_path = ['_static'] -html_css_files = ['custom.css'] +html_theme = "sphinx_book_theme" +html_logo = "_static/logo.png" +html_favicon = "_static/favicon.ico" +html_static_path = ["_static"] +html_css_files = ["custom.css"] html_theme_options = { - 'repository_url': 'https://github.com/mllam/neural-lam', - 'use_repository_button': True, - 'use_issues_button': True, - 'use_edit_page_button': True, - 'repository_branch': 'main', - 'path_to_docs': 'docs', - 'show_navbar_depth': 2, - 'show_toc_level': 2, - 'logo': { - 'text': 'Neural-LAM' - }, - 'extra_footer': '

Built with Sphinx | Source

', + "repository_url": "https://github.com/mllam/neural-lam", + "use_repository_button": True, + "use_issues_button": True, + "use_edit_page_button": True, + "repository_branch": "main", + "path_to_docs": "docs", + "show_navbar_depth": 2, + "show_toc_level": 2, + "logo": {"text": "Neural-LAM"}, + "extra_footer": ( + '

Built with Sphinx | ' + 'Source

' + ), } # OpenGraph settings -ogp_site_url = 'https://mllam.github.io/neural-lam/' -ogp_image = '_static/logo.png' +ogp_site_url = "https://mllam.github.io/neural-lam/" +ogp_image = "_static/logo.png" ogp_use_first_image = True # Mermaid settings mermaid_d3_zoom = False -mermaid_version = '11' +mermaid_version = "11" -suppress_warnings = ['autoapi.python_import_resolution'] +suppress_warnings = ["autoapi.python_import_resolution"] # Linkcheck settings linkcheck_ignore = [ - r'https://kutt\.it/mllam', # Returns 403 Forbidden for bots - r'https://docs\.pytorch\.org/.*', # Flaky anchors in intersphinx + r"https://kutt\.it/mllam", # Returns 403 Forbidden for bots + r"https://docs\.pytorch\.org/.*", # Flaky anchors in intersphinx ] diff --git a/neural_lam/create_graph.py b/neural_lam/create_graph.py index 3f6fb5f3c..cedf130c4 100644 --- a/neural_lam/create_graph.py +++ b/neural_lam/create_graph.py @@ -275,33 +275,39 @@ def create_graph( `graph_dir_path`. Creates the following files for all graphs: - + - g2m_edge_index.pt [2, N_g2m_edges] - g2m_features.pt [N_g2m_edges, d_features] - m2g_edge_index.pt [2, N_m2m_edges] - m2g_features.pt [N_m2m_edges, d_features] - m2m_edge_index.pt list of [2, N_m2m_edges_level], length==n_levels - - m2m_features.pt list of [N_m2m_edges_level, d_features], length==n_levels - - mesh_features.pt list of [N_mesh_nodes_level, d_mesh_static], length==n_levels + - m2m_features.pt list of [N_m2m_edges_level, d_features], + length==n_levels + - mesh_features.pt list of [N_mesh_nodes_level, d_mesh_static], + length==n_levels where: - + * d_features: number of features per edge (currently d_features==3, for edge-length, x and y) * N_g2m_edges: number of edges in the graph from grid-to-mesh * N_m2g_edges: number of edges in the graph from mesh-to-grid - * N_m2m_edges_level: number of edges in the graph from mesh-to-mesh at a given level - (list index corresponds to the level) + * N_m2m_edges_level: number of edges in the graph from mesh-to-mesh + at a given level (list index corresponds to the level) * d_mesh_static: number of static features per mesh node (currently d_mesh_static==2, for x and y) * N_mesh_nodes_level: number of nodes in the mesh at a given level And in addition for hierarchical graphs: - - - mesh_up_edge_index.pt: list of [2, N_mesh_updown_edges_level], length==n_levels-1 - - mesh_up_features.pt: list of [N_mesh_updown_edges_level, d_features], length==n_levels-1 - - mesh_down_edge_index.pt: list of [2, N_mesh_updown_edges_level], length==n_levels-1 - - mesh_down_features.pt: list of [N_mesh_updown_edges_level, d_features], length==n_levels-1 + + - mesh_up_edge_index.pt: list of [2, N_mesh_updown_edges_level], + length==n_levels-1 + - mesh_up_features.pt: list of [N_mesh_updown_edges_level, d_features], + length==n_levels-1 + - mesh_down_edge_index.pt: list of [2, N_mesh_updown_edges_level], + length==n_levels-1 + - mesh_down_features.pt: list of [N_mesh_updown_edges_level, d_features], + length==n_levels-1 where N_mesh_updown_edges_level is the number of edges in the graph from mesh-to-mesh between two consecutive levels (list index corresponds index diff --git a/neural_lam/datastore/base.py b/neural_lam/datastore/base.py index 8c17282f5..e5307d9dc 100644 --- a/neural_lam/datastore/base.py +++ b/neural_lam/datastore/base.py @@ -482,7 +482,7 @@ class BaseRegularGridDatastore(BaseDatastore): The following methods and attributes must be implemented for datastore that represents regular-gridded data: - + * `grid_shape_state` (property): 2D shape of the grid for the state variables. * `get_xy` (method): Return the x, y coordinates of the dataset, with the @@ -529,7 +529,7 @@ def get_xy(self, category: str, stacked: bool) -> np.ndarray: np.ndarray The x, y coordinates of the dataset, returned differently based on the value of `stacked`: - + * `stacked==True`: shape `(n_grid_points, 2)` where n_grid_points=N_x*N_y. * `stacked==False`: shape `(N_x, N_y, 2)` diff --git a/neural_lam/datastore/mdp.py b/neural_lam/datastore/mdp.py index cceacc5fe..55e91dc2b 100644 --- a/neural_lam/datastore/mdp.py +++ b/neural_lam/datastore/mdp.py @@ -500,7 +500,7 @@ def get_xy(self, category: str, stacked: bool) -> ndarray: np.ndarray The x, y coordinates of the dataset, returned differently based on the value of `stacked`: - + * `stacked==True`: shape `(n_grid_points, 2)` where n_grid_points=N_x*N_y. * `stacked==False`: shape `(N_x, N_y, 2)` diff --git a/neural_lam/datastore/npyfilesmeps/store.py b/neural_lam/datastore/npyfilesmeps/store.py index e86c5e70e..029452fb8 100644 --- a/neural_lam/datastore/npyfilesmeps/store.py +++ b/neural_lam/datastore/npyfilesmeps/store.py @@ -244,9 +244,11 @@ def get_dataarray( xr.DataArray The data array for the given category and split, with dimensions per category: - - * state: ``[elapsed_forecast_duration, analysis_time, grid_index, feature, ensemble_member]`` - * forcing: ``[elapsed_forecast_duration, analysis_time, grid_index, feature]`` + + * state: ``[elapsed_forecast_duration, analysis_time, grid_index,`` + ``feature, ensemble_member]`` + * forcing: ``[elapsed_forecast_duration, analysis_time,`` + ``grid_index, feature]`` * static: ``[grid_index, feature]`` """ @@ -668,7 +670,7 @@ def get_xy(self, category: str, stacked: bool) -> np.ndarray: np.ndarray The x, y coordinates of the dataset (with x first then y second), returned differently based on the value of `stacked`: - + * `stacked==True`: shape `(n_grid_points, 2)` where n_grid_points=N_x*N_y. * `stacked==False`: shape `(N_x, N_y, 2)` diff --git a/neural_lam/gnn_layers.py b/neural_lam/gnn_layers.py index 99a00144d..9468cbcba 100644 --- a/neural_lam/gnn_layers.py +++ b/neural_lam/gnn_layers.py @@ -182,7 +182,7 @@ def aggregate( ) -> tuple[torch.Tensor, torch.Tensor]: """ Overridden aggregation function to: - + * return both aggregated and per-edge messages, * only aggregate to the number of receiver nodes (``self.num_rec``) rather than to ``dim_size``. @@ -264,7 +264,8 @@ def get_gnn_class(gnn_type: str) -> Type[pyg.nn.MessagePassing]: Parameters ---------- gnn_type : str - One of the keys in GNN_TYPES (currently "InteractionNet" or "PropagationNet") + One of the keys in GNN_TYPES (currently "InteractionNet" or + "PropagationNet") Returns ------- From 2776af6111f5d6494b249b56e3519ea83c10d3fe Mon Sep 17 00:00:00 2001 From: Mohit-Lakra Date: Mon, 22 Jun 2026 22:43:34 +0530 Subject: [PATCH 09/15] - Remove tool.setuptools from pyproject.toml - Update OpenGraph URL to ReadTheDocs and drop empty favicon - Remove --co flag from quickstart test command - Drop unused UML diagrams and custom CSS - Restore dataset notebook and add to toctree - Align RTD config with CI python version and uv sync - Add CHANGELOG entry for docs migration --- .readthedocs.yaml | 6 +- CHANGELOG.md | 2 + docs/_static/custom.css | 260 ------------------ docs/_static/favicon.ico | 0 docs/_static/uml/classes_models.mmd | 133 --------- docs/_static/uml/packages_models.mmd | 51 ---- docs/conf.py | 12 +- docs/getting-started/quickstart.md | 4 +- docs/index.md | 1 + .../create_reduced_meps_dataset.ipynb | 239 ++++++++++++++++ pyproject.toml | 3 - 11 files changed, 255 insertions(+), 456 deletions(-) delete mode 100644 docs/_static/custom.css delete mode 100644 docs/_static/favicon.ico delete mode 100644 docs/_static/uml/classes_models.mmd delete mode 100644 docs/_static/uml/packages_models.mmd create mode 100644 docs/notebooks/create_reduced_meps_dataset.ipynb diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 22c12b446..fa9ce7472 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -3,9 +3,9 @@ version: 2 build: os: ubuntu-22.04 tools: - python: "3.10" + python: "3.11" commands: - pip install uv - - uv pip install --system -e ".[cpu,docs]" - - sphinx-build -W --keep-going -b html docs/ docs/_build/html/ + - uv sync --extra cpu --extra docs --group dev + - uv run sphinx-build -W --keep-going -b html docs/ docs/_build/html/ - cp -r docs/_build/html/* $READTHEDOCS_OUTPUT/html/ diff --git a/CHANGELOG.md b/CHANGELOG.md index ccf79fdb1..d8413af0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add a Sphinx + `sphinx-autoapi` documentation site (intro, install, quickstart, auto-generated API reference) on `myst-nb` and `sphinx-book-theme`, with a `docs` CI build/linkcheck job and a ReadTheDocs config. closes #61 [\#272](https://github.com/mllam/neural-lam/pull/272) @Mohit-Lakra + - Add `PropagationNet` GNN layer that incentivises directional message propagation from sender to receiver nodes, and expose it alongside `InteractionNet` through four new CLI arguments (`--g2m_gnn_type`, diff --git a/docs/_static/custom.css b/docs/_static/custom.css deleted file mode 100644 index 56b7b15ac..000000000 --- a/docs/_static/custom.css +++ /dev/null @@ -1,260 +0,0 @@ -/* Neural-LAM Documentation — High-Visibility Custom Styles */ - -/* ========================================================================== - 1. OVERRIDE SPHINX BOOK THEME (PYDATA) VARIABLES - ========================================================================== */ -:root { - /* High contrast colors */ - --pst-color-primary: #1d4ed8; /* Strong Blue */ - --pst-color-primary-bg: #eff6ff; - --pst-color-secondary: #0f172a; /* Slate 900 */ - - /* Text colors - Pure high contrast */ - --pst-color-text-base: #0f172a; /* Nearly black */ - --pst-color-text-muted: #475569; /* Slate 600 */ - - /* Backgrounds */ - --pst-color-background: #ffffff; - --pst-color-surface: #f8fafc; /* Slate 50 */ - --pst-color-border: #cbd5e1; /* Slate 300 */ - - /* Typography */ - --pst-font-family-base: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; - --pst-font-family-heading: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; - --pst-font-family-monospace: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace; - - --pst-font-size-base: 17px; /* Increased for better visibility */ -} - -/* Dark mode overrides (PyData theme automatically adds data-theme="dark") */ -html[data-theme="dark"] { - --pst-color-primary: #60a5fa; /* Light Blue */ - --pst-color-primary-bg: #1e3a8a; - - --pst-color-text-base: #f8fafc; /* Nearly white */ - --pst-color-text-muted: #cbd5e1; - - --pst-color-background: #0f172a; /* Slate 900 */ - --pst-color-surface: #1e293b; /* Slate 800 */ - --pst-color-border: #334155; /* Slate 700 */ -} - -/* ========================================================================== - 2. GLOBAL TYPOGRAPHY & LAYOUT - ========================================================================== */ -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap'); - -html { - scroll-behavior: smooth; -} - -body { - font-family: var(--pst-font-family-base); - font-size: var(--pst-font-size-base); - line-height: 1.75; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -/* Make headings stand out more */ -h1, h2, h3, h4, h5, h6 { - color: var(--pst-color-text-base); - font-weight: 700; - letter-spacing: -0.02em; -} - -h1 { - font-size: 2.5rem; - font-weight: 800; - margin-bottom: 1.5rem; - line-height: 1.2; -} - -h2 { - font-size: 1.8rem; - margin-top: 2.5rem; - margin-bottom: 1.25rem; - border-bottom: 2px solid var(--pst-color-border); - padding-bottom: 0.5rem; -} - -h3 { - font-size: 1.4rem; - margin-top: 2rem; -} - -p { - margin-bottom: 1.25rem; -} - -a { - color: var(--pst-color-primary); - text-decoration: none; - font-weight: 500; -} - -a:hover { - text-decoration: underline; - text-decoration-thickness: 2px; - text-underline-offset: 4px; -} - -/* ========================================================================== - 3. SIDEBAR NAVIGATION - ========================================================================== */ -.bd-sidebar { - background-color: var(--pst-color-surface); - border-right: 1px solid var(--pst-color-border); -} - -.bd-sidebar .nav-link { - font-size: 1rem; - color: var(--pst-color-text-muted); - padding: 0.5rem 1rem; - margin: 0.25rem 0; - border-radius: 6px; - transition: all 0.2s; -} - -.bd-sidebar .nav-link:hover { - background-color: var(--pst-color-primary-bg); - color: var(--pst-color-primary); -} - -.bd-sidebar .nav-link.active { - background-color: var(--pst-color-primary-bg); - color: var(--pst-color-primary); - font-weight: 600; - border-left: 4px solid var(--pst-color-primary); -} - -/* ========================================================================== - 4. CODE BLOCKS & INLINE CODE - ========================================================================== */ -pre { - background-color: var(--pst-color-surface); - border: 1px solid var(--pst-color-border); - border-radius: 8px; - padding: 1.25rem; - font-size: 0.9rem; - line-height: 1.6; - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05); -} - -code { - font-family: var(--pst-font-family-monospace); - background-color: var(--pst-color-surface); - border: 1px solid var(--pst-color-border); - color: var(--pst-color-primary); - padding: 0.2em 0.4em; - border-radius: 4px; - font-size: 0.9em; - font-weight: 500; -} - -pre code { - background-color: transparent; - border: none; - color: inherit; - padding: 0; -} - -/* ========================================================================== - 5. TABLES - ========================================================================== */ -table.table, table.docutils { - width: 100%; - border-collapse: separate; - border-spacing: 0; - border: 1px solid var(--pst-color-border); - border-radius: 8px; - overflow: hidden; - margin-bottom: 2rem; - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05); -} - -th { - background-color: var(--pst-color-surface); - color: var(--pst-color-text-base); - font-weight: 700; - text-transform: uppercase; - font-size: 0.85rem; - letter-spacing: 0.05em; - padding: 1rem; - border-bottom: 2px solid var(--pst-color-border); -} - -td { - padding: 1rem; - border-bottom: 1px solid var(--pst-color-border); - vertical-align: top; -} - -tr:last-child td { - border-bottom: none; -} - -tr:hover td { - background-color: var(--pst-color-surface); -} - -/* ========================================================================== - 6. ADMONITIONS (Callouts) - ========================================================================== */ -.admonition { - border-radius: 8px; - border: 1px solid var(--pst-color-border); - border-left-width: 6px; - padding: 1.5rem; - margin-bottom: 2rem; - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05); - background-color: var(--pst-color-background); -} - -.admonition-title { - font-weight: 700 !important; - font-size: 1.1rem !important; - margin-bottom: 0.75rem !important; - margin-top: 0 !important; - text-transform: none !important; -} - -/* ========================================================================== - 7. GRID CARDS (Landing Page) - ========================================================================== */ -.sd-card { - border: 1px solid var(--pst-color-border) !important; - border-radius: 12px !important; - transition: transform 0.2s, box-shadow 0.2s !important; - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05) !important; - background-color: var(--pst-color-surface) !important; -} - -.sd-card:hover { - transform: translateY(-5px); - box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1) !important; - border-color: var(--pst-color-primary) !important; -} - -.sd-card-title { - font-size: 1.3rem !important; - font-weight: 700 !important; - color: var(--pst-color-text-base) !important; -} - -.sd-card-text { - color: var(--pst-color-text-muted) !important; - font-size: 1.05rem !important; -} - -/* ========================================================================== - 8. MERMAID DIAGRAMS - ========================================================================== */ -.mermaid { - text-align: center; - padding: 1.5rem; - background-color: var(--pst-color-surface); - border: 1px solid var(--pst-color-border); - border-radius: 8px; - margin-bottom: 2rem; -} diff --git a/docs/_static/favicon.ico b/docs/_static/favicon.ico deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/_static/uml/classes_models.mmd b/docs/_static/uml/classes_models.mmd deleted file mode 100644 index 2eef0bce9..000000000 --- a/docs/_static/uml/classes_models.mmd +++ /dev/null @@ -1,133 +0,0 @@ -classDiagram - class ARForecaster { - predictor - predicts_std : bool - forward(init_states: torch.Tensor, forcing_features: torch.Tensor, boundary_states: torch.Tensor) tuple[torch.Tensor, torch.Tensor | None] - } - class BaseGraphModel { - encoding_grid_mlp : Sequential - g2m_edges - g2m_embedder : Sequential - g2m_gnn : InteractionNet - g2m_gnn_type : str - grid_embedder : Sequential - grid_input_dim - hidden_dim : int - hidden_layers : int - hierarchical - m2g_edges - m2g_embedder : Sequential - m2g_gnn : InteractionNet - m2g_gnn_type : str - mesh_aggr : str - mlp_blueprint_end : list - num_mesh_nodes - output_map : Sequential - processor_layers : int - embedd_mesh_nodes()* - forward(prev_state, prev_prev_state, forcing) - get_num_mesh()* - process_step(mesh_rep)* - } - class BaseHiGraphModel { - level_mesh_sizes - mesh_down_embedders : ModuleList - mesh_down_gnn_type : str - mesh_embedders : ModuleList - mesh_init_gnns : ModuleList - mesh_read_gnns : ModuleList - mesh_same_embedders : ModuleList - mesh_up_embedders : ModuleList - mesh_up_gnn_type : str - num_levels - embedd_mesh_nodes() - get_num_mesh() - hi_processor_step(mesh_rep_levels, mesh_same_rep, mesh_up_rep, mesh_down_rep)* - process_step(mesh_rep) - } - class Forecaster { - predicts_std : bool - forward(init_states: torch.Tensor, forcing_features: torch.Tensor, boundary_states: torch.Tensor)* tuple[torch.Tensor, torch.Tensor | None] - } - class ForecasterModule { - create_gif : bool - datastore : BaseDatastore - forcing_mean : NoneType - forcing_mean_tiled - forcing_std : NoneType - forcing_std_tiled - forecaster - loss - matched_metrics : set - n_example_pred : int - per_var_std : NoneType - plotted_examples : int - restore_opt : bool - spatial_loss_maps : list[Any] - test_metrics : dict[str, list] - time_step_int : int - time_step_unit : str - val_metrics : dict[str, list] - aggregate_and_plot_metrics(metrics_dict, prefix) - all_gather_cat(tensor_to_gather) - common_step(batch) - configure_optimizers() - create_metric_log_dict(metric_tensor, prefix, metric_name) - on_after_batch_transfer(batch, dataloader_idx) - on_load_checkpoint(checkpoint) - on_test_epoch_end() - on_validation_epoch_end() - plot_examples(batch, n_examples, split, prediction) - test_step(batch, batch_idx) - training_step(batch) - validation_step(batch, batch_idx) - } - class GraphLAM { - m2m_embedder : Sequential - mesh_embedder : Sequential - processor - embedd_mesh_nodes() - get_num_mesh() - process_step(mesh_rep) - } - class HiLAM { - mesh_down_gnns : ModuleList - mesh_down_same_gnns : ModuleList - mesh_up_gnns : ModuleList - mesh_up_same_gnns : ModuleList - hi_processor_step(mesh_rep_levels, mesh_same_rep, mesh_up_rep, mesh_down_rep) - make_down_gnns() - make_same_gnns() - make_up_gnns() - mesh_down_step(mesh_rep_levels, mesh_same_rep, mesh_down_rep, down_gnns, same_gnns) - mesh_up_step(mesh_rep_levels, mesh_same_rep, mesh_up_rep, up_gnns, same_gnns) - } - class HiLAMParallel { - edge_split_sections - processor - hi_processor_step(mesh_rep_levels, mesh_same_rep, mesh_up_rep, mesh_down_rep) - } - class StepPredictor { - clamp_lower - clamp_lower_upper - clamp_upper - grid_output_dim - inverse_clamp_lower - inverse_clamp_lower_upper - inverse_clamp_upper - num_grid_nodes - output_std : bool - predicts_std : bool - expand_to_batch(x: torch.Tensor, batch_size: int) torch.Tensor - forward(prev_state: torch.Tensor, prev_prev_state: torch.Tensor, forcing: torch.Tensor)* tuple[torch.Tensor, torch.Tensor | None] - get_clamped_new_state(state_delta, prev_state) - prepare_clamping_params(datastore: BaseDatastore) - } - ARForecaster --|> Forecaster - BaseGraphModel --|> StepPredictor - GraphLAM --|> BaseGraphModel - HiLAM --|> BaseHiGraphModel - HiLAMParallel --|> BaseHiGraphModel - BaseHiGraphModel --|> BaseGraphModel - Forecaster --o ForecasterModule : forecaster - StepPredictor --o ARForecaster : predictor diff --git a/docs/_static/uml/packages_models.mmd b/docs/_static/uml/packages_models.mmd deleted file mode 100644 index 955e7a966..000000000 --- a/docs/_static/uml/packages_models.mmd +++ /dev/null @@ -1,51 +0,0 @@ -classDiagram - class models { - } - class forecasters { - } - class autoregressive { - } - class base { - } - class module { - } - class step_predictors { - } - class base { - } - class graph { - } - class base { - } - class graph_lam { - } - class hi_lam { - } - class hi_lam_parallel { - } - class hierarchical { - } - models --> autoregressive - models --> base - models --> module - models --> base - models --> base - models --> graph_lam - models --> hi_lam - models --> hi_lam_parallel - models --> hierarchical - forecasters --> autoregressive - forecasters --> base - autoregressive --> base - module --> base - step_predictors --> base - graph --> base - graph --> graph_lam - graph --> hi_lam - graph --> hi_lam_parallel - graph --> hierarchical - base --> base - graph_lam --> base - hi_lam --> hierarchical - hi_lam_parallel --> hierarchical - hierarchical --> base diff --git a/docs/conf.py b/docs/conf.py index 21bd7110a..4335442e6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -9,6 +9,9 @@ copyright = "2024–2026, MLLAM Community" author = "MLLAM Community" +# General Sphinx configuration +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + extensions = [ "autoapi.extension", "myst_nb", @@ -78,9 +81,7 @@ # HTML Theme html_theme = "sphinx_book_theme" html_logo = "_static/logo.png" -html_favicon = "_static/favicon.ico" html_static_path = ["_static"] -html_css_files = ["custom.css"] html_theme_options = { "repository_url": "https://github.com/mllam/neural-lam", @@ -99,7 +100,7 @@ } # OpenGraph settings -ogp_site_url = "https://mllam.github.io/neural-lam/" +ogp_site_url = "https://neural-lam.readthedocs.io/en/latest/" ogp_image = "_static/logo.png" ogp_use_first_image = True @@ -107,7 +108,10 @@ mermaid_d3_zoom = False mermaid_version = "11" -suppress_warnings = ["autoapi.python_import_resolution"] +suppress_warnings = [ + "autoapi.python_import_resolution", + "myst.xref_missing", +] # Linkcheck settings linkcheck_ignore = [ diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index 96fbd115d..04b24df69 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -13,8 +13,8 @@ This guide walks through the minimum steps to get started with Neural-LAM: When running tests for the first time, example data is automatically downloaded from S3. You can trigger this download by running a minimal test. ```{code-block} bash -# Run a minimal test to trigger data download -pytest tests/test_training.py -vv -s -k "test_training" --co +# Run a minimal test to trigger the data download +pytest tests/test_training.py -vv -s -k "test_training" ``` Alternatively, you can use the `DummyDatastore` for quick testing without downloading real data. diff --git a/docs/index.md b/docs/index.md index 2bafde6a2..3f05de219 100644 --- a/docs/index.md +++ b/docs/index.md @@ -10,6 +10,7 @@ Neural-LAM is a PyTorch and PyTorch Lightning framework for high-resolution weat getting-started/installation getting-started/quickstart +notebooks/create_reduced_meps_dataset api/index ``` diff --git a/docs/notebooks/create_reduced_meps_dataset.ipynb b/docs/notebooks/create_reduced_meps_dataset.ipynb new file mode 100644 index 000000000..78ce14b25 --- /dev/null +++ b/docs/notebooks/create_reduced_meps_dataset.ipynb @@ -0,0 +1,239 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Creating meps_example_reduced\n", + "This notebook outlines how the small-size test dataset ```meps_example_reduced``` was created based on the slightly larger dataset ```meps_example```. The zipped up datasets are 263 MB and 2.6 GB, respectively. See [README.md](../../README.md) for info on how to download ```meps_example```.\n", + "\n", + "The dataset was reduced in size by reducing the number of grid points and variables.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# Standard library\n", + "import os\n", + "\n", + "# Third-party\n", + "import numpy as np\n", + "import torch" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "The number of grid points was reduced to 1/4 by halving the number of coordinates in both the x and y direction. This was done by removing a quarter of the grid points along each outer edge, so the center grid points would stay centered in the new set.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Load existing grid\n", + "grid_xy = np.load('data/meps_example/static/nwp_xy.npy')\n", + "# Get slices in each dimension by cutting off a quarter along each edge\n", + "num_x, num_y = grid_xy.shape[1:]\n", + "x_slice = slice(num_x//4, 3*num_x//4)\n", + "y_slice = slice(num_y//4, 3*num_y//4)\n", + "# Index and save reduced grid\n", + "grid_xy_reduced = grid_xy[:, x_slice, y_slice]\n", + "np.save('data/meps_example_reduced/static/nwp_xy.npy', grid_xy_reduced)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "This cut out the border, so a new perimeter of 10 grid points was established as border (10 was also the border size in the original \"meps_example\").\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# Outer 10 grid points are border\n", + "old_border_mask = np.load('data/meps_example/static/border_mask.npy')\n", + "assert np.all(old_border_mask[10:-10, 10:-10] == False)\n", + "assert np.all(old_border_mask[:10, :] == True)\n", + "assert np.all(old_border_mask[:, :10] == True)\n", + "assert np.all(old_border_mask[-10:,:] == True)\n", + "assert np.all(old_border_mask[:,-10:] == True)\n", + "\n", + "# Create new array with False everywhere but the outer 10 grid points\n", + "border_mask = np.zeros_like(grid_xy_reduced[0,:,:], dtype=bool)\n", + "border_mask[:10] = True\n", + "border_mask[:,:10] = True\n", + "border_mask[-10:] = True\n", + "border_mask[:,-10:] = True\n", + "np.save('data/meps_example_reduced/static/border_mask.npy', border_mask)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A few other files also needed to be copied using only the new reduced grid" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Load surface_geopotential.npy, index only values from the reduced grid, and save to new file\n", + "surface_geopotential = np.load('data/meps_example/static/surface_geopotential.npy')\n", + "surface_geopotential_reduced = surface_geopotential[x_slice, y_slice]\n", + "np.save('data/meps_example_reduced/static/surface_geopotential.npy', surface_geopotential_reduced)\n", + "\n", + "# Load pytorch file grid_features.pt\n", + "grid_features = torch.load('data/meps_example/static/grid_features.pt')\n", + "# Index only values from the reduced grid. \n", + "# First reshape from (num_grid_points_total, 4) to (num_grid_points_x, num_grid_points_y, 4), \n", + "# then index, then reshape back to new total number of grid points\n", + "print(grid_features.shape)\n", + "grid_features_new = grid_features.reshape(num_x, num_y, 4)[x_slice,y_slice,:].reshape((-1, 4))\n", + "# Save to new file\n", + "torch.save(grid_features_new, 'data/meps_example_reduced/static/grid_features.pt')\n", + "\n", + "# flux_stats.pt is just a vector of length 2, so the grid shape and variable changes does not change this file\n", + "torch.save(torch.load('data/meps_example/static/flux_stats.pt'), 'data/meps_example_reduced/static/flux_stats.pt')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "The number of variables was reduced by truncating the variable list to the first 8." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "num_vars = 8\n", + "\n", + "# Load parameter_weights.npy, truncate to first 8 variables, and save to new file\n", + "parameter_weights = np.load('data/meps_example/static/parameter_weights.npy')\n", + "parameter_weights_reduced = parameter_weights[:num_vars]\n", + "np.save('data/meps_example_reduced/static/parameter_weights.npy', parameter_weights_reduced)\n", + "\n", + "# Do the same for following 4 pytorch files\n", + "for file in ['diff_mean', 'diff_std', 'parameter_mean', 'parameter_std']:\n", + " old_file = torch.load(f'data/meps_example/static/{file}.pt')\n", + " new_file = old_file[:num_vars]\n", + " torch.save(new_file, f'data/meps_example_reduced/static/{file}.pt')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Lastly the files in each of the directories train, test, and val have to be reduced. The folders all have the same structure with files of the following types:\n", + "```\n", + "nwp_YYYYMMDDHH_mbrXXX.npy\n", + "wtr_YYYYMMDDHH.npy\n", + "nwp_toa_downwelling_shortwave_flux_YYYYMMDDHH.npy\n", + "```\n", + "with ```YYYYMMDDHH``` being some date with hours, and ```XXX``` being some 3-digit integer.\n", + "\n", + "The first type of file has x and y in dimensions 1 and 2, and variable index in dimension 3. Dimension 0 is unchanged.\n", + "The second type has has x and y in dimensions 1 and 2. Dimension 0 is unchanged.\n", + "The last type has just x and y as the only 2 dimensions.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(65, 268, 238, 18)\n", + "(65, 268, 238)\n" + ] + } + ], + "source": [ + "print(np.load('data/meps_example/samples/train/nwp_2022040100_mbr000.npy').shape)\n", + "print(np.load('data/meps_example/samples/train/nwp_toa_downwelling_shortwave_flux_2022040112.npy').shape)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following loop goes through each file in each sample folder and indexes them according to the dimensions given by the file name." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for sample in ['train', 'test', 'val']:\n", + " files = os.listdir(f'data/meps_example/samples/{sample}')\n", + "\n", + " for f in files:\n", + " data = np.load(f'data/meps_example/samples/{sample}/{f}')\n", + " if 'mbr' in f:\n", + " data = data[:,x_slice,y_slice,:num_vars]\n", + " elif 'wtr' in f:\n", + " data = data[x_slice, y_slice]\n", + " else:\n", + " data = data[:,x_slice,y_slice]\n", + " np.save(f'data/meps_example_reduced/samples/{sample}/{f}', data)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Lastly, the file ```data_config.yaml``` is modified manually by truncating the variable units, long and short names, and setting the new grid shape. Also the unit descriptions containing ```^``` was automatically parsed using latex, and to avoid having to install latex in the GitHub CI/CD pipeline, this was changed to ```**```. \n", + "\n", + "This new config file was placed in ```data/meps_example_reduced```, and that directory was then zipped and placed in a European Weather Cloud S3 bucket." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.14" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/pyproject.toml b/pyproject.toml index 38ada844e..0a2d3dd5e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,9 +93,6 @@ torch = [ { index = "pytorch-cu128", extra = "gpu-cu128" }, ] -[tool.setuptools] -py-modules = ["neural_lam"] - [tool.black] line-length = 80 From 282139802591c088726bd43c9fd1199fcc56a64e Mon Sep 17 00:00:00 2001 From: Mohit-Lakra Date: Fri, 26 Jun 2026 21:06:04 +0530 Subject: [PATCH 10/15] Removing mermaid dependencies, correcting slack channel link, and some docs correction --- docs/conf.py | 7 +------ docs/getting-started/quickstart.md | 4 +--- docs/index.md | 4 +--- pyproject.toml | 2 -- 4 files changed, 3 insertions(+), 14 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 4335442e6..3ae488cf7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,7 +19,6 @@ "sphinx.ext.viewcode", "sphinx.ext.intersphinx", "sphinx_copybutton", - "sphinxcontrib.mermaid", "sphinxext.opengraph", ] @@ -104,10 +103,6 @@ ogp_image = "_static/logo.png" ogp_use_first_image = True -# Mermaid settings -mermaid_d3_zoom = False -mermaid_version = "11" - suppress_warnings = [ "autoapi.python_import_resolution", "myst.xref_missing", @@ -115,6 +110,6 @@ # Linkcheck settings linkcheck_ignore = [ - r"https://kutt\.it/mllam", # Returns 403 Forbidden for bots + r"https://kutt\.to/mllam", # Returns 403 Forbidden for bots r"https://docs\.pytorch\.org/.*", # Flaky anchors in intersphinx ] diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index 04b24df69..e51ccd1c8 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -17,8 +17,6 @@ When running tests for the first time, example data is automatically downloaded pytest tests/test_training.py -vv -s -k "test_training" ``` -Alternatively, you can use the `DummyDatastore` for quick testing without downloading real data. - ## Step 2: Create a Graph Before training, you must construct a graph mesh for your data. @@ -37,7 +35,7 @@ Now you can train a model using the graph and configuration. python -m neural_lam.train_model --config_path --model graph_lam --graph ``` -Neural-LAM supports several models like `graph_lam`, `hilam`, and `hilam_parallel`. +Neural-LAM supports several models like `graph_lam`, `hilam`, and `hi_lam_parallel`. ## Step 4: Evaluate diff --git a/docs/index.md b/docs/index.md index 3f05de219..b69a41c16 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,7 +1,5 @@ # Neural-LAM -**Graph-based neural weather prediction for Limited Area Modeling** - Neural-LAM is a PyTorch and PyTorch Lightning framework for high-resolution weather prediction using Graph Neural Networks. It provides a modular approach to Limited Area Modeling, supporting multiple graph-based architectures such as GraphLAM, HiLAM, and HiLAMParallel to process and predict meteorological data efficiently. ```{toctree} @@ -28,4 +26,4 @@ api/index - [GitHub Repository](https://github.com/mllam/neural-lam) - [Issue Tracker](https://github.com/mllam/neural-lam/issues) -- [MLLAM Community Slack](https://kutt.it/mllam) +- [MLLAM Community Slack](https://kutt.to/mllam) diff --git a/pyproject.toml b/pyproject.toml index 0a2d3dd5e..70e485337 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,10 +53,8 @@ docs = [ "sphinx-autoapi>=3.0.0", "sphinx-copybutton>=0.5.2", "sphinx-book-theme>=1.1.0", - "sphinxcontrib-mermaid>=0.9.2", "sphinx>=7.2.6", "myst-nb>=1.0.0", - "myst-parser>=2.0.0", "sphinxext-opengraph>=0.9.0", "linkify-it-py>=2.0.0", ] From 517175b4842b7301cc7f317a053b2d45c66ad008 Mon Sep 17 00:00:00 2001 From: Mohit-Lakra Date: Fri, 26 Jun 2026 21:44:53 +0530 Subject: [PATCH 11/15] docs: address review feedback --- .github/workflows/docs.yml | 4 +- .readthedocs.yaml | 2 +- docs/conf.py | 1 - docs/getting-started/installation.md | 49 +- docs/index.md | 9 +- .../create_reduced_meps_dataset.ipynb | 468 +++++++++--------- uv.lock | 18 - 7 files changed, 248 insertions(+), 303 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 74537fdc0..f1677884a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -40,7 +40,7 @@ jobs: python-version: "3.11" - name: Install docs dependencies - run: uv sync --extra cpu --extra docs --group dev --no-cache + run: uv sync --extra docs --group dev --no-cache @@ -72,7 +72,7 @@ jobs: python-version: "3.11" - name: Install docs dependencies - run: uv sync --extra cpu --extra docs --group dev --no-cache + run: uv sync --extra docs --group dev --no-cache diff --git a/.readthedocs.yaml b/.readthedocs.yaml index fa9ce7472..b147538aa 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -6,6 +6,6 @@ build: python: "3.11" commands: - pip install uv - - uv sync --extra cpu --extra docs --group dev + - uv sync --extra docs --group dev - uv run sphinx-build -W --keep-going -b html docs/ docs/_build/html/ - cp -r docs/_build/html/* $READTHEDOCS_OUTPUT/html/ diff --git a/docs/conf.py b/docs/conf.py index 3ae488cf7..2447234c8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -91,7 +91,6 @@ "path_to_docs": "docs", "show_navbar_depth": 2, "show_toc_level": 2, - "logo": {"text": "Neural-LAM"}, "extra_footer": ( '

Built with Sphinx | ' 'Source

' diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 802032f24..930d25ef8 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -5,52 +5,9 @@ - Git - (Optional) CUDA-capable GPU for training -## Quick Install with uv (Recommended) - -uv is the recommended tool for installing Neural-LAM and its dependencies. - -```{code-block} bash -# Clone the repository -git clone https://github.com/mllam/neural-lam.git -cd neural-lam - -# CPU-only install -uv sync --extra cpu --group dev --locked - -# GPU install (CUDA 13.0, default) -uv sync --extra gpu --group dev --locked - -# GPU install (CUDA 12.8) -uv sync --extra gpu-cu128 --group dev --locked -``` - -```{note} -The extras system (`cpu` / `gpu` / `gpu-cu128`) selects the correct PyTorch index via `[tool.uv.sources]` in `pyproject.toml`. -``` - -## Install with pip (Alternative) - -Alternatively, you can install the dependencies using pip. Make sure to install the correct version of PyTorch for your system before installing the package. - -```{code-block} bash -# Install PyTorch (example for CUDA 11.8) -pip install torch --index-url https://download.pytorch.org/whl/cu118 - -# Install neural-lam -pip install -e .[dev] -``` - -## Verify Installation - -```{code-block} bash -# Activate the environment -source .venv/bin/activate - -# Verify torch is installed -python -c "import torch; print(f'PyTorch {torch.__version__}')" - -# Verify neural-lam is importable -python -c "import neural_lam; print('Neural-LAM OK')" +```{include} ../../README.md +:start-after: "# Installing Neural-LAM" +:end-before: "# Using Neural-LAM" ``` ## Building Documentation Locally diff --git a/docs/index.md b/docs/index.md index b69a41c16..c829a278d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,10 +8,17 @@ Neural-LAM is a PyTorch and PyTorch Lightning framework for high-resolution weat getting-started/installation getting-started/quickstart -notebooks/create_reduced_meps_dataset api/index ``` +```{toctree} +:maxdepth: 2 +:caption: Tutorials +:hidden: + +notebooks/create_reduced_meps_dataset +``` + - **[🚀 Getting Started](getting-started/installation.md)**: Installation guide and quickstart tutorial to get you up and running. - **[📚 API Reference](api/index)**: Auto-generated reference for all modules, classes, and functions. diff --git a/docs/notebooks/create_reduced_meps_dataset.ipynb b/docs/notebooks/create_reduced_meps_dataset.ipynb index 78ce14b25..daba23c44 100644 --- a/docs/notebooks/create_reduced_meps_dataset.ipynb +++ b/docs/notebooks/create_reduced_meps_dataset.ipynb @@ -1,239 +1,239 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Creating meps_example_reduced\n", - "This notebook outlines how the small-size test dataset ```meps_example_reduced``` was created based on the slightly larger dataset ```meps_example```. The zipped up datasets are 263 MB and 2.6 GB, respectively. See [README.md](../../README.md) for info on how to download ```meps_example```.\n", - "\n", - "The dataset was reduced in size by reducing the number of grid points and variables.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "# Standard library\n", - "import os\n", - "\n", - "# Third-party\n", - "import numpy as np\n", - "import torch" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "The number of grid points was reduced to 1/4 by halving the number of coordinates in both the x and y direction. This was done by removing a quarter of the grid points along each outer edge, so the center grid points would stay centered in the new set.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Load existing grid\n", - "grid_xy = np.load('data/meps_example/static/nwp_xy.npy')\n", - "# Get slices in each dimension by cutting off a quarter along each edge\n", - "num_x, num_y = grid_xy.shape[1:]\n", - "x_slice = slice(num_x//4, 3*num_x//4)\n", - "y_slice = slice(num_y//4, 3*num_y//4)\n", - "# Index and save reduced grid\n", - "grid_xy_reduced = grid_xy[:, x_slice, y_slice]\n", - "np.save('data/meps_example_reduced/static/nwp_xy.npy', grid_xy_reduced)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "This cut out the border, so a new perimeter of 10 grid points was established as border (10 was also the border size in the original \"meps_example\").\n" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "# Outer 10 grid points are border\n", - "old_border_mask = np.load('data/meps_example/static/border_mask.npy')\n", - "assert np.all(old_border_mask[10:-10, 10:-10] == False)\n", - "assert np.all(old_border_mask[:10, :] == True)\n", - "assert np.all(old_border_mask[:, :10] == True)\n", - "assert np.all(old_border_mask[-10:,:] == True)\n", - "assert np.all(old_border_mask[:,-10:] == True)\n", - "\n", - "# Create new array with False everywhere but the outer 10 grid points\n", - "border_mask = np.zeros_like(grid_xy_reduced[0,:,:], dtype=bool)\n", - "border_mask[:10] = True\n", - "border_mask[:,:10] = True\n", - "border_mask[-10:] = True\n", - "border_mask[:,-10:] = True\n", - "np.save('data/meps_example_reduced/static/border_mask.npy', border_mask)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A few other files also needed to be copied using only the new reduced grid" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Load surface_geopotential.npy, index only values from the reduced grid, and save to new file\n", - "surface_geopotential = np.load('data/meps_example/static/surface_geopotential.npy')\n", - "surface_geopotential_reduced = surface_geopotential[x_slice, y_slice]\n", - "np.save('data/meps_example_reduced/static/surface_geopotential.npy', surface_geopotential_reduced)\n", - "\n", - "# Load pytorch file grid_features.pt\n", - "grid_features = torch.load('data/meps_example/static/grid_features.pt')\n", - "# Index only values from the reduced grid. \n", - "# First reshape from (num_grid_points_total, 4) to (num_grid_points_x, num_grid_points_y, 4), \n", - "# then index, then reshape back to new total number of grid points\n", - "print(grid_features.shape)\n", - "grid_features_new = grid_features.reshape(num_x, num_y, 4)[x_slice,y_slice,:].reshape((-1, 4))\n", - "# Save to new file\n", - "torch.save(grid_features_new, 'data/meps_example_reduced/static/grid_features.pt')\n", - "\n", - "# flux_stats.pt is just a vector of length 2, so the grid shape and variable changes does not change this file\n", - "torch.save(torch.load('data/meps_example/static/flux_stats.pt'), 'data/meps_example_reduced/static/flux_stats.pt')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "The number of variables was reduced by truncating the variable list to the first 8." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "num_vars = 8\n", - "\n", - "# Load parameter_weights.npy, truncate to first 8 variables, and save to new file\n", - "parameter_weights = np.load('data/meps_example/static/parameter_weights.npy')\n", - "parameter_weights_reduced = parameter_weights[:num_vars]\n", - "np.save('data/meps_example_reduced/static/parameter_weights.npy', parameter_weights_reduced)\n", - "\n", - "# Do the same for following 4 pytorch files\n", - "for file in ['diff_mean', 'diff_std', 'parameter_mean', 'parameter_std']:\n", - " old_file = torch.load(f'data/meps_example/static/{file}.pt')\n", - " new_file = old_file[:num_vars]\n", - " torch.save(new_file, f'data/meps_example_reduced/static/{file}.pt')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Lastly the files in each of the directories train, test, and val have to be reduced. The folders all have the same structure with files of the following types:\n", - "```\n", - "nwp_YYYYMMDDHH_mbrXXX.npy\n", - "wtr_YYYYMMDDHH.npy\n", - "nwp_toa_downwelling_shortwave_flux_YYYYMMDDHH.npy\n", - "```\n", - "with ```YYYYMMDDHH``` being some date with hours, and ```XXX``` being some 3-digit integer.\n", - "\n", - "The first type of file has x and y in dimensions 1 and 2, and variable index in dimension 3. Dimension 0 is unchanged.\n", - "The second type has has x and y in dimensions 1 and 2. Dimension 0 is unchanged.\n", - "The last type has just x and y as the only 2 dimensions.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(65, 268, 238, 18)\n", - "(65, 268, 238)\n" - ] - } - ], - "source": [ - "print(np.load('data/meps_example/samples/train/nwp_2022040100_mbr000.npy').shape)\n", - "print(np.load('data/meps_example/samples/train/nwp_toa_downwelling_shortwave_flux_2022040112.npy').shape)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The following loop goes through each file in each sample folder and indexes them according to the dimensions given by the file name." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for sample in ['train', 'test', 'val']:\n", - " files = os.listdir(f'data/meps_example/samples/{sample}')\n", - "\n", - " for f in files:\n", - " data = np.load(f'data/meps_example/samples/{sample}/{f}')\n", - " if 'mbr' in f:\n", - " data = data[:,x_slice,y_slice,:num_vars]\n", - " elif 'wtr' in f:\n", - " data = data[x_slice, y_slice]\n", - " else:\n", - " data = data[:,x_slice,y_slice]\n", - " np.save(f'data/meps_example_reduced/samples/{sample}/{f}', data)" - ] - }, + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Creating meps_example_reduced\n", + "This notebook outlines how the small-size test dataset ```meps_example_reduced``` was created based on the slightly larger dataset ```meps_example```. The zipped up datasets are 263 MB and 2.6 GB, respectively. See [README.md](../../README.md) for info on how to download ```meps_example```.\n", + "\n", + "The dataset was reduced in size by reducing the number of grid points and variables.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# Standard library\n", + "import os\n", + "\n", + "# Third-party\n", + "import numpy as np\n", + "import torch" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "The number of grid points was reduced to 1/4 by halving the number of coordinates in both the x and y direction. This was done by removing a quarter of the grid points along each outer edge, so the center grid points would stay centered in the new set.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Load existing grid\n", + "grid_xy = np.load('data/meps_example/static/nwp_xy.npy')\n", + "# Get slices in each dimension by cutting off a quarter along each edge\n", + "num_x, num_y = grid_xy.shape[1:]\n", + "x_slice = slice(num_x//4, 3*num_x//4)\n", + "y_slice = slice(num_y//4, 3*num_y//4)\n", + "# Index and save reduced grid\n", + "grid_xy_reduced = grid_xy[:, x_slice, y_slice]\n", + "np.save('data/meps_example_reduced/static/nwp_xy.npy', grid_xy_reduced)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "This cut out the border, so a new perimeter of 10 grid points was established as border (10 was also the border size in the original \"meps_example\").\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# Outer 10 grid points are border\n", + "old_border_mask = np.load('data/meps_example/static/border_mask.npy')\n", + "assert np.all(old_border_mask[10:-10, 10:-10] == False)\n", + "assert np.all(old_border_mask[:10, :] == True)\n", + "assert np.all(old_border_mask[:, :10] == True)\n", + "assert np.all(old_border_mask[-10:,:] == True)\n", + "assert np.all(old_border_mask[:,-10:] == True)\n", + "\n", + "# Create new array with False everywhere but the outer 10 grid points\n", + "border_mask = np.zeros_like(grid_xy_reduced[0,:,:], dtype=bool)\n", + "border_mask[:10] = True\n", + "border_mask[:,:10] = True\n", + "border_mask[-10:] = True\n", + "border_mask[:,-10:] = True\n", + "np.save('data/meps_example_reduced/static/border_mask.npy', border_mask)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A few other files also needed to be copied using only the new reduced grid" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Load surface_geopotential.npy, index only values from the reduced grid, and save to new file\n", + "surface_geopotential = np.load('data/meps_example/static/surface_geopotential.npy')\n", + "surface_geopotential_reduced = surface_geopotential[x_slice, y_slice]\n", + "np.save('data/meps_example_reduced/static/surface_geopotential.npy', surface_geopotential_reduced)\n", + "\n", + "# Load pytorch file grid_features.pt\n", + "grid_features = torch.load('data/meps_example/static/grid_features.pt')\n", + "# Index only values from the reduced grid. \n", + "# First reshape from (num_grid_points_total, 4) to (num_grid_points_x, num_grid_points_y, 4), \n", + "# then index, then reshape back to new total number of grid points\n", + "print(grid_features.shape)\n", + "grid_features_new = grid_features.reshape(num_x, num_y, 4)[x_slice,y_slice,:].reshape((-1, 4))\n", + "# Save to new file\n", + "torch.save(grid_features_new, 'data/meps_example_reduced/static/grid_features.pt')\n", + "\n", + "# flux_stats.pt is just a vector of length 2, so the grid shape and variable changes does not change this file\n", + "torch.save(torch.load('data/meps_example/static/flux_stats.pt'), 'data/meps_example_reduced/static/flux_stats.pt')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "The number of variables was reduced by truncating the variable list to the first 8." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "num_vars = 8\n", + "\n", + "# Load parameter_weights.npy, truncate to first 8 variables, and save to new file\n", + "parameter_weights = np.load('data/meps_example/static/parameter_weights.npy')\n", + "parameter_weights_reduced = parameter_weights[:num_vars]\n", + "np.save('data/meps_example_reduced/static/parameter_weights.npy', parameter_weights_reduced)\n", + "\n", + "# Do the same for following 4 pytorch files\n", + "for file in ['diff_mean', 'diff_std', 'parameter_mean', 'parameter_std']:\n", + " old_file = torch.load(f'data/meps_example/static/{file}.pt')\n", + " new_file = old_file[:num_vars]\n", + " torch.save(new_file, f'data/meps_example_reduced/static/{file}.pt')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Lastly the files in each of the directories train, test, and val have to be reduced. The folders all have the same structure with files of the following types:\n", + "```\n", + "nwp_YYYYMMDDHH_mbrXXX.npy\n", + "wtr_YYYYMMDDHH.npy\n", + "nwp_toa_downwelling_shortwave_flux_YYYYMMDDHH.npy\n", + "```\n", + "with ```YYYYMMDDHH``` being some date with hours, and ```XXX``` being some 3-digit integer.\n", + "\n", + "The first type of file has x and y in dimensions 1 and 2, and variable index in dimension 3. Dimension 0 is unchanged.\n", + "The second type has has x and y in dimensions 1 and 2. Dimension 0 is unchanged.\n", + "The last type has just x and y as the only 2 dimensions.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Lastly, the file ```data_config.yaml``` is modified manually by truncating the variable units, long and short names, and setting the new grid shape. Also the unit descriptions containing ```^``` was automatically parsed using latex, and to avoid having to install latex in the GitHub CI/CD pipeline, this was changed to ```**```. \n", - "\n", - "This new config file was placed in ```data/meps_example_reduced```, and that directory was then zipped and placed in a European Weather Cloud S3 bucket." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.14" + "name": "stdout", + "output_type": "stream", + "text": [ + "(65, 268, 238, 18)\n", + "(65, 268, 238)\n" + ] } + ], + "source": [ + "print(np.load('data/meps_example/samples/train/nwp_2022040100_mbr000.npy').shape)\n", + "print(np.load('data/meps_example/samples/train/nwp_toa_downwelling_shortwave_flux_2022040112.npy').shape)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following loop goes through each file in each sample folder and indexes them according to the dimensions given by the file name." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for sample in ['train', 'test', 'val']:\n", + " files = os.listdir(f'data/meps_example/samples/{sample}')\n", + "\n", + " for f in files:\n", + " data = np.load(f'data/meps_example/samples/{sample}/{f}')\n", + " if 'mbr' in f:\n", + " data = data[:,x_slice,y_slice,:num_vars]\n", + " elif 'wtr' in f:\n", + " data = data[x_slice, y_slice]\n", + " else:\n", + " data = data[:,x_slice,y_slice]\n", + " np.save(f'data/meps_example_reduced/samples/{sample}/{f}', data)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Lastly, the file ```data_config.yaml``` is modified manually by truncating the variable units, long and short names, and setting the new grid shape. Also the unit descriptions containing ```^``` was automatically parsed using latex, and to avoid having to install latex in the GitHub CI/CD pipeline, this was changed to ```**```. \n", + "\n", + "This new config file was placed in ```data/meps_example_reduced```, and that directory was then zipped and placed in a European Weather Cloud S3 bucket." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" }, - "nbformat": 4, - "nbformat_minor": 2 + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.14" + } + }, + "nbformat": 4, + "nbformat_minor": 2 } diff --git a/uv.lock b/uv.lock index 5d125a729..7db1c0b6c 100644 --- a/uv.lock +++ b/uv.lock @@ -3083,13 +3083,11 @@ cpu = [ docs = [ { name = "linkify-it-py" }, { name = "myst-nb" }, - { name = "myst-parser" }, { name = "sphinx" }, { name = "sphinx-autoapi" }, { name = "sphinx-book-theme", version = "1.1.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, { name = "sphinx-book-theme", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu') or (extra == 'extra-10-neural-lam-cpu' and extra == 'extra-10-neural-lam-gpu-cu128') or (extra == 'extra-10-neural-lam-gpu' and extra == 'extra-10-neural-lam-gpu-cu128')" }, { name = "sphinx-copybutton" }, - { name = "sphinxcontrib-mermaid" }, { name = "sphinxext-opengraph" }, ] gpu = [ @@ -3116,7 +3114,6 @@ requires-dist = [ { name = "mlflow", specifier = ">=2.16.2" }, { name = "mllam-data-prep", specifier = ">=0.5.0" }, { name = "myst-nb", marker = "extra == 'docs'", specifier = ">=1.0.0" }, - { name = "myst-parser", marker = "extra == 'docs'", specifier = ">=2.0.0" }, { name = "networkx", specifier = ">=3.0" }, { name = "numpy", specifier = ">=1.24.2" }, { name = "nvidia-ml-py", specifier = ">=13.580.82" }, @@ -3131,7 +3128,6 @@ requires-dist = [ { name = "sphinx-autoapi", marker = "extra == 'docs'", specifier = ">=3.0.0" }, { name = "sphinx-book-theme", marker = "extra == 'docs'", specifier = ">=1.1.0" }, { name = "sphinx-copybutton", marker = "extra == 'docs'", specifier = ">=0.5.2" }, - { name = "sphinxcontrib-mermaid", marker = "extra == 'docs'", specifier = ">=0.9.2" }, { name = "sphinxext-opengraph", marker = "extra == 'docs'", specifier = ">=0.9.0" }, { name = "torch", specifier = ">=2.3.0" }, { name = "torch", marker = "extra == 'cpu'", specifier = ">=2.12,<2.13", index = "https://download.pytorch.org/whl/cpu", conflict = { package = "neural-lam", extra = "cpu" } }, @@ -6051,20 +6047,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, ] -[[package]] -name = "sphinxcontrib-mermaid" -version = "2.0.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jinja2" }, - { name = "pyyaml" }, - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/19/75/3a1cc926da8c563c58ddc124a7b3fe5ccadcae96c96e3a6f8ac3653a210a/sphinxcontrib_mermaid-2.0.2.tar.gz", hash = "sha256:f09576c78ca93fa0e3034fd9c45aaffa7c44ab449de9c43b8b8d262afe52bc66", size = 19265, upload-time = "2026-05-05T13:59:02.959Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/8d/93be7e0f7fa915a576859b3bfac7a7baa3303181c44d7db7eefbd3e8a69f/sphinxcontrib_mermaid-2.0.2-py3-none-any.whl", hash = "sha256:d862e514991279fb4816302c5cfe167d2557bf3ce7125ae0cb47dac80a0f46ce", size = 14094, upload-time = "2026-05-05T13:59:01.585Z" }, -] - [[package]] name = "sphinxcontrib-qthelp" version = "2.0.0" From 5bc53ee29ec478751d105fa4f1c112076fec7767 Mon Sep 17 00:00:00 2001 From: Mohit Lakra Date: Mon, 29 Jun 2026 14:18:05 +0530 Subject: [PATCH 12/15] changed hilam to hi_lam Co-authored-by: sadamov <45732287+sadamov@users.noreply.github.com> --- docs/getting-started/quickstart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index e51ccd1c8..4dd71f1ed 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -35,7 +35,7 @@ Now you can train a model using the graph and configuration. python -m neural_lam.train_model --config_path --model graph_lam --graph ``` -Neural-LAM supports several models like `graph_lam`, `hilam`, and `hi_lam_parallel`. +Neural-LAM supports several models like `graph_lam`, `hi_lam`, and `hi_lam_parallel`. ## Step 4: Evaluate From 589e52a87684caad102180d8ffad9947d889b43d Mon Sep 17 00:00:00 2001 From: Mohit Lakra Date: Mon, 29 Jun 2026 14:18:54 +0530 Subject: [PATCH 13/15] dropping cpu --extra Co-authored-by: sadamov <45732287+sadamov@users.noreply.github.com> --- docs/getting-started/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 930d25ef8..fb88f259c 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -14,7 +14,7 @@ ```{code-block} bash # Install docs dependencies -uv sync --extra cpu --extra docs --group dev +uv sync --extra docs --group dev # Build the documentation sphinx-build -b html docs/ docs/_build/html/ From ad849a5f3b71ffcf7a9eb58c1730165a39e5c78b Mon Sep 17 00:00:00 2001 From: sadamov Date: Sun, 12 Jul 2026 22:23:09 +0200 Subject: [PATCH 14/15] added graph_storage_spec to toctree --- docs/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.md b/docs/index.md index c829a278d..b3c494c7e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,6 +8,7 @@ Neural-LAM is a PyTorch and PyTorch Lightning framework for high-resolution weat getting-started/installation getting-started/quickstart +graph_storage_spec api/index ``` From 9f5a79f2d145f303310217ba17504cdaf6ec88b2 Mon Sep 17 00:00:00 2001 From: sadamov Date: Sun, 12 Jul 2026 22:50:17 +0200 Subject: [PATCH 15/15] moved the References into their own section --- docs/graph_storage_spec.md | 2 +- docs/index.md | 11 +++++++++-- docs/validate_graph.py | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/graph_storage_spec.md b/docs/graph_storage_spec.md index 9259c9fc0..48af14f43 100644 --- a/docs/graph_storage_spec.md +++ b/docs/graph_storage_spec.md @@ -1,7 +1,7 @@ -# Neural-LAM Graph Storage Specification +# Graph Storage Specification Version: 0.1.0 diff --git a/docs/index.md b/docs/index.md index b3c494c7e..40fefd700 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,8 +8,6 @@ Neural-LAM is a PyTorch and PyTorch Lightning framework for high-resolution weat getting-started/installation getting-started/quickstart -graph_storage_spec -api/index ``` ```{toctree} @@ -20,6 +18,15 @@ api/index notebooks/create_reduced_meps_dataset ``` +```{toctree} +:maxdepth: 2 +:caption: Reference +:hidden: + +api/index +graph_storage_spec +``` + - **[🚀 Getting Started](getting-started/installation.md)**: Installation guide and quickstart tutorial to get you up and running. - **[📚 API Reference](api/index)**: Auto-generated reference for all modules, classes, and functions. diff --git a/docs/validate_graph.py b/docs/validate_graph.py index ed6535d46..aefd0d3c7 100644 --- a/docs/validate_graph.py +++ b/docs/validate_graph.py @@ -1273,7 +1273,7 @@ def validate_graph_directory( spec_text = textwrap.dedent( f"""\ - # Neural-LAM Graph Storage Specification + # Graph Storage Specification Version: {CURRENT_GRAPH_FORMAT_SPEC_VERSION}