Skip to content

Commit 71f7ec2

Browse files
committed
Initial code release
1 parent e404570 commit 71f7ec2

File tree

102 files changed

+11465
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

102 files changed

+11465
-0
lines changed

.github/workflows/build_docs.yaml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Build documentation
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
concurrency:
9+
group: "pages"
10+
cancel-in-progress: false
11+
12+
permissions:
13+
contents: write
14+
15+
jobs:
16+
build-docs:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v2
20+
21+
- name: "Set up Python 3.10"
22+
uses: actions/setup-python@v3
23+
with:
24+
python-version: "3.10"
25+
26+
- name: Install dependencies
27+
run: |
28+
python -m pip install -e ".[dev]"
29+
30+
- name: Build
31+
run: |
32+
jupyter-book build docs
33+
34+
- name: Copy extras
35+
run: |
36+
cp -r docs/_extras/* docs/_build/html/
37+
38+
- name: Deploy to GitHub Pages
39+
uses: peaceiris/actions-gh-pages@v4
40+
with:
41+
github_token: ${{ secrets.GITHUB_TOKEN }}
42+
publish_dir: docs/_build/html

.github/workflows/ci.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
version: ["3.10", "3.11"]
16+
17+
name: Test with Python ${{ matrix.version }}
18+
steps:
19+
- uses: actions/checkout@v2
20+
- name: Set up Python ${{ matrix.version }}
21+
uses: actions/setup-python@v2
22+
with:
23+
python-version: ${{ matrix.version }}
24+
- name: Install dependencies
25+
run: |
26+
python -m pip install --upgrade pip
27+
python -m pip install --upgrade --no-cache-dir -e '.[dev]'
28+
- name: Run tests
29+
run: |
30+
pytest -v --cov=aurora --cov-report term-missing

.github/workflows/formatting.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: Check formatting
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches: [main]
7+
8+
jobs:
9+
pre-commit:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v3
13+
- uses: actions/setup-python@v3
14+
with:
15+
python-version: "3.10"
16+
17+
- uses: pre-commit/[email protected]

.github/workflows/publish.yaml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Publish to PyPI
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
permissions:
8+
contents: read
9+
id-token: write
10+
11+
jobs:
12+
deploy:
13+
runs-on: ubuntu-latest
14+
environment: publish-to-pypi
15+
16+
steps:
17+
- uses: actions/checkout@v2
18+
19+
# Make sure tags are fetched, so we can get a version.
20+
- run: |
21+
git fetch --prune --unshallow --tags
22+
23+
- name: Set up Python
24+
uses: actions/setup-python@v2
25+
with:
26+
python-version: "3.10"
27+
28+
- name: Install dependencies
29+
run: |
30+
python -m pip install --upgrade pip
31+
python -m pip install --upgrade build twine
32+
33+
- name: Build
34+
run: |
35+
python -m build
36+
37+
- name: Publish
38+
uses: pypa/gh-action-pypi-publish@release/v1

.idea/inspectionProfiles/profiles_settings.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.pre-commit-config.yaml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
ci:
2+
autoupdate_commit_msg: "chore: Update pre-commit hooks"
3+
autofix_commit_msg: "style: Pre-commit fixes"
4+
5+
default_language_version:
6+
python: python3.10
7+
8+
repos:
9+
- repo: meta
10+
hooks:
11+
- id: check-useless-excludes
12+
13+
- repo: https://github.com/abravalheri/validate-pyproject
14+
rev: v0.16
15+
hooks:
16+
- id: validate-pyproject
17+
18+
- repo: https://github.com/pre-commit/pre-commit-hooks
19+
rev: "v4.6.0"
20+
hooks:
21+
- id: check-added-large-files
22+
- id: check-case-conflict
23+
- id: check-merge-conflict
24+
- id: check-yaml
25+
- id: debug-statements
26+
- id: end-of-file-fixer
27+
- id: mixed-line-ending
28+
- id: trailing-whitespace
29+
30+
- repo: https://github.com/astral-sh/ruff-pre-commit
31+
rev: "v0.4.1"
32+
hooks:
33+
# Run the linter.
34+
- id: ruff
35+
types_or: [python, pyi, jupyter]
36+
args: ["--fix", "--show-fixes"]
37+
# Run the formatter.
38+
- id: ruff-format
39+
types_or: [python, pyi, jupyter]
40+
41+
- repo: https://github.com/pre-commit/mirrors-mypy
42+
rev: "v1.11.0"
43+
hooks:
44+
- id: mypy
45+
additional_dependencies:
46+
- "types-requests"

LICENSE.txt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
MIT License
2+
3+
Implementation of the Aurora model.
4+
5+
Copyright (c) Microsoft Corporation.
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software and associated documentation files (the "Software"), to deal
9+
in the Software without restriction, including without limitation the rights
10+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the Software is
12+
furnished to do so, subject to the following conditions:
13+
14+
The above copyright notice and this permission notice shall be included in all
15+
copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
SOFTWARE.

Makefile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.PHONY: install test docs
2+
3+
install:
4+
pip install --upgrade pip
5+
pip install -e ".[dev]"
6+
pre-commit install
7+
8+
test:
9+
pytest tests -v --cov=aurora --cov-report=term --cov-report=html
10+
11+
docs:
12+
jupyter-book build docs
13+
cp -r docs/_extras/* docs/_build/html/

aurora/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""Copyright (c) Microsoft Corporation. Licensed under the MIT license."""
2+
3+
from aurora.batch import Batch, Metadata
4+
from aurora.model.aurora import Aurora, AuroraHighRes, AuroraSmall
5+
from aurora.rollout import rollout
6+
7+
__all__ = [
8+
"Aurora",
9+
"AuroraHighRes",
10+
"AuroraSmall",
11+
"Batch",
12+
"Metadata",
13+
"rollout",
14+
]

aurora/area.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
"""Copyright (c) Microsoft Corporation. Licensed under the MIT license."""
2+
3+
import torch
4+
5+
__all__ = ["area", "compute_patch_areas", "radius_earth"]
6+
7+
8+
radius_earth = 6378137 / 1000
9+
"""float: Radius of the earth in kilometers."""
10+
11+
12+
def area(polygon: torch.Tensor) -> torch.Tensor:
13+
"""Compute the area of a polygon specified by latitudes and longitudes in degrees.
14+
15+
This function is a PyTorch port of the PyPI package `area`. In particular, it is heavily
16+
inspired by the following file:
17+
18+
https://github.com/scisco/area/blob/9d9549d6ebffcbe4bffe11b71efa2d406d1c9fe9/area/__init__.py
19+
20+
Args:
21+
polygon (:class:`torch.Tensor`): Polygon of the shape `(*b, n, 2)` where `b` is an optional
22+
multidimensional batch size, `n` is the number of points of the polygon, and 2
23+
concatenates first latitudes and then longitudes. The polygon does not have be closed.
24+
25+
Returns:
26+
:class:`torch.Tensor`: Area in square kilometers.
27+
"""
28+
# Be sure to close the loop.
29+
polygon = torch.cat((polygon, polygon[..., -1:, :]), axis=-2)
30+
31+
area = torch.zeros(polygon.shape[:-2], dtype=polygon.dtype, device=polygon.device)
32+
n = polygon.shape[-2] # Number of points of the polygon
33+
34+
rad = torch.deg2rad # Convert degrees to radians.
35+
36+
if n > 2:
37+
for i in range(n):
38+
i_lower = i
39+
i_middle = (i + 1) % n
40+
i_upper = (i + 2) % n
41+
42+
lon_lower = polygon[..., i_lower, 1]
43+
lat_middle = polygon[..., i_middle, 0]
44+
lon_upper = polygon[..., i_upper, 1]
45+
46+
area = area + (rad(lon_upper) - rad(lon_lower)) * torch.sin(rad(lat_middle))
47+
48+
area = area * radius_earth * radius_earth / 2
49+
50+
return torch.abs(area)
51+
52+
53+
def expand_matrix(matrix: torch.Tensor) -> torch.Tensor:
54+
"""Expand matrix by adding one row and one column to each side, using
55+
linear interpolation.
56+
57+
Args:
58+
matrix (:class:`torch.Tensor`): Matrix to expand.
59+
60+
Returns:
61+
:class:`torch.Tensor`: `matrix`, but with two extra rows and two extra columns.
62+
"""
63+
# Add top and bottom rows.
64+
matrix = torch.cat(
65+
(
66+
2 * matrix[0:1] - matrix[1:2],
67+
matrix,
68+
2 * matrix[-1:] - matrix[-2:-1],
69+
),
70+
dim=0,
71+
)
72+
73+
# Add left and right columns.
74+
matrix = torch.cat(
75+
(
76+
2 * matrix[:, 0:1] - matrix[:, 1:2],
77+
matrix,
78+
2 * matrix[:, -1:] - matrix[:, -2:-1],
79+
),
80+
dim=1,
81+
)
82+
83+
return matrix
84+
85+
86+
def compute_patch_areas(lat: torch.Tensor, lon: torch.Tensor) -> torch.Tensor:
87+
"""A pair of latitude and longitude matrices defines a number non-intersecting patches on the
88+
Earth. For a global grid, these patches span the entire surface of the Earth. For a local grid,
89+
the patches might span only a country or a continent. This function computes the area of every
90+
specified patch.
91+
92+
To divide the Earth into patches, the idea is to let a grid point be the _center_ of the
93+
corresponding patch. The vertices of this patch will then sit exactly inbetween the grid
94+
point and the grid points immediately diagonally and non-diagonally above, below, left, and
95+
right. For a grid point at the very top of the grid, for example, there is no immediately above
96+
grid point. In that case, we enlarge the grid by a row at the top by linearly interpolating the
97+
latitudinal progression.
98+
99+
Summary of algorithm:
100+
1. Enlarge the latitude and longitude matrices by adding one row and one column to each side.
101+
2. Calculate the patch vertices by averaging every 2x2 square in the enlarged grid. We also
102+
call these points the midpoints.
103+
3. By using the vertices of the patches, i.e. the midpoints, compute the areas of the patches.
104+
105+
Args:
106+
lat (:class:`torch.Tensor`): Latitude matrix. Must be decreasing along rows.
107+
lon (:class:`torch.Tensor`): Longitude matrix. Must be increasing along columns.
108+
109+
Returns:
110+
:class:`torch.Tensor`: Areas in square kilometer.
111+
"""
112+
if not (lat.dim() == lon.dim() == 2):
113+
raise ValueError("`lat` and `lon` must both be matrices.")
114+
if lat.shape != lat.shape:
115+
raise ValueError("`lat` and `lon` must have the same shape.")
116+
117+
# Check that the latitude matrix is decreasing in the appropriate way.
118+
if not torch.all(lat[1:] - lat[:-1] <= 0):
119+
raise ValueError("`lat` must be decreasing along rows.")
120+
121+
# Check that the longitude matrix is increasing in the appropriate way.
122+
if not torch.all(lon[:, 1:] - lon[:, :-1] >= 0):
123+
raise ValueError("`lon` must be increasing along columns.")
124+
125+
# Enlarge the latitude and longitude matrices for the midpoint computation.
126+
lat = expand_matrix(lat)
127+
lon = expand_matrix(lon)
128+
129+
# Latitudes cannot expand beyond the poles.
130+
lat = torch.clamp(lat, -90, 90)
131+
132+
# Calculate midpoints between entries in lat/lon. This is very important for symmetry of the
133+
# resulting areas.
134+
lat_midpoints = (lat[:-1, :-1] + lat[:-1, 1:] + lat[1:, :-1] + lat[1:, 1:]) / 4
135+
lon_midpoints = (lon[:-1, :-1] + lon[:-1, 1:] + lon[1:, :-1] + lon[1:, 1:]) / 4
136+
137+
# Determine squares and return the area of those squares.
138+
top_left = torch.stack((lat_midpoints[1:, :-1], lon_midpoints[1:, :-1]), dim=-1)
139+
top_right = torch.stack((lat_midpoints[1:, 1:], lon_midpoints[1:, 1:]), dim=-1)
140+
bottom_left = torch.stack((lat_midpoints[:-1, :-1], lon_midpoints[:-1, :-1]), dim=-1)
141+
bottom_right = torch.stack((lat_midpoints[:-1, 1:], lon_midpoints[:-1, 1:]), dim=-1)
142+
polygon = torch.stack((top_left, top_right, bottom_right, bottom_left), dim=-2)
143+
144+
return area(polygon)

0 commit comments

Comments
 (0)