From 752680fdc1314b57fdb77fbad8a280d73c4b08d7 Mon Sep 17 00:00:00 2001 From: Baudouin Raoult Date: Wed, 17 Apr 2024 07:57:45 +0100 Subject: [PATCH] first commit --- .github/workflows/python-publish.yml | 84 +++++++++++ .gitignore | 186 +++++++++++++++++++++++++ .pre-commit-config.yaml | 63 +++++++++ .readthedocs.yaml | 13 ++ LICENSE | 201 +++++++++++++++++++++++++++ README.md | 42 ++++++ anemoi/models/__init__.py | 9 ++ docs/Makefile | 22 +++ docs/_static/logo.png | Bin 0 -> 17128 bytes docs/_static/style.css | 48 +++++++ docs/_templates/.gitkeep | 0 docs/conf.py | 116 ++++++++++++++++ docs/index.rst | 49 +++++++ docs/installing.rst | 26 ++++ docs/requirements.txt | 9 ++ setup.py | 71 ++++++++++ tests/requirements.txt | 1 + tests/test_training.py | 14 ++ 18 files changed, 954 insertions(+) create mode 100644 .github/workflows/python-publish.yml create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 .readthedocs.yaml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 anemoi/models/__init__.py create mode 100644 docs/Makefile create mode 100644 docs/_static/logo.png create mode 100644 docs/_static/style.css create mode 100644 docs/_templates/.gitkeep create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/installing.rst create mode 100644 docs/requirements.txt create mode 100644 setup.py create mode 100644 tests/requirements.txt create mode 100644 tests/test_training.py diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 00000000..3ca7ce0c --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,84 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries + +name: Upload Python Package + +on: + + push: {} + + release: + types: [created] + +jobs: + quality: + name: Code QA + runs-on: ubuntu-latest + steps: + - run: sudo apt-get install -y pandoc # Needed by sphinx for notebooks + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - uses: pre-commit/action@v3.0.1 + + checks: + strategy: + fail-fast: false + matrix: + platform: ["ubuntu-latest", "macos-latest"] + python-version: ["3.10"] + + name: Python ${{ matrix.python-version }} on ${{ matrix.platform }} + runs-on: ${{ matrix.platform }} + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install + run: | + pip install pytest + pip install -e .[all] + pip install -r tests/requirements.txt + pip freeze + + - name: Tests + run: pytest + + deploy: + + if: ${{ github.event_name == 'release' }} + runs-on: ubuntu-latest + needs: [checks, quality] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.10' + + - name: Check that tag version matches code version + run: | + tag=${GITHUB_REF#refs/tags/} + version=$(python setup.py --version) + echo 'tag='$tag + echo "version file="$version + test "$tag" == "$version" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: | + python setup.py sdist + twine upload dist/* diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..fdc24f30 --- /dev/null +++ b/.gitignore @@ -0,0 +1,186 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +*.grib +*.onnx +*.ckpt +*.swp +*.npy +*.download +? +?.* +foo +bar +*.grib +*.nc +*.npz +*.json +*.zarr/ +~$images.pptx +test.py +cutout.png +*.out + +_build/ +? +?.* +~* +*.sync diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..7b01a875 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,63 @@ +repos: + +# Empty notebookds +- repo: local + hooks: + - id: clear-notebooks-output + name: clear-notebooks-output + files: tools/.*\.ipynb$ + stages: [commit] + language: python + entry: jupyter nbconvert --ClearOutputPreprocessor.enabled=True --inplace + additional_dependencies: [jupyter] + + +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-yaml # Check YAML files for syntax errors only + args: [--unsafe, --allow-multiple-documents] + - id: debug-statements # Check for debugger imports and py37+ breakpoint() + - id: end-of-file-fixer # Ensure files end in a newline + - id: trailing-whitespace # Trailing whitespace checker + - id: no-commit-to-branch # Prevent committing to main / master + - id: check-added-large-files # Check for large files added to git + - id: check-merge-conflict # Check for files that contain merge conflict + +- repo: https://github.com/psf/black-pre-commit-mirror + rev: 24.1.1 + hooks: + - id: black + args: [--line-length=120] + +- repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + args: + - -l 120 + - --force-single-line-imports + - --profile black + + +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.3.0 + hooks: + - id: ruff + exclude: '(dev/.*|.*_)\.py$' + args: + - --line-length=120 + - --fix + - --exit-non-zero-on-fix + - --preview + +- repo: https://github.com/sphinx-contrib/sphinx-lint + rev: v0.9.1 + hooks: + - id: sphinx-lint + +# For now, we use it. But it does not support a lot of sphinx features +- repo: https://github.com/dzhu/rstfmt + rev: v0.0.14 + hooks: + - id: rstfmt diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..45958d1f --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,13 @@ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.10" + +sphinx: + configuration: docs/conf.py + +python: + install: + - requirements: docs/requirements.txt diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 00000000..b20a7be4 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# anemoi-utils + +**DISCLAIMER** +This project is **BETA** and will be **Experimental** for the foreseeable future. +Interfaces and functionality are likely to change, and the project itself may be scrapped. +**DO NOT** use this software in any project/software that is operational. + +Miscellanous tools for training data-driven weather forecasts. + +## Documentation + +The documentation can be found at https://anemoi-training.readthedocs.io/. + +## Install + +Install via `pip` with: + +``` +$ pip install anemoi-training +``` + +## License + +``` +Copyright 2022, European Centre for Medium Range Weather Forecasts. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +In applying this licence, ECMWF does not waive the privileges and immunities +granted to it by virtue of its status as an intergovernmental organisation +nor does it submit to any jurisdiction. +``` diff --git a/anemoi/models/__init__.py b/anemoi/models/__init__.py new file mode 100644 index 00000000..6c631a72 --- /dev/null +++ b/anemoi/models/__init__.py @@ -0,0 +1,9 @@ +# (C) Copyright 2024 European Centre for Medium-Range Weather Forecasts. +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. + + +__version__ = "0.0.1" diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..6c0762a4 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,22 @@ +#!/usr/bin/env make -f + +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_static/logo.png b/docs/_static/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f78572e73c02e791661a509be50fb9e90f0d3a56 GIT binary patch literal 17128 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 T1l9fP=$w?8ylAbkLD2sLIU~(~ literal 0 HcmV?d00001 diff --git a/docs/_static/style.css b/docs/_static/style.css new file mode 100644 index 00000000..9a2b3af1 --- /dev/null +++ b/docs/_static/style.css @@ -0,0 +1,48 @@ +.wy-side-nav-search { + background-color: #f7f7f7; +} + +/*There is a clash between xarray notebook styles and readthedoc*/ + +.rst-content dl.xr-attrs dt { + all: revert; + font-size: 95%; + white-space: nowrap; +} + +.rst-content dl.xr-attrs dd { + font-size: 95%; +} + +.xr-wrap { + font-size: 85%; +} + +.wy-table-responsive table td, .wy-table-responsive table th { + white-space: inherit; +} + +/* +.wy-table-responsive table td, +.wy-table-responsive table th { + white-space: normal !important; + vertical-align: top !important; +} + +.wy-table-responsive { + margin-bottom: 24px; + max-width: 100%; + overflow: visible; +} */ + +/* Hide notebooks warnings */ +.nboutput .stderr { + display: none; +} + +/* +Set logo size +*/ +.wy-side-nav-search .wy-dropdown > a img.logo, .wy-side-nav-search > a img.logo { + width: 200px; +} diff --git a/docs/_templates/.gitkeep b/docs/_templates/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..208e3d06 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,116 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import datetime +import os +import sys + +sys.path.insert(0, os.path.abspath("..")) + + +read_the_docs_build = os.environ.get("READTHEDOCS", None) == "True" + +# top = os.path.realpath(os.path.dirname(os.path.dirname(__file__))) +# sys.path.insert(0, top) + + +source_suffix = ".rst" +master_doc = "index" +pygments_style = "sphinx" +html_theme_options = {"logo_only": True} +html_logo = "_static/logo.png" + + +# -- Project information ----------------------------------------------------- + +project = "Anemoi" + +author = "ECMWF" + +year = datetime.datetime.now().year +if year == 2024: + years = "2024" +else: + years = "2024-%s" % (year,) + +copyright = "%s, ECMWF" % (years,) + + +release = "0.1.0" + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.todo", + "sphinx_rtd_theme", + "nbsphinx", + "sphinx.ext.graphviz", + "sphinx.ext.intersphinx", + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", +] + +# Add any paths that contain templates here, relative to this directory. +# templates_path = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "'**.ipynb_checkpoints'"] + +intersphinx_mapping = { + "python": ("https://python.readthedocs.io/en/latest", None), + "anemoi-utils": ( + "https://anemoi-utils.readthedocs.io/en/latest/", + ("../../anemoi-utils/docs/_build/html/objects.inv", None), + ), + "anemoi-datasets": ( + "https://anemoi-datasets.readthedocs.io/en/latest/", + ("../../anemoi-datasets/docs/_build/html/objects.inv", None), + ), + "anemoi-models": ( + "https://anemoi-models.readthedocs.io/en/latest/", + ("../../anemoi-models/docs/_build/html/objects.inv", None), + ), + "anemoi-training": ( + "https://anemoi-training.readthedocs.io/en/latest/", + ("../../anemoi-training/docs/_build/html/objects.inv", None), + ), + "anemoi-inference": ( + "https://anemoi-inference.readthedocs.io/en/latest/", + ("../../anemoi-inference/docs/_build/html/objects.inv", None), + ), +} + + +# https://www.notion.so/Deepnote-Launch-Buttons-63c642a5e875463495ed2341e83a4b2a + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "sphinx_rtd_theme" + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] +html_css_files = ["style.css"] + + +todo_include_todos = not read_the_docs_build diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..d9b5de2e --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,49 @@ +.. _anemoi-utils: + +.. _index-page: + +#################################### + Welcome to Anemoi's documentation! +#################################### + +.. warning:: + + This documentation is work in progress. + +*Anemoi* is a framework for developing machine learning weather +forecasting models. It comprises of components or packages for preparing +training datasets, conducting ML model training and a registry for +datasets and trained models. *Anemoi* provides tools for operational +inference, including interfacing to verification software. As a +framework it seeks to handle many of the complexities that +meteorological organisations will share, allowing them to easily train +models from existing recipes but with their own data. + +This package provides a series of utility functions for used by the rest +of the *Anemoi* packages. + +- :doc:`installing` + +.. toctree:: + :maxdepth: 1 + :hidden: + + installing + +***************** + Anemoi packages +***************** + +- :ref:`anemoi-utils ` +- :ref:`anemoi-datasets ` +- :ref:`anemoi-models ` +- :ref:`anemoi-training ` +- :ref:`anemoi-inference ` + +********* + License +********* + +*Anemoi* is available under the open source `Apache License`__. + +.. __: http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/docs/installing.rst b/docs/installing.rst new file mode 100644 index 00000000..7c703b3a --- /dev/null +++ b/docs/installing.rst @@ -0,0 +1,26 @@ +############ + Installing +############ + +To install the package, you can use the following command: + +.. code:: bash + + pip install anemoi-training + +************** + Contributing +************** + +.. code:: bash + + git clone ... + cd anemoi-training + pip install .[dev] + pip install -r docs/requirements.txt + +You may also have to install pandoc on MacOS: + +.. code:: bash + + brew install pandoc diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..c85e9f14 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,9 @@ +# These are the requirements for readthedoc +sphinx +sphinx_rtd_theme +nbsphinx + +# Also requires `brew install pandoc` on Mac +pandoc + +rstfmt diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..4a4f1465 --- /dev/null +++ b/setup.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# (C) Copyright 2024 ECMWF. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. +# + + +import io +import os + +import setuptools + + +def read(fname): + file_path = os.path.join(os.path.dirname(__file__), fname) + return io.open(file_path, encoding="utf-8").read() + + +version = None +for line in read("anemoi/training/__init__.py").split("\n"): + if line.startswith("__version__"): + version = line.split("=")[-1].strip()[1:-1] + + +assert version + + +install_requires = [] + + +doc_requires = ["sphinx", "sphinx_rtd_theme", "nbsphinx", "pandoc"] + +all_requires = install_requires +dev_requires = doc_requires + all_requires + +setuptools.setup( + name="anemoi-training", + version=version, + description="A package to hold various functions to support training of ML models on ECMWF data.", + long_description=read("README.md"), + long_description_content_type="text/markdown", + author="European Centre for Medium-Range Weather Forecasts (ECMWF)", + author_email="software.support@ecmwf.int", + license="Apache License Version 2.0", + url="https://github.com/ecmwf/anemoi-training", + packages=setuptools.find_namespace_packages(include=["anemoi.*"]), + include_package_data=True, + install_requires=install_requires, + extras_require={ + "dev": dev_requires, + "all": all_requires, + }, + zip_safe=True, + keywords="tool", + classifiers=[ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Operating System :: OS Independent", + ], +) diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 00000000..fd69f0b5 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1 @@ +# Empty for now diff --git a/tests/test_training.py b/tests/test_training.py new file mode 100644 index 00000000..7a025cf7 --- /dev/null +++ b/tests/test_training.py @@ -0,0 +1,14 @@ +# (C) Copyright 2024 European Centre for Medium-Range Weather Forecasts. +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. + + +def test_training(): + pass + + +if __name__ == "__main__": + test_training()