Upload folder using huggingface_hub
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +3 -0
- .gitignore +8 -0
- README.md +204 -0
- conf/config.yaml +14 -0
- configuration.json +1 -0
- model/__init__.py +1 -0
- model/aardvark_adapter.py +153 -0
- model/sample_dataset.py +76 -0
- official-src/LICENSE +121 -0
- official-src/README.md +71 -0
- official-src/aardvark/architectures.py +37 -0
- official-src/aardvark/data_shapes.py +31 -0
- official-src/aardvark/e2e_model.py +241 -0
- official-src/aardvark/e2e_train.py +242 -0
- official-src/aardvark/finetune.py +431 -0
- official-src/aardvark/generate_initial_condition_single.py +148 -0
- official-src/aardvark/loader.py +1825 -0
- official-src/aardvark/loader_utils_new.py +88 -0
- official-src/aardvark/loss_functions.py +166 -0
- official-src/aardvark/misc_downscaling_functionality.py +273 -0
- official-src/aardvark/models.py +390 -0
- official-src/aardvark/set_convs.py +101 -0
- official-src/aardvark/train_module.py +315 -0
- official-src/aardvark/trainer.py +602 -0
- official-src/aardvark/unet_wrap_padding.py +310 -0
- official-src/aardvark/utils.py +23 -0
- official-src/aardvark/vit.py +298 -0
- official-src/data/grid_lon_lat/era5_x_1.npy +3 -0
- official-src/data/grid_lon_lat/era5_y_1.npy +3 -0
- official-src/data/norm_factors/mean_4u_1.npy +3 -0
- official-src/data/norm_factors/mean_diff_4u_1.npy +3 -0
- official-src/data/norm_factors/mean_hadisd_tas.npy +3 -0
- official-src/data/norm_factors/mean_hadisd_ws.npy +3 -0
- official-src/data/norm_factors/std_4u_1.npy +3 -0
- official-src/data/norm_factors/std_diff_4u_1.npy +3 -0
- official-src/data/norm_factors/std_hadisd_tas.npy +3 -0
- official-src/data/norm_factors/std_hadisd_ws.npy +3 -0
- official-src/data/sample_data_final.pkl +3 -0
- official-src/environment.yml +328 -0
- official-src/notebooks/data_demo.ipynb +386 -0
- official-src/notebooks/e2e_finetune_demo.ipynb +0 -0
- official-src/notebooks/forecast_demo.ipynb +0 -0
- official-src/training/downscaling_run_pipeline.sh +23 -0
- official-src/training/finetune.sh +9 -0
- official-src/training/test/config.pkl +3 -0
- official-src/training/train_decoder.sh +43 -0
- official-src/training/train_e2e.sh +21 -0
- official-src/training/train_encoder.sh +16 -0
- official-src/training/train_processor.sh +17 -0
- scripts/inference.py +57 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
weights/trained_model/decoder/tas/lt_1/epoch_18 filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
weights/trained_model/encoder/epoch_96 filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
weights/trained_model/processor/forecast_1/epoch_0 filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.py[cod]
|
| 3 |
+
*.pdf
|
| 4 |
+
result/
|
| 5 |
+
weight/*
|
| 6 |
+
!weight/.gitkeep
|
| 7 |
+
.pytest_cache/
|
| 8 |
+
.ipynb_checkpoints/
|
README.md
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: other
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
- zh
|
| 6 |
+
tags:
|
| 7 |
+
- OneScience
|
| 8 |
+
- Earth Science
|
| 9 |
+
- Weather Forecasting
|
| 10 |
+
- Medium-Range Weather Forecasting
|
| 11 |
+
frameworks: PyTorch
|
| 12 |
+
datasets:
|
| 13 |
+
- OneScience/Aardvark-Weather
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
<p align="center">
|
| 17 |
+
<strong><span style="font-size: 30px;">Aardvark Weather</span></strong>
|
| 18 |
+
</p>
|
| 19 |
+
|
| 20 |
+
# Model Overview
|
| 21 |
+
|
| 22 |
+
Aardvark Weather is an end-to-end multimodal weather forecasting model that generates global gridded forecasts and station-level predictions through an observation encoder, a global forecast processor, and a station decoder.
|
| 23 |
+
|
| 24 |
+
Paper: *End-to-end data-driven weather prediction*
|
| 25 |
+
|
| 26 |
+
https://www.nature.com/articles/s41586-025-08897-0
|
| 27 |
+
|
| 28 |
+
# Model Description
|
| 29 |
+
|
| 30 |
+
This model package reuses the official code and weights to provide the following run pipeline:
|
| 31 |
+
|
| 32 |
+
```text
|
| 33 |
+
Official Multimodal Sample
|
| 34 |
+
-> Encoder
|
| 35 |
+
-> Day-1 Processor
|
| 36 |
+
-> TAS Decoder
|
| 37 |
+
-> 1-day global forecast and station 2-meter temperature
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
# Use Cases
|
| 41 |
+
|
| 42 |
+
| Scenario | Description |
|
| 43 |
+
| :---: | :--- |
|
| 44 |
+
| Official Model Verification | Inspect official samples, configuration, and checkpoints. |
|
| 45 |
+
| Global Weather Forecasting | Output a global 1.5° gridded state of 24 variables. |
|
| 46 |
+
| Station Temperature Forecasting | Output 2-meter temperature at 8,719 stations. |
|
| 47 |
+
|
| 48 |
+
# Usage
|
| 49 |
+
|
| 50 |
+
## 1. OneCode
|
| 51 |
+
|
| 52 |
+
[Click to experience intelligent one-click AI4S programming](https://web-2069360198568017922-iaaj.ksai.scnet.cn:58043/home)
|
| 53 |
+
|
| 54 |
+
## 2. Manual Installation & Usage
|
| 55 |
+
|
| 56 |
+
**Hardware Requirements**
|
| 57 |
+
|
| 58 |
+
- Inference with the official weights requires an NVIDIA GPU.
|
| 59 |
+
- CPU can be used for resource and checkpoint inspection; running full inference on CPU is not recommended.
|
| 60 |
+
|
| 61 |
+
### Download the Model Package
|
| 62 |
+
|
| 63 |
+
```bash
|
| 64 |
+
hf download --model OneScience-Group/Aardvark-Weather --local-dir ./Aardvark-Weather
|
| 65 |
+
cd Aardvark-Weather
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
### Set Up the Runtime Environment
|
| 69 |
+
|
| 70 |
+
**DCU Environment**
|
| 71 |
+
|
| 72 |
+
```bash
|
| 73 |
+
conda create -n onescience311 python=3.11 -y
|
| 74 |
+
conda activate onescience311
|
| 75 |
+
pip install onescience[earth-dcu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
**GPU Environment**
|
| 79 |
+
|
| 80 |
+
```bash
|
| 81 |
+
conda create -n onescience311 python=3.11 -y libstdcxx-ng=12 libgcc-ng=12 gcc_linux-64=12 gxx_linux-64=12
|
| 82 |
+
conda activate onescience311
|
| 83 |
+
pip install onescience[earth-gpu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
If the environment lacks Aardvark dependencies, refer to `official-src/environment.yml` for additional installation. The current adaptation is compatible with the `Block` parameter of the newer `timm` package.
|
| 87 |
+
|
| 88 |
+
### Data & Weights
|
| 89 |
+
|
| 90 |
+
The model package already includes the resources required for 1-day temperature inference:
|
| 91 |
+
|
| 92 |
+
```text
|
| 93 |
+
weights/sample_data/sample_data_final.pkl
|
| 94 |
+
weights/trained_model/encoder/epoch_96
|
| 95 |
+
weights/trained_model/processor/forecast_1/epoch_0
|
| 96 |
+
weights/trained_model/decoder/tas/lt_1/epoch_18
|
| 97 |
+
official-src/data/grid_lon_lat/
|
| 98 |
+
official-src/data/norm_factors/
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
To re-download from source:
|
| 102 |
+
|
| 103 |
+
```text
|
| 104 |
+
Official Code: https://github.com/anna-allen/aardvark-weather-public
|
| 105 |
+
Official Weights: https://huggingface.co/datasets/av555/aardvark-weather
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
### Training
|
| 109 |
+
|
| 110 |
+
The training entry point provides a complete pipeline with epochs, validation, early stopping, learning rate scheduling, best/latest checkpointing, and resumption. By default, the Encoder and Day-1 Processor are frozen and only the TAS Decoder is trained:
|
| 111 |
+
|
| 112 |
+
```bash
|
| 113 |
+
python scripts/train.py
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
Paper-style end-to-end joint fine-tuning:
|
| 117 |
+
|
| 118 |
+
```bash
|
| 119 |
+
python scripts/train.py --train-modules all --epochs 10 --train-steps 100
|
| 120 |
+
```
|
| 121 |
+
|
| 122 |
+
Resuming training:
|
| 123 |
+
|
| 124 |
+
```bash
|
| 125 |
+
python scripts/train.py --resume weight/training/last.pth
|
| 126 |
+
```
|
| 127 |
+
|
| 128 |
+
The default configuration resides in `conf/config.yaml`. `--data` can point to an official-schema pickle file or a directory containing multiple `.pkl` files; multiple files are partitioned into train and validation sets in a deterministic manner. `--batch-size` concatenates multiple tasks along the existing batch dimension of the official task. When only the single official sample is included, training and validation reuse the same task — this allows end-to-end validation of the training software pipeline but does not constitute an independent validation set, nor can it provide the data diversity required to reproduce paper-level accuracy.
|
| 129 |
+
|
| 130 |
+
Training artifacts:
|
| 131 |
+
|
| 132 |
+
```text
|
| 133 |
+
weight/training/best.pth
|
| 134 |
+
weight/training/last.pth
|
| 135 |
+
weight/training/history.json
|
| 136 |
+
weight/training/train.json
|
| 137 |
+
```
|
| 138 |
+
|
| 139 |
+
### Inference
|
| 140 |
+
|
| 141 |
+
```bash
|
| 142 |
+
python scripts/inference.py
|
| 143 |
+
```
|
| 144 |
+
|
| 145 |
+
Using weights obtained from training:
|
| 146 |
+
|
| 147 |
+
```bash
|
| 148 |
+
python scripts/inference.py --checkpoint weight/training/best.pth
|
| 149 |
+
```
|
| 150 |
+
|
| 151 |
+
Inference loads the official sample and the Day-1 `tas` weights by default. Results are saved to:
|
| 152 |
+
|
| 153 |
+
```text
|
| 154 |
+
result/inference_one_day.json
|
| 155 |
+
result/prediction.pt
|
| 156 |
+
result/target.pt
|
| 157 |
+
```
|
| 158 |
+
|
| 159 |
+
### Result Inspection
|
| 160 |
+
|
| 161 |
+
```bash
|
| 162 |
+
python scripts/result.py
|
| 163 |
+
```
|
| 164 |
+
|
| 165 |
+
Verified output shapes:
|
| 166 |
+
|
| 167 |
+
```text
|
| 168 |
+
initial_state: [1, 121, 240, 24]
|
| 169 |
+
global_forecast: [1, 121, 240, 24]
|
| 170 |
+
station_tas: [1, 8719]
|
| 171 |
+
```
|
| 172 |
+
|
| 173 |
+
Current results constitute a connectivity/end-to-end verification and do not reproduce the paper's RMSE/MAE metrics.
|
| 174 |
+
|
| 175 |
+
The result script additionally produces `result/metrics.json` and `result/comparison.png`. The `normalized_mae` and `normalized_rmse` are computed in the normalized space of the official sample and should not be directly compared to the paper's physical-unit metrics.
|
| 176 |
+
|
| 177 |
+
### Paper vs. Current Implementation I/O
|
| 178 |
+
|
| 179 |
+
| Item | Paper | Current Package |
|
| 180 |
+
| --- | --- | --- |
|
| 181 |
+
| Input | Multimodal satellite, station, ship, and radiosonde observations | Bundled official sample pickle with a field structure consistent with the official Encoder |
|
| 182 |
+
| Global State | `24 × 121 × 240`, 1.5° | Day-1 supported; output `[1,121,240,24]` |
|
| 183 |
+
| Station Output | 2 m temperature and 10 m wind, up to Day-10 | Day-1 TAS only; `[1,8719]` |
|
| 184 |
+
| Training | Staged pre-training followed by ~25,000 steps of end-to-end fine-tuning | Configurable full training loop; supports Decoder-only or full-model joint fine-tuning |
|
| 185 |
+
| Evaluation | Grid-point weighted RMSE and station MAE in physical units | MAE/RMSE in normalized sample space |
|
| 186 |
+
|
| 187 |
+
All commands should be run from the project root; `scripts/inference.py --root` converts to an absolute path. The official model internally depends on CUDA, so CPU is currently unavailable for inference. Training data must adhere to the official multimodal task dictionary schema; the current directory does not synthesize satellite or station observations — the bundled official sample serves as the default training-pipeline validator. The model package retains only official resources under `weights/` and does not carry local training artifacts under `weight/` or generated outputs under `result/`. Paper-level training still requires preparing observation data spanning the full date range and converting it into the same `.pkl` task contract.
|
| 188 |
+
|
| 189 |
+
### Real Data
|
| 190 |
+
|
| 191 |
+
Using real dates requires preparing ASCAT, AMSU-A/B, HIRS, IASI, GridSat, HadISD, ICOADS, IGRA, ERA5, topography, climatology, and their corresponding normalization statistics.
|
| 192 |
+
|
| 193 |
+
# OneScience Official Information
|
| 194 |
+
|
| 195 |
+
| Platform | OneScience Main Repository | Skills Repository |
|
| 196 |
+
| --- | --- | --- |
|
| 197 |
+
| Gitee | https://gitee.com/onescience-ai/onescience | https://gitee.com/onescience-ai/oneskills |
|
| 198 |
+
| GitHub | https://github.com/onescience-ai/OneScience | https://github.com/onescience-ai/oneskills |
|
| 199 |
+
|
| 200 |
+
# Citation & License
|
| 201 |
+
|
| 202 |
+
- Official Code: https://github.com/anna-allen/aardvark-weather-public
|
| 203 |
+
- This directory is an independent adaptation of the official Aardvark Weather model.
|
| 204 |
+
- Code, weights, and data are subject to their respective official licenses and data terms.
|
conf/config.yaml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
training:
|
| 2 |
+
data: weights/sample_data/sample_data_final.pkl
|
| 3 |
+
output_dir: weight/training
|
| 4 |
+
epochs: 10
|
| 5 |
+
train_steps: 8
|
| 6 |
+
validation_steps: 2
|
| 7 |
+
validation_fraction: 0.2
|
| 8 |
+
batch_size: 1
|
| 9 |
+
learning_rate: 1.0e-5
|
| 10 |
+
weight_decay: 1.0e-6
|
| 11 |
+
gradient_clip: 1.0
|
| 12 |
+
patience: 3
|
| 13 |
+
seed: 2026
|
| 14 |
+
train_modules: decoder
|
configuration.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"framework":"Pytorch","task":"weather-forecasting","implementation":"official-one-day-tas-adapter"}
|
model/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""Aardvark-local adapters around the pinned official implementation."""
|
model/aardvark_adapter.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Thin, model-local wrapper for the official Aardvark Weather model."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import hashlib
|
| 6 |
+
import importlib
|
| 7 |
+
import os
|
| 8 |
+
import pickle
|
| 9 |
+
import sys
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
from typing import Any
|
| 12 |
+
|
| 13 |
+
import numpy as np
|
| 14 |
+
import torch
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
TOP_LEVEL_KEYS = {"assimilation", "forecast", "downscaling", "y_target"}
|
| 18 |
+
REQUIRED_ASSIMILATION_KEYS = {
|
| 19 |
+
"x_context_hadisd_current", "y_context_hadisd_current", "climatology_current",
|
| 20 |
+
"sat_x_current", "sat_current", "icoads_x_current", "icoads_current",
|
| 21 |
+
"igra_x_current", "igra_current", "amsua_current", "amsua_x_current",
|
| 22 |
+
"amsub_current", "amsub_x_current", "iasi_current", "iasi_x_current",
|
| 23 |
+
"ascat_current", "ascat_x_current", "hirs_current", "hirs_x_current",
|
| 24 |
+
"y_target_current", "era5_x_current",
|
| 25 |
+
"era5_elev_current", "era5_lonlat_current", "aux_time_current", "lt",
|
| 26 |
+
"y_target",
|
| 27 |
+
}
|
| 28 |
+
REQUIRED_FORECAST_KEYS = {"y_context", "y_target", "lt"}
|
| 29 |
+
REQUIRED_DOWNSCALING_KEYS = {
|
| 30 |
+
"x_target", "alt_target", "y_target", "y_context", "x_context", "aux_time", "lt",
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def sha256(path: Path) -> str:
|
| 35 |
+
digest = hashlib.sha256()
|
| 36 |
+
with path.open("rb") as handle:
|
| 37 |
+
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
| 38 |
+
digest.update(chunk)
|
| 39 |
+
return digest.hexdigest()
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def validate_sample(sample_path: Path) -> dict[str, Any]:
|
| 43 |
+
with sample_path.open("rb") as handle:
|
| 44 |
+
sample = pickle.load(handle)
|
| 45 |
+
if not isinstance(sample, dict) or set(sample) != TOP_LEVEL_KEYS:
|
| 46 |
+
raise ValueError(f"sample top-level keys mismatch: {list(sample) if isinstance(sample, dict) else type(sample)}")
|
| 47 |
+
expected = {
|
| 48 |
+
"assimilation": REQUIRED_ASSIMILATION_KEYS,
|
| 49 |
+
"forecast": REQUIRED_FORECAST_KEYS,
|
| 50 |
+
"downscaling": REQUIRED_DOWNSCALING_KEYS,
|
| 51 |
+
}
|
| 52 |
+
for name, keys in expected.items():
|
| 53 |
+
if not isinstance(sample[name], dict) or set(sample[name]) != keys:
|
| 54 |
+
raise ValueError(f"sample {name} keys mismatch: {list(sample[name])}")
|
| 55 |
+
if not isinstance(sample["y_target"], torch.Tensor) or sample["y_target"].ndim != 2:
|
| 56 |
+
raise ValueError("sample y_target must be a rank-2 torch.Tensor")
|
| 57 |
+
return {
|
| 58 |
+
"top_level_keys": sorted(sample),
|
| 59 |
+
"nested_keys": {name: sorted(value) for name, value in expected.items()},
|
| 60 |
+
"y_target_shape": list(sample["y_target"].shape),
|
| 61 |
+
"nan_counts": {
|
| 62 |
+
name: int(value.isnan().sum())
|
| 63 |
+
for name, value in sample["downscaling"].items()
|
| 64 |
+
if isinstance(value, torch.Tensor) and value.is_floating_point()
|
| 65 |
+
},
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def validate_checkpoint(path: Path) -> dict[str, Any]:
|
| 70 |
+
checkpoint = torch.load(path, map_location="cpu")
|
| 71 |
+
if not isinstance(checkpoint, dict) or "model_state_dict" not in checkpoint:
|
| 72 |
+
raise ValueError(f"checkpoint contract mismatch: {path}")
|
| 73 |
+
state = checkpoint["model_state_dict"]
|
| 74 |
+
if not isinstance(state, dict) or not state:
|
| 75 |
+
raise ValueError(f"empty model_state_dict: {path}")
|
| 76 |
+
return {"path": str(path), "key_count": len(state), "has_model_state_dict": True}
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def load_sample(sample_path: Path) -> dict[str, Any]:
|
| 80 |
+
with sample_path.open("rb") as handle:
|
| 81 |
+
return pickle.load(handle)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def build_one_day_model(weights_root: Path, official_root: Path, device: str):
|
| 85 |
+
encoder = weights_root / "trained_model/encoder"
|
| 86 |
+
processor = weights_root / "trained_model/processor"
|
| 87 |
+
decoder = weights_root / "trained_model/decoder/tas"
|
| 88 |
+
sys.path.insert(0, str(official_root / "aardvark"))
|
| 89 |
+
_install_timm_compatibility()
|
| 90 |
+
official_e2e = importlib.import_module("e2e_model")
|
| 91 |
+
caller_dir = Path.cwd()
|
| 92 |
+
try:
|
| 93 |
+
os.chdir(official_root / "aardvark")
|
| 94 |
+
model = official_e2e.ConvCNPWeatherE2E(
|
| 95 |
+
device=device,
|
| 96 |
+
lead_time=1,
|
| 97 |
+
se_model_path=str(encoder),
|
| 98 |
+
forecast_model_path=str(processor),
|
| 99 |
+
sf_model_path=str(decoder) + "/",
|
| 100 |
+
return_gridded=True,
|
| 101 |
+
aux_data_path=str(official_root / "data") + "/",
|
| 102 |
+
)
|
| 103 |
+
finally:
|
| 104 |
+
os.chdir(caller_dir)
|
| 105 |
+
return model
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def run_one_day(sample_path: Path, weights_root: Path, official_root: Path, device: str) -> dict[str, Any]:
|
| 109 |
+
sample_report = validate_sample(sample_path)
|
| 110 |
+
encoder = weights_root / "trained_model/encoder"
|
| 111 |
+
processor = weights_root / "trained_model/processor"
|
| 112 |
+
decoder = weights_root / "trained_model/decoder/tas"
|
| 113 |
+
checkpoint_report = [
|
| 114 |
+
validate_checkpoint(encoder / "epoch_96"),
|
| 115 |
+
validate_checkpoint(processor / "forecast_1/epoch_0"),
|
| 116 |
+
validate_checkpoint(decoder / "lt_1/epoch_18"),
|
| 117 |
+
]
|
| 118 |
+
sample = load_sample(sample_path)
|
| 119 |
+
model = build_one_day_model(weights_root, official_root, device)
|
| 120 |
+
model.eval()
|
| 121 |
+
with torch.inference_mode():
|
| 122 |
+
station, global_forecast, initial_state = model(sample)
|
| 123 |
+
for name, tensor in (("station_tas", station), ("global_forecast", global_forecast), ("initial_state", initial_state)):
|
| 124 |
+
if not isinstance(tensor, torch.Tensor) or not bool(torch.isfinite(tensor).all()):
|
| 125 |
+
raise ValueError(f"{name} contains non-finite values")
|
| 126 |
+
return {
|
| 127 |
+
"sample": sample_report,
|
| 128 |
+
"checkpoints": checkpoint_report,
|
| 129 |
+
"device": device,
|
| 130 |
+
"lead_time_days": 1,
|
| 131 |
+
"station_tas_shape": list(station.shape),
|
| 132 |
+
"global_forecast_shape": list(global_forecast.shape),
|
| 133 |
+
"initial_state_shape": list(initial_state.shape),
|
| 134 |
+
"finite_outputs": True,
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def _install_timm_compatibility() -> None:
|
| 139 |
+
"""Bridge the old timm 0.6 Block constructor used by the official code."""
|
| 140 |
+
import timm.models.vision_transformer as vision_transformer
|
| 141 |
+
|
| 142 |
+
original = vision_transformer.Block
|
| 143 |
+
if getattr(original, "_aardvark_compat", False):
|
| 144 |
+
return
|
| 145 |
+
|
| 146 |
+
class AardvarkBlock(original):
|
| 147 |
+
_aardvark_compat = True
|
| 148 |
+
|
| 149 |
+
def __init__(self, *args: Any, drop: float = 0.0, **kwargs: Any) -> None:
|
| 150 |
+
kwargs.setdefault("proj_drop", drop)
|
| 151 |
+
super().__init__(*args, **kwargs)
|
| 152 |
+
|
| 153 |
+
vision_transformer.Block = AardvarkBlock
|
model/sample_dataset.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Datasets for training Aardvark from official-schema pickle tasks."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import copy
|
| 6 |
+
import pickle
|
| 7 |
+
import random
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
from typing import Any
|
| 10 |
+
|
| 11 |
+
import numpy as np
|
| 12 |
+
import torch
|
| 13 |
+
from torch.utils.data import Dataset
|
| 14 |
+
|
| 15 |
+
from .aardvark_adapter import validate_sample
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def discover_samples(path: Path) -> list[Path]:
|
| 19 |
+
path = path.resolve()
|
| 20 |
+
samples = sorted(path.glob("*.pkl")) if path.is_dir() else [path]
|
| 21 |
+
if not samples or any(not sample.is_file() for sample in samples):
|
| 22 |
+
raise FileNotFoundError(f"No Aardvark sample pickle found at {path}")
|
| 23 |
+
for sample in samples:
|
| 24 |
+
validate_sample(sample)
|
| 25 |
+
return samples
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def split_samples(samples: list[Path], validation_fraction: float, seed: int) -> tuple[list[Path], list[Path]]:
|
| 29 |
+
if not 0.0 < validation_fraction < 1.0:
|
| 30 |
+
raise ValueError("validation_fraction must be between 0 and 1")
|
| 31 |
+
if len(samples) == 1:
|
| 32 |
+
return samples, samples
|
| 33 |
+
shuffled = samples.copy()
|
| 34 |
+
random.Random(seed).shuffle(shuffled)
|
| 35 |
+
validation_count = max(1, round(len(shuffled) * validation_fraction))
|
| 36 |
+
validation_count = min(validation_count, len(shuffled) - 1)
|
| 37 |
+
return shuffled[validation_count:], shuffled[:validation_count]
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class AardvarkTaskDataset(Dataset):
|
| 41 |
+
"""Repeat one or more already-batched official tasks for a fixed number of steps."""
|
| 42 |
+
|
| 43 |
+
def __init__(self, samples: list[Path], steps: int) -> None:
|
| 44 |
+
if steps < 1:
|
| 45 |
+
raise ValueError("steps must be at least 1")
|
| 46 |
+
self.tasks = [self._load(path) for path in samples]
|
| 47 |
+
self.steps = steps
|
| 48 |
+
|
| 49 |
+
@staticmethod
|
| 50 |
+
def _load(path: Path) -> dict[str, Any]:
|
| 51 |
+
with path.open("rb") as handle:
|
| 52 |
+
return pickle.load(handle)
|
| 53 |
+
|
| 54 |
+
def __len__(self) -> int:
|
| 55 |
+
return self.steps
|
| 56 |
+
|
| 57 |
+
def __getitem__(self, index: int) -> dict[str, Any]:
|
| 58 |
+
return copy.deepcopy(self.tasks[index % len(self.tasks)])
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def collate_tasks(items: list[Any]) -> Any:
|
| 62 |
+
"""Concatenate the batch dimension already present in official task tensors."""
|
| 63 |
+
first = items[0]
|
| 64 |
+
if isinstance(first, torch.Tensor):
|
| 65 |
+
return torch.cat(items, dim=0)
|
| 66 |
+
if isinstance(first, np.ndarray):
|
| 67 |
+
return np.concatenate(items, axis=0)
|
| 68 |
+
if isinstance(first, dict):
|
| 69 |
+
return {key: collate_tasks([item[key] for item in items]) for key in first}
|
| 70 |
+
if isinstance(first, tuple):
|
| 71 |
+
return tuple(collate_tasks(list(values)) for values in zip(*items))
|
| 72 |
+
if isinstance(first, list):
|
| 73 |
+
return [collate_tasks(list(values)) for values in zip(*items)]
|
| 74 |
+
if all(item == first for item in items):
|
| 75 |
+
return first
|
| 76 |
+
raise TypeError(f"Cannot collate Aardvark values of type {type(first).__name__}")
|
official-src/LICENSE
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Creative Commons Legal Code
|
| 2 |
+
|
| 3 |
+
CC0 1.0 Universal
|
| 4 |
+
|
| 5 |
+
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
| 6 |
+
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
| 7 |
+
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
| 8 |
+
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
| 9 |
+
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
| 10 |
+
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
| 11 |
+
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
| 12 |
+
HEREUNDER.
|
| 13 |
+
|
| 14 |
+
Statement of Purpose
|
| 15 |
+
|
| 16 |
+
The laws of most jurisdictions throughout the world automatically confer
|
| 17 |
+
exclusive Copyright and Related Rights (defined below) upon the creator
|
| 18 |
+
and subsequent owner(s) (each and all, an "owner") of an original work of
|
| 19 |
+
authorship and/or a database (each, a "Work").
|
| 20 |
+
|
| 21 |
+
Certain owners wish to permanently relinquish those rights to a Work for
|
| 22 |
+
the purpose of contributing to a commons of creative, cultural and
|
| 23 |
+
scientific works ("Commons") that the public can reliably and without fear
|
| 24 |
+
of later claims of infringement build upon, modify, incorporate in other
|
| 25 |
+
works, reuse and redistribute as freely as possible in any form whatsoever
|
| 26 |
+
and for any purposes, including without limitation commercial purposes.
|
| 27 |
+
These owners may contribute to the Commons to promote the ideal of a free
|
| 28 |
+
culture and the further production of creative, cultural and scientific
|
| 29 |
+
works, or to gain reputation or greater distribution for their Work in
|
| 30 |
+
part through the use and efforts of others.
|
| 31 |
+
|
| 32 |
+
For these and/or other purposes and motivations, and without any
|
| 33 |
+
expectation of additional consideration or compensation, the person
|
| 34 |
+
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
| 35 |
+
is an owner of Copyright and Related Rights in the Work, voluntarily
|
| 36 |
+
elects to apply CC0 to the Work and publicly distribute the Work under its
|
| 37 |
+
terms, with knowledge of his or her Copyright and Related Rights in the
|
| 38 |
+
Work and the meaning and intended legal effect of CC0 on those rights.
|
| 39 |
+
|
| 40 |
+
1. Copyright and Related Rights. A Work made available under CC0 may be
|
| 41 |
+
protected by copyright and related or neighboring rights ("Copyright and
|
| 42 |
+
Related Rights"). Copyright and Related Rights include, but are not
|
| 43 |
+
limited to, the following:
|
| 44 |
+
|
| 45 |
+
i. the right to reproduce, adapt, distribute, perform, display,
|
| 46 |
+
communicate, and translate a Work;
|
| 47 |
+
ii. moral rights retained by the original author(s) and/or performer(s);
|
| 48 |
+
iii. publicity and privacy rights pertaining to a person's image or
|
| 49 |
+
likeness depicted in a Work;
|
| 50 |
+
iv. rights protecting against unfair competition in regards to a Work,
|
| 51 |
+
subject to the limitations in paragraph 4(a), below;
|
| 52 |
+
v. rights protecting the extraction, dissemination, use and reuse of data
|
| 53 |
+
in a Work;
|
| 54 |
+
vi. database rights (such as those arising under Directive 96/9/EC of the
|
| 55 |
+
European Parliament and of the Council of 11 March 1996 on the legal
|
| 56 |
+
protection of databases, and under any national implementation
|
| 57 |
+
thereof, including any amended or successor version of such
|
| 58 |
+
directive); and
|
| 59 |
+
vii. other similar, equivalent or corresponding rights throughout the
|
| 60 |
+
world based on applicable law or treaty, and any national
|
| 61 |
+
implementations thereof.
|
| 62 |
+
|
| 63 |
+
2. Waiver. To the greatest extent permitted by, but not in contravention
|
| 64 |
+
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
| 65 |
+
irrevocably and unconditionally waives, abandons, and surrenders all of
|
| 66 |
+
Affirmer's Copyright and Related Rights and associated claims and causes
|
| 67 |
+
of action, whether now known or unknown (including existing as well as
|
| 68 |
+
future claims and causes of action), in the Work (i) in all territories
|
| 69 |
+
worldwide, (ii) for the maximum duration provided by applicable law or
|
| 70 |
+
treaty (including future time extensions), (iii) in any current or future
|
| 71 |
+
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
| 72 |
+
including without limitation commercial, advertising or promotional
|
| 73 |
+
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
| 74 |
+
member of the public at large and to the detriment of Affirmer's heirs and
|
| 75 |
+
successors, fully intending that such Waiver shall not be subject to
|
| 76 |
+
revocation, rescission, cancellation, termination, or any other legal or
|
| 77 |
+
equitable action to disrupt the quiet enjoyment of the Work by the public
|
| 78 |
+
as contemplated by Affirmer's express Statement of Purpose.
|
| 79 |
+
|
| 80 |
+
3. Public License Fallback. Should any part of the Waiver for any reason
|
| 81 |
+
be judged legally invalid or ineffective under applicable law, then the
|
| 82 |
+
Waiver shall be preserved to the maximum extent permitted taking into
|
| 83 |
+
account Affirmer's express Statement of Purpose. In addition, to the
|
| 84 |
+
extent the Waiver is so judged Affirmer hereby grants to each affected
|
| 85 |
+
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
| 86 |
+
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
| 87 |
+
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
| 88 |
+
maximum duration provided by applicable law or treaty (including future
|
| 89 |
+
time extensions), (iii) in any current or future medium and for any number
|
| 90 |
+
of copies, and (iv) for any purpose whatsoever, including without
|
| 91 |
+
limitation commercial, advertising or promotional purposes (the
|
| 92 |
+
"License"). The License shall be deemed effective as of the date CC0 was
|
| 93 |
+
applied by Affirmer to the Work. Should any part of the License for any
|
| 94 |
+
reason be judged legally invalid or ineffective under applicable law, such
|
| 95 |
+
partial invalidity or ineffectiveness shall not invalidate the remainder
|
| 96 |
+
of the License, and in such case Affirmer hereby affirms that he or she
|
| 97 |
+
will not (i) exercise any of his or her remaining Copyright and Related
|
| 98 |
+
Rights in the Work or (ii) assert any associated claims and causes of
|
| 99 |
+
action with respect to the Work, in either case contrary to Affirmer's
|
| 100 |
+
express Statement of Purpose.
|
| 101 |
+
|
| 102 |
+
4. Limitations and Disclaimers.
|
| 103 |
+
|
| 104 |
+
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
| 105 |
+
surrendered, licensed or otherwise affected by this document.
|
| 106 |
+
b. Affirmer offers the Work as-is and makes no representations or
|
| 107 |
+
warranties of any kind concerning the Work, express, implied,
|
| 108 |
+
statutory or otherwise, including without limitation warranties of
|
| 109 |
+
title, merchantability, fitness for a particular purpose, non
|
| 110 |
+
infringement, or the absence of latent or other defects, accuracy, or
|
| 111 |
+
the present or absence of errors, whether or not discoverable, all to
|
| 112 |
+
the greatest extent permissible under applicable law.
|
| 113 |
+
c. Affirmer disclaims responsibility for clearing rights of other persons
|
| 114 |
+
that may apply to the Work or any use thereof, including without
|
| 115 |
+
limitation any person's Copyright and Related Rights in the Work.
|
| 116 |
+
Further, Affirmer disclaims responsibility for obtaining any necessary
|
| 117 |
+
consents, permissions or other rights required for any use of the
|
| 118 |
+
Work.
|
| 119 |
+
d. Affirmer understands and acknowledges that Creative Commons is not a
|
| 120 |
+
party to this document and has no duty or obligation with respect to
|
| 121 |
+
this CC0 or use of the Work.
|
official-src/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Aardvark Weather
|
| 2 |
+
|
| 3 |
+
This repo contains code and weights used to run the Aardvark Weather model (https://www.nature.com/articles/s41586-025-08897-0). We provide sample data, model weights and code to demonstrate how the trained model is run to produce forecasts.
|
| 4 |
+
|
| 5 |
+
## Model weights
|
| 6 |
+
Weights for the trained model presented in the paper are provided at `https://huggingface.co/datasets/av555/aardvark-weather/tree/main/trained_model`.
|
| 7 |
+
Within this folder there are weights for the encoder, decoder and processor and end to end finetuned weights for one day lead time.
|
| 8 |
+
For the decoder and end-to-end, we provide weights for both temperature and wind speed.
|
| 9 |
+
|
| 10 |
+
## Data
|
| 11 |
+
Sample data is provided in `data/sample_data_final.pkl`. In addition we provide normalisation factors used to generate plots of predictions. For a visualisation of what is included in each timeslice of data passed to the model, see notebooks/data_demo.ipynb. For those interested in training their own end-to-end models, we have additionally prepared a machine learning ready dataset for the data sources utilised in the paper available at https://huggingface.co/datasets/av555/aardvark-weather.
|
| 12 |
+
|
| 13 |
+
## Forecast notebooks
|
| 14 |
+
Notebooks demonstrating producing a forecast using the trained models are included in the `notebooks/` folder.
|
| 15 |
+
Aardvark produces multiple modalities of forecasts.
|
| 16 |
+
The notebook `forecast_demo.ipynb` demonstrates loading the complete Aardvark Weather model, generating predictions from the sample data and provides visualisations of the output global gridded and station forecasts.
|
| 17 |
+
The notebook `e2e_finetune_demo.ipynb` provides a demonstration of generating optimised station forecasts from the sample data using the end to end finetuned model.
|
| 18 |
+
|
| 19 |
+
## Training
|
| 20 |
+
Aardvark weather is trained in multiple stages.
|
| 21 |
+
Scripts to train the model are included in `training/`.
|
| 22 |
+
Training of the three main modules is handled in `train_module.py`, processor finetuning in `finetune.py` and end-to-end finetuning in `e2e_train.py`.
|
| 23 |
+
|
| 24 |
+
__Please note:__ the commands and related scripts under the "Encoder," "Processor," and "Decoder" sections below cannot be executed as they depend on local data loading pipelines, setup for the specific training compute infrastructure. The purpose of these training and finetuning commands is to illustrate the salient points of the training process for the purposes of transperancy and for those interested in the details of the training process and not to provide an executable version.
|
| 25 |
+
|
| 26 |
+
### Encoder
|
| 27 |
+
To train the encoder, run
|
| 28 |
+
```
|
| 29 |
+
bash train_encoder.sh
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
### Processor
|
| 33 |
+
To pre-train the processor, we use the code in
|
| 34 |
+
```
|
| 35 |
+
bash train_processor.sh
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
To finetune the processor, we use the code in
|
| 39 |
+
```
|
| 40 |
+
bash finetune.sh
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
### Decoder
|
| 44 |
+
To train the decoder, we use the code in
|
| 45 |
+
```
|
| 46 |
+
bash train_decoder.sh
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
### End-to-end
|
| 50 |
+
To tune the model end-to-end, we use the code in
|
| 51 |
+
```
|
| 52 |
+
bash train_e2e.sh
|
| 53 |
+
```
|
| 54 |
+
## FAQ
|
| 55 |
+
|
| 56 |
+
#### Can I access the data the model was trained on?
|
| 57 |
+
We provide a dataset with observational data from 2007-2019 at 24 hour resolution at https://huggingface.co/datasets/av555/aardvark-weather. We hope that this will allow others to develop their own end-to-end weather models and explore the vast design space of ML architectures for this task.
|
| 58 |
+
|
| 59 |
+
#### Can I run the model in real time?
|
| 60 |
+
Unfortunately the datasets used in this initial prototype are not available in real time. We are however in the process of building a fully operational system, Aardvark 2.0. Updates to this will be provided here.
|
| 61 |
+
|
| 62 |
+
#### This model is at lower resolution than other AI models. Do you have any plans to improve on this?
|
| 63 |
+
We are currently working on a new version of our model which runs at 0.25 degrees for a wider range of pressure levels.
|
| 64 |
+
|
| 65 |
+
#### Will further versions of the model and dataset be provided?
|
| 66 |
+
|
| 67 |
+
Yes, this project is ongoing and we aim to continue updating this dataset and model. Updates currently in progress include the development of a fully operational system with real time data feed, extension of current historical dataset to 2025 and inclusion of further instruments. If you would like to receive updates as new products become available please email av555@cam.ac.uk with subject line 'Aardvark updates'.
|
| 68 |
+
|
| 69 |
+
#### Can this dataset be used commercially?
|
| 70 |
+
|
| 71 |
+
The dataset is released under a non-commercial no-derivatives license. This is due to the lisencing inherited from the source datasets.
|
official-src/aardvark/architectures.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
|
| 4 |
+
from utils import *
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class MLP(nn.Module):
|
| 8 |
+
"""
|
| 9 |
+
Multi-layer perceptron
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
def __init__(
|
| 13 |
+
self,
|
| 14 |
+
in_channels,
|
| 15 |
+
out_channels,
|
| 16 |
+
h_channels=64,
|
| 17 |
+
h_layers=4,
|
| 18 |
+
):
|
| 19 |
+
|
| 20 |
+
super().__init__()
|
| 21 |
+
|
| 22 |
+
def hidden_block(h_channels):
|
| 23 |
+
h = nn.Sequential(
|
| 24 |
+
nn.Linear(h_channels, h_channels),
|
| 25 |
+
nn.ReLU(),
|
| 26 |
+
)
|
| 27 |
+
return h
|
| 28 |
+
|
| 29 |
+
self.mlp = nn.Sequential(
|
| 30 |
+
nn.Linear(in_channels, h_channels),
|
| 31 |
+
nn.ReLU(),
|
| 32 |
+
*[hidden_block(h_channels) for _ in range(h_layers)],
|
| 33 |
+
nn.Linear(h_channels, out_channels)
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
def forward(self, x):
|
| 37 |
+
return self.mlp(x)
|
official-src/aardvark/data_shapes.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Data is stored in memmaps for access speed. Here we give the shapes required to load these files
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
CLIMATOLOGY_SHAPE = (4, 366, 24, 240, 121)
|
| 6 |
+
|
| 7 |
+
ICOADS_Y_SHAPE = (33601, 5, 12000)
|
| 8 |
+
ICOADS_X_SHAPE = (33601, 2, 12000)
|
| 9 |
+
|
| 10 |
+
IGRA_Y_SHAPE = (33604, 24, 1375)
|
| 11 |
+
IGRA_X_SHAPE = (1375, 2)
|
| 12 |
+
|
| 13 |
+
AMSUA_Y_SHAPE = (21916, 180, 360, 13)
|
| 14 |
+
AMSUB_Y_SHAPE = (21916, 360, 181, 12)
|
| 15 |
+
ASCAT_Y_SHAPE = (21913, 360, 181, 17)
|
| 16 |
+
HIRS_Y_SHAPE = (21913, 360, 181, 26)
|
| 17 |
+
GRIDSAT_Y_SHAPE = (48211, 2, 514, 200)
|
| 18 |
+
IASI_Y_SHAPE = (23373, 360, 181, 52)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def get_hadisd_shape(mode):
|
| 22 |
+
"""
|
| 23 |
+
Return the shape of the HadISD array depending on variable
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
if mode != "train":
|
| 27 |
+
dim_1 = 415
|
| 28 |
+
else:
|
| 29 |
+
var_dict = {"tas": 8719, "tds": 8617, "psl": 8016, "u": 8721, "v": 8721}
|
| 30 |
+
dim_1 = var_dict[var]
|
| 31 |
+
return (106652, dim_1)
|
official-src/aardvark/e2e_model.py
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pickle
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn as nn
|
| 5 |
+
import numpy as np
|
| 6 |
+
|
| 7 |
+
from models import ConvCNPWeather
|
| 8 |
+
from misc_downscaling_functionality import ConvCNPWeatherOnToOff
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class ConvCNPWeatherE2E(nn.Module):
|
| 12 |
+
"""
|
| 13 |
+
Complete Aardvark weather model. This chains together the trained encoder,
|
| 14 |
+
processor and decoder modules to create complete forecasts. It can be finetuned
|
| 15 |
+
end to end to optimise predictions for a specific variable and location.
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
def __init__(
|
| 19 |
+
self,
|
| 20 |
+
device,
|
| 21 |
+
lead_time,
|
| 22 |
+
se_model_path,
|
| 23 |
+
forecast_model_path,
|
| 24 |
+
sf_model_path,
|
| 25 |
+
return_gridded=False,
|
| 26 |
+
aux_data_path=None,
|
| 27 |
+
):
|
| 28 |
+
|
| 29 |
+
super().__init__()
|
| 30 |
+
|
| 31 |
+
self.device = device
|
| 32 |
+
self.lead_time = lead_time
|
| 33 |
+
self.return_gridded = return_gridded
|
| 34 |
+
|
| 35 |
+
# Load encoder
|
| 36 |
+
self.se_model = self.load_se_model(se_model_path)
|
| 37 |
+
|
| 38 |
+
# Load processor
|
| 39 |
+
self.forecast_model = nn.ModuleList(
|
| 40 |
+
[
|
| 41 |
+
self.load_forecast_model(forecast_model_path, l + 1)
|
| 42 |
+
for l in range(lead_time)
|
| 43 |
+
]
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
# Load decoder
|
| 47 |
+
self.sf_model = self.load_sf_model(sf_model_path, lead_time)
|
| 48 |
+
self.decoder = None
|
| 49 |
+
|
| 50 |
+
# Setup normalisation factors
|
| 51 |
+
self.forecast_input_means = (
|
| 52 |
+
self.to_tensor(np.load(aux_data_path + "norm_factors/mean_4u_1.npy"))
|
| 53 |
+
.unsqueeze(0)
|
| 54 |
+
.unsqueeze(0)
|
| 55 |
+
.unsqueeze(0)
|
| 56 |
+
)
|
| 57 |
+
self.forecast_input_stds = (
|
| 58 |
+
self.to_tensor(np.load(aux_data_path + "norm_factors/std_4u_1.npy"))
|
| 59 |
+
.unsqueeze(0)
|
| 60 |
+
.unsqueeze(0)
|
| 61 |
+
.unsqueeze(0)
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
self.forecast_pred_diff_means = (
|
| 65 |
+
self.to_tensor(np.load(aux_data_path + "norm_factors/mean_diff_4u_1.npy"))
|
| 66 |
+
.unsqueeze(0)
|
| 67 |
+
.unsqueeze(0)
|
| 68 |
+
.unsqueeze(0)
|
| 69 |
+
)
|
| 70 |
+
self.forecast_pred_diff_stds = (
|
| 71 |
+
self.to_tensor(np.load(aux_data_path + "norm_factors/std_diff_4u_1.npy"))
|
| 72 |
+
.unsqueeze(0)
|
| 73 |
+
.unsqueeze(0)
|
| 74 |
+
.unsqueeze(0)
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
def to_tensor(self, arr):
|
| 78 |
+
return torch.from_numpy(arr).float().to(self.device)
|
| 79 |
+
|
| 80 |
+
def load_se_model(self, se_model_path):
|
| 81 |
+
"""
|
| 82 |
+
Load the trained encoder module
|
| 83 |
+
"""
|
| 84 |
+
|
| 85 |
+
with open(se_model_path + "/config.pkl", "rb") as handle:
|
| 86 |
+
forecast_config = pickle.load(handle)
|
| 87 |
+
|
| 88 |
+
model = ConvCNPWeather(
|
| 89 |
+
in_channels=forecast_config["in_channels"],
|
| 90 |
+
out_channels=forecast_config["out_channels"],
|
| 91 |
+
int_channels=forecast_config["int_channels"],
|
| 92 |
+
device="cuda",
|
| 93 |
+
res=forecast_config["res"],
|
| 94 |
+
gnp=bool(0),
|
| 95 |
+
decoder=forecast_config["decoder"],
|
| 96 |
+
mode=forecast_config["mode"],
|
| 97 |
+
film=bool(0),
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
best_epoch = np.argmin(np.load("{}/losses_0.npy".format(se_model_path)))
|
| 101 |
+
state_dict = torch.load(
|
| 102 |
+
"{}/epoch_{}".format(se_model_path, best_epoch),
|
| 103 |
+
map_location="cuda",
|
| 104 |
+
)["model_state_dict"]
|
| 105 |
+
state_dict = {k[7:]: v for k, v in zip(state_dict.keys(), state_dict.values())}
|
| 106 |
+
model.load_state_dict(state_dict)
|
| 107 |
+
model = model.to("cuda")
|
| 108 |
+
return model
|
| 109 |
+
|
| 110 |
+
def load_forecast_model(self, forecast_model_path, lead_time):
|
| 111 |
+
"""
|
| 112 |
+
Load the trained processor module
|
| 113 |
+
"""
|
| 114 |
+
|
| 115 |
+
with open(forecast_model_path + "/config.pkl", "rb") as handle:
|
| 116 |
+
forecast_config = pickle.load(handle)
|
| 117 |
+
|
| 118 |
+
model = ConvCNPWeather(
|
| 119 |
+
in_channels=forecast_config["in_channels"],
|
| 120 |
+
out_channels=forecast_config["out_channels"],
|
| 121 |
+
int_channels=forecast_config["int_channels"],
|
| 122 |
+
device="cuda",
|
| 123 |
+
res=forecast_config["res"],
|
| 124 |
+
gnp=bool(0),
|
| 125 |
+
decoder=forecast_config["decoder"],
|
| 126 |
+
mode=forecast_config["mode"],
|
| 127 |
+
film=False,
|
| 128 |
+
)
|
| 129 |
+
state_dict = torch.load(
|
| 130 |
+
f"{forecast_model_path}/forecast_{lead_time}/epoch_0",
|
| 131 |
+
map_location="cuda",
|
| 132 |
+
)["model_state_dict"]
|
| 133 |
+
state_dict = {k[7:]: v for k, v in zip(state_dict.keys(), state_dict.values())}
|
| 134 |
+
model.load_state_dict(state_dict)
|
| 135 |
+
model = model.to("cuda")
|
| 136 |
+
return model
|
| 137 |
+
|
| 138 |
+
def load_sf_model(self, sf_model_path, lead_time):
|
| 139 |
+
"""
|
| 140 |
+
Load the trained decoder module
|
| 141 |
+
"""
|
| 142 |
+
|
| 143 |
+
with open(sf_model_path + "config.pkl", "rb") as handle:
|
| 144 |
+
config = pickle.load(handle)
|
| 145 |
+
|
| 146 |
+
model = ConvCNPWeatherOnToOff(
|
| 147 |
+
in_channels=config["in_channels"],
|
| 148 |
+
out_channels=config["out_channels"],
|
| 149 |
+
int_channels=config["int_channels"],
|
| 150 |
+
device="cuda",
|
| 151 |
+
res=config["res"],
|
| 152 |
+
decoder=config["decoder"],
|
| 153 |
+
mode=config["mode"],
|
| 154 |
+
film=False,
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
best_epoch = np.argmin(
|
| 158 |
+
np.load("{}/lt_{}/losses_0.npy".format(sf_model_path, lead_time))
|
| 159 |
+
)
|
| 160 |
+
full_state_dict = torch.load(
|
| 161 |
+
sf_model_path + f"/lt_{lead_time}/epoch_{best_epoch}", map_location="cuda"
|
| 162 |
+
)
|
| 163 |
+
state_dict = full_state_dict["model_state_dict"]
|
| 164 |
+
state_dict = {k[7:]: v for k, v in zip(state_dict.keys(), state_dict.values())}
|
| 165 |
+
model.load_state_dict(state_dict)
|
| 166 |
+
model = model.to("cuda")
|
| 167 |
+
model.eval()
|
| 168 |
+
|
| 169 |
+
return model
|
| 170 |
+
|
| 171 |
+
def process_se_output(self, task, x):
|
| 172 |
+
"""
|
| 173 |
+
Reshape and normalise encoder output for input to processor
|
| 174 |
+
"""
|
| 175 |
+
|
| 176 |
+
task["forecast"]["y_context"][:, :24, ...] = x.permute(0, 3, 2, 1)
|
| 177 |
+
if self.return_gridded:
|
| 178 |
+
return task, x.permute(0, 3, 2, 1)
|
| 179 |
+
return task
|
| 180 |
+
|
| 181 |
+
def process_forecast_output(self, task, x, last=False):
|
| 182 |
+
"""
|
| 183 |
+
Reshape and normalise processor output for input to decoder
|
| 184 |
+
"""
|
| 185 |
+
|
| 186 |
+
base_context = task["forecast"]["y_context"][:, :-11, ...].permute(0, 2, 3, 1)
|
| 187 |
+
|
| 188 |
+
base_context = (
|
| 189 |
+
base_context * self.forecast_input_stds + self.forecast_input_means
|
| 190 |
+
).permute(0, 3, 2, 1)
|
| 191 |
+
|
| 192 |
+
x = self.forecast_pred_diff_means + x * self.forecast_pred_diff_stds
|
| 193 |
+
|
| 194 |
+
unnorm_x = x + base_context.permute(0, 2, 3, 1)
|
| 195 |
+
|
| 196 |
+
forecast = unnorm_x
|
| 197 |
+
|
| 198 |
+
x = (unnorm_x - self.forecast_input_means) / self.forecast_input_stds
|
| 199 |
+
|
| 200 |
+
task["downscaling"]["y_context"][:, :24, ...] = x.permute(0, 3, 2, 1)
|
| 201 |
+
task["forecast"]["y_context"] = torch.cat(
|
| 202 |
+
[
|
| 203 |
+
x.permute(0, 3, 2, 1),
|
| 204 |
+
task["forecast"]["y_context"][:, 24:, ...],
|
| 205 |
+
],
|
| 206 |
+
axis=1,
|
| 207 |
+
)
|
| 208 |
+
if self.return_gridded:
|
| 209 |
+
return task, forecast
|
| 210 |
+
return task
|
| 211 |
+
|
| 212 |
+
def forward(self, task, film_index=None):
|
| 213 |
+
"""
|
| 214 |
+
Produce a forecast
|
| 215 |
+
"""
|
| 216 |
+
|
| 217 |
+
# Generate initial state
|
| 218 |
+
x = self.se_model(task["assimilation"], film_index=None)
|
| 219 |
+
if self.return_gridded:
|
| 220 |
+
task, initial_state = self.process_se_output(task, x)
|
| 221 |
+
else:
|
| 222 |
+
task = self.process_se_output(task, x)
|
| 223 |
+
|
| 224 |
+
# Generate forecast
|
| 225 |
+
for lt in range(self.lead_time):
|
| 226 |
+
x = self.forecast_model[lt](task["forecast"], film_index=None)
|
| 227 |
+
if self.return_gridded:
|
| 228 |
+
task, forecast = self.process_forecast_output(task, x)
|
| 229 |
+
else:
|
| 230 |
+
task = self.process_forecast_output(task, x)
|
| 231 |
+
|
| 232 |
+
# Generate station forecast
|
| 233 |
+
x = self.sf_model(task["downscaling"], film_index=None)
|
| 234 |
+
|
| 235 |
+
if self.return_gridded:
|
| 236 |
+
initial_state = (
|
| 237 |
+
initial_state.permute(0, 3, 2, 1) * self.forecast_input_stds
|
| 238 |
+
+ self.forecast_input_means
|
| 239 |
+
)
|
| 240 |
+
return x, forecast, initial_state
|
| 241 |
+
return x
|
official-src/aardvark/e2e_train.py
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import pickle
|
| 4 |
+
import argparse
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
from torch.utils.data import DataLoader
|
| 8 |
+
import torch.distributed as dist
|
| 9 |
+
import torch.utils.data.distributed
|
| 10 |
+
import torch.multiprocessing as mp
|
| 11 |
+
from torch.utils.data.distributed import DistributedSampler
|
| 12 |
+
from torch.distributed import init_process_group, destroy_process_group
|
| 13 |
+
|
| 14 |
+
from misc_downscaling_functionality import DownscalingRmseLoss
|
| 15 |
+
from e2e_model import *
|
| 16 |
+
from loader import *
|
| 17 |
+
from models import *
|
| 18 |
+
from unet_wrap_padding import *
|
| 19 |
+
from trainer import DDPTrainerE2E
|
| 20 |
+
from loss_functions import WeightedRmseLoss, PressureWeightedRmseLoss, RmseLoss
|
| 21 |
+
|
| 22 |
+
torch.set_float32_matmul_precision("medium")
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def ddp_setup(rank, world_size, master_port):
|
| 26 |
+
"""
|
| 27 |
+
Setup DDP
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
os.environ["MASTER_ADDR"] = "localhost"
|
| 31 |
+
os.environ["MASTER_PORT"] = master_port
|
| 32 |
+
init_process_group(
|
| 33 |
+
backend="nccl",
|
| 34 |
+
rank=rank,
|
| 35 |
+
world_size=world_size,
|
| 36 |
+
)
|
| 37 |
+
torch.cuda.set_device(rank)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def start_date(name):
|
| 41 |
+
"""
|
| 42 |
+
Set split start dates
|
| 43 |
+
"""
|
| 44 |
+
|
| 45 |
+
if name == "train":
|
| 46 |
+
return "2007-01-02"
|
| 47 |
+
elif name == "val":
|
| 48 |
+
return "2019-01-01"
|
| 49 |
+
elif name == "test":
|
| 50 |
+
return "2018-01-01"
|
| 51 |
+
else:
|
| 52 |
+
raise Exception(f"Unrecognised split name {name}")
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def end_date(name):
|
| 56 |
+
"""
|
| 57 |
+
Set split end dates
|
| 58 |
+
"""
|
| 59 |
+
|
| 60 |
+
if name == "train":
|
| 61 |
+
return "2017-12-31"
|
| 62 |
+
elif name == "val":
|
| 63 |
+
return "2019-11-01"
|
| 64 |
+
elif name == "test":
|
| 65 |
+
return "2018-12-21"
|
| 66 |
+
else:
|
| 67 |
+
raise Exception(f"Unrecognised split name {name}")
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def main(rank, world_size, output_dir, args):
|
| 71 |
+
"""
|
| 72 |
+
Run end to end training
|
| 73 |
+
"""
|
| 74 |
+
|
| 75 |
+
master_port = args.master_port
|
| 76 |
+
lead_time = args.lead_time
|
| 77 |
+
era5_mode = args.era5_mode
|
| 78 |
+
ddp_setup(rank, world_size, master_port)
|
| 79 |
+
|
| 80 |
+
# Setup loss function
|
| 81 |
+
if args.loss == "lw_rmse":
|
| 82 |
+
lf = WeightedRmseLoss(
|
| 83 |
+
args.res,
|
| 84 |
+
start_ind=0,
|
| 85 |
+
end_ind=24,
|
| 86 |
+
weight_per_variable=False,
|
| 87 |
+
)
|
| 88 |
+
elif args.loss == "lw_rmse_pressure_weighted":
|
| 89 |
+
lf = PressureWeightedRmseLoss(args.res, era5_mode)
|
| 90 |
+
elif args.loss == "rmse":
|
| 91 |
+
lf = RmseLoss()
|
| 92 |
+
elif args.loss == "downscaling_rmse":
|
| 93 |
+
lf = DownscalingRmseLoss()
|
| 94 |
+
|
| 95 |
+
# Instantiate model
|
| 96 |
+
model = ConvCNPWeatherE2E(
|
| 97 |
+
device="cuda",
|
| 98 |
+
lead_time=lead_time,
|
| 99 |
+
se_model_path=args.se_model_path,
|
| 100 |
+
forecast_model_path=args.forecast_model_path,
|
| 101 |
+
sf_model_path=args.sf_model_path,
|
| 102 |
+
)
|
| 103 |
+
dist.barrier()
|
| 104 |
+
|
| 105 |
+
# Instantiate loaders
|
| 106 |
+
train_dataset = WeatherDatasetE2E(
|
| 107 |
+
device="cuda",
|
| 108 |
+
hadisd_mode="train",
|
| 109 |
+
start_date="2007-01-02",
|
| 110 |
+
end_date="2017-12-31",
|
| 111 |
+
lead_time=lead_time,
|
| 112 |
+
era5_mode="4u",
|
| 113 |
+
mode="train",
|
| 114 |
+
res=args.res,
|
| 115 |
+
var_start=0,
|
| 116 |
+
var_end=24,
|
| 117 |
+
diff=bool(0),
|
| 118 |
+
two_frames=bool(0),
|
| 119 |
+
region=args.region,
|
| 120 |
+
hadisd_var=args.var,
|
| 121 |
+
max_steps_per_epoch=args.max_steps_per_epoch,
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
val_dataset = WeatherDatasetE2E(
|
| 125 |
+
device="cuda",
|
| 126 |
+
hadisd_mode="train",
|
| 127 |
+
start_date="2019-01-01",
|
| 128 |
+
end_date="2019-12-21",
|
| 129 |
+
lead_time=lead_time,
|
| 130 |
+
era5_mode="4u",
|
| 131 |
+
mode="train",
|
| 132 |
+
res=args.res,
|
| 133 |
+
var_start=0,
|
| 134 |
+
var_end=24,
|
| 135 |
+
diff=bool(0),
|
| 136 |
+
two_frames=bool(0),
|
| 137 |
+
region=args.region,
|
| 138 |
+
hadisd_var=args.var,
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
test_dataset = WeatherDatasetE2E(
|
| 142 |
+
device="cuda",
|
| 143 |
+
hadisd_mode="train",
|
| 144 |
+
start_date="2018-01-01",
|
| 145 |
+
end_date="2018-12-21",
|
| 146 |
+
lead_time=lead_time,
|
| 147 |
+
era5_mode="4u",
|
| 148 |
+
mode="train",
|
| 149 |
+
res=args.res,
|
| 150 |
+
var_start=0,
|
| 151 |
+
var_end=24,
|
| 152 |
+
diff=bool(0),
|
| 153 |
+
two_frames=bool(0),
|
| 154 |
+
region=args.region,
|
| 155 |
+
hadisd_var=args.var,
|
| 156 |
+
)
|
| 157 |
+
|
| 158 |
+
train_sampler = DistributedSampler(train_dataset)
|
| 159 |
+
val_sampler = DistributedSampler(val_dataset)
|
| 160 |
+
test_sampler = DistributedSampler(test_dataset)
|
| 161 |
+
|
| 162 |
+
train_loader = DataLoader(
|
| 163 |
+
train_dataset,
|
| 164 |
+
batch_size=args.batch_size,
|
| 165 |
+
shuffle=False,
|
| 166 |
+
sampler=train_sampler,
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
val_loader = DataLoader(
|
| 170 |
+
val_dataset,
|
| 171 |
+
batch_size=args.batch_size,
|
| 172 |
+
shuffle=False,
|
| 173 |
+
sampler=val_sampler,
|
| 174 |
+
)
|
| 175 |
+
|
| 176 |
+
test_loader = DataLoader(
|
| 177 |
+
test_dataset,
|
| 178 |
+
batch_size=args.batch_size,
|
| 179 |
+
shuffle=False,
|
| 180 |
+
sampler=test_sampler,
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
# Instantiate trainer
|
| 184 |
+
trainer = DDPTrainerE2E(
|
| 185 |
+
model,
|
| 186 |
+
rank,
|
| 187 |
+
train_loader,
|
| 188 |
+
val_loader,
|
| 189 |
+
lf,
|
| 190 |
+
output_dir,
|
| 191 |
+
args.lr,
|
| 192 |
+
train_sampler,
|
| 193 |
+
hadisd_variable_name=args.var,
|
| 194 |
+
weight_decay=args.weight_decay,
|
| 195 |
+
weights_path=None,
|
| 196 |
+
tune_film=0,
|
| 197 |
+
test_loader=test_loader,
|
| 198 |
+
)
|
| 199 |
+
|
| 200 |
+
# Train model
|
| 201 |
+
trainer.train(n_epochs=args.epoch)
|
| 202 |
+
destroy_process_group()
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
if __name__ == "__main__":
|
| 206 |
+
|
| 207 |
+
parser = argparse.ArgumentParser()
|
| 208 |
+
parser.add_argument("--output_dir")
|
| 209 |
+
parser.add_argument("--loss")
|
| 210 |
+
parser.add_argument("--batch_size", type=int, default=3)
|
| 211 |
+
parser.add_argument("--epoch", type=int, default=10)
|
| 212 |
+
parser.add_argument("--master_port", default="12345")
|
| 213 |
+
parser.add_argument("--lr", type=float, default=5e-5)
|
| 214 |
+
parser.add_argument("--lead_time", type=int)
|
| 215 |
+
parser.add_argument("--era5_mode", default="4u")
|
| 216 |
+
parser.add_argument("--sf_model_path")
|
| 217 |
+
parser.add_argument("--se_model_path")
|
| 218 |
+
parser.add_argument("--forecast_model_path")
|
| 219 |
+
|
| 220 |
+
parser.add_argument("--weight_decay", type=float, default=1e-6)
|
| 221 |
+
parser.add_argument("--max_steps_per_epoch", type=int)
|
| 222 |
+
parser.add_argument("--res", type=int, default=1)
|
| 223 |
+
parser.add_argument("--frequency", type=int, default=6)
|
| 224 |
+
parser.add_argument("--region", default="global")
|
| 225 |
+
parser.add_argument("--var", choices=["tas", "u", "v", "psl", "ws"])
|
| 226 |
+
args = parser.parse_args()
|
| 227 |
+
|
| 228 |
+
torch.device("cuda")
|
| 229 |
+
|
| 230 |
+
# Make results directory
|
| 231 |
+
output_dir = args.output_dir
|
| 232 |
+
try:
|
| 233 |
+
os.mkdir(output_dir)
|
| 234 |
+
except:
|
| 235 |
+
pass
|
| 236 |
+
|
| 237 |
+
# Save config
|
| 238 |
+
with open(output_dir + "/config.pkl", "wb") as f:
|
| 239 |
+
pickle.dump(vars(args), f)
|
| 240 |
+
|
| 241 |
+
world_size = torch.cuda.device_count()
|
| 242 |
+
mp.spawn(main, args=[world_size, output_dir, args], nprocs=world_size)
|
official-src/aardvark/finetune.py
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
NB: this script is for illustration purposes only and is not runnable as our
|
| 3 |
+
full dataset is not provided as part of the submission due to size constraints.
|
| 4 |
+
Many of the relevant paths to the data have been thus replaced by dummy paths.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
import argparse
|
| 9 |
+
import pickle
|
| 10 |
+
import subprocess
|
| 11 |
+
from collections import OrderedDict
|
| 12 |
+
|
| 13 |
+
import numpy as np
|
| 14 |
+
from tqdm import tqdm
|
| 15 |
+
import torch
|
| 16 |
+
import torch.multiprocessing as mp
|
| 17 |
+
import torch.utils.data.distributed
|
| 18 |
+
from torch.utils.data import DataLoader, DistributedSampler
|
| 19 |
+
from torch.distributed import init_process_group, destroy_process_group
|
| 20 |
+
|
| 21 |
+
from loss_functions import WeightedRmseLoss, PressureWeightedRmseLoss, RmseLoss
|
| 22 |
+
from trainer import DDPTrainer
|
| 23 |
+
from loader import *
|
| 24 |
+
from models import *
|
| 25 |
+
from unet_wrap_padding import *
|
| 26 |
+
|
| 27 |
+
torch.set_float32_matmul_precision("medium")
|
| 28 |
+
|
| 29 |
+
weights = np.load(
|
| 30 |
+
"path_to_lat_weights/weights_lat_1.npy",
|
| 31 |
+
).T[np.newaxis, ..., np.newaxis]
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def weighted_rmse_loss(target, output):
|
| 35 |
+
return np.sqrt(np.nanmean(((target - output) ** 2) * weights, axis=(0, 1, 2)))
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def unnorm_era5(x, mean, std):
|
| 39 |
+
x = x * std + mean
|
| 40 |
+
return x
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def norm_era5(x, mean, std):
|
| 44 |
+
x = (x - mean) / std
|
| 45 |
+
return x
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def ddp_setup(rank, world_size, master_port):
|
| 49 |
+
os.environ["MASTER_ADDR"] = "localhost"
|
| 50 |
+
os.environ["MASTER_PORT"] = master_port
|
| 51 |
+
init_process_group(backend="nccl", rank=rank, world_size=world_size)
|
| 52 |
+
torch.cuda.set_device(rank)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def main(rank, world_size, output_dir, args):
|
| 56 |
+
"""
|
| 57 |
+
Finetune the processor module for each leadtime
|
| 58 |
+
"""
|
| 59 |
+
|
| 60 |
+
# Setup
|
| 61 |
+
master_port = args.master_port
|
| 62 |
+
lead_time = args.lead_time
|
| 63 |
+
quicklook_id = args.output_dir
|
| 64 |
+
era5_mode = args.era5_mode
|
| 65 |
+
ddp_setup(rank, world_size, master_port)
|
| 66 |
+
|
| 67 |
+
# Instantiate loss function
|
| 68 |
+
if args.loss == "lw_rmse":
|
| 69 |
+
lf = WeightedRmseLoss(args.res, weight_per_variable=False)
|
| 70 |
+
elif args.loss == "lw_rmse_pressure_weighted":
|
| 71 |
+
lf = PressureWeightedRmseLoss(args.res, args.era5_mode)
|
| 72 |
+
elif args.loss == "rmse":
|
| 73 |
+
lf = RmseLoss()
|
| 74 |
+
|
| 75 |
+
# Load the pre-trained model
|
| 76 |
+
with open(args.forecast_model_path + "/config.pkl", "rb") as handle:
|
| 77 |
+
forecast_config = pickle.load(handle)
|
| 78 |
+
|
| 79 |
+
model = ConvCNPWeather(
|
| 80 |
+
in_channels=forecast_config["in_channels"],
|
| 81 |
+
out_channels=forecast_config["out_channels"],
|
| 82 |
+
int_channels=forecast_config["int_channels"],
|
| 83 |
+
device="cuda",
|
| 84 |
+
res=forecast_config["res"],
|
| 85 |
+
gnp=bool(0),
|
| 86 |
+
decoder=forecast_config["decoder"],
|
| 87 |
+
mode=forecast_config["mode"],
|
| 88 |
+
film=bool(forecast_config["film"]),
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
try:
|
| 92 |
+
best_epoch = np.argmin(np.load(forecast_config["output_dir"] + "/losses_0.npy"))
|
| 93 |
+
checkpoint = torch.load(
|
| 94 |
+
forecast_config["output_dir"] + "/epoch_{}".format(best_epoch)
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
except:
|
| 98 |
+
best_epoch = np.argmin(
|
| 99 |
+
np.load("../results/" + forecast_config["output_dir"] + "/losses_0.npy")
|
| 100 |
+
)
|
| 101 |
+
checkpoint = torch.load(
|
| 102 |
+
"../results/"
|
| 103 |
+
+ forecast_config["output_dir"]
|
| 104 |
+
+ "/epoch_{}".format(best_epoch)
|
| 105 |
+
)
|
| 106 |
+
|
| 107 |
+
new_state_dict = OrderedDict()
|
| 108 |
+
for k, v in checkpoint["model_state_dict"].items():
|
| 109 |
+
name = k[7:] # remove `module.`
|
| 110 |
+
new_state_dict[name] = v
|
| 111 |
+
|
| 112 |
+
# Set the path to the prediction from the encoder
|
| 113 |
+
path_to_context = args.assimilation_model_path
|
| 114 |
+
|
| 115 |
+
# Finetune for each leadtime
|
| 116 |
+
for lead_time in range(1, 11):
|
| 117 |
+
print(f"Training lead time {lead_time}")
|
| 118 |
+
|
| 119 |
+
# Setup the model
|
| 120 |
+
model = ConvCNPWeather(
|
| 121 |
+
in_channels=forecast_config["in_channels"],
|
| 122 |
+
out_channels=forecast_config["out_channels"],
|
| 123 |
+
int_channels=forecast_config["int_channels"],
|
| 124 |
+
device="cuda",
|
| 125 |
+
res=forecast_config["res"],
|
| 126 |
+
gnp=bool(0),
|
| 127 |
+
decoder=forecast_config["decoder"],
|
| 128 |
+
mode=forecast_config["mode"],
|
| 129 |
+
film=bool(forecast_config["film"]),
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
+
model.load_state_dict(new_state_dict)
|
| 133 |
+
|
| 134 |
+
# Setup the loaders
|
| 135 |
+
if lead_time == 1:
|
| 136 |
+
path_to_context = args.assimilation_model_path
|
| 137 |
+
else:
|
| 138 |
+
path_to_context = f"{args.output_dir}/"
|
| 139 |
+
|
| 140 |
+
print(f"Loading context data from {path_to_context}...")
|
| 141 |
+
train_dataset = ForecastLoader(
|
| 142 |
+
device="cuda",
|
| 143 |
+
mode="train",
|
| 144 |
+
lead_time=lead_time,
|
| 145 |
+
era5_mode=args.era5_mode,
|
| 146 |
+
res=args.res,
|
| 147 |
+
frequency=args.frequency,
|
| 148 |
+
diff=bool(args.diff),
|
| 149 |
+
ic_path=path_to_context,
|
| 150 |
+
finetune_step=lead_time,
|
| 151 |
+
random_lt=False,
|
| 152 |
+
finetune_eval_every=250,
|
| 153 |
+
eval_steps=False,
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
val_dataset = ForecastLoader(
|
| 157 |
+
device="cuda",
|
| 158 |
+
mode="val",
|
| 159 |
+
lead_time=lead_time,
|
| 160 |
+
era5_mode=args.era5_mode,
|
| 161 |
+
res=args.res,
|
| 162 |
+
frequency=args.frequency,
|
| 163 |
+
diff=bool(args.diff),
|
| 164 |
+
ic_path=path_to_context,
|
| 165 |
+
finetune_step=lead_time,
|
| 166 |
+
)
|
| 167 |
+
|
| 168 |
+
test_dataset = ForecastLoader(
|
| 169 |
+
device="cuda",
|
| 170 |
+
mode="test",
|
| 171 |
+
lead_time=lead_time,
|
| 172 |
+
era5_mode=args.era5_mode,
|
| 173 |
+
res=args.res,
|
| 174 |
+
frequency=args.frequency,
|
| 175 |
+
diff=bool(args.diff),
|
| 176 |
+
ic_path=path_to_context,
|
| 177 |
+
finetune_step=lead_time,
|
| 178 |
+
)
|
| 179 |
+
|
| 180 |
+
train_sampler = DistributedSampler(train_dataset)
|
| 181 |
+
val_sampler = DistributedSampler(val_dataset)
|
| 182 |
+
test_sampler = DistributedSampler(test_dataset)
|
| 183 |
+
|
| 184 |
+
train_loader = DataLoader(
|
| 185 |
+
train_dataset,
|
| 186 |
+
batch_size=args.batch_size,
|
| 187 |
+
shuffle=False,
|
| 188 |
+
sampler=train_sampler,
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
val_loader = DataLoader(
|
| 192 |
+
val_dataset, batch_size=args.batch_size, shuffle=False, sampler=val_sampler
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
test_loader = DataLoader(
|
| 196 |
+
test_dataset,
|
| 197 |
+
batch_size=args.batch_size,
|
| 198 |
+
shuffle=False,
|
| 199 |
+
sampler=test_sampler,
|
| 200 |
+
)
|
| 201 |
+
|
| 202 |
+
# Setup output dir
|
| 203 |
+
try:
|
| 204 |
+
output_dir = f"{args.output_dir}/forecast_{lead_time}/"
|
| 205 |
+
os.mkdir(output_dir)
|
| 206 |
+
except:
|
| 207 |
+
pass
|
| 208 |
+
|
| 209 |
+
# Run the finetuning
|
| 210 |
+
trainer = DDPTrainer(
|
| 211 |
+
model,
|
| 212 |
+
rank,
|
| 213 |
+
train_loader,
|
| 214 |
+
val_loader,
|
| 215 |
+
lf,
|
| 216 |
+
output_dir,
|
| 217 |
+
args.lr,
|
| 218 |
+
train_sampler,
|
| 219 |
+
weight_decay=args.weight_decay,
|
| 220 |
+
tune_film=False,
|
| 221 |
+
)
|
| 222 |
+
|
| 223 |
+
# Finetune
|
| 224 |
+
n_epochs = args.finetune_epochs
|
| 225 |
+
trainer.train(n_epochs=n_epochs)
|
| 226 |
+
|
| 227 |
+
torch.distributed.barrier()
|
| 228 |
+
|
| 229 |
+
# Load the model just trained
|
| 230 |
+
|
| 231 |
+
best_epoch = np.argmin(
|
| 232 |
+
np.load(f"{args.output_dir}/forecast_{lead_time}/losses_0.npy")
|
| 233 |
+
)
|
| 234 |
+
checkpoint = torch.load(
|
| 235 |
+
f"{args.output_dir}/forecast_{lead_time}/epoch_{best_epoch}"
|
| 236 |
+
)
|
| 237 |
+
|
| 238 |
+
new_state_dict = OrderedDict()
|
| 239 |
+
for k, v in checkpoint["model_state_dict"].items():
|
| 240 |
+
name = k[7:]
|
| 241 |
+
new_state_dict[name] = v
|
| 242 |
+
|
| 243 |
+
model.load_state_dict(new_state_dict)
|
| 244 |
+
model.eval()
|
| 245 |
+
|
| 246 |
+
# Now need to make predictions using this model to create data to train on for the next leadtime
|
| 247 |
+
|
| 248 |
+
# Setup the output arrays
|
| 249 |
+
if rank == 0:
|
| 250 |
+
val_ic = np.memmap(
|
| 251 |
+
f"{args.output_dir}/ic_val_{lead_time}.mmap",
|
| 252 |
+
dtype="float32",
|
| 253 |
+
mode="w+",
|
| 254 |
+
shape=(len(val_dataset), 121, 240, 24),
|
| 255 |
+
)
|
| 256 |
+
|
| 257 |
+
test_ic = np.memmap(
|
| 258 |
+
f"{args.output_dir}/ic_test_{lead_time}.mmap",
|
| 259 |
+
dtype="float32",
|
| 260 |
+
mode="w+",
|
| 261 |
+
shape=(len(test_dataset), 121, 240, 24),
|
| 262 |
+
)
|
| 263 |
+
|
| 264 |
+
test_ic_target = np.memmap(
|
| 265 |
+
f"{args.output_dir}/ic_test_target_{lead_time}.mmap",
|
| 266 |
+
dtype="float32",
|
| 267 |
+
mode="w+",
|
| 268 |
+
shape=(len(test_dataset), 121, 240, 24),
|
| 269 |
+
)
|
| 270 |
+
|
| 271 |
+
train_ic = np.memmap(
|
| 272 |
+
f"{args.output_dir}/ic_train_{lead_time}.mmap",
|
| 273 |
+
dtype="float32",
|
| 274 |
+
mode="w+",
|
| 275 |
+
shape=(len(train_dataset), 121, 240, 24),
|
| 276 |
+
)
|
| 277 |
+
|
| 278 |
+
torch.distributed.barrier()
|
| 279 |
+
val_ic = np.memmap(
|
| 280 |
+
f"{args.output_dir}/ic_val_{lead_time}.mmap",
|
| 281 |
+
dtype="float32",
|
| 282 |
+
mode="r+",
|
| 283 |
+
shape=(len(val_dataset), 121, 240, 24),
|
| 284 |
+
)
|
| 285 |
+
|
| 286 |
+
train_ic = np.memmap(
|
| 287 |
+
f"{args.output_dir}/ic_train_{lead_time}.mmap",
|
| 288 |
+
dtype="float32",
|
| 289 |
+
mode="r+",
|
| 290 |
+
shape=(len(train_dataset), 121, 240, 24),
|
| 291 |
+
)
|
| 292 |
+
|
| 293 |
+
test_ic = np.memmap(
|
| 294 |
+
f"{args.output_dir}/ic_test_{lead_time}.mmap",
|
| 295 |
+
dtype="float32",
|
| 296 |
+
mode="r+",
|
| 297 |
+
shape=(len(test_dataset), 121, 240, 24),
|
| 298 |
+
)
|
| 299 |
+
|
| 300 |
+
test_ic_target = np.memmap(
|
| 301 |
+
f"{args.output_dir}/ic_test_target_{lead_time}.mmap",
|
| 302 |
+
dtype="float32",
|
| 303 |
+
mode="r+",
|
| 304 |
+
shape=(len(test_dataset), 121, 240, 24),
|
| 305 |
+
)
|
| 306 |
+
|
| 307 |
+
print(f"{args.output_dir}/ic_train_{lead_time}.mmap")
|
| 308 |
+
if lead_time > 1:
|
| 309 |
+
if rank == 0:
|
| 310 |
+
subprocess.run(
|
| 311 |
+
[
|
| 312 |
+
"rm",
|
| 313 |
+
f"{args.output_dir}/ic_train_{lead_time-1}.mmap",
|
| 314 |
+
]
|
| 315 |
+
)
|
| 316 |
+
|
| 317 |
+
torch.distributed.barrier()
|
| 318 |
+
|
| 319 |
+
# Generate predictions on the test set
|
| 320 |
+
with tqdm(test_loader, unit="batch") as tepoch:
|
| 321 |
+
for count, batch in enumerate(tepoch):
|
| 322 |
+
inds = batch["target_index"].detach().cpu().numpy().astype(int)[:, 0]
|
| 323 |
+
out = model(batch, film_index=batch["lt"])
|
| 324 |
+
|
| 325 |
+
base_context = test_loader.dataset.unnorm_base_context(
|
| 326 |
+
batch["y_context"][:, :-11, ...]
|
| 327 |
+
).permute(0, 3, 2, 1)
|
| 328 |
+
unnorm_pred = test_loader.dataset.unnorm_pred(out)
|
| 329 |
+
unnorm_target = test_loader.dataset.unnorm_pred(batch["y_target"])
|
| 330 |
+
|
| 331 |
+
unnorm_pred = unnorm_pred + base_context
|
| 332 |
+
unnorm_target = unnorm_target + base_context
|
| 333 |
+
|
| 334 |
+
test_ic[inds, ...] = unnorm_pred.detach().cpu()
|
| 335 |
+
test_ic_target[inds, ...] = unnorm_target.detach().cpu()
|
| 336 |
+
|
| 337 |
+
torch.distributed.barrier()
|
| 338 |
+
|
| 339 |
+
# Generate predictions on the validation set
|
| 340 |
+
with tqdm(val_loader, unit="batch") as tepoch:
|
| 341 |
+
for count, batch in enumerate(tepoch):
|
| 342 |
+
inds = batch["target_index"].detach().cpu().numpy().astype(int)[:, 0]
|
| 343 |
+
out = model(batch, film_index=batch["lt"])
|
| 344 |
+
|
| 345 |
+
base_context = val_loader.dataset.unnorm_base_context(
|
| 346 |
+
batch["y_context"][:, :-11, ...]
|
| 347 |
+
).permute(0, 3, 2, 1)
|
| 348 |
+
unnorm_pred = val_loader.dataset.unnorm_pred(out)
|
| 349 |
+
unnorm_pred = unnorm_pred + base_context
|
| 350 |
+
|
| 351 |
+
val_ic[inds, ...] = unnorm_pred.detach().cpu()
|
| 352 |
+
|
| 353 |
+
torch.distributed.barrier()
|
| 354 |
+
|
| 355 |
+
# Generate predictions on the train set
|
| 356 |
+
with tqdm(train_loader, unit="batch") as tepoch:
|
| 357 |
+
for count, batch in enumerate(tepoch):
|
| 358 |
+
inds = batch["target_index"].detach().cpu().numpy().astype(int)[:, 0]
|
| 359 |
+
out = model(batch, film_index=batch["lt"])
|
| 360 |
+
base_context = val_loader.dataset.unnorm_base_context(
|
| 361 |
+
batch["y_context"][:, :-11, ...]
|
| 362 |
+
).permute(0, 3, 2, 1)
|
| 363 |
+
unnorm_pred = val_loader.dataset.unnorm_pred(out)
|
| 364 |
+
unnorm_pred = unnorm_pred + base_context
|
| 365 |
+
|
| 366 |
+
train_ic[inds, ...] = unnorm_pred.detach().cpu()
|
| 367 |
+
|
| 368 |
+
torch.distributed.barrier()
|
| 369 |
+
|
| 370 |
+
print("Setting up next lead time...")
|
| 371 |
+
path_to_context = f"{args.output_dir}/"
|
| 372 |
+
|
| 373 |
+
# Set the initial model weights for the next leadtime to be the current trained weights
|
| 374 |
+
if lead_time > 1:
|
| 375 |
+
best_epoch = np.argmin(
|
| 376 |
+
np.load(f"{args.output_dir}/forecast_{lead_time}/losses_0.npy")
|
| 377 |
+
)
|
| 378 |
+
checkpoint = torch.load(
|
| 379 |
+
f"{args.output_dir}/forecast_{lead_time}/epoch_{best_epoch}"
|
| 380 |
+
)
|
| 381 |
+
new_state_dict = OrderedDict()
|
| 382 |
+
for k, v in checkpoint["model_state_dict"].items():
|
| 383 |
+
name = k[7:]
|
| 384 |
+
new_state_dict[name] = v
|
| 385 |
+
|
| 386 |
+
torch.cuda.empty_cache()
|
| 387 |
+
|
| 388 |
+
destroy_process_group()
|
| 389 |
+
|
| 390 |
+
|
| 391 |
+
if __name__ == "__main__":
|
| 392 |
+
parser = argparse.ArgumentParser()
|
| 393 |
+
|
| 394 |
+
parser.add_argument("--output_dir")
|
| 395 |
+
parser.add_argument("--assimilation_model_path")
|
| 396 |
+
parser.add_argument("--forecast_model_path")
|
| 397 |
+
|
| 398 |
+
parser.add_argument("--loss", default="lw_rmse_pressure_weighted")
|
| 399 |
+
parser.add_argument("--batch_size", type=int, default=12)
|
| 400 |
+
parser.add_argument("--master_port", default="12345")
|
| 401 |
+
parser.add_argument("--lr", type=float, default=1e-4)
|
| 402 |
+
parser.add_argument("--lead_time", type=int, default=1)
|
| 403 |
+
parser.add_argument("--era5_mode", default="4u")
|
| 404 |
+
parser.add_argument("--weight_decay", type=float, default=1e-6)
|
| 405 |
+
parser.add_argument("--res", type=int, default=1)
|
| 406 |
+
parser.add_argument("--frequency", type=int, default=6)
|
| 407 |
+
|
| 408 |
+
parser.add_argument("--diff", type=int, default=1)
|
| 409 |
+
parser.add_argument("--start_ind", type=int, default=0)
|
| 410 |
+
parser.add_argument("--end_ind", type=int, default=24)
|
| 411 |
+
parser.add_argument("--finetune_epochs", type=int, default=5)
|
| 412 |
+
|
| 413 |
+
args = parser.parse_args()
|
| 414 |
+
|
| 415 |
+
device = torch.device("cuda")
|
| 416 |
+
|
| 417 |
+
# Setup results directory
|
| 418 |
+
output_dir = args.output_dir
|
| 419 |
+
try:
|
| 420 |
+
os.mkdir(output_dir)
|
| 421 |
+
except:
|
| 422 |
+
pass
|
| 423 |
+
|
| 424 |
+
# Save config
|
| 425 |
+
config = vars(args)
|
| 426 |
+
|
| 427 |
+
with open(output_dir + "/config.pkl", "wb") as f:
|
| 428 |
+
pickle.dump(config, f)
|
| 429 |
+
|
| 430 |
+
world_size = torch.cuda.device_count()
|
| 431 |
+
mp.spawn(main, args=[world_size, output_dir, args], nprocs=world_size)
|
official-src/aardvark/generate_initial_condition_single.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
NB: this script is for illustration purposes only and is not runnable as our
|
| 3 |
+
full dataset is not provided as part of the submission due to size constraints.
|
| 4 |
+
Many of the relevant paths to the data have been thus replaced by dummy paths.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import argparse
|
| 8 |
+
import pickle
|
| 9 |
+
|
| 10 |
+
import numpy as np
|
| 11 |
+
import pandas as pd
|
| 12 |
+
from tqdm import tqdm
|
| 13 |
+
import torch
|
| 14 |
+
import torch.nn as nn
|
| 15 |
+
import torch.utils.data.distributed
|
| 16 |
+
from torch.utils.data import DataLoader
|
| 17 |
+
|
| 18 |
+
from loader import WeatherDatasetAssimilation
|
| 19 |
+
from models import *
|
| 20 |
+
|
| 21 |
+
torch.set_float32_matmul_precision("medium")
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def unnorm(x, mean, std, diff=False, av_2019=None):
|
| 25 |
+
|
| 26 |
+
x = x * std + mean
|
| 27 |
+
if diff:
|
| 28 |
+
return x + av_2019.transpose(0, 3, 2, 1)
|
| 29 |
+
return x
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
if __name__ == "__main__":
|
| 33 |
+
|
| 34 |
+
"""
|
| 35 |
+
Generate encoder predictions to be used as finetuning data for the processor module
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
parser = argparse.ArgumentParser()
|
| 39 |
+
parser.add_argument("--encoder_model_path")
|
| 40 |
+
args = parser.parse_args()
|
| 41 |
+
|
| 42 |
+
# Load experiment config
|
| 43 |
+
with open(args.encoder_model_path + "/config.pkl", "rb") as handle:
|
| 44 |
+
forecast_config = pickle.load(handle)
|
| 45 |
+
|
| 46 |
+
device = "cuda"
|
| 47 |
+
|
| 48 |
+
# Setup normalisation factors
|
| 49 |
+
era5_mean_spatial = None
|
| 50 |
+
means = np.load(
|
| 51 |
+
"aux_data_path/norm_factors/mean_{}_{}.npy".format(
|
| 52 |
+
forecast_config["era5_mode"], forecast_config["res"]
|
| 53 |
+
)
|
| 54 |
+
)[np.newaxis, np.newaxis, np.newaxis, :]
|
| 55 |
+
stds = np.load(
|
| 56 |
+
"aux_data_path/norm_factors/std_{}_{}.npy".format(
|
| 57 |
+
forecast_config["era5_mode"], forecast_config["res"]
|
| 58 |
+
)
|
| 59 |
+
)[np.newaxis, np.newaxis, np.newaxis, :]
|
| 60 |
+
|
| 61 |
+
# Specify dates to generate predictions for
|
| 62 |
+
labels = ["train", "test", "val"]
|
| 63 |
+
dates = [
|
| 64 |
+
["2007-01-02", "2017-12-31"],
|
| 65 |
+
["2018-01-01", "2018-12-31"],
|
| 66 |
+
["2019-01-01", "2019-12-31"],
|
| 67 |
+
]
|
| 68 |
+
|
| 69 |
+
# Iterate over loaders
|
| 70 |
+
for label, date in zip(labels, dates):
|
| 71 |
+
|
| 72 |
+
n_times = pd.date_range(date[0], date[1], freq="6H")
|
| 73 |
+
|
| 74 |
+
# Setup a memmap to write out to
|
| 75 |
+
ic = np.memmap(
|
| 76 |
+
"{}/ic_{}.mmap".format(args.encoder_model_path, label),
|
| 77 |
+
dtype="float32",
|
| 78 |
+
mode="w+",
|
| 79 |
+
shape=(len(n_times), 121, 240, 24),
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
var_group_preds = []
|
| 83 |
+
var_group_targets = []
|
| 84 |
+
|
| 85 |
+
# Setup loader
|
| 86 |
+
dataset = WeatherDatasetAssimilation(
|
| 87 |
+
device="cuda",
|
| 88 |
+
hadisd_mode="train",
|
| 89 |
+
start_date=date[0],
|
| 90 |
+
end_date=date[1],
|
| 91 |
+
lead_time=0,
|
| 92 |
+
era5_mode="4u",
|
| 93 |
+
res=1,
|
| 94 |
+
var_start=0,
|
| 95 |
+
var_end=24,
|
| 96 |
+
diff=False,
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
loader = DataLoader(dataset, batch_size=64, shuffle=False) # ,
|
| 100 |
+
|
| 101 |
+
# Instantiate and load model
|
| 102 |
+
model = ConvCNPWeather(
|
| 103 |
+
in_channels=forecast_config["in_channels"],
|
| 104 |
+
out_channels=forecast_config["out_channels"],
|
| 105 |
+
int_channels=forecast_config["int_channels"],
|
| 106 |
+
device="cuda",
|
| 107 |
+
res=forecast_config["res"],
|
| 108 |
+
gnp=bool(0),
|
| 109 |
+
decoder=forecast_config["decoder"],
|
| 110 |
+
mode=forecast_config["mode"],
|
| 111 |
+
film=bool(0),
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
best_epoch = np.argmin(
|
| 115 |
+
np.load("{}/losses_0.npy".format(args.encoder_model_path))
|
| 116 |
+
)
|
| 117 |
+
state_dict = torch.load(
|
| 118 |
+
"{}/epoch_{}".format(args.encoder_model_path, best_epoch),
|
| 119 |
+
map_location=device,
|
| 120 |
+
)["model_state_dict"]
|
| 121 |
+
state_dict = {k[7:]: v for k, v in zip(state_dict.keys(), state_dict.values())}
|
| 122 |
+
model.load_state_dict(state_dict)
|
| 123 |
+
model = nn.DataParallel(model)
|
| 124 |
+
model = model.cuda()
|
| 125 |
+
|
| 126 |
+
model.eval()
|
| 127 |
+
|
| 128 |
+
# Generate predictions
|
| 129 |
+
total = []
|
| 130 |
+
target = []
|
| 131 |
+
|
| 132 |
+
sum_count = 0
|
| 133 |
+
with torch.no_grad():
|
| 134 |
+
with tqdm(loader, unit="batch") as tepoch:
|
| 135 |
+
for count, batch in enumerate(tepoch):
|
| 136 |
+
|
| 137 |
+
out = model(batch, film_index=batch["lt"]).detach().cpu().numpy()
|
| 138 |
+
|
| 139 |
+
out_unnorm = unnorm(
|
| 140 |
+
out,
|
| 141 |
+
means,
|
| 142 |
+
stds,
|
| 143 |
+
diff=False,
|
| 144 |
+
av_2019=era5_mean_spatial,
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
ic[sum_count : sum_count + out.shape[0], ...] = out_unnorm
|
| 148 |
+
sum_count += out.shape[0]
|
official-src/aardvark/loader.py
ADDED
|
@@ -0,0 +1,1825 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import time as timelib
|
| 2 |
+
from time import time
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import pandas as pd
|
| 6 |
+
import torch
|
| 7 |
+
from torch.utils.data import Dataset
|
| 8 |
+
|
| 9 |
+
from loader_utils_new import *
|
| 10 |
+
from data_shapes import *
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class WeatherDataset(Dataset):
|
| 14 |
+
"""
|
| 15 |
+
Base weather dataset class
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
def __init__(
|
| 19 |
+
self,
|
| 20 |
+
device,
|
| 21 |
+
hadisd_mode,
|
| 22 |
+
start_date,
|
| 23 |
+
end_date,
|
| 24 |
+
lead_time,
|
| 25 |
+
era5_mode="train",
|
| 26 |
+
res=1,
|
| 27 |
+
filter_dates=None,
|
| 28 |
+
diff=None,
|
| 29 |
+
):
|
| 30 |
+
|
| 31 |
+
super().__init__()
|
| 32 |
+
|
| 33 |
+
# Setup
|
| 34 |
+
self.device = device
|
| 35 |
+
self.mode = hadisd_mode
|
| 36 |
+
self.data_path = "path_to_data/"
|
| 37 |
+
self.aux_data_path = "path_to_auxiliary_data/"
|
| 38 |
+
self.start_date = start_date
|
| 39 |
+
self.end_date = end_date
|
| 40 |
+
self.lead_time = lead_time
|
| 41 |
+
self.era5_mode = era5_mode
|
| 42 |
+
self.res = res
|
| 43 |
+
self.filter_dates = filter_dates
|
| 44 |
+
self.diff = diff
|
| 45 |
+
|
| 46 |
+
# Date indexing
|
| 47 |
+
self.dates = pd.date_range(start_date, end_date, freq="6H")
|
| 48 |
+
if self.filter_dates == "start":
|
| 49 |
+
self.index = np.array([i for i, d in enumerate(self.dates) if d.month < 7])
|
| 50 |
+
elif self.filter_dates == "end":
|
| 51 |
+
self.index = np.array([i for i, d in enumerate(self.dates) if d.month >= 7])
|
| 52 |
+
else:
|
| 53 |
+
self.index = np.array(range(len(self.dates)))
|
| 54 |
+
|
| 55 |
+
# Load the input modalities
|
| 56 |
+
print("Loading IGRA")
|
| 57 |
+
self.load_igra()
|
| 58 |
+
|
| 59 |
+
print("Loading AMSU-A")
|
| 60 |
+
self.load_amsua()
|
| 61 |
+
|
| 62 |
+
print("Loading AMSU-B")
|
| 63 |
+
self.load_amsub()
|
| 64 |
+
|
| 65 |
+
print("Loading ICOADS")
|
| 66 |
+
self.load_icoads()
|
| 67 |
+
|
| 68 |
+
print("Loading IASI")
|
| 69 |
+
self.load_iasi()
|
| 70 |
+
|
| 71 |
+
print("Loading GEO")
|
| 72 |
+
self.load_sat_data()
|
| 73 |
+
|
| 74 |
+
print("Loading HADISD")
|
| 75 |
+
self.load_hadisd(self.mode)
|
| 76 |
+
|
| 77 |
+
print("Loading ASCAT")
|
| 78 |
+
self.load_ascat_data()
|
| 79 |
+
self.load_hirs_data()
|
| 80 |
+
|
| 81 |
+
# Load the ground truth data for training
|
| 82 |
+
print("Loading ERA5")
|
| 83 |
+
self.era5_sfc = [
|
| 84 |
+
self.load_era5(year)
|
| 85 |
+
for year in range(int(start_date[:4]), int(end_date[:4]) + 1)
|
| 86 |
+
]
|
| 87 |
+
|
| 88 |
+
# Internal grid to longitude latitude correspondence
|
| 89 |
+
self.era5_x = [
|
| 90 |
+
self.to_tensor(
|
| 91 |
+
np.load(self.data_path + "era5/era5_x_{}.npy".format(self.res))
|
| 92 |
+
)
|
| 93 |
+
/ LATLON_SCALE_FACTOR,
|
| 94 |
+
self.to_tensor(
|
| 95 |
+
np.load(self.data_path + "era5/era5_y_{}.npy".format(self.res))
|
| 96 |
+
)
|
| 97 |
+
/ LATLON_SCALE_FACTOR,
|
| 98 |
+
]
|
| 99 |
+
|
| 100 |
+
# Orography
|
| 101 |
+
self.era5_elev = self.to_tensor(
|
| 102 |
+
np.load(self.data_path + "era5/elev_vars_{}.npy".format(self.res))
|
| 103 |
+
)
|
| 104 |
+
self.era5_elev = torch.flip(self.era5_elev.permute(0, 2, 1), [-1])
|
| 105 |
+
xx, yy = torch.meshgrid(self.era5_x[0], self.era5_x[1])
|
| 106 |
+
self.era5_lonlat = torch.stack([xx, yy])
|
| 107 |
+
|
| 108 |
+
# Climatology
|
| 109 |
+
self.climatology = np.memmap(
|
| 110 |
+
self.data_path + "climatology_data.mmap",
|
| 111 |
+
dtype="float32",
|
| 112 |
+
mode="r",
|
| 113 |
+
shape=CLIMATOLOGY_SHAPE,
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
# Setup normalisation factors
|
| 117 |
+
if self.diff:
|
| 118 |
+
self.era5_mean_spatial = np.load(
|
| 119 |
+
self.aux_data_path + "era5_spatial_means.npy"
|
| 120 |
+
)[0, ...]
|
| 121 |
+
self.means = np.load(self.aux_data_path + "era5_avdiff_means.npy")[
|
| 122 |
+
:, np.newaxis, np.newaxis, ...
|
| 123 |
+
]
|
| 124 |
+
self.stds = np.load(self.aux_data_path + "era5_avdiff_stds.npy")[
|
| 125 |
+
:, np.newaxis, np.newaxis, ...
|
| 126 |
+
]
|
| 127 |
+
else:
|
| 128 |
+
self.means = np.load(
|
| 129 |
+
self.aux_data_path
|
| 130 |
+
+ "norm_factors/mean_{}_{}.npy".format(self.era5_mode, self.res)
|
| 131 |
+
)[:, np.newaxis, np.newaxis, ...]
|
| 132 |
+
self.stds = np.load(
|
| 133 |
+
self.aux_data_path
|
| 134 |
+
+ "norm_factors/std_{}_{}.npy".format(self.era5_mode, self.res)
|
| 135 |
+
)[:, np.newaxis, np.newaxis, ...]
|
| 136 |
+
|
| 137 |
+
def load_icoads(self):
|
| 138 |
+
"""
|
| 139 |
+
Load the ICOADS data
|
| 140 |
+
"""
|
| 141 |
+
|
| 142 |
+
self.icoads_y = np.memmap(
|
| 143 |
+
self.data_path + "icoads/1999_2021_icoads_y.mmap",
|
| 144 |
+
dtype="float32",
|
| 145 |
+
mode="r",
|
| 146 |
+
shape=ICOADS_Y_SHAPE,
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
self.icoads_x = (
|
| 150 |
+
np.memmap(
|
| 151 |
+
self.data_path + "icoads/1999_2021_icoads_x.mmap",
|
| 152 |
+
dtype="float32",
|
| 153 |
+
mode="r",
|
| 154 |
+
shape=ICOADS_X_SHAPE,
|
| 155 |
+
)
|
| 156 |
+
/ LATLON_SCALE_FACTOR
|
| 157 |
+
)
|
| 158 |
+
self.icoads_means = self.to_tensor(
|
| 159 |
+
np.load(self.aux_data_path + "norm_factors/mean_icoads.npy")
|
| 160 |
+
)
|
| 161 |
+
self.icoads_stds = self.to_tensor(
|
| 162 |
+
np.load(self.aux_data_path + "norm_factors/std_icoads.npy")
|
| 163 |
+
)
|
| 164 |
+
self.icoads_means = self.to_tensor(
|
| 165 |
+
np.nanmean(self.icoads_y[-365 * 4 :, ...], axis=(0, 2))[:, np.newaxis]
|
| 166 |
+
)
|
| 167 |
+
self.icoads_stds = self.to_tensor(
|
| 168 |
+
np.nanstd(self.icoads_y[-365 * 4 :, ...], axis=(0, 2))[:, np.newaxis]
|
| 169 |
+
)
|
| 170 |
+
self.icoads_index_offset = ICOADS_OFFSETS[self.start_date]
|
| 171 |
+
return
|
| 172 |
+
|
| 173 |
+
def load_igra(self):
|
| 174 |
+
"""
|
| 175 |
+
Load the IGRA data
|
| 176 |
+
"""
|
| 177 |
+
|
| 178 |
+
self.igra_y = np.memmap(
|
| 179 |
+
self.data_path + "igra/1999_2021_igra_y.mmap",
|
| 180 |
+
dtype="float32",
|
| 181 |
+
mode="r",
|
| 182 |
+
shape=IGRA_Y_SHAPE,
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
self.igra_x = np.copy(
|
| 186 |
+
np.memmap(
|
| 187 |
+
self.data_path + "igra/1999_2021_igra_x.mmap",
|
| 188 |
+
dtype="float32",
|
| 189 |
+
mode="r",
|
| 190 |
+
shape=IGRA_X_SHAPE,
|
| 191 |
+
)
|
| 192 |
+
)
|
| 193 |
+
self.igra_x = self.igra_x / LATLON_SCALE_FACTOR
|
| 194 |
+
|
| 195 |
+
self.igra_means = self.to_tensor(
|
| 196 |
+
np.load(self.aux_data_path + "norm_factors/mean_igra.npy")
|
| 197 |
+
)
|
| 198 |
+
self.igra_stds = self.to_tensor(
|
| 199 |
+
np.load(self.aux_data_path + "norm_factors/std_igra.npy")
|
| 200 |
+
)
|
| 201 |
+
|
| 202 |
+
self.igra_index_offset = IGRA_OFFSETS[self.start_date]
|
| 203 |
+
|
| 204 |
+
return
|
| 205 |
+
|
| 206 |
+
def load_amsua(self):
|
| 207 |
+
"""
|
| 208 |
+
Load the AMSU-A data
|
| 209 |
+
"""
|
| 210 |
+
|
| 211 |
+
self.amsua_y = np.memmap(
|
| 212 |
+
self.data_path + "amsua/2007_2021_amsua.mmap",
|
| 213 |
+
dtype="float32",
|
| 214 |
+
mode="r",
|
| 215 |
+
shape=AMSUA_Y_SHAPE,
|
| 216 |
+
)
|
| 217 |
+
self.amsua_index_offset = AMSUA_OFFSETS[self.start_date]
|
| 218 |
+
|
| 219 |
+
xx = np.linspace(-180, 179, 360, dtype=np.float32)
|
| 220 |
+
xx = ((xx + 360) % 360) / LATLON_SCALE_FACTOR
|
| 221 |
+
yy = np.linspace(90, -90, 180, dtype=np.float32) / LATLON_SCALE_FACTOR
|
| 222 |
+
self.amsua_x = [xx, yy]
|
| 223 |
+
|
| 224 |
+
self.amsua_means = self.to_tensor(
|
| 225 |
+
np.load(self.aux_data_path + "norm_factors/mean_amsua.npy")
|
| 226 |
+
)
|
| 227 |
+
self.amsua_stds = self.to_tensor(
|
| 228 |
+
np.load(self.aux_data_path + "norm_factors/std_amsua.npy")
|
| 229 |
+
)
|
| 230 |
+
|
| 231 |
+
return
|
| 232 |
+
|
| 233 |
+
def load_amsub(self):
|
| 234 |
+
"""
|
| 235 |
+
Load the AMSU-B data
|
| 236 |
+
"""
|
| 237 |
+
|
| 238 |
+
self.amsub_y = np.memmap(
|
| 239 |
+
self.data_path + "amsub_mhs/2007_2021_amsub.mmap",
|
| 240 |
+
dtype="float32",
|
| 241 |
+
mode="r",
|
| 242 |
+
shape=AMSUB_Y_SHAPE,
|
| 243 |
+
)
|
| 244 |
+
self.amsub_index_offset = AMSUB_OFFSETS[self.start_date]
|
| 245 |
+
|
| 246 |
+
xx = np.linspace(0, 359, 360, dtype=np.float32)
|
| 247 |
+
xx = ((xx + 360) % 360) / LATLON_SCALE_FACTOR
|
| 248 |
+
yy = np.linspace(90, -90, 181, dtype=np.float32) / LATLON_SCALE_FACTOR
|
| 249 |
+
self.amsub_x = [xx, yy]
|
| 250 |
+
|
| 251 |
+
self.amsub_means = self.to_tensor(
|
| 252 |
+
np.load(self.aux_data_path + "norm_factors/mean_amsub.npy")
|
| 253 |
+
)
|
| 254 |
+
self.amsub_stds = self.to_tensor(
|
| 255 |
+
np.load(self.aux_data_path + "norm_factors/std_amsub.npy")
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
+
return
|
| 259 |
+
|
| 260 |
+
def load_ascat_data(self):
|
| 261 |
+
"""
|
| 262 |
+
Load the ASCAT data
|
| 263 |
+
"""
|
| 264 |
+
|
| 265 |
+
self.ascat_y = np.memmap(
|
| 266 |
+
self.data_path + "ascat/2007_2021_ascat.mmap",
|
| 267 |
+
dtype="float32",
|
| 268 |
+
mode="r",
|
| 269 |
+
shape=ASCAT_Y_SHAPE,
|
| 270 |
+
)
|
| 271 |
+
self.ascat_index_offset = ASCAT_OFFSETS[self.start_date]
|
| 272 |
+
|
| 273 |
+
xx = np.linspace(0, 359, 360, dtype=np.float32)
|
| 274 |
+
xx = ((xx + 360) % 360) / LATLON_SCALE_FACTOR
|
| 275 |
+
yy = np.linspace(-90, 90, 181, dtype=np.float32) / LATLON_SCALE_FACTOR
|
| 276 |
+
self.ascat_x = [xx, np.copy(yy[::-1])]
|
| 277 |
+
|
| 278 |
+
self.ascat_means = self.to_tensor(
|
| 279 |
+
np.load(self.aux_data_path + "norm_factors/mean_ascat.npy")
|
| 280 |
+
)
|
| 281 |
+
self.ascat_stds = self.to_tensor(
|
| 282 |
+
np.load(self.aux_data_path + "norm_factors/std_ascat.npy")
|
| 283 |
+
)
|
| 284 |
+
|
| 285 |
+
return
|
| 286 |
+
|
| 287 |
+
def load_hirs_data(self):
|
| 288 |
+
"""
|
| 289 |
+
Load the HIRS data
|
| 290 |
+
"""
|
| 291 |
+
|
| 292 |
+
self.hirs_y = np.memmap(
|
| 293 |
+
self.data_path + "hirs/2007_2021_hirs.mmap",
|
| 294 |
+
dtype="float32",
|
| 295 |
+
mode="r",
|
| 296 |
+
shape=HIRS_Y_SHAPE,
|
| 297 |
+
)
|
| 298 |
+
self.hirs_index_offset = ASCAT_OFFSETS[self.start_date]
|
| 299 |
+
|
| 300 |
+
xx = np.linspace(0, 359, 360, dtype=np.float32)
|
| 301 |
+
xx = ((xx + 360) % 360) / LATLON_SCALE_FACTOR
|
| 302 |
+
yy = np.linspace(-90, 90, 181, dtype=np.float32) / LATLON_SCALE_FACTOR
|
| 303 |
+
self.hirs_x = [xx, np.copy(yy[::-1])]
|
| 304 |
+
|
| 305 |
+
self.hirs_means = self.to_tensor(
|
| 306 |
+
np.load(self.aux_data_path + "norm_factors/hirs_means.npy")
|
| 307 |
+
)
|
| 308 |
+
self.hirs_stds = self.to_tensor(
|
| 309 |
+
np.load(self.aux_data_path + "norm_factors/hirs_stds.npy")
|
| 310 |
+
)
|
| 311 |
+
|
| 312 |
+
return
|
| 313 |
+
|
| 314 |
+
def load_sat_data(self):
|
| 315 |
+
"""
|
| 316 |
+
Load the GRIDSAT data
|
| 317 |
+
"""
|
| 318 |
+
|
| 319 |
+
self.sat_y = np.memmap(
|
| 320 |
+
self.data_path + "gridsat/gridsat_data.mmap",
|
| 321 |
+
dtype="float32",
|
| 322 |
+
mode="r",
|
| 323 |
+
shape=GRIDSAT_Y_SHAPE,
|
| 324 |
+
)
|
| 325 |
+
|
| 326 |
+
xx = np.load(self.data_path + "gridsat/sat_x.npy") / LATLON_SCALE_FACTOR
|
| 327 |
+
yy = np.load(self.data_path + "gridsat/sat_y.npy") / LATLON_SCALE_FACTOR
|
| 328 |
+
self.sat_x = [xx, yy]
|
| 329 |
+
self.sat_index_offset = SAT_OFFSETS[self.start_date]
|
| 330 |
+
|
| 331 |
+
self.sat_means = self.to_tensor(
|
| 332 |
+
np.load(self.aux_data_path + "norm_factors/mean_sat.npy")
|
| 333 |
+
)
|
| 334 |
+
self.sat_stds = self.to_tensor(
|
| 335 |
+
np.load(self.aux_data_path + "norm_factors/std_sat.npy")
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
return
|
| 339 |
+
|
| 340 |
+
def load_iasi(self):
|
| 341 |
+
"""
|
| 342 |
+
Load the IASI data
|
| 343 |
+
"""
|
| 344 |
+
|
| 345 |
+
self.iasi = np.memmap(
|
| 346 |
+
self.data_path + "2007_2021_iasi_subset.mmap",
|
| 347 |
+
dtype="float32",
|
| 348 |
+
mode="r",
|
| 349 |
+
shape=IASI_Y_SHAPE,
|
| 350 |
+
)
|
| 351 |
+
self.iasi_index_offset = ASCAT_OFFSETS[self.start_date]
|
| 352 |
+
|
| 353 |
+
xx = np.linspace(0, 359, 360, dtype=np.float32)
|
| 354 |
+
xx = ((xx + 360) % 360) / LATLON_SCALE_FACTOR
|
| 355 |
+
yy = np.linspace(-90, 90, 181, dtype=np.float32) / LATLON_SCALE_FACTOR
|
| 356 |
+
self.iasi_x = [xx, np.copy(yy[::-1])]
|
| 357 |
+
|
| 358 |
+
self.iasi_means = self.to_tensor(
|
| 359 |
+
np.load(self.aux_data_path + "norm_factors/mean_iasi.npy")
|
| 360 |
+
)
|
| 361 |
+
self.iasi_stds = self.to_tensor(
|
| 362 |
+
np.load(self.aux_data_path + "norm_factors/std_iasi.npy")
|
| 363 |
+
)
|
| 364 |
+
|
| 365 |
+
return
|
| 366 |
+
|
| 367 |
+
def load_hadisd(self, mode):
|
| 368 |
+
"""
|
| 369 |
+
Load the HADISD data
|
| 370 |
+
"""
|
| 371 |
+
|
| 372 |
+
self.hadisd_x = []
|
| 373 |
+
self.hadisd_alt = []
|
| 374 |
+
self.hadisd_y = []
|
| 375 |
+
hadisd_vars = ["tas", "tds", "psl", "u", "v"]
|
| 376 |
+
for var in hadisd_vars:
|
| 377 |
+
lon = lon_to_0_360(
|
| 378 |
+
np.load(
|
| 379 |
+
self.data_path + "hadisd_processed/{}_lon_{}.npy".format(var, mode)
|
| 380 |
+
)
|
| 381 |
+
)
|
| 382 |
+
lat = np.load(
|
| 383 |
+
self.data_path + "hadisd_processed/{}_lat_{}.npy".format(var, mode)
|
| 384 |
+
)
|
| 385 |
+
alt = np.load(
|
| 386 |
+
self.data_path + "hadisd_processed/{}_alt_{}.npy".format(var, mode)
|
| 387 |
+
)
|
| 388 |
+
|
| 389 |
+
vals = np.memmap(
|
| 390 |
+
self.data_path
|
| 391 |
+
+ "hadisd_processed/{}_vals_{}.memmap".format(var, self.mode),
|
| 392 |
+
dtype="float32",
|
| 393 |
+
mode="r",
|
| 394 |
+
shape=get_hadisd_shape(mode),
|
| 395 |
+
)
|
| 396 |
+
|
| 397 |
+
self.hadisd_x.append(np.stack([lon, lat], axis=-1) / LATLON_SCALE_FACTOR)
|
| 398 |
+
self.hadisd_alt.append(alt)
|
| 399 |
+
self.hadisd_y.append(vals)
|
| 400 |
+
|
| 401 |
+
self.hadisd_index_offset = HADISD_OFFSETS[self.start_date]
|
| 402 |
+
|
| 403 |
+
self.hadisd_means = [
|
| 404 |
+
self.to_tensor(
|
| 405 |
+
np.load(
|
| 406 |
+
self.aux_data_path + "norm_factors/mean_hadisd_{}.npy".format(var)
|
| 407 |
+
)
|
| 408 |
+
)
|
| 409 |
+
for var in hadisd_vars
|
| 410 |
+
]
|
| 411 |
+
self.hadisd_stds = [
|
| 412 |
+
self.to_tensor(
|
| 413 |
+
np.load(
|
| 414 |
+
self.aux_data_path + "norm_factors/std_hadisd_{}.npy".format(var)
|
| 415 |
+
)
|
| 416 |
+
)
|
| 417 |
+
for var in hadisd_vars
|
| 418 |
+
]
|
| 419 |
+
|
| 420 |
+
return
|
| 421 |
+
|
| 422 |
+
def load_era5(self, year):
|
| 423 |
+
"""
|
| 424 |
+
Load the ERA5 training data
|
| 425 |
+
"""
|
| 426 |
+
|
| 427 |
+
if year % 4 == 0:
|
| 428 |
+
d = 366 * 4
|
| 429 |
+
else:
|
| 430 |
+
d = 365 * 4
|
| 431 |
+
|
| 432 |
+
if self.era5_mode == "sfc":
|
| 433 |
+
levels = 4
|
| 434 |
+
elif self.era5_mode == "13u":
|
| 435 |
+
levels = 69
|
| 436 |
+
else:
|
| 437 |
+
levels = 24
|
| 438 |
+
|
| 439 |
+
if self.res == 1:
|
| 440 |
+
x = 240
|
| 441 |
+
y = 121
|
| 442 |
+
elif self.res == 5:
|
| 443 |
+
x = 64
|
| 444 |
+
y = 32
|
| 445 |
+
mmap = np.memmap(
|
| 446 |
+
self.data_path
|
| 447 |
+
+ "/era5/era5_{}_{}_6_{}.memmap".format(self.era5_mode, self.res, year),
|
| 448 |
+
dtype="float32",
|
| 449 |
+
mode="r",
|
| 450 |
+
shape=(d, levels, x, y),
|
| 451 |
+
)
|
| 452 |
+
return mmap
|
| 453 |
+
|
| 454 |
+
def norm_era5(self, x):
|
| 455 |
+
|
| 456 |
+
x = (x - self.means) / self.stds
|
| 457 |
+
return x
|
| 458 |
+
|
| 459 |
+
def unnorm_era5(self, x):
|
| 460 |
+
|
| 461 |
+
x = x * self.stds + self.means
|
| 462 |
+
return x
|
| 463 |
+
|
| 464 |
+
def norm_data(self, x, means, stds):
|
| 465 |
+
return (x - means) / stds
|
| 466 |
+
|
| 467 |
+
def norm_hadisd(self, x):
|
| 468 |
+
for i in range(5):
|
| 469 |
+
x[i] = (x[i] - self.hadisd_means[i]) / self.hadisd_stds[i]
|
| 470 |
+
return x
|
| 471 |
+
|
| 472 |
+
def __len__(self):
|
| 473 |
+
return self.index.shape[0] - 1 - 1
|
| 474 |
+
|
| 475 |
+
def to_tensor(self, arr):
|
| 476 |
+
return torch.from_numpy(arr).float().to(self.device)
|
| 477 |
+
|
| 478 |
+
def get_time_aux(self, current_date):
|
| 479 |
+
"""
|
| 480 |
+
Return the auxiliary temporal channels given a date
|
| 481 |
+
"""
|
| 482 |
+
|
| 483 |
+
doy = current_date.dayofyear
|
| 484 |
+
year = (current_date.year - 2007) / 15
|
| 485 |
+
time_of_day = current_date.hour
|
| 486 |
+
return np.array(
|
| 487 |
+
[
|
| 488 |
+
np.cos(np.pi * 2 * doy / DAYS_IN_YEAR),
|
| 489 |
+
np.sin(np.pi * 2 * doy / DAYS_IN_YEAR),
|
| 490 |
+
np.cos(np.pi * 2 * time_of_day / 24),
|
| 491 |
+
np.sin(np.pi * 2 * time_of_day / 24),
|
| 492 |
+
year,
|
| 493 |
+
]
|
| 494 |
+
)
|
| 495 |
+
|
| 496 |
+
|
| 497 |
+
class WeatherDatasetAssimilation(WeatherDataset):
|
| 498 |
+
"""
|
| 499 |
+
Encoder training loader
|
| 500 |
+
"""
|
| 501 |
+
|
| 502 |
+
def __init__(
|
| 503 |
+
self,
|
| 504 |
+
device,
|
| 505 |
+
hadisd_mode,
|
| 506 |
+
start_date,
|
| 507 |
+
end_date,
|
| 508 |
+
lead_time,
|
| 509 |
+
era5_mode="sfc",
|
| 510 |
+
res=1,
|
| 511 |
+
filter_dates=None,
|
| 512 |
+
var_start=0,
|
| 513 |
+
var_end=24,
|
| 514 |
+
diff=False,
|
| 515 |
+
two_frames=False,
|
| 516 |
+
):
|
| 517 |
+
|
| 518 |
+
super().__init__(
|
| 519 |
+
device,
|
| 520 |
+
hadisd_mode,
|
| 521 |
+
start_date,
|
| 522 |
+
end_date,
|
| 523 |
+
lead_time,
|
| 524 |
+
era5_mode,
|
| 525 |
+
res=res,
|
| 526 |
+
filter_dates=filter_dates,
|
| 527 |
+
diff=diff,
|
| 528 |
+
)
|
| 529 |
+
|
| 530 |
+
# Setup
|
| 531 |
+
|
| 532 |
+
self.var_start = var_start
|
| 533 |
+
self.var_end = var_end
|
| 534 |
+
self.diff = diff
|
| 535 |
+
self.two_frames = two_frames
|
| 536 |
+
|
| 537 |
+
def load_era5_time(self, index):
|
| 538 |
+
"""
|
| 539 |
+
ERA5 ground truth data loading
|
| 540 |
+
"""
|
| 541 |
+
|
| 542 |
+
date = self.dates[index]
|
| 543 |
+
year = date.year
|
| 544 |
+
hour = date.hour
|
| 545 |
+
doy = (date.dayofyear - 1) * 4 + (hour // 6)
|
| 546 |
+
|
| 547 |
+
era5 = self.era5_sfc[year - int(self.start_date[:4])][doy, ...]
|
| 548 |
+
era5 = np.copy(era5)
|
| 549 |
+
if self.diff:
|
| 550 |
+
era5 = era5 - self.era5_mean_spatial
|
| 551 |
+
era5 = self.norm_era5(era5[np.newaxis, ...])[0, ...]
|
| 552 |
+
return era5
|
| 553 |
+
|
| 554 |
+
def load_year_end(self, year, doy):
|
| 555 |
+
data_1 = self.era5_sfc[year - int(self.start_date[:4])][doy : doy + 1, ...]
|
| 556 |
+
missing = self.lead_time - data_1.shape[0] + 1
|
| 557 |
+
data_2 = self.era5_sfc[year - int(self.start_date[:4]) + 1][:missing, ...]
|
| 558 |
+
data = np.concatenate([data_1, data_2])
|
| 559 |
+
return data
|
| 560 |
+
|
| 561 |
+
def load_era5_slice(self, index):
|
| 562 |
+
"""
|
| 563 |
+
ERA5 ground truth data loading
|
| 564 |
+
"""
|
| 565 |
+
|
| 566 |
+
date = self.dates[index]
|
| 567 |
+
year = date.year
|
| 568 |
+
doy = (date.dayofyear - 1) * 4
|
| 569 |
+
|
| 570 |
+
next_date = self.dates[index + 1]
|
| 571 |
+
next_year = next_date.year
|
| 572 |
+
|
| 573 |
+
if next_year != year:
|
| 574 |
+
era5 = self.load_year_end(year, doy)
|
| 575 |
+
else:
|
| 576 |
+
era5 = self.era5_sfc[year - int(self.start_date[:4])][doy : doy + 1, ...]
|
| 577 |
+
|
| 578 |
+
era5 = self.norm_era5(np.copy(era5))
|
| 579 |
+
return era5
|
| 580 |
+
|
| 581 |
+
def __getitem__(self, index):
|
| 582 |
+
|
| 583 |
+
if self.two_frames:
|
| 584 |
+
# Case 1: loading t=0 and t=-1
|
| 585 |
+
index = index + 1
|
| 586 |
+
current = self.get_index(index, "current")
|
| 587 |
+
prev = self.get_index(index - 1, "prev")
|
| 588 |
+
current["y_target"] = current["y_target_current"]
|
| 589 |
+
|
| 590 |
+
return {**current, **prev}
|
| 591 |
+
else:
|
| 592 |
+
# Case 2: loading t=0
|
| 593 |
+
current = self.get_index(index, "current")
|
| 594 |
+
current["y_target"] = current["y_target_current"]
|
| 595 |
+
|
| 596 |
+
return {**current}
|
| 597 |
+
|
| 598 |
+
def unnorm_pred(self, x):
|
| 599 |
+
dev = x.device
|
| 600 |
+
x = x.detach().cpu().numpy()
|
| 601 |
+
|
| 602 |
+
x = (
|
| 603 |
+
x
|
| 604 |
+
* self.stds[np.newaxis, ...].transpose(0, 2, 3, 1)[
|
| 605 |
+
..., self.var_start : self.var_end
|
| 606 |
+
]
|
| 607 |
+
+ self.means[np.newaxis, ...].transpose(0, 2, 3, 1)[
|
| 608 |
+
..., self.var_start : self.var_end
|
| 609 |
+
]
|
| 610 |
+
)
|
| 611 |
+
if bool(self.diff):
|
| 612 |
+
x = (
|
| 613 |
+
x
|
| 614 |
+
+ self.era5_mean_spatial[np.newaxis, ...].transpose(0, 3, 2, 1)[
|
| 615 |
+
..., self.var_start : self.var_end
|
| 616 |
+
]
|
| 617 |
+
)
|
| 618 |
+
return torch.from_numpy(x).float().to(dev)
|
| 619 |
+
|
| 620 |
+
def get_index(self, index, prefix):
|
| 621 |
+
"""
|
| 622 |
+
Load data for the relevant index respecting different offsets depending on the modality
|
| 623 |
+
"""
|
| 624 |
+
|
| 625 |
+
index = self.index[index]
|
| 626 |
+
date = self.dates[index]
|
| 627 |
+
|
| 628 |
+
# ICOADS
|
| 629 |
+
icoads_x = self.icoads_x[index + self.icoads_index_offset, ...]
|
| 630 |
+
icoads_y = self.icoads_y[index + self.icoads_index_offset, ...]
|
| 631 |
+
icoads_x = [icoads_x[0, :], icoads_x[1, :]]
|
| 632 |
+
icoads_x = [self.to_tensor(i) for i in icoads_x]
|
| 633 |
+
icoads_y = self.to_tensor(icoads_y)
|
| 634 |
+
icoads_y = self.norm_data(icoads_y, self.icoads_means, self.icoads_stds)
|
| 635 |
+
|
| 636 |
+
# GRIDSAT
|
| 637 |
+
sat_y = self.sat_y[index + self.sat_index_offset, ...]
|
| 638 |
+
sat_x = [self.to_tensor(i) for i in self.sat_x]
|
| 639 |
+
sat_y = self.to_tensor(sat_y)
|
| 640 |
+
sat_y = self.norm_data(sat_y, self.sat_means, self.sat_stds)
|
| 641 |
+
|
| 642 |
+
# AMSU-A
|
| 643 |
+
amsua_y = self.to_tensor(self.amsua_y[index + self.amsua_index_offset, ...])
|
| 644 |
+
amsua_y[amsua_y < -998] = torch.nan
|
| 645 |
+
amsua_x = [self.to_tensor(i) for i in self.amsua_x]
|
| 646 |
+
amsua_y[amsua_y < -998] = np.nan
|
| 647 |
+
amsua_y = self.norm_data(amsua_y, self.amsua_means, self.amsua_stds)
|
| 648 |
+
|
| 649 |
+
# AMSU-B
|
| 650 |
+
amsub_y = self.to_tensor(self.amsub_y[index + self.amsub_index_offset, ...])
|
| 651 |
+
amsub_y[amsub_y < -998] = torch.nan
|
| 652 |
+
amsub_x = [self.to_tensor(i) for i in self.amsub_x]
|
| 653 |
+
amsub_y[amsub_y < -998] = np.nan
|
| 654 |
+
amsub_y = self.norm_data(amsub_y, self.amsub_means, self.amsub_stds)
|
| 655 |
+
|
| 656 |
+
# IASI
|
| 657 |
+
iasi_y = self.to_tensor(self.iasi[index + self.iasi_index_offset, ...])
|
| 658 |
+
iasi_x = [self.to_tensor(i) for i in self.iasi_x]
|
| 659 |
+
iasi_y = self.norm_data(iasi_y, self.iasi_means, self.iasi_stds)
|
| 660 |
+
|
| 661 |
+
# IGRA
|
| 662 |
+
igra_y = self.to_tensor(self.igra_y[index + self.igra_index_offset, ...])
|
| 663 |
+
igra_x = [self.igra_x[:, 0], self.igra_x[:, 1]]
|
| 664 |
+
igra_x = [self.to_tensor(i) for i in igra_x]
|
| 665 |
+
igra_y = self.norm_data(igra_y, self.igra_means, self.igra_stds)
|
| 666 |
+
|
| 667 |
+
# ASCAT
|
| 668 |
+
ascat_y = self.to_tensor(self.ascat_y[index + self.ascat_index_offset, ...])
|
| 669 |
+
ascat_x = [self.to_tensor(i) for i in self.ascat_x]
|
| 670 |
+
ascat_y[..., 4][ascat_y[..., 4] < -990] = np.nan
|
| 671 |
+
ascat_y = self.norm_data(ascat_y, self.ascat_means, self.ascat_stds)
|
| 672 |
+
|
| 673 |
+
# HIRS
|
| 674 |
+
hirs_y = self.to_tensor(self.hirs_y[index + self.hirs_index_offset, ...])
|
| 675 |
+
hirs_y[hirs_y < -998] = np.nan
|
| 676 |
+
hirs_x = [self.to_tensor(i) for i in self.hirs_x]
|
| 677 |
+
hirs_y = self.norm_data(hirs_y, self.hirs_means, self.hirs_stds)
|
| 678 |
+
|
| 679 |
+
# HadISD
|
| 680 |
+
x_context_hadisd = self.hadisd_x
|
| 681 |
+
y_context_hadisd = [
|
| 682 |
+
i[index + self.hadisd_index_offset, :] for i in self.hadisd_y
|
| 683 |
+
]
|
| 684 |
+
x_context_hadisd = [self.to_tensor(i).permute(1, 0) for i in x_context_hadisd]
|
| 685 |
+
y_context_hadisd = [self.to_tensor(i) for i in y_context_hadisd]
|
| 686 |
+
y_context_hadisd = self.norm_hadisd(y_context_hadisd)
|
| 687 |
+
|
| 688 |
+
# ERA5
|
| 689 |
+
era5 = self.to_tensor(self.load_era5_time(index))
|
| 690 |
+
era5_target = era5.permute(2, 1, 0)
|
| 691 |
+
era5_x = self.era5_x
|
| 692 |
+
|
| 693 |
+
# AUxiliary variables
|
| 694 |
+
aux_time = self.to_tensor(self.get_time_aux(date))
|
| 695 |
+
climatology = self.climatology[date.hour // 6, date.dayofyear - 1, ...]
|
| 696 |
+
|
| 697 |
+
task = {
|
| 698 |
+
"x_context_hadisd_{}".format(prefix): x_context_hadisd,
|
| 699 |
+
"y_context_hadisd_{}".format(prefix): y_context_hadisd,
|
| 700 |
+
"climatology_{}".format(prefix): self.to_tensor(climatology),
|
| 701 |
+
"sat_x_{}".format(prefix): sat_x,
|
| 702 |
+
"sat_{}".format(prefix): sat_y,
|
| 703 |
+
"icoads_x_{}".format(prefix): icoads_x,
|
| 704 |
+
"icoads_{}".format(prefix): icoads_y,
|
| 705 |
+
"igra_x_{}".format(prefix): igra_x,
|
| 706 |
+
"igra_{}".format(prefix): igra_y,
|
| 707 |
+
"amsua_{}".format(prefix): amsua_y,
|
| 708 |
+
"amsua_x_{}".format(prefix): amsua_x,
|
| 709 |
+
"amsub_{}".format(prefix): amsub_y,
|
| 710 |
+
"amsub_x_{}".format(prefix): amsub_x,
|
| 711 |
+
"iasi_{}".format(prefix): iasi_y,
|
| 712 |
+
"iasi_x_{}".format(prefix): iasi_x,
|
| 713 |
+
"ascat_{}".format(prefix): ascat_y,
|
| 714 |
+
"ascat_x_{}".format(prefix): ascat_x,
|
| 715 |
+
"hirs_{}".format(prefix): hirs_y,
|
| 716 |
+
"hirs_x_{}".format(prefix): hirs_x,
|
| 717 |
+
"y_target_{}".format(prefix): era5_target[
|
| 718 |
+
..., self.var_start : self.var_end
|
| 719 |
+
],
|
| 720 |
+
"era5_x_{}".format(prefix): era5_x,
|
| 721 |
+
"era5_elev_{}".format(prefix): self.era5_elev,
|
| 722 |
+
"era5_lonlat_{}".format(prefix): self.era5_lonlat,
|
| 723 |
+
"aux_time_{}".format(prefix): aux_time,
|
| 724 |
+
"lt": torch.Tensor([self.var_start]),
|
| 725 |
+
}
|
| 726 |
+
|
| 727 |
+
return task
|
| 728 |
+
|
| 729 |
+
|
| 730 |
+
class HadISDDataset(Dataset):
|
| 731 |
+
"""
|
| 732 |
+
HadISD dataset for decoder training
|
| 733 |
+
"""
|
| 734 |
+
|
| 735 |
+
def __init__(self, var, mode, device, start_date, end_date):
|
| 736 |
+
super().__init__()
|
| 737 |
+
|
| 738 |
+
# Setup
|
| 739 |
+
if not mode in ["train", "val", "test"]:
|
| 740 |
+
raise Exception(f"mode is {mode}. Must be train, val, or test.")
|
| 741 |
+
|
| 742 |
+
self.var = var
|
| 743 |
+
self.mode = mode
|
| 744 |
+
self.start_date = start_date
|
| 745 |
+
self.device = device
|
| 746 |
+
dates = pd.date_range(start_date, end_date, freq="6H")
|
| 747 |
+
self.index = np.array(range(len(dates)))
|
| 748 |
+
|
| 749 |
+
# Load the hadISD data
|
| 750 |
+
self.load_hadisd()
|
| 751 |
+
|
| 752 |
+
def load_hadisd(self):
|
| 753 |
+
"""
|
| 754 |
+
Load the raw HadISD data
|
| 755 |
+
"""
|
| 756 |
+
|
| 757 |
+
data_path = "path_to_data/"
|
| 758 |
+
aux_data_path = "path_to_auxiliary_data/"
|
| 759 |
+
var = self.var
|
| 760 |
+
mode = self.mode
|
| 761 |
+
|
| 762 |
+
vals = np.memmap(
|
| 763 |
+
data_path + f"hadisd_processed/{var}_vals_{mode}.memmap",
|
| 764 |
+
dtype="float32",
|
| 765 |
+
mode="r",
|
| 766 |
+
shape=get_hadisd_shape(mode),
|
| 767 |
+
)
|
| 768 |
+
|
| 769 |
+
lon = lon_to_0_360(
|
| 770 |
+
np.load(data_path + f"hadisd_processed/{var}_lon_{mode}.npy")
|
| 771 |
+
)
|
| 772 |
+
lat = np.load(data_path + f"hadisd_processed/{var}_lat_{mode}.npy")
|
| 773 |
+
self.hadisd_x = np.stack([lon, lat], axis=-1) / LATLON_SCALE_FACTOR
|
| 774 |
+
self.hadisd_alt = np.load(
|
| 775 |
+
data_path + f"hadisd_processed/{var}_alt_{mode}_final.npy"
|
| 776 |
+
)
|
| 777 |
+
self.hadisd_y = vals
|
| 778 |
+
|
| 779 |
+
self.hadisd_index_offset = HADISD_OFFSETS[self.start_date]
|
| 780 |
+
self.hadisd_means = self.to_tensor(
|
| 781 |
+
np.load(aux_data_path + f"norm_factors/mean_hadisd_{var}.npy")
|
| 782 |
+
)
|
| 783 |
+
self.hadisd_stds = self.to_tensor(
|
| 784 |
+
np.load(aux_data_path + f"norm_factors/std_hadisd_{var}.npy")
|
| 785 |
+
)
|
| 786 |
+
return
|
| 787 |
+
|
| 788 |
+
def norm_hadisd(self, x):
|
| 789 |
+
return (x - self.hadisd_means) / self.hadisd_stds
|
| 790 |
+
|
| 791 |
+
def unnorm_pred(self, x):
|
| 792 |
+
return self.hadisd_means + self.hadisd_stds * x
|
| 793 |
+
|
| 794 |
+
def __len__(self):
|
| 795 |
+
return self.index.shape[0] - 2
|
| 796 |
+
|
| 797 |
+
def to_tensor(self, arr):
|
| 798 |
+
return torch.from_numpy(np.array(arr)).float().to(self.device)
|
| 799 |
+
|
| 800 |
+
def __getitem__(self, index):
|
| 801 |
+
index = self.index[index]
|
| 802 |
+
|
| 803 |
+
# Get longitude-latitude locations
|
| 804 |
+
x_target = self.to_tensor(self.hadisd_x).permute(1, 0)
|
| 805 |
+
|
| 806 |
+
# Get altitude and normalise
|
| 807 |
+
m_alt = np.expand_dims(np.load("path_to_mean_alt.npy"), 1)
|
| 808 |
+
s_alt = np.expand_dims(np.load("path_to_std_alt.npy"), 1)
|
| 809 |
+
alt_target = self.to_tensor((self.hadisd_alt - m_alt) / s_alt)[:, :]
|
| 810 |
+
|
| 811 |
+
# Get observations
|
| 812 |
+
y_target = self.norm_hadisd(
|
| 813 |
+
self.to_tensor(self.hadisd_y[index + self.hadisd_index_offset, :])
|
| 814 |
+
)
|
| 815 |
+
|
| 816 |
+
assert x_target.shape[0] == 2
|
| 817 |
+
n_stations = x_target.shape[1]
|
| 818 |
+
assert alt_target.shape[1] == n_stations
|
| 819 |
+
assert y_target.shape[0] == n_stations
|
| 820 |
+
|
| 821 |
+
return {"x": x_target, "altitude": alt_target, "y": y_target}
|
| 822 |
+
|
| 823 |
+
|
| 824 |
+
class AardvarkICDataset(Dataset):
|
| 825 |
+
"""
|
| 826 |
+
Helper dataset to handle initial condition loading for decoder training
|
| 827 |
+
"""
|
| 828 |
+
|
| 829 |
+
def __init__(self, device, start_date, end_date, lead_time=0):
|
| 830 |
+
super().__init__()
|
| 831 |
+
|
| 832 |
+
# Setup
|
| 833 |
+
|
| 834 |
+
if lead_time == 0:
|
| 835 |
+
# If leadtime is 0 load the output of the encoder
|
| 836 |
+
if start_date == "2007-01-02" and end_date == "2017-12-31":
|
| 837 |
+
ic_fname = "ic_train.mmap"
|
| 838 |
+
elif start_date == "2019-01-01" and end_date == "2019-12-01":
|
| 839 |
+
ic_fname = "ic_val.mmap"
|
| 840 |
+
elif start_date == "2018-01-01" and end_date == "2018-12-31":
|
| 841 |
+
ic_fname = "ic_test.mmap"
|
| 842 |
+
else:
|
| 843 |
+
print((start_date, end_date))
|
| 844 |
+
raise Exception("Invalid start and end date")
|
| 845 |
+
|
| 846 |
+
dates = pd.date_range(start_date, end_date, freq="6H")
|
| 847 |
+
|
| 848 |
+
self.data = np.memmap(
|
| 849 |
+
"path_to_encoder_predictions/" + ic_fname,
|
| 850 |
+
dtype="float32",
|
| 851 |
+
mode="r",
|
| 852 |
+
shape=(len(dates), 121, 240, 24), # shape of the output
|
| 853 |
+
)
|
| 854 |
+
else:
|
| 855 |
+
# if leadtime >0 load the forecast generated from the encoder prediction
|
| 856 |
+
if start_date == "2007-01-02" and end_date == "2017-12-31":
|
| 857 |
+
ic_fname = f"ic_train_{lead_time}.mmap"
|
| 858 |
+
|
| 859 |
+
elif start_date == "2019-01-01" and end_date == "2019-12-01":
|
| 860 |
+
ic_fname = f"ic_val_{lead_time}.mmap"
|
| 861 |
+
elif start_date == "2018-01-01" and end_date == "2018-12-31":
|
| 862 |
+
ic_fname = f"ic_test_{lead_time}.mmap"
|
| 863 |
+
else:
|
| 864 |
+
print((start_date, end_date))
|
| 865 |
+
raise Exception("Invalid start and end date.")
|
| 866 |
+
|
| 867 |
+
dates = pd.date_range(start_date, end_date, freq="6H")[(lead_time) * 4 :]
|
| 868 |
+
ic_shape = (len(dates), 121, 240, 24)
|
| 869 |
+
|
| 870 |
+
self.data = np.memmap(
|
| 871 |
+
self.data_path + "forecast_finetune/" + ic_fname,
|
| 872 |
+
dtype="float32",
|
| 873 |
+
mode="r",
|
| 874 |
+
shape=ic_shape,
|
| 875 |
+
)
|
| 876 |
+
|
| 877 |
+
self.device = device
|
| 878 |
+
|
| 879 |
+
# Normalisation
|
| 880 |
+
aux_data_path = "path_to_auxiliary_data/"
|
| 881 |
+
mean_factors_path = aux_data_path + f"norm_factors/mean_4u_1.npy"
|
| 882 |
+
std_factors_path = aux_data_path + f"norm_factors/std_4u_1.npy"
|
| 883 |
+
self.means = np.load(mean_factors_path)[:, np.newaxis, np.newaxis, ...]
|
| 884 |
+
self.stds = np.load(std_factors_path)[:, np.newaxis, np.newaxis, ...]
|
| 885 |
+
|
| 886 |
+
def __getitem__(self, index):
|
| 887 |
+
# Load Aardvark prediction and normalise
|
| 888 |
+
data_raw = np.transpose(np.copy(self.data[index, :, :, :]), (2, 1, 0))
|
| 889 |
+
data = (data_raw - self.means) / self.stds
|
| 890 |
+
return torch.from_numpy(data).to(self.device)
|
| 891 |
+
|
| 892 |
+
|
| 893 |
+
class WeatherDatasetDownscaling(Dataset):
|
| 894 |
+
"""
|
| 895 |
+
Main decoder training dataset. Uses AardvarkICDataset and HadISDDataset to
|
| 896 |
+
handle processor output and station data
|
| 897 |
+
"""
|
| 898 |
+
|
| 899 |
+
def __init__(
|
| 900 |
+
self,
|
| 901 |
+
device,
|
| 902 |
+
hadisd_mode,
|
| 903 |
+
start_date,
|
| 904 |
+
end_date,
|
| 905 |
+
context_mode,
|
| 906 |
+
era5_mode="sfc",
|
| 907 |
+
res=1,
|
| 908 |
+
hadisd_var="tas",
|
| 909 |
+
lead_time=1,
|
| 910 |
+
):
|
| 911 |
+
# The context mode determines whether we make use of ERA5 or our own ICs.
|
| 912 |
+
if not context_mode in ["era5", "aardvark"]:
|
| 913 |
+
raise Exception(
|
| 914 |
+
f"context_mode must be era5 or aardvark, got {context_mode}"
|
| 915 |
+
)
|
| 916 |
+
|
| 917 |
+
super().__init__()
|
| 918 |
+
|
| 919 |
+
# Setup
|
| 920 |
+
self.lead_time = lead_time
|
| 921 |
+
|
| 922 |
+
self.device = device
|
| 923 |
+
self.data_path = "path_to_data/"
|
| 924 |
+
self.aux_data_path = "path_to_auxiliary_data/"
|
| 925 |
+
self.start_date = start_date
|
| 926 |
+
self.end_date = end_date
|
| 927 |
+
self.era5_mode = era5_mode
|
| 928 |
+
self.res = res
|
| 929 |
+
self.context_mode = context_mode
|
| 930 |
+
|
| 931 |
+
self.dates = pd.date_range(start_date, end_date, freq="6H")
|
| 932 |
+
self.index = np.array(range(len(self.dates)))
|
| 933 |
+
|
| 934 |
+
# Load ERA5 data for pre-training
|
| 935 |
+
self.era5_sfc = [
|
| 936 |
+
self.load_era5(year)
|
| 937 |
+
for year in range(int(start_date[:4]), int(end_date[:4]) + 1)
|
| 938 |
+
]
|
| 939 |
+
|
| 940 |
+
raw_era5_lon = np.load(self.data_path + f"era5/era5_x_{res}.npy")
|
| 941 |
+
raw_era5_lat = np.load(self.data_path + f"era5/era5_y_{res}.npy")
|
| 942 |
+
self.era5_x = [
|
| 943 |
+
self.to_tensor(raw_era5_lon) / LATLON_SCALE_FACTOR,
|
| 944 |
+
self.to_tensor(raw_era5_lat) / LATLON_SCALE_FACTOR,
|
| 945 |
+
]
|
| 946 |
+
|
| 947 |
+
# Load orography
|
| 948 |
+
elev_path = self.data_path + f"era5/elev_vars_{res}.npy"
|
| 949 |
+
self.era5_elev = self.to_tensor(np.load(elev_path)).permute(0, 2, 1)
|
| 950 |
+
|
| 951 |
+
# Normalisation
|
| 952 |
+
mean_factors_path = (
|
| 953 |
+
self.aux_data_path + f"norm_factors/mean_{era5_mode}_{res}.npy"
|
| 954 |
+
)
|
| 955 |
+
std_factors_path = (
|
| 956 |
+
self.aux_data_path + f"norm_factors/std_{era5_mode}_{res}.npy"
|
| 957 |
+
)
|
| 958 |
+
self.means = np.load(mean_factors_path)[:, np.newaxis, np.newaxis, ...]
|
| 959 |
+
self.stds = np.load(std_factors_path)[:, np.newaxis, np.newaxis, ...]
|
| 960 |
+
|
| 961 |
+
# HadISD data
|
| 962 |
+
self.hadisd_data = HadISDDataset(
|
| 963 |
+
var=hadisd_var,
|
| 964 |
+
mode=hadisd_mode,
|
| 965 |
+
device=device,
|
| 966 |
+
start_date=start_date,
|
| 967 |
+
end_date=end_date,
|
| 968 |
+
)
|
| 969 |
+
|
| 970 |
+
if context_mode == "aardvark":
|
| 971 |
+
# Load the Aardvark encoder predictions
|
| 972 |
+
self.aardvark_data = AardvarkICDataset(
|
| 973 |
+
device, start_date, end_date, lead_time
|
| 974 |
+
)
|
| 975 |
+
|
| 976 |
+
def load_era5(self, year):
|
| 977 |
+
"""
|
| 978 |
+
Load the raw ERA5 data
|
| 979 |
+
"""
|
| 980 |
+
|
| 981 |
+
if year % 4 == 0:
|
| 982 |
+
d = 366 * 4
|
| 983 |
+
else:
|
| 984 |
+
d = 365 * 4
|
| 985 |
+
|
| 986 |
+
if self.era5_mode == "sfc":
|
| 987 |
+
levels = 4
|
| 988 |
+
elif self.era5_mode == "13u":
|
| 989 |
+
levels = 69
|
| 990 |
+
else:
|
| 991 |
+
levels = 24
|
| 992 |
+
|
| 993 |
+
if self.res == 1:
|
| 994 |
+
x = 240
|
| 995 |
+
y = 121
|
| 996 |
+
elif self.res == 5:
|
| 997 |
+
x = 64
|
| 998 |
+
y = 32
|
| 999 |
+
mmap = np.memmap(
|
| 1000 |
+
self.data_path
|
| 1001 |
+
+ "era5/era5_{}_{}_6_{}.memmap".format(self.era5_mode, self.res, year),
|
| 1002 |
+
dtype="float32",
|
| 1003 |
+
mode="r",
|
| 1004 |
+
shape=(d, levels, x, y),
|
| 1005 |
+
)
|
| 1006 |
+
return mmap
|
| 1007 |
+
|
| 1008 |
+
def norm_era5(self, x):
|
| 1009 |
+
x = (x - self.means) / self.stds
|
| 1010 |
+
return x
|
| 1011 |
+
|
| 1012 |
+
def unnorm_era5(self, x):
|
| 1013 |
+
x = x * self.stds + self.means
|
| 1014 |
+
return x
|
| 1015 |
+
|
| 1016 |
+
def unnorm_pred(self, x):
|
| 1017 |
+
return self.hadisd_data.unnorm_pred(x)
|
| 1018 |
+
|
| 1019 |
+
def norm_data(self, x, means, stds):
|
| 1020 |
+
return (x - means) / stds
|
| 1021 |
+
|
| 1022 |
+
def __len__(self):
|
| 1023 |
+
return self.index.shape[0] - (self.lead_time) * 4
|
| 1024 |
+
|
| 1025 |
+
def to_tensor(self, arr):
|
| 1026 |
+
return torch.from_numpy(np.array(arr)).float().to(self.device)
|
| 1027 |
+
|
| 1028 |
+
def get_time_aux(self, current_date):
|
| 1029 |
+
"""
|
| 1030 |
+
Get auxiliary time variables for a given date
|
| 1031 |
+
"""
|
| 1032 |
+
|
| 1033 |
+
doy = current_date.dayofyear
|
| 1034 |
+
year = (current_date.year - 2007) / 15
|
| 1035 |
+
time_of_day = current_date.hour
|
| 1036 |
+
return np.array(
|
| 1037 |
+
[
|
| 1038 |
+
np.cos(np.pi * 2 * doy / DAYS_IN_YEAR),
|
| 1039 |
+
np.sin(np.pi * 2 * doy / DAYS_IN_YEAR),
|
| 1040 |
+
np.cos(np.pi * 2 * time_of_day / 24),
|
| 1041 |
+
np.sin(np.pi * 2 * time_of_day / 24),
|
| 1042 |
+
year,
|
| 1043 |
+
]
|
| 1044 |
+
)
|
| 1045 |
+
|
| 1046 |
+
def load_era5_time(self, index):
|
| 1047 |
+
"""
|
| 1048 |
+
Load ERA5 training data
|
| 1049 |
+
"""
|
| 1050 |
+
|
| 1051 |
+
date = self.dates[index]
|
| 1052 |
+
year = date.year
|
| 1053 |
+
hour = date.hour
|
| 1054 |
+
doy = (date.dayofyear - 1) * 4 + (hour // 6)
|
| 1055 |
+
|
| 1056 |
+
era5 = self.era5_sfc[year - int(self.start_date[:4])][doy, ...]
|
| 1057 |
+
era5 = np.copy(era5)
|
| 1058 |
+
era5 = self.norm_era5(era5[np.newaxis, ...])[0, ...]
|
| 1059 |
+
return era5
|
| 1060 |
+
|
| 1061 |
+
def load_year_end(self, year, doy):
|
| 1062 |
+
data_1 = self.era5_sfc[year - int(self.start_date[:4])][doy : doy + 1, ...]
|
| 1063 |
+
missing = self.lead_time - data_1.shape[0] + 1
|
| 1064 |
+
data_2 = self.era5_sfc[year - int(self.start_date[:4]) + 1][:missing, ...]
|
| 1065 |
+
data = np.concatenate([data_1, data_2])
|
| 1066 |
+
return data
|
| 1067 |
+
|
| 1068 |
+
def __getitem__(self, index):
|
| 1069 |
+
|
| 1070 |
+
index = self.index[index]
|
| 1071 |
+
date = self.dates[index + 4 * self.lead_time]
|
| 1072 |
+
|
| 1073 |
+
# Get HadISD data
|
| 1074 |
+
hadisd_slice = self.hadisd_data[index + 4 * self.lead_time]
|
| 1075 |
+
|
| 1076 |
+
# Get lon-lat
|
| 1077 |
+
x_context = self.era5_x
|
| 1078 |
+
n_lon = x_context[0].shape[0]
|
| 1079 |
+
n_lat = x_context[1].shape[0]
|
| 1080 |
+
|
| 1081 |
+
# Get auxiliary time
|
| 1082 |
+
aux_time = torch.reshape(self.to_tensor(self.get_time_aux(date)), (-1, 1, 1))
|
| 1083 |
+
|
| 1084 |
+
# Load the context (either aardvark or ERA5 for use in pre-training)
|
| 1085 |
+
if self.context_mode == "era5":
|
| 1086 |
+
y_context_obs = self.to_tensor(
|
| 1087 |
+
self.load_era5_time(index + 4 * self.lead_time)
|
| 1088 |
+
)
|
| 1089 |
+
|
| 1090 |
+
elif self.context_mode == "aardvark":
|
| 1091 |
+
y_context_obs = self.aardvark_data[index]
|
| 1092 |
+
|
| 1093 |
+
else:
|
| 1094 |
+
raise Exception
|
| 1095 |
+
|
| 1096 |
+
y_context = torch.cat(
|
| 1097 |
+
[
|
| 1098 |
+
y_context_obs,
|
| 1099 |
+
self.era5_elev.permute(0, 2, 1),
|
| 1100 |
+
aux_time.repeat(1, n_lon, n_lat),
|
| 1101 |
+
]
|
| 1102 |
+
)
|
| 1103 |
+
|
| 1104 |
+
assert y_context.shape[1] == n_lon
|
| 1105 |
+
assert y_context.shape[2] == n_lat
|
| 1106 |
+
|
| 1107 |
+
x = hadisd_slice["x"]
|
| 1108 |
+
alt = hadisd_slice["altitude"]
|
| 1109 |
+
y = hadisd_slice["y"]
|
| 1110 |
+
|
| 1111 |
+
return {
|
| 1112 |
+
"x_target": x,
|
| 1113 |
+
"alt_target": alt,
|
| 1114 |
+
"y_target": y,
|
| 1115 |
+
"y_context": y_context,
|
| 1116 |
+
"x_context": x_context,
|
| 1117 |
+
"aux_time": aux_time,
|
| 1118 |
+
"lt": torch.Tensor([0]),
|
| 1119 |
+
}
|
| 1120 |
+
|
| 1121 |
+
|
| 1122 |
+
class ForecasterDatasetDownscaling(Dataset):
|
| 1123 |
+
"""
|
| 1124 |
+
Dataset to generate decoder predictions from pre-saved Aardvark forecasts
|
| 1125 |
+
"""
|
| 1126 |
+
|
| 1127 |
+
def __init__(
|
| 1128 |
+
self,
|
| 1129 |
+
start_date,
|
| 1130 |
+
end_date,
|
| 1131 |
+
lead_time,
|
| 1132 |
+
hadisd_var,
|
| 1133 |
+
mode,
|
| 1134 |
+
device,
|
| 1135 |
+
forecast_path,
|
| 1136 |
+
region="global",
|
| 1137 |
+
):
|
| 1138 |
+
super().__init__()
|
| 1139 |
+
|
| 1140 |
+
# Setup
|
| 1141 |
+
|
| 1142 |
+
if not mode in ["train", "val", "test"]:
|
| 1143 |
+
raise Exception(f"Mode is {mode}. Must be either train, val, or test")
|
| 1144 |
+
|
| 1145 |
+
self.device = device
|
| 1146 |
+
self.start_date = start_date
|
| 1147 |
+
self.end_date = end_date
|
| 1148 |
+
self.lead_time = lead_time
|
| 1149 |
+
self.mode = mode
|
| 1150 |
+
self.offset = np.timedelta64(lead_time, "D").astype("timedelta64[ns]")
|
| 1151 |
+
|
| 1152 |
+
self.dates = pd.date_range(start_date, end_date, freq="6H")[:-30]
|
| 1153 |
+
|
| 1154 |
+
# Normalisation
|
| 1155 |
+
aux_data_path = "auxiliary_data_path/"
|
| 1156 |
+
self.means = np.load(aux_data_path + "norm_factors/mean_4u_1.npy")
|
| 1157 |
+
self.stds = np.load(aux_data_path + "norm_factors/std_4u_1.npy")
|
| 1158 |
+
|
| 1159 |
+
# Load auxiliary data
|
| 1160 |
+
self.load_npy_file()
|
| 1161 |
+
data_path = "data_path/"
|
| 1162 |
+
res = "1"
|
| 1163 |
+
raw_era5_lon = np.load(data_path + f"era5/era5_x_{res}.npy")
|
| 1164 |
+
raw_era5_lat = np.load(data_path + f"era5/era5_y_{res}.npy")
|
| 1165 |
+
self.era5_x = [
|
| 1166 |
+
self.to_tensor(raw_era5_lon) / LATLON_SCALE_FACTOR,
|
| 1167 |
+
self.to_tensor(raw_era5_lat) / LATLON_SCALE_FACTOR,
|
| 1168 |
+
]
|
| 1169 |
+
elev_path = data_path + f"era5/elev_vars_{res}.npy"
|
| 1170 |
+
self.era5_elev = self.to_tensor(np.load(elev_path)).permute(0, 2, 1)
|
| 1171 |
+
|
| 1172 |
+
# Load hadISD
|
| 1173 |
+
self.hadisd_data = HadISDDataset(
|
| 1174 |
+
var=hadisd_var,
|
| 1175 |
+
mode="train",
|
| 1176 |
+
device=device,
|
| 1177 |
+
start_date=start_date,
|
| 1178 |
+
end_date=end_date,
|
| 1179 |
+
)
|
| 1180 |
+
|
| 1181 |
+
# Subset to region
|
| 1182 |
+
self.region = region
|
| 1183 |
+
if self.region != "global":
|
| 1184 |
+
self.mask = np.load(
|
| 1185 |
+
self.data_path + f"hadisd_processed/tas_mask_train_{region}.npy"
|
| 1186 |
+
)
|
| 1187 |
+
|
| 1188 |
+
def date_range(self):
|
| 1189 |
+
return np.arange(
|
| 1190 |
+
start=np.datetime64(self.start_date).astype("datetime64[ns]"),
|
| 1191 |
+
stop=np.datetime64(self.end_date).astype("datetime64[ns]"),
|
| 1192 |
+
step=np.timedelta64(1, "D").astype("timedelta64[ns]"),
|
| 1193 |
+
)
|
| 1194 |
+
|
| 1195 |
+
def load_npy_file(self):
|
| 1196 |
+
"""
|
| 1197 |
+
Load the pre-saved Aardvark forecasts
|
| 1198 |
+
"""
|
| 1199 |
+
|
| 1200 |
+
dates = pd.date_range(self.start_date, self.end_date, freq="6H")
|
| 1201 |
+
|
| 1202 |
+
if self.mode == "train":
|
| 1203 |
+
dates = dates[:-40] # Need 10 day offset at end of year
|
| 1204 |
+
|
| 1205 |
+
self.Y_context = np.memmap(
|
| 1206 |
+
"path_to_forecasts/forecast_{}.mmap".format(self.mode),
|
| 1207 |
+
dtype="float32",
|
| 1208 |
+
mode="r",
|
| 1209 |
+
shape=(len(dates), 121, 240, 24, 11),
|
| 1210 |
+
)
|
| 1211 |
+
|
| 1212 |
+
return
|
| 1213 |
+
|
| 1214 |
+
def norm_era5(self, x):
|
| 1215 |
+
return (x - self.means) / self.stds
|
| 1216 |
+
|
| 1217 |
+
def norm_hadisd(self, x):
|
| 1218 |
+
return self.hadisd_data.norm_hadisd(x)
|
| 1219 |
+
|
| 1220 |
+
def unnorm_pred(self, x):
|
| 1221 |
+
return self.hadisd_data.unnorm_pred(x)
|
| 1222 |
+
|
| 1223 |
+
def __len__(self):
|
| 1224 |
+
return len(self.dates) - 40 # Need 10 day offset at end of year
|
| 1225 |
+
|
| 1226 |
+
def to_tensor(self, arr):
|
| 1227 |
+
return torch.from_numpy(np.array(arr)).float().to(self.device)
|
| 1228 |
+
|
| 1229 |
+
def get_time_aux(self, index):
|
| 1230 |
+
"""
|
| 1231 |
+
Get the auxiliary time variables
|
| 1232 |
+
"""
|
| 1233 |
+
|
| 1234 |
+
current_date = (self.dates + self.offset)[index]
|
| 1235 |
+
doy = current_date.dayofyear
|
| 1236 |
+
year = (current_date.year - 2007) / 15
|
| 1237 |
+
time_of_day = current_date.hour
|
| 1238 |
+
return np.array(
|
| 1239 |
+
[
|
| 1240 |
+
np.cos(np.pi * 2 * doy / DAYS_IN_YEAR),
|
| 1241 |
+
np.sin(np.pi * 2 * doy / DAYS_IN_YEAR),
|
| 1242 |
+
np.cos(np.pi * 2 * time_of_day / 24),
|
| 1243 |
+
np.sin(np.pi * 2 * time_of_day / 24),
|
| 1244 |
+
year,
|
| 1245 |
+
]
|
| 1246 |
+
)
|
| 1247 |
+
|
| 1248 |
+
def __getitem__(self, index):
|
| 1249 |
+
|
| 1250 |
+
# Load target data
|
| 1251 |
+
hadisd_slice = self.hadisd_data[index + 4 * self.lead_time]
|
| 1252 |
+
|
| 1253 |
+
x_context = self.era5_x
|
| 1254 |
+
n_lon = x_context[0].shape[0]
|
| 1255 |
+
n_lat = x_context[1].shape[0]
|
| 1256 |
+
|
| 1257 |
+
# Load auxiliary time
|
| 1258 |
+
aux_time = torch.reshape(self.to_tensor(self.get_time_aux(index)), (-1, 1, 1))
|
| 1259 |
+
|
| 1260 |
+
# Load input
|
| 1261 |
+
y_context = self.norm_era5(self.Y_context[index, ..., self.lead_time])
|
| 1262 |
+
y_context = torch.cat(
|
| 1263 |
+
[
|
| 1264 |
+
self.to_tensor(y_context).permute(2, 1, 0),
|
| 1265 |
+
self.era5_elev.permute(0, 2, 1),
|
| 1266 |
+
aux_time.repeat(1, n_lon, n_lat),
|
| 1267 |
+
]
|
| 1268 |
+
)
|
| 1269 |
+
|
| 1270 |
+
assert y_context.shape[1] == n_lon
|
| 1271 |
+
assert y_context.shape[2] == n_lat
|
| 1272 |
+
|
| 1273 |
+
# Handle region masking
|
| 1274 |
+
if self.region != "global":
|
| 1275 |
+
hadisd_slice["y"][self.mask] = np.nan
|
| 1276 |
+
|
| 1277 |
+
return {
|
| 1278 |
+
"x_target": hadisd_slice["x"],
|
| 1279 |
+
"alt_target": hadisd_slice["altitude"],
|
| 1280 |
+
"y_target": hadisd_slice["y"],
|
| 1281 |
+
"y_context": y_context,
|
| 1282 |
+
"x_context": x_context,
|
| 1283 |
+
"aux_time": aux_time,
|
| 1284 |
+
"lt": torch.Tensor([0]),
|
| 1285 |
+
}
|
| 1286 |
+
|
| 1287 |
+
|
| 1288 |
+
class ForecastLoader(Dataset):
|
| 1289 |
+
"""
|
| 1290 |
+
Loader for finetuning the processor module
|
| 1291 |
+
"""
|
| 1292 |
+
|
| 1293 |
+
def __init__(
|
| 1294 |
+
self,
|
| 1295 |
+
device,
|
| 1296 |
+
mode,
|
| 1297 |
+
lead_time,
|
| 1298 |
+
era5_mode="sfc",
|
| 1299 |
+
res=5,
|
| 1300 |
+
frequency=24,
|
| 1301 |
+
norm=True,
|
| 1302 |
+
diff=False,
|
| 1303 |
+
rollout=False,
|
| 1304 |
+
random_lt=False,
|
| 1305 |
+
u_only=False,
|
| 1306 |
+
ic_path=None,
|
| 1307 |
+
finetune_step=None,
|
| 1308 |
+
finetune_eval_every=100,
|
| 1309 |
+
eval_steps=False,
|
| 1310 |
+
):
|
| 1311 |
+
|
| 1312 |
+
super().__init__()
|
| 1313 |
+
|
| 1314 |
+
# Setup
|
| 1315 |
+
self.device = device
|
| 1316 |
+
self.mode = mode
|
| 1317 |
+
self.data_path = "data_path/"
|
| 1318 |
+
|
| 1319 |
+
self.lead_time = lead_time
|
| 1320 |
+
self.era5_mode = era5_mode
|
| 1321 |
+
self.res = res
|
| 1322 |
+
self.frequency = frequency
|
| 1323 |
+
self.norm = norm
|
| 1324 |
+
self.diff = diff
|
| 1325 |
+
self.rollout = rollout
|
| 1326 |
+
self.random_lt = random_lt
|
| 1327 |
+
self.u_only = u_only
|
| 1328 |
+
self.ic_path = ic_path
|
| 1329 |
+
|
| 1330 |
+
self.finetune_step = finetune_step
|
| 1331 |
+
self.finetune_eval_every = finetune_eval_every
|
| 1332 |
+
self.eval_steps = eval_steps
|
| 1333 |
+
|
| 1334 |
+
if self.frequency == 6:
|
| 1335 |
+
self.lead_time = self.lead_time * 4
|
| 1336 |
+
freq = "6H"
|
| 1337 |
+
|
| 1338 |
+
else:
|
| 1339 |
+
freq = "1D"
|
| 1340 |
+
|
| 1341 |
+
if self.mode == "train":
|
| 1342 |
+
self.dates = pd.date_range("1979-01-01", "2017-12-31", freq=freq)
|
| 1343 |
+
elif self.mode == "tune":
|
| 1344 |
+
self.dates = pd.date_range("2018-01-01", "2018-12-31", freq=freq)
|
| 1345 |
+
elif self.mode == "test":
|
| 1346 |
+
self.dates = pd.date_range("2018-01-01", "2018-12-31", freq=freq)
|
| 1347 |
+
elif self.mode == "val":
|
| 1348 |
+
self.dates = pd.date_range("2019-01-01", "2019-12-31", freq=freq)
|
| 1349 |
+
|
| 1350 |
+
# Load the predictions from the previous leadtime to be the new context set
|
| 1351 |
+
if self.finetune_step is not None:
|
| 1352 |
+
|
| 1353 |
+
if self.mode == "train":
|
| 1354 |
+
self.dates = pd.date_range("2007-01-02", "2017-12-31", freq=freq)
|
| 1355 |
+
ic_shape = (
|
| 1356 |
+
len(self.dates) - max(0, (self.finetune_step - 1) * 4),
|
| 1357 |
+
121,
|
| 1358 |
+
240,
|
| 1359 |
+
24,
|
| 1360 |
+
)
|
| 1361 |
+
elif self.mode == "val":
|
| 1362 |
+
self.dates = pd.date_range("2019-01-01", "2019-12-31", freq=freq)
|
| 1363 |
+
ic_shape = (
|
| 1364 |
+
len(self.dates) - max(0, (self.finetune_step - 1) * 4),
|
| 1365 |
+
121,
|
| 1366 |
+
240,
|
| 1367 |
+
24,
|
| 1368 |
+
)
|
| 1369 |
+
elif self.mode == "test":
|
| 1370 |
+
self.dates = pd.date_range("2018-01-01", "2018-12-31", freq=freq)
|
| 1371 |
+
ic_shape = (
|
| 1372 |
+
len(self.dates) - max(0, (self.finetune_step - 1) * 4),
|
| 1373 |
+
121,
|
| 1374 |
+
240,
|
| 1375 |
+
24,
|
| 1376 |
+
)
|
| 1377 |
+
|
| 1378 |
+
if self.finetune_step > 1:
|
| 1379 |
+
print(ic_shape)
|
| 1380 |
+
self.ic = np.memmap(
|
| 1381 |
+
self.ic_path
|
| 1382 |
+
+ "ic_{}_{}.mmap".format(self.mode, self.finetune_step - 1),
|
| 1383 |
+
dtype="float32",
|
| 1384 |
+
mode="r",
|
| 1385 |
+
shape=ic_shape,
|
| 1386 |
+
)
|
| 1387 |
+
elif self.ic_path is not None:
|
| 1388 |
+
|
| 1389 |
+
self.ic = np.memmap(
|
| 1390 |
+
self.ic_path + "ic_{}.mmap".format(self.mode),
|
| 1391 |
+
dtype="float32",
|
| 1392 |
+
mode="r",
|
| 1393 |
+
shape=ic_shape,
|
| 1394 |
+
)
|
| 1395 |
+
|
| 1396 |
+
elif self.ic_path is not None:
|
| 1397 |
+
if self.mode == "train":
|
| 1398 |
+
self.dates = pd.date_range("2007-01-02", "2017-12-31", freq=freq)
|
| 1399 |
+
ic_shape = (len(self.dates), 121, 240, 24)
|
| 1400 |
+
|
| 1401 |
+
self.ic = np.memmap(
|
| 1402 |
+
self.ic_path + "/ic_{}.mmap".format(self.mode),
|
| 1403 |
+
dtype="float32",
|
| 1404 |
+
mode="r",
|
| 1405 |
+
shape=ic_shape,
|
| 1406 |
+
)
|
| 1407 |
+
|
| 1408 |
+
# Orography
|
| 1409 |
+
self.era5_elev = np.float32(
|
| 1410 |
+
np.load(self.data_path + "era5/elev_vars_{}.npy".format(res))
|
| 1411 |
+
)
|
| 1412 |
+
elev_mean = self.era5_elev.mean(axis=(1, 2))[:, np.newaxis, np.newaxis]
|
| 1413 |
+
elev_std = self.era5_elev.std(axis=(1, 2))[:, np.newaxis, np.newaxis]
|
| 1414 |
+
self.era5_elev = (self.era5_elev - elev_mean) / elev_std
|
| 1415 |
+
|
| 1416 |
+
# ERA5 ground truth data for training
|
| 1417 |
+
self.era5_sfc = [
|
| 1418 |
+
self.load_era5(year)
|
| 1419 |
+
for year in range(int(self.dates[0].year), int(self.dates[-1].year) + 1)
|
| 1420 |
+
]
|
| 1421 |
+
|
| 1422 |
+
# Noramalisation factors
|
| 1423 |
+
self.means = (
|
| 1424 |
+
self.to_tensor(
|
| 1425 |
+
np.load(
|
| 1426 |
+
self.data_path
|
| 1427 |
+
+ "norm_factors/mean_{}_{}.npy".format(self.era5_mode, self.res)
|
| 1428 |
+
)
|
| 1429 |
+
)
|
| 1430 |
+
.unsqueeze(1)
|
| 1431 |
+
.unsqueeze(1)
|
| 1432 |
+
)
|
| 1433 |
+
self.stds = (
|
| 1434 |
+
self.to_tensor(
|
| 1435 |
+
np.load(
|
| 1436 |
+
self.data_path
|
| 1437 |
+
+ "norm_factors/std_{}_{}.npy".format(self.era5_mode, self.res)
|
| 1438 |
+
)
|
| 1439 |
+
)
|
| 1440 |
+
.unsqueeze(1)
|
| 1441 |
+
.unsqueeze(1)
|
| 1442 |
+
)
|
| 1443 |
+
self.diff_means = (
|
| 1444 |
+
self.to_tensor(
|
| 1445 |
+
np.load(
|
| 1446 |
+
self.data_path
|
| 1447 |
+
+ "norm_factors/mean_diff_{}_{}.npy".format(
|
| 1448 |
+
self.era5_mode, self.res
|
| 1449 |
+
)
|
| 1450 |
+
)
|
| 1451 |
+
)
|
| 1452 |
+
.unsqueeze(0)
|
| 1453 |
+
.unsqueeze(0)
|
| 1454 |
+
)
|
| 1455 |
+
self.diff_stds = (
|
| 1456 |
+
self.to_tensor(
|
| 1457 |
+
np.load(
|
| 1458 |
+
self.data_path
|
| 1459 |
+
+ "norm_factors/std_diff_{}_{}.npy".format(self.era5_mode, self.res)
|
| 1460 |
+
)
|
| 1461 |
+
)
|
| 1462 |
+
.unsqueeze(0)
|
| 1463 |
+
.unsqueeze(0)
|
| 1464 |
+
)
|
| 1465 |
+
|
| 1466 |
+
self.diff_means_1 = (
|
| 1467 |
+
self.to_tensor(
|
| 1468 |
+
np.load(
|
| 1469 |
+
self.data_path
|
| 1470 |
+
+ "norm_factors/mean_diff_{}_{}_6h.npy".format(
|
| 1471 |
+
self.era5_mode, self.res
|
| 1472 |
+
)
|
| 1473 |
+
)
|
| 1474 |
+
)
|
| 1475 |
+
.unsqueeze(0)
|
| 1476 |
+
.unsqueeze(0)
|
| 1477 |
+
)
|
| 1478 |
+
self.diff_stds_1 = (
|
| 1479 |
+
self.to_tensor(
|
| 1480 |
+
np.load(
|
| 1481 |
+
self.data_path
|
| 1482 |
+
+ "norm_factors/std_diff_{}_{}_6h.npy".format(
|
| 1483 |
+
self.era5_mode, self.res
|
| 1484 |
+
)
|
| 1485 |
+
)
|
| 1486 |
+
)
|
| 1487 |
+
.unsqueeze(0)
|
| 1488 |
+
.unsqueeze(0)
|
| 1489 |
+
)
|
| 1490 |
+
|
| 1491 |
+
self.diff_means_2 = (
|
| 1492 |
+
self.to_tensor(
|
| 1493 |
+
np.load(
|
| 1494 |
+
self.data_path
|
| 1495 |
+
+ "norm_factors/mean_diff_{}_{}_12h.npy".format(
|
| 1496 |
+
self.era5_mode, self.res
|
| 1497 |
+
)
|
| 1498 |
+
)
|
| 1499 |
+
)
|
| 1500 |
+
.unsqueeze(0)
|
| 1501 |
+
.unsqueeze(0)
|
| 1502 |
+
)
|
| 1503 |
+
self.diff_stds_2 = (
|
| 1504 |
+
self.to_tensor(
|
| 1505 |
+
np.load(
|
| 1506 |
+
self.data_path
|
| 1507 |
+
+ "norm_factors/std_diff_{}_{}_12h.npy".format(
|
| 1508 |
+
self.era5_mode, self.res
|
| 1509 |
+
)
|
| 1510 |
+
)
|
| 1511 |
+
)
|
| 1512 |
+
.unsqueeze(0)
|
| 1513 |
+
.unsqueeze(0)
|
| 1514 |
+
)
|
| 1515 |
+
|
| 1516 |
+
self.means_dict = {
|
| 1517 |
+
0: self.diff_means,
|
| 1518 |
+
2: self.diff_means_2,
|
| 1519 |
+
3: self.diff_means_1,
|
| 1520 |
+
}
|
| 1521 |
+
|
| 1522 |
+
self.stds_dict = {0: self.diff_stds, 2: self.diff_stds_2, 3: self.diff_stds_1}
|
| 1523 |
+
|
| 1524 |
+
def __len__(self):
|
| 1525 |
+
if np.logical_and(self.eval_steps, self.mode == "train"):
|
| 1526 |
+
return self.finetune_eval_every * 12 * 4
|
| 1527 |
+
|
| 1528 |
+
return self.dates.shape[0] - self.lead_time
|
| 1529 |
+
|
| 1530 |
+
def to_tensor(self, arr):
|
| 1531 |
+
|
| 1532 |
+
return torch.from_numpy(arr).float().to(self.device)
|
| 1533 |
+
|
| 1534 |
+
def norm_era5(self, x):
|
| 1535 |
+
x = (x - self.means) / self.stds
|
| 1536 |
+
return x
|
| 1537 |
+
|
| 1538 |
+
def norm_era5_tendency(self, x, lt_offset):
|
| 1539 |
+
|
| 1540 |
+
x = (x - self.means_dict[lt_offset]) / self.stds_dict[lt_offset]
|
| 1541 |
+
return x
|
| 1542 |
+
|
| 1543 |
+
def unnorm_pred(self, x):
|
| 1544 |
+
x = x * self.diff_stds.unsqueeze(0) + self.diff_means.unsqueeze(0)
|
| 1545 |
+
return x
|
| 1546 |
+
|
| 1547 |
+
def unnorm_base_context(self, x):
|
| 1548 |
+
x = x * self.stds.unsqueeze(0) + self.means.unsqueeze(0)
|
| 1549 |
+
return x
|
| 1550 |
+
|
| 1551 |
+
def load_era5(self, year):
|
| 1552 |
+
"""
|
| 1553 |
+
Load ERA5 data for training
|
| 1554 |
+
"""
|
| 1555 |
+
|
| 1556 |
+
if year % 4 == 0:
|
| 1557 |
+
d = 366
|
| 1558 |
+
else:
|
| 1559 |
+
d = 365
|
| 1560 |
+
|
| 1561 |
+
if self.frequency == 6:
|
| 1562 |
+
d = d * 4
|
| 1563 |
+
|
| 1564 |
+
if self.era5_mode == "sfc":
|
| 1565 |
+
levels = 4
|
| 1566 |
+
elif self.era5_mode == "13u":
|
| 1567 |
+
levels = 69
|
| 1568 |
+
else:
|
| 1569 |
+
levels = 24
|
| 1570 |
+
|
| 1571 |
+
if self.res == 1:
|
| 1572 |
+
x = 240
|
| 1573 |
+
y = 121
|
| 1574 |
+
elif self.res == 5:
|
| 1575 |
+
x = 64
|
| 1576 |
+
y = 32
|
| 1577 |
+
|
| 1578 |
+
mmap = np.memmap(
|
| 1579 |
+
self.data_path
|
| 1580 |
+
+ "era5/era5_{}_{}_{}_{}.memmap".format(
|
| 1581 |
+
self.era5_mode, self.res, self.frequency, year
|
| 1582 |
+
),
|
| 1583 |
+
dtype="float32",
|
| 1584 |
+
mode="r",
|
| 1585 |
+
shape=(d, levels, x, y),
|
| 1586 |
+
)
|
| 1587 |
+
return mmap
|
| 1588 |
+
|
| 1589 |
+
def load_era5_time(self, index):
|
| 1590 |
+
"""
|
| 1591 |
+
Load ERA5 data for training
|
| 1592 |
+
"""
|
| 1593 |
+
date = self.dates[index]
|
| 1594 |
+
year = date.year
|
| 1595 |
+
doy = date.dayofyear - 1
|
| 1596 |
+
hour = date.hour
|
| 1597 |
+
if self.frequency == 6:
|
| 1598 |
+
era5 = self.era5_sfc[year - int(self.dates[0].year)][
|
| 1599 |
+
doy * 4 + hour // 6, ...
|
| 1600 |
+
]
|
| 1601 |
+
else:
|
| 1602 |
+
era5 = self.era5_sfc[year - int(self.dates[0].year)][doy, ...]
|
| 1603 |
+
|
| 1604 |
+
return np.copy(era5)
|
| 1605 |
+
|
| 1606 |
+
def make_time_channels(self, index, x, y):
|
| 1607 |
+
"""
|
| 1608 |
+
Make auxiliary time channels
|
| 1609 |
+
"""
|
| 1610 |
+
|
| 1611 |
+
date = self.dates[index]
|
| 1612 |
+
hour = date.hour
|
| 1613 |
+
doy = date.dayofyear - 1
|
| 1614 |
+
if date.year % 4 == 0:
|
| 1615 |
+
n_days = 366
|
| 1616 |
+
else:
|
| 1617 |
+
n_days = 365
|
| 1618 |
+
hour_sin = np.sin(hour * np.pi / 12) * np.float32(np.ones((1, x, y)))
|
| 1619 |
+
hour_cos = np.cos(hour * np.pi / 12) * np.float32(np.ones((1, x, y)))
|
| 1620 |
+
doy_sin = np.sin(doy * 2 * np.pi / n_days) * np.float32(np.ones((1, x, y)))
|
| 1621 |
+
doy_cos = np.cos(doy * 2 * np.pi / n_days) * np.float32(np.ones((1, x, y)))
|
| 1622 |
+
|
| 1623 |
+
return np.concatenate([hour_sin, hour_cos, doy_sin, doy_cos])
|
| 1624 |
+
|
| 1625 |
+
def __getitem__(self, index):
|
| 1626 |
+
|
| 1627 |
+
# Option to offset to random leadtime
|
| 1628 |
+
lt_offset = 0
|
| 1629 |
+
if self.random_lt:
|
| 1630 |
+
lt_offset = np.random.choice([0, 2, 3])
|
| 1631 |
+
|
| 1632 |
+
# Load ground truth data
|
| 1633 |
+
y_target = self.to_tensor(
|
| 1634 |
+
self.load_era5_time(index + self.lead_time - lt_offset)
|
| 1635 |
+
)
|
| 1636 |
+
|
| 1637 |
+
# Load either initial condition or ERA5 depending on task
|
| 1638 |
+
if self.ic_path is not None:
|
| 1639 |
+
era5_ts0 = self.ic[index].copy().transpose(2, 1, 0)
|
| 1640 |
+
|
| 1641 |
+
else:
|
| 1642 |
+
era5_ts0 = self.load_era5_time(index)
|
| 1643 |
+
|
| 1644 |
+
# Auxiliary time
|
| 1645 |
+
time = self.make_time_channels(index, era5_ts0.shape[1], era5_ts0.shape[2])
|
| 1646 |
+
era5_ts0 = self.to_tensor(
|
| 1647 |
+
np.concatenate([era5_ts0, self.era5_elev, time], axis=0)
|
| 1648 |
+
)
|
| 1649 |
+
y_context = era5_ts0.permute(0, 2, 1)[:, ...]
|
| 1650 |
+
|
| 1651 |
+
# Normalisation
|
| 1652 |
+
if self.diff:
|
| 1653 |
+
y_target = (y_target - era5_ts0[:24, ...]).permute(2, 1, 0)
|
| 1654 |
+
y_target = self.norm_era5_tendency(y_target, lt_offset)
|
| 1655 |
+
y_context[:24, ...] = self.norm_era5(y_context[:24, ...])
|
| 1656 |
+
|
| 1657 |
+
else:
|
| 1658 |
+
if self.norm:
|
| 1659 |
+
y_context[:24, ...] = self.norm_era5(y_context[:24, ...], lt_offset)
|
| 1660 |
+
y_target = self.norm_era5(y_target, lt_offset)
|
| 1661 |
+
y_target = y_target.permute(2, 1, 0)
|
| 1662 |
+
|
| 1663 |
+
if self.rollout:
|
| 1664 |
+
# Option to return entire timeseries of target data
|
| 1665 |
+
targets = []
|
| 1666 |
+
for t in range(self.lead_time + 1):
|
| 1667 |
+
t = self.to_tensor(self.load_era5_time(index + t))
|
| 1668 |
+
targets.append(t.permute(2, 1, 0))
|
| 1669 |
+
targets = torch.stack(targets, dim=-1)[..., ::4]
|
| 1670 |
+
|
| 1671 |
+
return {
|
| 1672 |
+
"y_context": y_context.permute(0, 2, 1),
|
| 1673 |
+
"y_target": y_target,
|
| 1674 |
+
"targets": targets,
|
| 1675 |
+
"lt": self.to_tensor(np.array([lt_offset])),
|
| 1676 |
+
}
|
| 1677 |
+
|
| 1678 |
+
else:
|
| 1679 |
+
return {
|
| 1680 |
+
"y_context": y_context.permute(0, 2, 1),
|
| 1681 |
+
"y_target": y_target[..., :],
|
| 1682 |
+
"lt": self.to_tensor(np.array([lt_offset])),
|
| 1683 |
+
"target_index": self.to_tensor(np.array([index])),
|
| 1684 |
+
}
|
| 1685 |
+
|
| 1686 |
+
|
| 1687 |
+
class WeatherDatasetE2E(WeatherDataset):
|
| 1688 |
+
"""
|
| 1689 |
+
Dataset for running Aardvark end-to-end
|
| 1690 |
+
"""
|
| 1691 |
+
|
| 1692 |
+
def __init__(
|
| 1693 |
+
self,
|
| 1694 |
+
device,
|
| 1695 |
+
hadisd_mode,
|
| 1696 |
+
start_date,
|
| 1697 |
+
end_date,
|
| 1698 |
+
lead_time,
|
| 1699 |
+
mode,
|
| 1700 |
+
hadisd_var,
|
| 1701 |
+
max_steps_per_epoch=None,
|
| 1702 |
+
era5_mode="sfc",
|
| 1703 |
+
res=1,
|
| 1704 |
+
filter_dates=None,
|
| 1705 |
+
var_start=0,
|
| 1706 |
+
var_end=24,
|
| 1707 |
+
diff=False,
|
| 1708 |
+
two_frames=False,
|
| 1709 |
+
region="global",
|
| 1710 |
+
):
|
| 1711 |
+
|
| 1712 |
+
super().__init__(
|
| 1713 |
+
device,
|
| 1714 |
+
hadisd_mode,
|
| 1715 |
+
start_date,
|
| 1716 |
+
end_date,
|
| 1717 |
+
lead_time,
|
| 1718 |
+
era5_mode,
|
| 1719 |
+
res=res,
|
| 1720 |
+
filter_dates=filter_dates,
|
| 1721 |
+
diff=diff,
|
| 1722 |
+
)
|
| 1723 |
+
|
| 1724 |
+
# Setup
|
| 1725 |
+
self.var_start = var_start
|
| 1726 |
+
self.var_end = var_end
|
| 1727 |
+
self.diff = diff
|
| 1728 |
+
self.two_frames = two_frames
|
| 1729 |
+
self.region = region
|
| 1730 |
+
self.lead_time = lead_time
|
| 1731 |
+
self.mode = mode
|
| 1732 |
+
self.max_steps_per_epoch = max_steps_per_epoch
|
| 1733 |
+
|
| 1734 |
+
# Initialise encoder dataset
|
| 1735 |
+
self.assimilation_dataset = WeatherDatasetAssimilation(
|
| 1736 |
+
device="cuda",
|
| 1737 |
+
hadisd_mode="train",
|
| 1738 |
+
start_date=start_date,
|
| 1739 |
+
end_date=end_date,
|
| 1740 |
+
lead_time=0,
|
| 1741 |
+
era5_mode="4u",
|
| 1742 |
+
res=1,
|
| 1743 |
+
var_start=0,
|
| 1744 |
+
var_end=24,
|
| 1745 |
+
diff=False,
|
| 1746 |
+
two_frames=False,
|
| 1747 |
+
)
|
| 1748 |
+
|
| 1749 |
+
# Initialise forecast dataset
|
| 1750 |
+
self.forecast_dataset = ForecastLoader(
|
| 1751 |
+
device="cuda",
|
| 1752 |
+
mode=mode,
|
| 1753 |
+
lead_time=lead_time,
|
| 1754 |
+
era5_mode=era5_mode,
|
| 1755 |
+
res=1,
|
| 1756 |
+
frequency=6,
|
| 1757 |
+
diff=True,
|
| 1758 |
+
u_only=False,
|
| 1759 |
+
random_lt=False,
|
| 1760 |
+
)
|
| 1761 |
+
|
| 1762 |
+
# Initialise downscaling dataset
|
| 1763 |
+
self.downscaling_dataset = ForecasterDatasetDownscaling(
|
| 1764 |
+
start_date=start_date,
|
| 1765 |
+
end_date=end_date,
|
| 1766 |
+
lead_time=lead_time,
|
| 1767 |
+
hadisd_var=hadisd_var,
|
| 1768 |
+
mode=mode,
|
| 1769 |
+
device=device,
|
| 1770 |
+
forecast_path=None,
|
| 1771 |
+
region=region,
|
| 1772 |
+
)
|
| 1773 |
+
|
| 1774 |
+
def __len__(self):
|
| 1775 |
+
if self.max_steps_per_epoch:
|
| 1776 |
+
return self.max_steps_per_epoch
|
| 1777 |
+
return len(self.downscaling_dataset) - 40 # Need 10 day offset at end of year
|
| 1778 |
+
|
| 1779 |
+
def __getitem__(self, index):
|
| 1780 |
+
|
| 1781 |
+
if self.max_steps_per_epoch:
|
| 1782 |
+
index = np.random.choice(
|
| 1783 |
+
np.arange(len(self.downscaling_dataset) - 40)
|
| 1784 |
+
) # Need 10 day offset at end of year
|
| 1785 |
+
|
| 1786 |
+
# Get data for each of the three datasets
|
| 1787 |
+
assimilation = self.assimilation_dataset.__getitem__(index)
|
| 1788 |
+
forecast = self.forecast_dataset.__getitem__(index)
|
| 1789 |
+
downscaling = self.downscaling_dataset.__getitem__(index)
|
| 1790 |
+
|
| 1791 |
+
# Create task
|
| 1792 |
+
task = {
|
| 1793 |
+
"assimilation": assimilation,
|
| 1794 |
+
"forecast": forecast,
|
| 1795 |
+
"downscaling": downscaling,
|
| 1796 |
+
"index": torch.tensor(index),
|
| 1797 |
+
}
|
| 1798 |
+
|
| 1799 |
+
# Add y target to allow for end to end finetuning if needed
|
| 1800 |
+
task["y_target"] = task["downscaling"]["y_target"]
|
| 1801 |
+
|
| 1802 |
+
return task
|
| 1803 |
+
|
| 1804 |
+
def unnorm_pred(self, x):
|
| 1805 |
+
|
| 1806 |
+
dev = x.device
|
| 1807 |
+
x = x.detach().cpu().numpy()
|
| 1808 |
+
|
| 1809 |
+
x = (
|
| 1810 |
+
x
|
| 1811 |
+
* self.stds[np.newaxis, ...].transpose(0, 2, 3, 1)[
|
| 1812 |
+
..., self.var_start : self.var_end
|
| 1813 |
+
]
|
| 1814 |
+
+ self.means[np.newaxis, ...].transpose(0, 2, 3, 1)[
|
| 1815 |
+
..., self.var_start : self.var_end
|
| 1816 |
+
]
|
| 1817 |
+
)
|
| 1818 |
+
if bool(self.diff):
|
| 1819 |
+
x = (
|
| 1820 |
+
x
|
| 1821 |
+
+ self.era5_mean_spatial[np.newaxis, ...].transpose(0, 3, 2, 1)[
|
| 1822 |
+
..., self.var_start : self.var_end
|
| 1823 |
+
]
|
| 1824 |
+
)
|
| 1825 |
+
return torch.from_numpy(x).float().to(dev)
|
official-src/aardvark/loader_utils_new.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import torch
|
| 3 |
+
import pandas as pd
|
| 4 |
+
|
| 5 |
+
LATLON_SCALE_FACTOR = 360
|
| 6 |
+
DAYS_IN_YEAR = 366
|
| 7 |
+
|
| 8 |
+
DAILY_SCALE_FACTOR = {
|
| 9 |
+
"ERA5": 4,
|
| 10 |
+
"HADISD": 4,
|
| 11 |
+
"IR": 4,
|
| 12 |
+
"SOUNDER": 1,
|
| 13 |
+
"ICOADS": 1,
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
date_list = [
|
| 17 |
+
"1979-01-01",
|
| 18 |
+
"1999-01-01",
|
| 19 |
+
"1999-01-02",
|
| 20 |
+
"2002-01-02",
|
| 21 |
+
"2007-01-01",
|
| 22 |
+
"2007-01-02",
|
| 23 |
+
"2007-01-03",
|
| 24 |
+
"2013-01-02",
|
| 25 |
+
"2014-01-01",
|
| 26 |
+
"2017-01-02",
|
| 27 |
+
"2018-01-01",
|
| 28 |
+
"2019-01-01",
|
| 29 |
+
"2020-01-02",
|
| 30 |
+
"2020-01-01",
|
| 31 |
+
"2021-01-02",
|
| 32 |
+
"2021-01-01",
|
| 33 |
+
]
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def generate_offsets(date_list, dates):
|
| 37 |
+
offsets = {}
|
| 38 |
+
for d in date_list:
|
| 39 |
+
try:
|
| 40 |
+
offsets[d] = np.where(dates == d)[0][0]
|
| 41 |
+
except:
|
| 42 |
+
offsets[d] = -1
|
| 43 |
+
return offsets
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
IC_OFFSETS = generate_offsets(
|
| 47 |
+
date_list, pd.date_range("1999-01-02", "2021-12-31 18:00", freq="6H")
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
AMSUA_OFFSETS = generate_offsets(
|
| 51 |
+
date_list, pd.date_range("2007-01-01", "2021-12-31 18:00", freq="6H")
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
AMSUB_OFFSETS = generate_offsets(
|
| 55 |
+
date_list, pd.date_range("2007-01-01", "2021-12-31 18:00", freq="6H")
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
ASCAT_OFFSETS = generate_offsets(
|
| 59 |
+
date_list, pd.date_range("2007-01-01", "2021-12-31", freq="6H")
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
ATMS_OFFSETS = generate_offsets(
|
| 63 |
+
date_list, pd.date_range("2013-01-02", "2021-12-31", freq="1D")
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
ICOADS_OFFSETS = generate_offsets(
|
| 67 |
+
date_list, pd.date_range("1999-01-01 06:00", "2021-12-31", freq="6H")
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
IGRA_OFFSETS = generate_offsets(
|
| 71 |
+
date_list, pd.date_range("1999-01-01 00:00", "2021-12-31 18:00", freq="6H")
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
SAT_OFFSETS = generate_offsets(
|
| 75 |
+
date_list, pd.date_range("1990-01-01 00:00", "2021-12-31 18:00", freq="6H")
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
HADISD_OFFSETS = generate_offsets(
|
| 79 |
+
date_list, pd.date_range("1950-01-01 00:00", "2021-12-31 18:00", freq="6H")
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def lon_to_0_360(x):
|
| 84 |
+
return (x + 360) % 360
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def lat_to_m90_90(x):
|
| 88 |
+
return torch.flip(x, [-1])
|
official-src/aardvark/loss_functions.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import torch
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class RmseLoss(nn.Module):
|
| 7 |
+
"""
|
| 8 |
+
RMSE loss
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
def __init__(self, start_ind=0, end_ind=24):
|
| 12 |
+
|
| 13 |
+
super().__init__()
|
| 14 |
+
self.start_ind = start_ind
|
| 15 |
+
self.end_ind = end_ind
|
| 16 |
+
|
| 17 |
+
def forward(
|
| 18 |
+
self,
|
| 19 |
+
target,
|
| 20 |
+
output,
|
| 21 |
+
prev_step_output,
|
| 22 |
+
fix_sigma=False,
|
| 23 |
+
unwrap=False,
|
| 24 |
+
expand=False,
|
| 25 |
+
):
|
| 26 |
+
|
| 27 |
+
squared_diff = ((target.to(output.device) - output) ** 2)[
|
| 28 |
+
..., self.start_ind : self.end_ind
|
| 29 |
+
]
|
| 30 |
+
return torch.mean(torch.sqrt(torch.nanmean(squared_diff, dim=(1, 2, 3))))
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class PressureWeightedRmseLoss(nn.Module):
|
| 34 |
+
"""
|
| 35 |
+
Latitude weighted pressure weighted RMSE loss used in training the processor
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
def __init__(
|
| 39 |
+
self,
|
| 40 |
+
res,
|
| 41 |
+
era5_mode,
|
| 42 |
+
data_dir,
|
| 43 |
+
aux_data_dir,
|
| 44 |
+
weight_per_variable=False,
|
| 45 |
+
):
|
| 46 |
+
super().__init__()
|
| 47 |
+
|
| 48 |
+
self.weights = torch.from_numpy(
|
| 49 |
+
np.load(aux_data_dir + "lat_weights/weights_lat_{}.npy".format(res)).T[
|
| 50 |
+
np.newaxis, ..., np.newaxis
|
| 51 |
+
]
|
| 52 |
+
).float()
|
| 53 |
+
|
| 54 |
+
self.weight_per_variable = weight_per_variable
|
| 55 |
+
self.variable_weights = torch.from_numpy(
|
| 56 |
+
np.load(aux_data_dir + "loss_weights.npy")[
|
| 57 |
+
np.newaxis, np.newaxis, np.newaxis, :
|
| 58 |
+
]
|
| 59 |
+
).float()
|
| 60 |
+
|
| 61 |
+
self.pressure_levels = (
|
| 62 |
+
torch.from_numpy(
|
| 63 |
+
np.load(
|
| 64 |
+
data_dir + "era5/era5_pressure_levels_{}.npy".format(era5_mode)
|
| 65 |
+
)[np.newaxis, np.newaxis, np.newaxis, :]
|
| 66 |
+
).float()
|
| 67 |
+
/ 1000
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
def forward(
|
| 71 |
+
self,
|
| 72 |
+
target,
|
| 73 |
+
output,
|
| 74 |
+
prev_step_output,
|
| 75 |
+
fix_sigma=False,
|
| 76 |
+
unwrap=False,
|
| 77 |
+
expand=False,
|
| 78 |
+
):
|
| 79 |
+
|
| 80 |
+
squared_diff = (target.to(output.device) - output) ** 2
|
| 81 |
+
|
| 82 |
+
if not expand:
|
| 83 |
+
weighted_sqared_diff = (
|
| 84 |
+
squared_diff
|
| 85 |
+
* self.weights.to(target.device)
|
| 86 |
+
* self.pressure_levels.to(target.device)
|
| 87 |
+
)
|
| 88 |
+
return torch.mean(
|
| 89 |
+
torch.sqrt(torch.nanmean(weighted_sqared_diff, dim=(1, 2, 3)))
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
weighted_sqared_diff = squared_diff * self.weights.to(target.device)
|
| 93 |
+
|
| 94 |
+
if self.weight_per_variable:
|
| 95 |
+
weighted_sqared_diff = weighted_sqared_diff * self.variable_weights.to(
|
| 96 |
+
weighted_sqared_diff.device
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
return torch.mean(
|
| 100 |
+
torch.sqrt(torch.nanmean(weighted_sqared_diff, dim=(1, 2))), dim=0
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
class WeightedRmseLoss(nn.Module):
|
| 105 |
+
"""
|
| 106 |
+
Latitude weighted RMSE loss
|
| 107 |
+
"""
|
| 108 |
+
|
| 109 |
+
def __init__(
|
| 110 |
+
self,
|
| 111 |
+
res,
|
| 112 |
+
data_dir,
|
| 113 |
+
aux_data_dir,
|
| 114 |
+
weight_per_variable=False,
|
| 115 |
+
start_ind=0,
|
| 116 |
+
end_ind=24,
|
| 117 |
+
):
|
| 118 |
+
|
| 119 |
+
super().__init__()
|
| 120 |
+
self.start_ind = start_ind
|
| 121 |
+
self.end_ind = end_ind
|
| 122 |
+
|
| 123 |
+
self.weights = torch.from_numpy(
|
| 124 |
+
np.load(aux_data_dir + "lat_weights/weights_lat_{}.npy".format(res)).T[
|
| 125 |
+
np.newaxis, ..., np.newaxis
|
| 126 |
+
]
|
| 127 |
+
).float()
|
| 128 |
+
|
| 129 |
+
self.weight_per_variable = weight_per_variable
|
| 130 |
+
self.variable_weights = torch.from_numpy(
|
| 131 |
+
np.load(aux_data_dir + "loss_weights.npy")[
|
| 132 |
+
np.newaxis, np.newaxis, np.newaxis, start_ind:end_ind
|
| 133 |
+
]
|
| 134 |
+
).float()
|
| 135 |
+
|
| 136 |
+
def forward(
|
| 137 |
+
self,
|
| 138 |
+
target,
|
| 139 |
+
output,
|
| 140 |
+
prev_step_output,
|
| 141 |
+
fix_sigma=False,
|
| 142 |
+
unwrap=False,
|
| 143 |
+
expand=False,
|
| 144 |
+
):
|
| 145 |
+
squared_diff = (target.to(output.device) - output) ** 2
|
| 146 |
+
|
| 147 |
+
if not expand:
|
| 148 |
+
weighted_sqared_diff = squared_diff * self.weights.to(target.device)
|
| 149 |
+
|
| 150 |
+
if self.weight_per_variable:
|
| 151 |
+
weighted_sqared_diff = weighted_sqared_diff * self.variable_weights.to(
|
| 152 |
+
weighted_sqared_diff.device
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
x = torch.nanmean(
|
| 156 |
+
torch.sqrt(torch.nanmean(weighted_sqared_diff, dim=(1, 2, 3)))
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
return x
|
| 160 |
+
|
| 161 |
+
weighted_sqared_diff = squared_diff * self.weights.to(target.device)
|
| 162 |
+
x = torch.mean(
|
| 163 |
+
torch.sqrt(torch.nanmean(weighted_sqared_diff, dim=(1, 2))), dim=0
|
| 164 |
+
)
|
| 165 |
+
|
| 166 |
+
return x
|
official-src/aardvark/misc_downscaling_functionality.py
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pickle
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch.nn as nn
|
| 6 |
+
|
| 7 |
+
from set_convs import convDeepSet
|
| 8 |
+
from unet_wrap_padding import Unet
|
| 9 |
+
from vit import *
|
| 10 |
+
from models import *
|
| 11 |
+
|
| 12 |
+
hadisd_publisher_shifts = {
|
| 13 |
+
"tas": 273.15,
|
| 14 |
+
"u": 0.0,
|
| 15 |
+
"v": 0.0,
|
| 16 |
+
"psl": 0.0,
|
| 17 |
+
"ws": 0.0,
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
hadisd_publisher_scales = {
|
| 21 |
+
"tas": 10,
|
| 22 |
+
"u": 10,
|
| 23 |
+
"v": 10,
|
| 24 |
+
"psl": 100,
|
| 25 |
+
"ws": 10.0,
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def hadisd_normalisation_factors(var: str):
|
| 30 |
+
path = "/home/azureuser/aux_data/norm_factors/"
|
| 31 |
+
return {
|
| 32 |
+
"mean": np.load(path + f"mean_hadisd_{var}.npy"),
|
| 33 |
+
"std": np.load(path + f"std_hadisd_{var}.npy"),
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def unnormalise_hadisd_var(x, var):
|
| 38 |
+
factors = hadisd_normalisation_factors(var)
|
| 39 |
+
hadisd_shift = hadisd_publisher_shifts[var]
|
| 40 |
+
hadisd_scale = hadisd_publisher_scales[var]
|
| 41 |
+
|
| 42 |
+
return hadisd_shift + hadisd_scale * (factors["mean"] + factors["std"] * x)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
class DownscalingRmseLoss(nn.Module):
|
| 46 |
+
def __init__(self):
|
| 47 |
+
super().__init__()
|
| 48 |
+
|
| 49 |
+
def forward(self, target, output, prev_step, fix_sigma=None, expand=True):
|
| 50 |
+
|
| 51 |
+
target = torch.flatten(target.to(output.device))
|
| 52 |
+
output = torch.flatten(output)
|
| 53 |
+
|
| 54 |
+
tmp = torch.isnan(target)
|
| 55 |
+
clean_target = target[~tmp]
|
| 56 |
+
clean_output = output[~tmp]
|
| 57 |
+
|
| 58 |
+
return torch.mean((clean_target - clean_output) ** 2)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
class ConvCNPWeatherOnToOff(nn.Module):
|
| 62 |
+
"""
|
| 63 |
+
ConvCNP for decoder
|
| 64 |
+
"""
|
| 65 |
+
|
| 66 |
+
def __init__(
|
| 67 |
+
self,
|
| 68 |
+
in_channels,
|
| 69 |
+
out_channels,
|
| 70 |
+
int_channels,
|
| 71 |
+
device,
|
| 72 |
+
res,
|
| 73 |
+
data_path="../data/",
|
| 74 |
+
mode="end_to_end",
|
| 75 |
+
decoder=None,
|
| 76 |
+
film=False,
|
| 77 |
+
):
|
| 78 |
+
|
| 79 |
+
super().__init__()
|
| 80 |
+
|
| 81 |
+
# Setup
|
| 82 |
+
self.device = device
|
| 83 |
+
|
| 84 |
+
self.in_channels = in_channels
|
| 85 |
+
self.out_channels = out_channels
|
| 86 |
+
self.int_channels = int_channels
|
| 87 |
+
self.decoder = decoder
|
| 88 |
+
self.int_x = 256
|
| 89 |
+
self.int_y = 128
|
| 90 |
+
self.mode = mode
|
| 91 |
+
self.film = film
|
| 92 |
+
|
| 93 |
+
# Load lon-lat of internal discretisation
|
| 94 |
+
self.era5_x = (
|
| 95 |
+
torch.from_numpy(
|
| 96 |
+
np.load(data_path + "grid_lon_lat/era5_x_{}.npy".format(res))
|
| 97 |
+
).float()
|
| 98 |
+
/ 360
|
| 99 |
+
)
|
| 100 |
+
self.era5_y = (
|
| 101 |
+
torch.from_numpy(
|
| 102 |
+
np.load(data_path + "grid_lon_lat/era5_y_{}.npy".format(res))
|
| 103 |
+
).float()
|
| 104 |
+
/ 360
|
| 105 |
+
)
|
| 106 |
+
|
| 107 |
+
# Setup setconv
|
| 108 |
+
self.sc_out = convDeepSet(
|
| 109 |
+
0.001, "OnToOff", density_channel=False, device=self.device
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
if self.mode not in ["downscaling", "end_to_end"]:
|
| 113 |
+
unet_out_channels = out_channels
|
| 114 |
+
else:
|
| 115 |
+
unet_out_channels = int_channels
|
| 116 |
+
|
| 117 |
+
# UNet backbone
|
| 118 |
+
if self.decoder == "base":
|
| 119 |
+
self.decoder_lr = Unet(
|
| 120 |
+
in_channels=in_channels,
|
| 121 |
+
out_channels=unet_out_channels,
|
| 122 |
+
div_factor=1,
|
| 123 |
+
film=film,
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
else:
|
| 127 |
+
raise Exception(f"Expected to use base decoder, but got {self.decoder}")
|
| 128 |
+
|
| 129 |
+
# Postprocessing MLP
|
| 130 |
+
self.mlp = DownscalingMLP(
|
| 131 |
+
in_channels=24 + 9,
|
| 132 |
+
out_channels=1,
|
| 133 |
+
h_channels=64,
|
| 134 |
+
h_layers=2,
|
| 135 |
+
)
|
| 136 |
+
|
| 137 |
+
def forward(self, task, film_index):
|
| 138 |
+
|
| 139 |
+
x = task["y_context"]
|
| 140 |
+
batch_size = x.shape[0]
|
| 141 |
+
|
| 142 |
+
# UNet backbone
|
| 143 |
+
x = self.decoder_lr(x, film_index=task["lt"])
|
| 144 |
+
|
| 145 |
+
# Transform to station predictions with setconv
|
| 146 |
+
num_channels = x.shape[3]
|
| 147 |
+
x = x.permute(0, 3, 1, 2)
|
| 148 |
+
assert list(x.shape) == [batch_size, num_channels, 240, 121]
|
| 149 |
+
x_target = task["x_target"]
|
| 150 |
+
num_stations = x_target.shape[2]
|
| 151 |
+
|
| 152 |
+
x = self.sc_out(
|
| 153 |
+
x_in=task["x_context"],
|
| 154 |
+
wt=x,
|
| 155 |
+
x_out=[x_target[:, 0, :], x_target[:, 1, :]],
|
| 156 |
+
)
|
| 157 |
+
assert x.shape[0] == batch_size
|
| 158 |
+
assert x.shape[2] == num_stations
|
| 159 |
+
|
| 160 |
+
# Concatenate auxiliary data at stations
|
| 161 |
+
alt_target = task["alt_target"]
|
| 162 |
+
assert torch.isnan(alt_target).sum() == 0
|
| 163 |
+
assert alt_target.shape[0] == batch_size
|
| 164 |
+
assert alt_target.shape[2] == num_stations
|
| 165 |
+
|
| 166 |
+
aux_time = task["aux_time"].squeeze(-1).repeat(1, 1, num_stations)
|
| 167 |
+
assert aux_time.shape[0] == batch_size
|
| 168 |
+
assert aux_time.shape[2] == num_stations
|
| 169 |
+
|
| 170 |
+
x = torch.cat([x, alt_target, x_target, aux_time], dim=1).permute(0, 2, 1)
|
| 171 |
+
assert x.shape[0] == batch_size
|
| 172 |
+
assert x.shape[1] == num_stations
|
| 173 |
+
|
| 174 |
+
tmp = self.mlp(x)
|
| 175 |
+
assert list(tmp.shape) == [batch_size, num_stations, 1]
|
| 176 |
+
y_hat = tmp.squeeze(-1)
|
| 177 |
+
assert list(y_hat.shape) == [batch_size, num_stations]
|
| 178 |
+
return y_hat
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
class ResidualBlock(nn.Module):
|
| 182 |
+
def __init__(self, n_channels):
|
| 183 |
+
super().__init__()
|
| 184 |
+
self.block = nn.Sequential(nn.Linear(n_channels, n_channels), nn.ReLU())
|
| 185 |
+
|
| 186 |
+
def forward(self, x):
|
| 187 |
+
return self.block(x) + x
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
class DownscalingMLP(nn.Module):
|
| 191 |
+
"""
|
| 192 |
+
MLP for handling auxiliary data at station locations
|
| 193 |
+
"""
|
| 194 |
+
|
| 195 |
+
def __init__(self, in_channels, out_channels, h_channels, h_layers):
|
| 196 |
+
super().__init__()
|
| 197 |
+
|
| 198 |
+
self.mlp = nn.Sequential(
|
| 199 |
+
nn.Linear(in_channels, h_channels),
|
| 200 |
+
*[ResidualBlock(h_channels) for _ in range(h_layers)],
|
| 201 |
+
nn.Linear(h_channels, out_channels),
|
| 202 |
+
)
|
| 203 |
+
|
| 204 |
+
def forward(self, x):
|
| 205 |
+
return self.mlp(x)
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
def find_epoch(results_dir: str) -> int:
|
| 209 |
+
losses = np.load(results_dir + "losses_0.npy")
|
| 210 |
+
epoch = np.argmin(losses)
|
| 211 |
+
|
| 212 |
+
return epoch
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
def load_config(results_dir: str) -> dict:
|
| 216 |
+
with open(results_dir + "config.pkl", "rb") as f:
|
| 217 |
+
return pickle.load(f)
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
def load_model(results_dir, device, config_dir=None, epoch=None):
|
| 221 |
+
"""
|
| 222 |
+
Load the ConvCNPWeatherOnToOff to generate forecasts
|
| 223 |
+
"""
|
| 224 |
+
|
| 225 |
+
config_dir = config_dir or results_dir
|
| 226 |
+
config = load_config(config_dir)
|
| 227 |
+
epoch = epoch or find_epoch(results_dir)
|
| 228 |
+
|
| 229 |
+
model = ConvCNPWeatherOnToOff(
|
| 230 |
+
in_channels=config["in_channels"],
|
| 231 |
+
out_channels=config["out_channels"],
|
| 232 |
+
int_channels=config["int_channels"],
|
| 233 |
+
device=device,
|
| 234 |
+
res=config["res"],
|
| 235 |
+
decoder=config["decoder"],
|
| 236 |
+
mode=config["mode"],
|
| 237 |
+
film=False,
|
| 238 |
+
)
|
| 239 |
+
|
| 240 |
+
full_state_dict = torch.load(results_dir + f"epoch_{epoch}", map_location=device)
|
| 241 |
+
state_dict = full_state_dict["model_state_dict"]
|
| 242 |
+
state_dict = {k[7:]: v for k, v in zip(state_dict.keys(), state_dict.values())}
|
| 243 |
+
model.load_state_dict(state_dict)
|
| 244 |
+
model = model.to(device)
|
| 245 |
+
model.eval()
|
| 246 |
+
|
| 247 |
+
return model
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
def load_model_e2e(results_dir, lead_time, device):
|
| 251 |
+
"""
|
| 252 |
+
Load the full E2E model to generate forecasts
|
| 253 |
+
"""
|
| 254 |
+
|
| 255 |
+
config = load_config(results_dir)
|
| 256 |
+
epoch = find_epoch(results_dir)
|
| 257 |
+
|
| 258 |
+
model = ConvCNPWeatherE2E(
|
| 259 |
+
device="cuda",
|
| 260 |
+
lead_time=lead_time,
|
| 261 |
+
se_model_path="../results/assimilation/all_v4_vit_ps3",
|
| 262 |
+
forecast_model_path="../results/forecast/vit_fnl_randomlt_mlp",
|
| 263 |
+
sf_model_path="../results/downscaling_NEW",
|
| 264 |
+
)
|
| 265 |
+
|
| 266 |
+
full_state_dict = torch.load(results_dir + f"epoch_{epoch}", map_location=device)
|
| 267 |
+
state_dict = full_state_dict["model_state_dict"]
|
| 268 |
+
state_dict = {k[7:]: v for k, v in zip(state_dict.keys(), state_dict.values())}
|
| 269 |
+
model.load_state_dict(state_dict)
|
| 270 |
+
model = model.to(device)
|
| 271 |
+
model.eval()
|
| 272 |
+
|
| 273 |
+
return model
|
official-src/aardvark/models.py
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch
|
| 5 |
+
import torch.nn as nn
|
| 6 |
+
|
| 7 |
+
from architectures import MLP
|
| 8 |
+
from set_convs import convDeepSet
|
| 9 |
+
from unet_wrap_padding import *
|
| 10 |
+
from vit import *
|
| 11 |
+
|
| 12 |
+
sys.path.append("../")
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class ConvCNPWeather(nn.Module):
|
| 16 |
+
"""
|
| 17 |
+
ConvCNP class used for the encoder and processor modules
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
def __init__(
|
| 21 |
+
self,
|
| 22 |
+
in_channels,
|
| 23 |
+
out_channels,
|
| 24 |
+
int_channels,
|
| 25 |
+
device,
|
| 26 |
+
res,
|
| 27 |
+
data_path="../data/",
|
| 28 |
+
gnp=False,
|
| 29 |
+
mode="assimilation",
|
| 30 |
+
decoder=None,
|
| 31 |
+
film=False,
|
| 32 |
+
two_frames=False,
|
| 33 |
+
):
|
| 34 |
+
|
| 35 |
+
super().__init__()
|
| 36 |
+
|
| 37 |
+
self.device = device
|
| 38 |
+
|
| 39 |
+
self.in_channels = in_channels
|
| 40 |
+
self.out_channels = out_channels
|
| 41 |
+
self.int_channels = int_channels
|
| 42 |
+
self.decoder = decoder
|
| 43 |
+
self.int_x = 256
|
| 44 |
+
self.int_y = 128
|
| 45 |
+
self.data_path = data_path
|
| 46 |
+
self.mode = mode
|
| 47 |
+
self.film = film
|
| 48 |
+
self.two_frames = two_frames
|
| 49 |
+
|
| 50 |
+
N_SAT_VARS = 2
|
| 51 |
+
N_ICOADS_VARS = 5
|
| 52 |
+
N_HADISD_VARS = 5
|
| 53 |
+
|
| 54 |
+
# Load internal grid longitude-latitude locations
|
| 55 |
+
self.era5_x = (
|
| 56 |
+
torch.from_numpy(
|
| 57 |
+
np.load(self.data_path + "grid_lon_lat/era5_x_{}.npy".format(res))
|
| 58 |
+
).float()
|
| 59 |
+
/ 360
|
| 60 |
+
)
|
| 61 |
+
self.era5_y = (
|
| 62 |
+
torch.from_numpy(
|
| 63 |
+
np.load(self.data_path + "grid_lon_lat/era5_y_{}.npy".format(res))
|
| 64 |
+
).float()
|
| 65 |
+
/ 360
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
self.int_grid = [
|
| 69 |
+
(torch.linspace(0, 360, 240) / 360).float().cuda(),
|
| 70 |
+
(torch.linspace(-90, 90, 121) / 360).float().cuda(),
|
| 71 |
+
]
|
| 72 |
+
|
| 73 |
+
self.int_grid = [self.int_grid[0].unsqueeze(0), self.int_grid[1].unsqueeze(0)]
|
| 74 |
+
|
| 75 |
+
# Create input setconvs for each data modality
|
| 76 |
+
self.ascat_setconvs = convDeepSet(
|
| 77 |
+
0.001, "OnToOn", density_channel=True, device=self.device
|
| 78 |
+
)
|
| 79 |
+
self.amsua_setconvs = [
|
| 80 |
+
convDeepSet(0.001, "OnToOn", density_channel=True, device=self.device)
|
| 81 |
+
for _ in range(13)
|
| 82 |
+
]
|
| 83 |
+
self.amsub_setconvs = [
|
| 84 |
+
convDeepSet(0.001, "OnToOn", density_channel=True, device=self.device)
|
| 85 |
+
for _ in range(12)
|
| 86 |
+
]
|
| 87 |
+
self.hirs_setconvs = [
|
| 88 |
+
convDeepSet(0.001, "OnToOn", density_channel=True, device=self.device)
|
| 89 |
+
for _ in range(26)
|
| 90 |
+
]
|
| 91 |
+
|
| 92 |
+
self.sat_setconvs = [
|
| 93 |
+
convDeepSet(0.001, "OnToOn", density_channel=True, device=self.device)
|
| 94 |
+
for _ in range(N_SAT_VARS)
|
| 95 |
+
]
|
| 96 |
+
self.hadisd_setconvs = [
|
| 97 |
+
convDeepSet(0.001, "OffToOn", density_channel=True, device=self.device)
|
| 98 |
+
for _ in range(N_HADISD_VARS)
|
| 99 |
+
]
|
| 100 |
+
self.icoads_setconvs = [
|
| 101 |
+
convDeepSet(0.001, "OffToOn", density_channel=True, device=self.device)
|
| 102 |
+
for _ in range(N_ICOADS_VARS)
|
| 103 |
+
]
|
| 104 |
+
self.igra_setconvs = [
|
| 105 |
+
convDeepSet(0.001, "OffToOn", density_channel=True, device=self.device)
|
| 106 |
+
for _ in range(24)
|
| 107 |
+
]
|
| 108 |
+
|
| 109 |
+
self.sc_out = convDeepSet(
|
| 110 |
+
0.001, "OnToOff", density_channel=False, device=self.device
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
# Instantiate the decoder. Here decoder refers to decoder in a convCNP (i.e the ViT backbone)
|
| 114 |
+
if self.decoder == "vit":
|
| 115 |
+
self.decoder_lr = ViT(
|
| 116 |
+
in_channels=in_channels,
|
| 117 |
+
out_channels=out_channels,
|
| 118 |
+
h_channels=512,
|
| 119 |
+
depth=16,
|
| 120 |
+
patch_size=5,
|
| 121 |
+
per_var_embedding=True,
|
| 122 |
+
img_size=[240, 121],
|
| 123 |
+
)
|
| 124 |
+
|
| 125 |
+
elif self.decoder == "vit_assimilation":
|
| 126 |
+
self.decoder_lr = ViT(
|
| 127 |
+
in_channels=256,
|
| 128 |
+
out_channels=out_channels,
|
| 129 |
+
h_channels=512,
|
| 130 |
+
depth=8,
|
| 131 |
+
patch_size=3,
|
| 132 |
+
per_var_embedding=False,
|
| 133 |
+
img_size=[256, 128],
|
| 134 |
+
)
|
| 135 |
+
|
| 136 |
+
self.mlp = MLP(
|
| 137 |
+
in_channels=out_channels,
|
| 138 |
+
out_channels=out_channels,
|
| 139 |
+
h_channels=128,
|
| 140 |
+
h_layers=4,
|
| 141 |
+
)
|
| 142 |
+
self.break_next = False
|
| 143 |
+
|
| 144 |
+
def encoder_hadisd(self, task, prefix):
|
| 145 |
+
"""
|
| 146 |
+
Data preprocessing for HadISD
|
| 147 |
+
"""
|
| 148 |
+
|
| 149 |
+
encodings = []
|
| 150 |
+
for channel in range(4):
|
| 151 |
+
encodings.append(
|
| 152 |
+
self.hadisd_setconvs[channel](
|
| 153 |
+
x_in=[
|
| 154 |
+
task["x_context_hadisd_{}".format(prefix)][channel][:, 0, :],
|
| 155 |
+
task["x_context_hadisd_{}".format(prefix)][channel][:, 1, :],
|
| 156 |
+
],
|
| 157 |
+
wt=task["y_context_hadisd_{}".format(prefix)][channel].unsqueeze(1),
|
| 158 |
+
x_out=self.int_grid,
|
| 159 |
+
)
|
| 160 |
+
)
|
| 161 |
+
encodings = torch.cat(encodings, dim=1)
|
| 162 |
+
return encodings
|
| 163 |
+
|
| 164 |
+
def encoder_sat(self, task, prefix):
|
| 165 |
+
"""
|
| 166 |
+
Data preprocessing for Gridsat
|
| 167 |
+
"""
|
| 168 |
+
|
| 169 |
+
encodings = []
|
| 170 |
+
for channel in range(task["sat_{}".format(prefix)].shape[1]):
|
| 171 |
+
encodings.append(
|
| 172 |
+
self.sat_setconvs[channel](
|
| 173 |
+
x_in=task["sat_x_{}".format(prefix)],
|
| 174 |
+
wt=task["sat_{}".format(prefix)][:, channel : channel + 1, ...],
|
| 175 |
+
x_out=self.int_grid,
|
| 176 |
+
)
|
| 177 |
+
)
|
| 178 |
+
encodings = torch.cat(encodings, dim=1)
|
| 179 |
+
return encodings
|
| 180 |
+
|
| 181 |
+
def encoder_icoads(self, task, prefix):
|
| 182 |
+
"""
|
| 183 |
+
Data preprocessing for ICOADS
|
| 184 |
+
"""
|
| 185 |
+
|
| 186 |
+
encodings = []
|
| 187 |
+
for channel in range(5):
|
| 188 |
+
encodings.append(
|
| 189 |
+
self.icoads_setconvs[channel](
|
| 190 |
+
x_in=task["icoads_x_{}".format(prefix)],
|
| 191 |
+
wt=task["icoads_{}".format(prefix)][:, channel, :].unsqueeze(1),
|
| 192 |
+
x_out=self.int_grid,
|
| 193 |
+
)
|
| 194 |
+
)
|
| 195 |
+
encodings = torch.cat(encodings, dim=1)
|
| 196 |
+
|
| 197 |
+
return encodings
|
| 198 |
+
|
| 199 |
+
def encoder_amsua(self, task, prefix):
|
| 200 |
+
"""
|
| 201 |
+
Data preprocessing for AMSU-A
|
| 202 |
+
"""
|
| 203 |
+
|
| 204 |
+
encodings = []
|
| 205 |
+
task["amsua_{}".format(prefix)][..., -1] = np.nan
|
| 206 |
+
task["amsua_{}".format(prefix)][task["amsua_{}".format(prefix)] == 0] = np.nan
|
| 207 |
+
for i in range(13):
|
| 208 |
+
encodings.append(
|
| 209 |
+
self.amsua_setconvs[i](
|
| 210 |
+
x_in=task["amsua_x_{}".format(prefix)],
|
| 211 |
+
wt=task["amsua_{}".format(prefix)].permute(0, 3, 2, 1)[
|
| 212 |
+
:, i : i + 1, ...
|
| 213 |
+
],
|
| 214 |
+
x_out=self.int_grid,
|
| 215 |
+
)
|
| 216 |
+
)
|
| 217 |
+
|
| 218 |
+
encodings = torch.cat(encodings, dim=1)
|
| 219 |
+
return encodings
|
| 220 |
+
|
| 221 |
+
def encoder_amsub(self, task, prefix):
|
| 222 |
+
"""
|
| 223 |
+
Data preprocessing for AMSU-B
|
| 224 |
+
"""
|
| 225 |
+
|
| 226 |
+
encodings = []
|
| 227 |
+
task["amsub_{}".format(prefix)][task["amsub_{}".format(prefix)] == 0] = np.nan
|
| 228 |
+
for i in range(12):
|
| 229 |
+
encodings.append(
|
| 230 |
+
self.amsua_setconvs[i](
|
| 231 |
+
x_in=task["amsub_x_{}".format(prefix)],
|
| 232 |
+
wt=task["amsub_{}".format(prefix)].permute(0, 3, 1, 2)[
|
| 233 |
+
:, i : i + 1, ...
|
| 234 |
+
],
|
| 235 |
+
x_out=self.int_grid,
|
| 236 |
+
)
|
| 237 |
+
)
|
| 238 |
+
|
| 239 |
+
encodings = torch.cat(encodings, dim=1)
|
| 240 |
+
return encodings
|
| 241 |
+
|
| 242 |
+
def encoder_hirs(self, task, prefix):
|
| 243 |
+
"""
|
| 244 |
+
Data preprocessing for HIRS
|
| 245 |
+
"""
|
| 246 |
+
|
| 247 |
+
encodings = []
|
| 248 |
+
|
| 249 |
+
task["hirs_{}".format(prefix)][task["hirs_{}".format(prefix)] == 0] = np.nan
|
| 250 |
+
for i in range(26):
|
| 251 |
+
encodings.append(
|
| 252 |
+
self.hirs_setconvs[i](
|
| 253 |
+
x_in=task["hirs_x_{}".format(prefix)],
|
| 254 |
+
wt=task["hirs_{}".format(prefix)].permute(0, 3, 1, 2)[
|
| 255 |
+
:, i : i + 1, ...
|
| 256 |
+
],
|
| 257 |
+
x_out=self.int_grid,
|
| 258 |
+
)
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
encodings = torch.cat(encodings, dim=1)
|
| 262 |
+
return encodings
|
| 263 |
+
|
| 264 |
+
def encoder_igra(self, task, prefix):
|
| 265 |
+
"""
|
| 266 |
+
Data preprocessing for IGRA
|
| 267 |
+
"""
|
| 268 |
+
|
| 269 |
+
encodings = []
|
| 270 |
+
for channel in range(24):
|
| 271 |
+
encodings.append(
|
| 272 |
+
self.igra_setconvs[channel](
|
| 273 |
+
x_in=task["igra_x_{}".format(prefix)],
|
| 274 |
+
wt=task["igra_{}".format(prefix)][:, channel, :].unsqueeze(1),
|
| 275 |
+
x_out=self.int_grid,
|
| 276 |
+
)
|
| 277 |
+
)
|
| 278 |
+
encodings = torch.cat(encodings, dim=1)
|
| 279 |
+
|
| 280 |
+
return encodings
|
| 281 |
+
|
| 282 |
+
def encoder_ascat(self, task, prefix):
|
| 283 |
+
"""
|
| 284 |
+
Data preprocessing for ASCAT
|
| 285 |
+
"""
|
| 286 |
+
|
| 287 |
+
task["ascat_{}".format(prefix)][
|
| 288 |
+
torch.isnan(task["ascat_{}".format(prefix)])
|
| 289 |
+
] = 0
|
| 290 |
+
e = nn.functional.interpolate(
|
| 291 |
+
task["ascat_{}".format(prefix)].permute(0, 3, 1, 2), size=(240, 121)
|
| 292 |
+
)
|
| 293 |
+
e = torch.flip(e, dims=[-1])
|
| 294 |
+
return e
|
| 295 |
+
|
| 296 |
+
def encoder_iasi(self, task, prefix):
|
| 297 |
+
"""
|
| 298 |
+
Data preprocessing for IASI
|
| 299 |
+
"""
|
| 300 |
+
|
| 301 |
+
task["iasi_{}".format(prefix)][torch.isnan(task["iasi_{}".format(prefix)])] = 0
|
| 302 |
+
e = nn.functional.interpolate(
|
| 303 |
+
task["iasi_{}".format(prefix)].permute(0, 3, 1, 2), size=(240, 121)
|
| 304 |
+
)
|
| 305 |
+
e = torch.flip(e, dims=[-1])
|
| 306 |
+
return e
|
| 307 |
+
|
| 308 |
+
def forward(self, task, film_index):
|
| 309 |
+
|
| 310 |
+
# Setup input
|
| 311 |
+
if self.mode == "assimilation":
|
| 312 |
+
|
| 313 |
+
self.int_grid = [i.to(task["y_target"].device) for i in self.int_grid]
|
| 314 |
+
elev = nn.functional.interpolate(
|
| 315 |
+
torch.flip(task["era5_elev_current"].permute(0, 1, 3, 2), dims=[2]),
|
| 316 |
+
size=(self.int_grid[0].shape[1], self.int_grid[1].shape[1]),
|
| 317 |
+
)
|
| 318 |
+
elev = torch.flip(task["era5_elev_current"].permute(0, 1, 3, 2), dims=[2])
|
| 319 |
+
|
| 320 |
+
if not self.two_frames:
|
| 321 |
+
encodings = [
|
| 322 |
+
self.encoder_iasi(task, "current"),
|
| 323 |
+
self.encoder_ascat(task, "current"),
|
| 324 |
+
self.encoder_hadisd(task, "current"),
|
| 325 |
+
self.encoder_icoads(task, "current"),
|
| 326 |
+
self.encoder_sat(task, "current"),
|
| 327 |
+
self.encoder_amsua(task, "current"),
|
| 328 |
+
self.encoder_amsub(task, "current"),
|
| 329 |
+
self.encoder_igra(task, "current"),
|
| 330 |
+
self.encoder_hirs(task, "current"),
|
| 331 |
+
elev,
|
| 332 |
+
task["climatology_current"],
|
| 333 |
+
torch.ones_like(elev[:, :5, ...])
|
| 334 |
+
* task["aux_time_current"].unsqueeze(-1).unsqueeze(-1),
|
| 335 |
+
]
|
| 336 |
+
else:
|
| 337 |
+
# Option to pass two timesteps (t=-1 and t=0) as input
|
| 338 |
+
encodings = [
|
| 339 |
+
self.encoder_iasi(task, "current"),
|
| 340 |
+
self.encoder_ascat(task, "current"),
|
| 341 |
+
self.encoder_hadisd(task, "current"),
|
| 342 |
+
self.encoder_icoads(task, "current"),
|
| 343 |
+
self.encoder_sat(task, "current"),
|
| 344 |
+
self.encoder_amsua(task, "current"),
|
| 345 |
+
self.encoder_amsub(task, "current"),
|
| 346 |
+
self.encoder_igra(task, "current"),
|
| 347 |
+
self.encoder_hirs(task, "current"),
|
| 348 |
+
self.encoder_iasi(task, "prev"),
|
| 349 |
+
self.encoder_ascat(task, "prev"),
|
| 350 |
+
self.encoder_hadisd(task, "prev"),
|
| 351 |
+
self.encoder_icoads(task, "prev"),
|
| 352 |
+
self.encoder_sat(task, "prev"),
|
| 353 |
+
self.encoder_amsua(task, "prev"),
|
| 354 |
+
self.encoder_amsub(task, "prev"),
|
| 355 |
+
self.encoder_igra(task, "prev"),
|
| 356 |
+
self.encoder_hirs(task, "prev"),
|
| 357 |
+
elev,
|
| 358 |
+
task["climatology_current"],
|
| 359 |
+
torch.ones_like(elev[:, :5, ...])
|
| 360 |
+
* task["aux_time_current"].unsqueeze(-1).unsqueeze(-1),
|
| 361 |
+
]
|
| 362 |
+
x = torch.cat(encodings, dim=1)
|
| 363 |
+
|
| 364 |
+
else:
|
| 365 |
+
x = task["y_context"]
|
| 366 |
+
|
| 367 |
+
if x.shape[-1] > x.shape[-2]:
|
| 368 |
+
x = x.permute(0, 1, 3, 2)
|
| 369 |
+
|
| 370 |
+
# Run ViT backbone
|
| 371 |
+
if self.decoder == "vit":
|
| 372 |
+
x = self.decoder_lr(x, lead_times=task["lt"])
|
| 373 |
+
x = x.permute(0, 3, 1, 2)
|
| 374 |
+
else:
|
| 375 |
+
x = nn.functional.interpolate(x, size=(256, 128))
|
| 376 |
+
x = self.decoder_lr(x, film_index=(task["lt"] * 0) + 1)
|
| 377 |
+
|
| 378 |
+
# Process outputs
|
| 379 |
+
|
| 380 |
+
if np.logical_and(
|
| 381 |
+
self.mode == "assimilation", self.decoder == "vit_assimilation"
|
| 382 |
+
):
|
| 383 |
+
x = nn.functional.interpolate(x.permute(0, 3, 1, 2), size=(240, 121))
|
| 384 |
+
return x.permute(0, 3, 2, 1)
|
| 385 |
+
|
| 386 |
+
elif self.mode == "forecast":
|
| 387 |
+
x = nn.functional.interpolate(x, size=(240, 121)).permute(0, 2, 3, 1)
|
| 388 |
+
return x.permute(0, 2, 1, 3)
|
| 389 |
+
|
| 390 |
+
return x
|
official-src/aardvark/set_convs.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class convDeepSet(nn.Module):
|
| 6 |
+
"""
|
| 7 |
+
ConvDeepSet used to translate ungridded observations to a gridded representation and
|
| 8 |
+
vice versa.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
def __init__(
|
| 12 |
+
self,
|
| 13 |
+
init_ls,
|
| 14 |
+
mode,
|
| 15 |
+
device,
|
| 16 |
+
density_channel=True,
|
| 17 |
+
step=0.25,
|
| 18 |
+
grid=False,
|
| 19 |
+
):
|
| 20 |
+
super().__init__()
|
| 21 |
+
self.init_ls = torch.nn.Parameter(torch.tensor([init_ls]))
|
| 22 |
+
self.grid = grid
|
| 23 |
+
self.step = step
|
| 24 |
+
self.density_channel = density_channel
|
| 25 |
+
self.mode = mode
|
| 26 |
+
|
| 27 |
+
self.init_ls.requires_grad = True
|
| 28 |
+
self.device = device
|
| 29 |
+
|
| 30 |
+
def compute_weights(self, x1, x2):
|
| 31 |
+
|
| 32 |
+
dists2 = self.pw_dists2(x1.unsqueeze(-1), x2.unsqueeze(-1))
|
| 33 |
+
|
| 34 |
+
d = torch.exp((-0.5 * dists2) / (self.init_ls.to(x1.device)) ** 2)
|
| 35 |
+
return d
|
| 36 |
+
|
| 37 |
+
def pw_dists2(self, a, b):
|
| 38 |
+
|
| 39 |
+
norms_a = torch.sum(a**2, axis=-1)[..., :, None]
|
| 40 |
+
norms_b = torch.sum(b**2, axis=-1)[..., None, :]
|
| 41 |
+
|
| 42 |
+
return norms_a + norms_b - 2 * torch.matmul(a, b.permute(0, 2, 1))
|
| 43 |
+
|
| 44 |
+
def forward(self, x_in, wt, x_out):
|
| 45 |
+
|
| 46 |
+
# Add a density channel
|
| 47 |
+
density_channel = torch.ones_like(wt[:, 0:1, ...])
|
| 48 |
+
density_channel[torch.isnan(wt[:, 0:1, ...])] = 0
|
| 49 |
+
|
| 50 |
+
wt = torch.cat([density_channel, wt], dim=1)
|
| 51 |
+
wt[torch.isnan(wt)] = 0
|
| 52 |
+
|
| 53 |
+
if self.mode == "OffToOn":
|
| 54 |
+
# Case 1: converting off-the-grid data to a gridded representation
|
| 55 |
+
|
| 56 |
+
in_lon_mask = ~torch.isnan(x_in[0])
|
| 57 |
+
in_lat_mask = ~torch.isnan(x_in[1])
|
| 58 |
+
|
| 59 |
+
x_in[0][~in_lon_mask] = 0
|
| 60 |
+
x_in[1][~in_lat_mask] = 0
|
| 61 |
+
|
| 62 |
+
ws = [self.compute_weights(xzi, xi) for xzi, xi in zip(x_in, x_out)]
|
| 63 |
+
|
| 64 |
+
ws[0] = ws[0] * in_lon_mask.unsqueeze(-1).int()
|
| 65 |
+
ws[1] = ws[1] * in_lat_mask.unsqueeze(-1).int()
|
| 66 |
+
|
| 67 |
+
ee = torch.einsum("...cw,...wx,...wy->...cxy", wt, ws[0], ws[1])
|
| 68 |
+
|
| 69 |
+
elif self.mode == "OnToOn":
|
| 70 |
+
# Case 2: converting between gridded representations
|
| 71 |
+
|
| 72 |
+
ws = [self.compute_weights(xzi, xi) for xzi, xi in zip(x_in, x_out)]
|
| 73 |
+
ee = torch.einsum("...cwh,...wx,...hy->...cxy", wt, ws[0], ws[1])
|
| 74 |
+
|
| 75 |
+
elif self.mode == "OnToOff":
|
| 76 |
+
|
| 77 |
+
# Case 3: converting a gridded representation to off-the-grid predictions
|
| 78 |
+
out_lon_mask = ~torch.isnan(x_out[0])
|
| 79 |
+
out_lat_mask = ~torch.isnan(x_out[1])
|
| 80 |
+
x_out[0][~out_lon_mask] = 0
|
| 81 |
+
x_out[1][~out_lat_mask] = 0
|
| 82 |
+
|
| 83 |
+
ws = [self.compute_weights(xzi, xi) for xzi, xi in zip(x_in, x_out)]
|
| 84 |
+
|
| 85 |
+
ws[0] = ws[0] * out_lon_mask.unsqueeze(-2).int()
|
| 86 |
+
ws[1] = ws[1] * out_lat_mask.unsqueeze(-2).int()
|
| 87 |
+
|
| 88 |
+
ee = torch.einsum("...cwh,...wx,...hx->...cx", wt, ws[0], ws[1])
|
| 89 |
+
if self.density_channel:
|
| 90 |
+
ee = torch.cat(
|
| 91 |
+
[
|
| 92 |
+
ee[:, 0:1, ...],
|
| 93 |
+
ee[:, 1:, ...] / torch.clamp(ee[:, 0:1, ...], min=1e-6, max=1e5),
|
| 94 |
+
],
|
| 95 |
+
dim=1,
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
return ee
|
| 99 |
+
else:
|
| 100 |
+
ee = ee[:, 1:, ...] / torch.clamp(ee[:, 0:1, ...], min=1e-6, max=1e5)
|
| 101 |
+
return ee
|
official-src/aardvark/train_module.py
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
NB: this script is for illustration purposes only and is not runnable as our
|
| 3 |
+
full dataset is not provided as part of the submission, due to size constraints.
|
| 4 |
+
Many of the relevant paths to the data have been thus replaced by dummy paths.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
import sys
|
| 9 |
+
import pickle
|
| 10 |
+
import argparse
|
| 11 |
+
|
| 12 |
+
import torch
|
| 13 |
+
import torch.multiprocessing as mp
|
| 14 |
+
from torch.utils.data import DataLoader
|
| 15 |
+
import torch.utils.data.distributed
|
| 16 |
+
from torch.utils.data.distributed import DistributedSampler
|
| 17 |
+
from torch.distributed import init_process_group, destroy_process_group
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
from trainer import DDPTrainer
|
| 21 |
+
from loss_functions import WeightedRmseLoss, PressureWeightedRmseLoss, RmseLoss
|
| 22 |
+
from misc_downscaling_functionality import ConvCNPWeatherOnToOff, DownscalingRmseLoss
|
| 23 |
+
from loader import *
|
| 24 |
+
from models import *
|
| 25 |
+
from unet_wrap_padding import *
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
sys.path.append("../npw/data")
|
| 29 |
+
torch.set_float32_matmul_precision("medium")
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def ddp_setup(rank, world_size, master_port):
|
| 33 |
+
"""
|
| 34 |
+
Args:
|
| 35 |
+
rank: Unique identifier of each process
|
| 36 |
+
world_size: Total number of processes
|
| 37 |
+
"""
|
| 38 |
+
|
| 39 |
+
os.environ["MASTER_ADDR"] = "localhost"
|
| 40 |
+
os.environ["MASTER_PORT"] = master_port
|
| 41 |
+
init_process_group(backend="nccl", rank=rank, world_size=world_size)
|
| 42 |
+
torch.cuda.set_device(rank)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def start_date(name):
|
| 46 |
+
if name == "train":
|
| 47 |
+
return "2007-01-02"
|
| 48 |
+
elif name == "val":
|
| 49 |
+
return "2019-01-01"
|
| 50 |
+
elif name == "test":
|
| 51 |
+
return "2018-01-01"
|
| 52 |
+
else:
|
| 53 |
+
raise Exception(f"Unrecognised split name {name}")
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def end_date(name):
|
| 57 |
+
if name == "train":
|
| 58 |
+
return "2017-12-31"
|
| 59 |
+
elif name == "val":
|
| 60 |
+
return "2019-11-01"
|
| 61 |
+
elif name == "test":
|
| 62 |
+
return "2018-12-21"
|
| 63 |
+
else:
|
| 64 |
+
raise Exception(f"Unrecognised split name {name}")
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def main(rank, world_size, output_dir, args):
|
| 68 |
+
"""
|
| 69 |
+
Primary training script for the encoder, processor and decoder modules.
|
| 70 |
+
"""
|
| 71 |
+
|
| 72 |
+
master_port = args.master_port
|
| 73 |
+
lead_time = args.lead_time
|
| 74 |
+
era5_mode = args.era5_mode
|
| 75 |
+
weights_dir = args.weights_dir
|
| 76 |
+
ddp_setup(rank, world_size, master_port)
|
| 77 |
+
|
| 78 |
+
# Instantiate loss function
|
| 79 |
+
if args.loss == "lw_rmse":
|
| 80 |
+
lf = WeightedRmseLoss(
|
| 81 |
+
args.res,
|
| 82 |
+
start_ind=args.start_ind,
|
| 83 |
+
end_ind=args.end_ind,
|
| 84 |
+
weight_per_variable=bool(args.weight_per_variable),
|
| 85 |
+
)
|
| 86 |
+
elif args.loss == "lw_rmse_pressure_weighted":
|
| 87 |
+
lf = PressureWeightedRmseLoss(args.res, era5_mode)
|
| 88 |
+
elif args.loss == "rmse":
|
| 89 |
+
lf = RmseLoss()
|
| 90 |
+
elif args.loss == "downscaling_rmse":
|
| 91 |
+
lf = DownscalingRmseLoss()
|
| 92 |
+
|
| 93 |
+
# Setup datasets
|
| 94 |
+
|
| 95 |
+
# Case 1: training encoder
|
| 96 |
+
if args.mode == "assimilation":
|
| 97 |
+
train_dataset = WeatherDatasetAssimilation(
|
| 98 |
+
device="cuda",
|
| 99 |
+
hadisd_mode="train",
|
| 100 |
+
start_date="2007-01-02",
|
| 101 |
+
end_date="2017-12-31",
|
| 102 |
+
lead_time=0,
|
| 103 |
+
era5_mode="4u",
|
| 104 |
+
res=args.res,
|
| 105 |
+
var_start=args.start_ind,
|
| 106 |
+
var_end=args.end_ind,
|
| 107 |
+
diff=bool(args.diff),
|
| 108 |
+
two_frames=bool(args.two_frames),
|
| 109 |
+
)
|
| 110 |
+
val_dataset = WeatherDatasetAssimilation(
|
| 111 |
+
device="cuda",
|
| 112 |
+
hadisd_mode="train",
|
| 113 |
+
start_date="2019-01-01",
|
| 114 |
+
end_date="2019-12-31",
|
| 115 |
+
lead_time=0,
|
| 116 |
+
era5_mode="4u",
|
| 117 |
+
res=args.res,
|
| 118 |
+
var_start=args.start_ind,
|
| 119 |
+
var_end=args.end_ind,
|
| 120 |
+
diff=bool(args.diff),
|
| 121 |
+
two_frames=bool(args.two_frames),
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
# Case 2: training processor
|
| 125 |
+
elif args.mode == "forecast":
|
| 126 |
+
if args.ic == "aardvark":
|
| 127 |
+
train_dataset = FineTuneForecastLoaderNew(
|
| 128 |
+
device="cuda",
|
| 129 |
+
mode="train",
|
| 130 |
+
lead_time=lead_time,
|
| 131 |
+
era5_mode=era5_mode,
|
| 132 |
+
res=args.res,
|
| 133 |
+
frequency=args.frequency,
|
| 134 |
+
diff=bool(args.diff),
|
| 135 |
+
aardvark_ic_path=args.aardvark_ic_path,
|
| 136 |
+
random_lt=True,
|
| 137 |
+
)
|
| 138 |
+
val_dataset = FineTuneForecastLoaderNew(
|
| 139 |
+
device="cuda",
|
| 140 |
+
mode="val",
|
| 141 |
+
lead_time=lead_time,
|
| 142 |
+
era5_mode=era5_mode,
|
| 143 |
+
res=args.res,
|
| 144 |
+
frequency=args.frequency,
|
| 145 |
+
diff=bool(args.diff),
|
| 146 |
+
aardvark_ic_path=args.aardvark_ic_path,
|
| 147 |
+
)
|
| 148 |
+
else:
|
| 149 |
+
train_dataset = ForecastLoader(
|
| 150 |
+
device="cuda",
|
| 151 |
+
mode="train",
|
| 152 |
+
lead_time=lead_time,
|
| 153 |
+
era5_mode=era5_mode,
|
| 154 |
+
res=args.res,
|
| 155 |
+
frequency=args.frequency,
|
| 156 |
+
diff=bool(args.diff),
|
| 157 |
+
u_only=False,
|
| 158 |
+
random_lt=False,
|
| 159 |
+
)
|
| 160 |
+
val_dataset = ForecastLoader(
|
| 161 |
+
device="cuda",
|
| 162 |
+
mode="val",
|
| 163 |
+
lead_time=lead_time,
|
| 164 |
+
era5_mode=era5_mode,
|
| 165 |
+
res=args.res,
|
| 166 |
+
frequency=args.frequency,
|
| 167 |
+
diff=bool(args.diff),
|
| 168 |
+
u_only=False,
|
| 169 |
+
random_lt=False,
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
# Case 3: training decoder
|
| 173 |
+
elif args.mode == "downscaling":
|
| 174 |
+
|
| 175 |
+
train_dataset = ForecasterDatasetDownscaling(
|
| 176 |
+
start_date="2007-01-02",
|
| 177 |
+
end_date="2017-12-31",
|
| 178 |
+
lead_time=args.lead_time,
|
| 179 |
+
hadisd_var=args.var,
|
| 180 |
+
mode="train",
|
| 181 |
+
device="cuda",
|
| 182 |
+
forecast_path=None,
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
val_dataset = ForecasterDatasetDownscaling(
|
| 186 |
+
start_date="2019-01-01",
|
| 187 |
+
end_date="2019-12-21",
|
| 188 |
+
lead_time=args.lead_time,
|
| 189 |
+
hadisd_var=args.var,
|
| 190 |
+
mode="train",
|
| 191 |
+
device="cuda",
|
| 192 |
+
forecast_path=None,
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
try:
|
| 196 |
+
os.mkdir(f"{output_dir}lt_{args.lead_time}")
|
| 197 |
+
except FileExistsError:
|
| 198 |
+
pass
|
| 199 |
+
|
| 200 |
+
output_dir = f"{output_dir}lt_{args.lead_time}/"
|
| 201 |
+
|
| 202 |
+
# Instantiate model
|
| 203 |
+
|
| 204 |
+
if args.mode == "downscaling":
|
| 205 |
+
model = ConvCNPWeatherOnToOff(
|
| 206 |
+
in_channels=args.in_channels,
|
| 207 |
+
out_channels=args.end_ind - args.start_ind,
|
| 208 |
+
int_channels=args.int_channels,
|
| 209 |
+
device="cuda",
|
| 210 |
+
res=args.res,
|
| 211 |
+
decoder=args.decoder,
|
| 212 |
+
mode=args.mode,
|
| 213 |
+
film=bool(args.film),
|
| 214 |
+
)
|
| 215 |
+
else:
|
| 216 |
+
model = ConvCNPWeather(
|
| 217 |
+
in_channels=args.in_channels,
|
| 218 |
+
out_channels=args.end_ind - args.start_ind,
|
| 219 |
+
int_channels=args.int_channels,
|
| 220 |
+
device="cuda",
|
| 221 |
+
res=args.res,
|
| 222 |
+
gnp=bool(0),
|
| 223 |
+
decoder=args.decoder,
|
| 224 |
+
mode=args.mode,
|
| 225 |
+
film=bool(args.film),
|
| 226 |
+
two_frames=bool(args.two_frames),
|
| 227 |
+
)
|
| 228 |
+
|
| 229 |
+
# Instantiate loaders
|
| 230 |
+
train_sampler = DistributedSampler(train_dataset)
|
| 231 |
+
val_sampler = DistributedSampler(val_dataset)
|
| 232 |
+
|
| 233 |
+
train_loader = DataLoader(
|
| 234 |
+
train_dataset,
|
| 235 |
+
batch_size=args.batch_size,
|
| 236 |
+
shuffle=False,
|
| 237 |
+
sampler=train_sampler,
|
| 238 |
+
)
|
| 239 |
+
|
| 240 |
+
val_loader = DataLoader(
|
| 241 |
+
val_dataset,
|
| 242 |
+
batch_size=args.batch_size,
|
| 243 |
+
shuffle=False,
|
| 244 |
+
sampler=val_sampler,
|
| 245 |
+
)
|
| 246 |
+
|
| 247 |
+
# Instantiate trainer
|
| 248 |
+
|
| 249 |
+
trainer = DDPTrainer(
|
| 250 |
+
model,
|
| 251 |
+
rank,
|
| 252 |
+
train_loader,
|
| 253 |
+
val_loader,
|
| 254 |
+
lf,
|
| 255 |
+
output_dir,
|
| 256 |
+
args.lr,
|
| 257 |
+
train_sampler,
|
| 258 |
+
weight_decay=args.weight_decay,
|
| 259 |
+
weights_path=weights_dir,
|
| 260 |
+
tune_film=args.film,
|
| 261 |
+
)
|
| 262 |
+
|
| 263 |
+
# Train model
|
| 264 |
+
|
| 265 |
+
trainer.train(n_epochs=args.epoch)
|
| 266 |
+
destroy_process_group()
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
if __name__ == "__main__":
|
| 270 |
+
parser = argparse.ArgumentParser()
|
| 271 |
+
parser.add_argument("--output_dir")
|
| 272 |
+
parser.add_argument("--mode")
|
| 273 |
+
parser.add_argument("--weights_dir")
|
| 274 |
+
parser.add_argument("--in_channels", type=int)
|
| 275 |
+
parser.add_argument("--out_channels", type=int)
|
| 276 |
+
parser.add_argument("--int_channels", type=int)
|
| 277 |
+
parser.add_argument("--loss")
|
| 278 |
+
parser.add_argument("--ic")
|
| 279 |
+
parser.add_argument("--decoder")
|
| 280 |
+
parser.add_argument("--film")
|
| 281 |
+
parser.add_argument("--aardvark_ic_path")
|
| 282 |
+
parser.add_argument("--two_frames", type=int, default=0)
|
| 283 |
+
parser.add_argument("--weight_per_variable", type=int, default=0)
|
| 284 |
+
parser.add_argument("--batch_size", type=int, default=128)
|
| 285 |
+
parser.add_argument("--epoch", type=int, default=50)
|
| 286 |
+
parser.add_argument("--master_port", default="12345")
|
| 287 |
+
parser.add_argument("--lr", type=float, default=5e-4)
|
| 288 |
+
parser.add_argument("--lead_time", type=int)
|
| 289 |
+
parser.add_argument("--era5_mode", default="4u")
|
| 290 |
+
parser.add_argument("--weight_decay", type=float, default=1e-6)
|
| 291 |
+
parser.add_argument("--res", type=int, default=1)
|
| 292 |
+
parser.add_argument("--frequency", type=int, default=6)
|
| 293 |
+
parser.add_argument("--diff", type=int, default=1)
|
| 294 |
+
parser.add_argument("--start_ind", type=int, default=0)
|
| 295 |
+
parser.add_argument("--end_ind", type=int, default=24)
|
| 296 |
+
parser.add_argument("--downscaling_train_start_date", default="1979-01-01")
|
| 297 |
+
parser.add_argument("--downscaling_train_end_date", default="2017-12-31")
|
| 298 |
+
parser.add_argument("--downscaling_context", default="era5")
|
| 299 |
+
parser.add_argument("--downscaling_lead_time", type=int)
|
| 300 |
+
parser.add_argument("--var", default=None)
|
| 301 |
+
args = parser.parse_args()
|
| 302 |
+
|
| 303 |
+
torch.device("cuda")
|
| 304 |
+
|
| 305 |
+
# Create results directory
|
| 306 |
+
output_dir = args.output_dir
|
| 307 |
+
if not os.path.exists(output_dir):
|
| 308 |
+
os.mkdir(output_dir)
|
| 309 |
+
|
| 310 |
+
# Save config
|
| 311 |
+
with open(output_dir + "/config.pkl", "wb") as f:
|
| 312 |
+
pickle.dump(vars(args), f)
|
| 313 |
+
|
| 314 |
+
world_size = torch.cuda.device_count()
|
| 315 |
+
mp.spawn(main, args=[world_size, output_dir, args], nprocs=world_size)
|
official-src/aardvark/trainer.py
ADDED
|
@@ -0,0 +1,602 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import subprocess
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
from torch.nn.parallel import DistributedDataParallel as DDP
|
| 7 |
+
from tqdm import tqdm
|
| 8 |
+
|
| 9 |
+
from misc_downscaling_functionality import *
|
| 10 |
+
from models import *
|
| 11 |
+
from loss_functions import *
|
| 12 |
+
|
| 13 |
+
sys.path.append("..")
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class DDPTrainer:
|
| 17 |
+
"""
|
| 18 |
+
Main class for training models using DDP
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
def __init__(
|
| 22 |
+
self,
|
| 23 |
+
model,
|
| 24 |
+
rank,
|
| 25 |
+
train_loader,
|
| 26 |
+
val_loader,
|
| 27 |
+
loss_function,
|
| 28 |
+
save_path,
|
| 29 |
+
learning_rate,
|
| 30 |
+
sampler,
|
| 31 |
+
weight_decay,
|
| 32 |
+
test_loader=None,
|
| 33 |
+
weights_path=None,
|
| 34 |
+
tune_film=False,
|
| 35 |
+
):
|
| 36 |
+
self.rank = rank
|
| 37 |
+
self.model = model
|
| 38 |
+
self.sampler = sampler
|
| 39 |
+
self.tune_film = tune_film
|
| 40 |
+
self.train_loader = train_loader
|
| 41 |
+
self.val_loader = val_loader
|
| 42 |
+
self.save_path = save_path
|
| 43 |
+
self.loss_function = loss_function
|
| 44 |
+
self.best_loss = 1000
|
| 45 |
+
self.test_loader = test_loader
|
| 46 |
+
|
| 47 |
+
self.model = self.model.to(rank)
|
| 48 |
+
self.model = DDP(self.model, device_ids=[rank], find_unused_parameters=True)
|
| 49 |
+
|
| 50 |
+
if self.model.module.decoder == "vit":
|
| 51 |
+
self.opt = torch.optim.AdamW(
|
| 52 |
+
model.parameters(),
|
| 53 |
+
lr=learning_rate,
|
| 54 |
+
betas=(0.9, 0.95),
|
| 55 |
+
weight_decay=1e-5,
|
| 56 |
+
)
|
| 57 |
+
self.scheduler = optim.lr_scheduler.CosineAnnealingLR(self.opt, 891 * 80)
|
| 58 |
+
|
| 59 |
+
else:
|
| 60 |
+
self.opt = torch.optim.Adam(
|
| 61 |
+
model.parameters(), lr=learning_rate, weight_decay=weight_decay
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
self.losses = []
|
| 65 |
+
self.train_losses = []
|
| 66 |
+
self.maes = []
|
| 67 |
+
|
| 68 |
+
def _unravel_to_numpy(self, x):
|
| 69 |
+
return x.view(-1).detach().cpu().numpy()
|
| 70 |
+
|
| 71 |
+
def eval_epoch(self, fix_sigma, epoch):
|
| 72 |
+
|
| 73 |
+
self.model.eval()
|
| 74 |
+
|
| 75 |
+
mae = []
|
| 76 |
+
lf = []
|
| 77 |
+
lf_unnorm = []
|
| 78 |
+
|
| 79 |
+
with torch.no_grad():
|
| 80 |
+
for count, task in enumerate(self.val_loader):
|
| 81 |
+
|
| 82 |
+
out = self.model(task, film_index=0)
|
| 83 |
+
|
| 84 |
+
prev_step = None
|
| 85 |
+
|
| 86 |
+
mae.append(
|
| 87 |
+
np.nanmean(
|
| 88 |
+
np.abs(
|
| 89 |
+
task["y_target"].detach().cpu()
|
| 90 |
+
- out[..., : task["y_target"].shape[-1]].detach().cpu()
|
| 91 |
+
)
|
| 92 |
+
)
|
| 93 |
+
)
|
| 94 |
+
l = (
|
| 95 |
+
self.loss_function(
|
| 96 |
+
task["y_target"], out, prev_step, fix_sigma=fix_sigma
|
| 97 |
+
)
|
| 98 |
+
.detach()
|
| 99 |
+
.item()
|
| 100 |
+
)
|
| 101 |
+
lf.append(l)
|
| 102 |
+
|
| 103 |
+
try:
|
| 104 |
+
ic = self.train_loader.dataset.unnorm_base_context(
|
| 105 |
+
task["y_context"][:, :-11, ...]
|
| 106 |
+
).permute(0, 3, 2, 1)
|
| 107 |
+
unnorm_pred = self.train_loader.dataset.unnorm_pred(out)
|
| 108 |
+
unnorm_target = self.train_loader.dataset.unnorm_pred(
|
| 109 |
+
task["y_target"]
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
unnorm_pred = unnorm_pred + ic
|
| 113 |
+
unnorm_target = unnorm_target + ic
|
| 114 |
+
|
| 115 |
+
lu = (
|
| 116 |
+
self.loss_function(
|
| 117 |
+
unnorm_target,
|
| 118 |
+
unnorm_pred,
|
| 119 |
+
prev_step,
|
| 120 |
+
fix_sigma=fix_sigma,
|
| 121 |
+
expand=True,
|
| 122 |
+
)
|
| 123 |
+
.detach()
|
| 124 |
+
.cpu()
|
| 125 |
+
.numpy()
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
lf_unnorm.append(lu)
|
| 129 |
+
|
| 130 |
+
except:
|
| 131 |
+
pass
|
| 132 |
+
|
| 133 |
+
if self.test_loader is not None:
|
| 134 |
+
forecasts = []
|
| 135 |
+
targets = []
|
| 136 |
+
stations = []
|
| 137 |
+
|
| 138 |
+
for count, task in enumerate(self.test_loader):
|
| 139 |
+
out = self.model(task, film_index=0)
|
| 140 |
+
forecasts.append(unnormalise_hadisd_tas(out.detach().cpu().numpy()))
|
| 141 |
+
targets.append(
|
| 142 |
+
unnormalise_hadisd_tas(task["y_target"].detach().cpu().numpy())
|
| 143 |
+
)
|
| 144 |
+
stations.append(
|
| 145 |
+
task["downscaling"]["x_target"].detach().cpu().numpy() * 360
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
# Save the test outputs
|
| 149 |
+
np.save(
|
| 150 |
+
self.save_path + f"forecast_{self.rank}_{self.epoch}.npy",
|
| 151 |
+
np.concatenate(forecasts, axis=0),
|
| 152 |
+
)
|
| 153 |
+
np.save(
|
| 154 |
+
self.save_path + f"targets_{self.rank}_{self.epoch}.npy",
|
| 155 |
+
np.concatenate(targets, axis=0),
|
| 156 |
+
)
|
| 157 |
+
np.save(
|
| 158 |
+
self.save_path + f"stations_{self.rank}_{self.epoch}.npy",
|
| 159 |
+
np.concatenate(stations, axis=0),
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
log_loss = np.nanmean(np.array(lf))
|
| 163 |
+
|
| 164 |
+
if log_loss < self.best_loss:
|
| 165 |
+
np.save(
|
| 166 |
+
self.save_path + "unnorm_preds.npy",
|
| 167 |
+
self.train_loader.dataset.unnorm_pred(out).detach().cpu().numpy(),
|
| 168 |
+
)
|
| 169 |
+
np.save(
|
| 170 |
+
self.save_path + "unnorm_targets.npy",
|
| 171 |
+
self.train_loader.dataset.unnorm_pred(task["y_target"])
|
| 172 |
+
.detach()
|
| 173 |
+
.cpu()
|
| 174 |
+
.numpy(),
|
| 175 |
+
)
|
| 176 |
+
log_loss_unnorm = np.nanmean(np.stack(lf_unnorm), axis=0)
|
| 177 |
+
|
| 178 |
+
if np.logical_and(self.rank == 0, self.epoch % 5 == 0):
|
| 179 |
+
|
| 180 |
+
np.save(self.save_path + "preds_eval.npy", out.cpu().numpy())
|
| 181 |
+
np.save(
|
| 182 |
+
self.save_path + "y_target_eval.npy", task["y_target"].cpu().numpy()
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
return log_loss, log_loss_unnorm
|
| 186 |
+
|
| 187 |
+
def train(self, n_epochs=100):
|
| 188 |
+
|
| 189 |
+
torch.cuda.set_device(self.rank)
|
| 190 |
+
subprocess.run(["cp", "reproduce_training.sh", f"{self.save_path}"])
|
| 191 |
+
|
| 192 |
+
train_loss = []
|
| 193 |
+
ll = []
|
| 194 |
+
|
| 195 |
+
fix_sigma = False
|
| 196 |
+
prev_step = None
|
| 197 |
+
|
| 198 |
+
self.epoch = 0
|
| 199 |
+
epoch_loss, log_loss_unnorm = self.eval_epoch(fix_sigma, 0)
|
| 200 |
+
train_loss = np.mean(train_loss)
|
| 201 |
+
|
| 202 |
+
for epoch in range(n_epochs):
|
| 203 |
+
self.epoch = epoch
|
| 204 |
+
|
| 205 |
+
self.sampler.set_epoch(epoch)
|
| 206 |
+
|
| 207 |
+
self.model.train()
|
| 208 |
+
train_loss = []
|
| 209 |
+
with tqdm(self.train_loader, unit="batch") as tepoch:
|
| 210 |
+
for count, task in enumerate(tepoch):
|
| 211 |
+
|
| 212 |
+
out = self.model(task, film_index=0)
|
| 213 |
+
|
| 214 |
+
loss = self.loss_function(
|
| 215 |
+
task["y_target"], out, prev_step, fix_sigma=fix_sigma
|
| 216 |
+
)
|
| 217 |
+
|
| 218 |
+
loss.backward()
|
| 219 |
+
tepoch.set_postfix(loss=loss.item())
|
| 220 |
+
prev_step = out
|
| 221 |
+
|
| 222 |
+
self.opt.step()
|
| 223 |
+
self.opt.zero_grad()
|
| 224 |
+
train_loss.append(loss.item())
|
| 225 |
+
try:
|
| 226 |
+
if self.model.module.decoder == "vit":
|
| 227 |
+
if epoch > 20:
|
| 228 |
+
self.scheduler.step()
|
| 229 |
+
except:
|
| 230 |
+
pass
|
| 231 |
+
|
| 232 |
+
epoch_loss, log_loss_unnorm = self.eval_epoch(fix_sigma, epoch)
|
| 233 |
+
train_loss = np.mean(train_loss)
|
| 234 |
+
ll.append(log_loss_unnorm)
|
| 235 |
+
|
| 236 |
+
self.losses.append(epoch_loss)
|
| 237 |
+
self.train_losses.append(train_loss)
|
| 238 |
+
np.save(
|
| 239 |
+
self.save_path + "losses_{}.npy".format(self.rank),
|
| 240 |
+
np.array(self.losses),
|
| 241 |
+
)
|
| 242 |
+
np.save(
|
| 243 |
+
self.save_path + "train_losses_{}.npy".format(self.rank),
|
| 244 |
+
np.array(self.train_losses),
|
| 245 |
+
)
|
| 246 |
+
np.save(self.save_path + "rmse_{}.npy".format(self.rank), np.array(ll))
|
| 247 |
+
|
| 248 |
+
if epoch_loss < self.best_loss:
|
| 249 |
+
self.best_loss = epoch_loss
|
| 250 |
+
|
| 251 |
+
if self.rank == 0:
|
| 252 |
+
if self.model.module.decoder == "vit":
|
| 253 |
+
torch.save(
|
| 254 |
+
{
|
| 255 |
+
"epoch": epoch,
|
| 256 |
+
"model_state_dict": self.model.state_dict(),
|
| 257 |
+
"optimizer_state_dict": self.opt.state_dict(),
|
| 258 |
+
"scheduler_state_dict": self.scheduler.state_dict(),
|
| 259 |
+
"loss": epoch_loss,
|
| 260 |
+
},
|
| 261 |
+
self.save_path + "epoch_{}".format(epoch),
|
| 262 |
+
)
|
| 263 |
+
else:
|
| 264 |
+
torch.save(
|
| 265 |
+
{
|
| 266 |
+
"epoch": epoch,
|
| 267 |
+
"model_state_dict": self.model.state_dict(),
|
| 268 |
+
"optimizer_state_dict": self.opt.state_dict(),
|
| 269 |
+
"loss": epoch_loss,
|
| 270 |
+
},
|
| 271 |
+
self.save_path + "epoch_{}".format(epoch),
|
| 272 |
+
)
|
| 273 |
+
|
| 274 |
+
try:
|
| 275 |
+
np.save(
|
| 276 |
+
self.save_path + "preds_train.npy".format(epoch),
|
| 277 |
+
out.detach().cpu().numpy(),
|
| 278 |
+
)
|
| 279 |
+
np.save(
|
| 280 |
+
self.save_path + "y_target_train.npy".format(epoch),
|
| 281 |
+
task["y_target"].detach().cpu().numpy(),
|
| 282 |
+
)
|
| 283 |
+
except:
|
| 284 |
+
pass
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
class DDPTrainerE2E:
|
| 288 |
+
"""
|
| 289 |
+
Training class for E2E
|
| 290 |
+
"""
|
| 291 |
+
|
| 292 |
+
def __init__(
|
| 293 |
+
self,
|
| 294 |
+
model,
|
| 295 |
+
rank,
|
| 296 |
+
train_loader,
|
| 297 |
+
val_loader,
|
| 298 |
+
loss_function,
|
| 299 |
+
save_path,
|
| 300 |
+
learning_rate,
|
| 301 |
+
sampler,
|
| 302 |
+
weight_decay,
|
| 303 |
+
hadisd_variable_name,
|
| 304 |
+
test_loader=None,
|
| 305 |
+
weights_path=None,
|
| 306 |
+
tune_film=False,
|
| 307 |
+
):
|
| 308 |
+
|
| 309 |
+
self.rank = rank
|
| 310 |
+
self.model = model
|
| 311 |
+
self.sampler = sampler
|
| 312 |
+
self.tune_film = tune_film
|
| 313 |
+
self.train_loader = train_loader
|
| 314 |
+
self.val_loader = val_loader
|
| 315 |
+
self.save_path = save_path
|
| 316 |
+
self.loss_function = loss_function
|
| 317 |
+
self.best_loss = 1000
|
| 318 |
+
self.test_loader = test_loader
|
| 319 |
+
self.hadisd_variable_name = hadisd_variable_name
|
| 320 |
+
|
| 321 |
+
self.model = self.model.to(rank)
|
| 322 |
+
self.model = DDP(self.model, device_ids=[rank], find_unused_parameters=True)
|
| 323 |
+
|
| 324 |
+
if self.model.module.decoder == "vit":
|
| 325 |
+
self.opt = torch.optim.AdamW(
|
| 326 |
+
model.parameters(),
|
| 327 |
+
lr=learning_rate,
|
| 328 |
+
betas=(0.9, 0.95),
|
| 329 |
+
weight_decay=1e-5,
|
| 330 |
+
)
|
| 331 |
+
self.scheduler = optim.lr_scheduler.CosineAnnealingLR(self.opt, 891 * 80)
|
| 332 |
+
else:
|
| 333 |
+
self.opt = torch.optim.Adam(
|
| 334 |
+
model.parameters(), lr=learning_rate, weight_decay=weight_decay
|
| 335 |
+
)
|
| 336 |
+
|
| 337 |
+
self.losses = []
|
| 338 |
+
self.train_losses = []
|
| 339 |
+
|
| 340 |
+
self.maes = []
|
| 341 |
+
|
| 342 |
+
def _unravel_to_numpy(self, x):
|
| 343 |
+
return x.view(-1).detach().cpu().numpy()
|
| 344 |
+
|
| 345 |
+
def eval_epoch(self, fix_sigma, epoch):
|
| 346 |
+
|
| 347 |
+
self.model.eval()
|
| 348 |
+
|
| 349 |
+
mae = []
|
| 350 |
+
lf = []
|
| 351 |
+
lf_unnorm = []
|
| 352 |
+
|
| 353 |
+
with torch.no_grad():
|
| 354 |
+
forecasts = []
|
| 355 |
+
targets = []
|
| 356 |
+
stations = []
|
| 357 |
+
indices = []
|
| 358 |
+
for count, task in tqdm(enumerate(self.val_loader)):
|
| 359 |
+
|
| 360 |
+
out = self.model(task, film_index=0)
|
| 361 |
+
forecasts.append(
|
| 362 |
+
unnormalise_hadisd_var(
|
| 363 |
+
out.detach().cpu().numpy(), self.hadisd_variable_name
|
| 364 |
+
)
|
| 365 |
+
)
|
| 366 |
+
targets.append(
|
| 367 |
+
unnormalise_hadisd_var(
|
| 368 |
+
task["y_target"].detach().cpu().numpy(),
|
| 369 |
+
self.hadisd_variable_name,
|
| 370 |
+
)
|
| 371 |
+
)
|
| 372 |
+
stations.append(
|
| 373 |
+
task["downscaling"]["x_target"].detach().cpu().numpy() * 360
|
| 374 |
+
)
|
| 375 |
+
indices.append(task["index"])
|
| 376 |
+
|
| 377 |
+
prev_step = None
|
| 378 |
+
|
| 379 |
+
mae.append(
|
| 380 |
+
np.nanmean(
|
| 381 |
+
np.abs(
|
| 382 |
+
task["y_target"].detach().cpu()
|
| 383 |
+
- out[..., : task["y_target"].shape[-1]].detach().cpu()
|
| 384 |
+
)
|
| 385 |
+
)
|
| 386 |
+
)
|
| 387 |
+
l = (
|
| 388 |
+
self.loss_function(
|
| 389 |
+
task["y_target"], out, prev_step, fix_sigma=fix_sigma
|
| 390 |
+
)
|
| 391 |
+
.detach()
|
| 392 |
+
.item()
|
| 393 |
+
)
|
| 394 |
+
lf.append(l)
|
| 395 |
+
|
| 396 |
+
try:
|
| 397 |
+
ic = self.train_loader.dataset.unnorm_base_context(
|
| 398 |
+
task["y_context"][:, :-11, ...]
|
| 399 |
+
).permute(0, 3, 2, 1)
|
| 400 |
+
unnorm_pred = self.train_loader.dataset.unnorm_pred(out)
|
| 401 |
+
unnorm_target = self.train_loader.dataset.unnorm_pred(
|
| 402 |
+
task["y_target"]
|
| 403 |
+
)
|
| 404 |
+
|
| 405 |
+
unnorm_pred = unnorm_pred + ic
|
| 406 |
+
unnorm_target = unnorm_target + ic
|
| 407 |
+
|
| 408 |
+
lu = (
|
| 409 |
+
self.loss_function(
|
| 410 |
+
unnorm_target,
|
| 411 |
+
unnorm_pred,
|
| 412 |
+
prev_step,
|
| 413 |
+
fix_sigma=fix_sigma,
|
| 414 |
+
expand=True,
|
| 415 |
+
)
|
| 416 |
+
.detach()
|
| 417 |
+
.cpu()
|
| 418 |
+
.numpy()
|
| 419 |
+
)
|
| 420 |
+
|
| 421 |
+
lf_unnorm.append(lu)
|
| 422 |
+
|
| 423 |
+
except:
|
| 424 |
+
pass
|
| 425 |
+
|
| 426 |
+
# Save the test outputs
|
| 427 |
+
np.save(
|
| 428 |
+
self.save_path + f"val_forecast_{self.rank}_{self.epoch}.npy",
|
| 429 |
+
np.concatenate(forecasts, axis=0),
|
| 430 |
+
)
|
| 431 |
+
np.save(
|
| 432 |
+
self.save_path + f"val_targets_{self.rank}_{self.epoch}.npy",
|
| 433 |
+
np.concatenate(targets, axis=0),
|
| 434 |
+
)
|
| 435 |
+
np.save(
|
| 436 |
+
self.save_path + f"val_stations_{self.rank}_{self.epoch}.npy",
|
| 437 |
+
np.concatenate(stations, axis=0),
|
| 438 |
+
)
|
| 439 |
+
np.save(
|
| 440 |
+
self.save_path + f"val_indices_{self.rank}_{self.epoch}.npy",
|
| 441 |
+
np.concatenate(indices, axis=0),
|
| 442 |
+
)
|
| 443 |
+
|
| 444 |
+
if self.test_loader is not None:
|
| 445 |
+
forecasts = []
|
| 446 |
+
targets = []
|
| 447 |
+
stations = []
|
| 448 |
+
indices = []
|
| 449 |
+
|
| 450 |
+
for count, task in enumerate(self.test_loader):
|
| 451 |
+
out = self.model(task, film_index=0)
|
| 452 |
+
forecasts.append(
|
| 453 |
+
unnormalise_hadisd_var(
|
| 454 |
+
out.detach().cpu().numpy(), self.hadisd_variable_name
|
| 455 |
+
)
|
| 456 |
+
)
|
| 457 |
+
targets.append(
|
| 458 |
+
unnormalise_hadisd_var(
|
| 459 |
+
task["y_target"].detach().cpu().numpy(),
|
| 460 |
+
self.hadisd_variable_name,
|
| 461 |
+
)
|
| 462 |
+
)
|
| 463 |
+
stations.append(
|
| 464 |
+
task["downscaling"]["x_target"].detach().cpu().numpy() * 360
|
| 465 |
+
)
|
| 466 |
+
indices.append(task["index"])
|
| 467 |
+
|
| 468 |
+
np.save(
|
| 469 |
+
self.save_path + f"test_forecast_{self.rank}_{self.epoch}.npy",
|
| 470 |
+
np.concatenate(forecasts, axis=0),
|
| 471 |
+
)
|
| 472 |
+
np.save(
|
| 473 |
+
self.save_path + f"test_targets_{self.rank}_{self.epoch}.npy",
|
| 474 |
+
np.concatenate(targets, axis=0),
|
| 475 |
+
)
|
| 476 |
+
np.save(
|
| 477 |
+
self.save_path + f"test_stations_{self.rank}_{self.epoch}.npy",
|
| 478 |
+
np.concatenate(stations, axis=0),
|
| 479 |
+
)
|
| 480 |
+
np.save(
|
| 481 |
+
self.save_path + f"test_indices_{self.rank}_{self.epoch}.npy",
|
| 482 |
+
np.concatenate(indices, axis=0),
|
| 483 |
+
)
|
| 484 |
+
|
| 485 |
+
log_loss = np.nanmean(np.array(lf))
|
| 486 |
+
|
| 487 |
+
if log_loss < self.best_loss:
|
| 488 |
+
np.save(
|
| 489 |
+
self.save_path + "unnorm_preds.npy",
|
| 490 |
+
self.train_loader.dataset.unnorm_pred(out).detach().cpu().numpy(),
|
| 491 |
+
)
|
| 492 |
+
np.save(
|
| 493 |
+
self.save_path + "unnorm_targets.npy",
|
| 494 |
+
self.train_loader.dataset.unnorm_pred(task["y_target"])
|
| 495 |
+
.detach()
|
| 496 |
+
.cpu()
|
| 497 |
+
.numpy(),
|
| 498 |
+
)
|
| 499 |
+
log_loss_unnorm = np.nanmean(np.stack(lf_unnorm), axis=0)
|
| 500 |
+
|
| 501 |
+
if np.logical_and(self.rank == 0, self.epoch % 5 == 0):
|
| 502 |
+
|
| 503 |
+
np.save(self.save_path + "preds_eval.npy", out.cpu().numpy())
|
| 504 |
+
np.save(
|
| 505 |
+
self.save_path + "y_target_eval.npy", task["y_target"].cpu().numpy()
|
| 506 |
+
)
|
| 507 |
+
|
| 508 |
+
return log_loss, log_loss_unnorm
|
| 509 |
+
|
| 510 |
+
def train(self, n_epochs=100):
|
| 511 |
+
|
| 512 |
+
torch.cuda.set_device(self.rank)
|
| 513 |
+
subprocess.run(["cp", "reproduce_training.sh", f"{self.save_path}"])
|
| 514 |
+
|
| 515 |
+
train_loss = []
|
| 516 |
+
ll = []
|
| 517 |
+
|
| 518 |
+
fix_sigma = False
|
| 519 |
+
prev_step = None
|
| 520 |
+
|
| 521 |
+
self.epoch = 0
|
| 522 |
+
epoch_loss, log_loss_unnorm = self.eval_epoch(fix_sigma, 0)
|
| 523 |
+
train_loss = np.mean(train_loss)
|
| 524 |
+
|
| 525 |
+
for epoch in range(n_epochs):
|
| 526 |
+
self.epoch = epoch
|
| 527 |
+
self.sampler.set_epoch(epoch)
|
| 528 |
+
|
| 529 |
+
epoch_loss, log_loss_unnorm = self.eval_epoch(fix_sigma, epoch)
|
| 530 |
+
train_loss = np.mean(train_loss)
|
| 531 |
+
ll.append(log_loss_unnorm)
|
| 532 |
+
|
| 533 |
+
self.losses.append(epoch_loss)
|
| 534 |
+
self.train_losses.append(train_loss)
|
| 535 |
+
np.save(
|
| 536 |
+
self.save_path + "losses_{}.npy".format(self.rank),
|
| 537 |
+
np.array(self.losses),
|
| 538 |
+
)
|
| 539 |
+
np.save(
|
| 540 |
+
self.save_path + "train_losses_{}.npy".format(self.rank),
|
| 541 |
+
np.array(self.train_losses),
|
| 542 |
+
)
|
| 543 |
+
np.save(self.save_path + "rmse_{}.npy".format(self.rank), np.array(ll))
|
| 544 |
+
|
| 545 |
+
if epoch_loss < self.best_loss:
|
| 546 |
+
self.best_loss = epoch_loss
|
| 547 |
+
|
| 548 |
+
if self.rank == 0:
|
| 549 |
+
if self.model.module.decoder == "vit":
|
| 550 |
+
torch.save(
|
| 551 |
+
{
|
| 552 |
+
"epoch": epoch,
|
| 553 |
+
"model_state_dict": self.model.state_dict(),
|
| 554 |
+
"optimizer_state_dict": self.opt.state_dict(),
|
| 555 |
+
"scheduler_state_dict": self.scheduler.state_dict(),
|
| 556 |
+
"loss": epoch_loss,
|
| 557 |
+
},
|
| 558 |
+
self.save_path + "epoch_{}".format(epoch),
|
| 559 |
+
)
|
| 560 |
+
else:
|
| 561 |
+
torch.save(
|
| 562 |
+
{
|
| 563 |
+
"epoch": epoch,
|
| 564 |
+
"model_state_dict": self.model.state_dict(),
|
| 565 |
+
"optimizer_state_dict": self.opt.state_dict(),
|
| 566 |
+
"loss": epoch_loss,
|
| 567 |
+
},
|
| 568 |
+
self.save_path + "epoch_{}".format(epoch),
|
| 569 |
+
)
|
| 570 |
+
|
| 571 |
+
try:
|
| 572 |
+
np.save(
|
| 573 |
+
self.save_path + "preds_train.npy".format(epoch),
|
| 574 |
+
out.detach().cpu().numpy(),
|
| 575 |
+
)
|
| 576 |
+
np.save(
|
| 577 |
+
self.save_path + "y_target_train.npy".format(epoch),
|
| 578 |
+
task["y_target"].detach().cpu().numpy(),
|
| 579 |
+
)
|
| 580 |
+
except:
|
| 581 |
+
pass
|
| 582 |
+
|
| 583 |
+
self.model.train()
|
| 584 |
+
train_loss = []
|
| 585 |
+
with tqdm(self.train_loader, unit="batch") as tepoch:
|
| 586 |
+
for count, task in tqdm(enumerate(tepoch)):
|
| 587 |
+
out = self.model(task, film_index=0)
|
| 588 |
+
|
| 589 |
+
loss = self.loss_function(
|
| 590 |
+
task["y_target"], out, prev_step, fix_sigma=fix_sigma
|
| 591 |
+
)
|
| 592 |
+
|
| 593 |
+
loss.backward()
|
| 594 |
+
tepoch.set_postfix(loss=loss.item())
|
| 595 |
+
prev_step = out
|
| 596 |
+
|
| 597 |
+
self.opt.step()
|
| 598 |
+
self.opt.zero_grad()
|
| 599 |
+
train_loss.append(loss.item())
|
| 600 |
+
if self.model.module.decoder == "vit":
|
| 601 |
+
if epoch > 20:
|
| 602 |
+
self.scheduler.step()
|
official-src/aardvark/unet_wrap_padding.py
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn as nn
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def cylindrical_conv_pad(x, w_pad):
|
| 8 |
+
return torch.cat([x[..., -w_pad:], x, x[..., :w_pad]], axis=-1)
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class CylindricalConv2D(nn.Conv2d):
|
| 12 |
+
"""
|
| 13 |
+
UNet with cylinderical boundary conditions
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
def __init__(
|
| 17 |
+
self,
|
| 18 |
+
in_channels: int,
|
| 19 |
+
out_channels: int,
|
| 20 |
+
kernel_size: int,
|
| 21 |
+
stride: int,
|
| 22 |
+
):
|
| 23 |
+
super().__init__(
|
| 24 |
+
in_channels=in_channels,
|
| 25 |
+
out_channels=out_channels,
|
| 26 |
+
kernel_size=kernel_size,
|
| 27 |
+
stride=stride,
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
assert self.kernel_size[0] % 2 == 1
|
| 31 |
+
assert self.kernel_size[1] % 2 == 1
|
| 32 |
+
|
| 33 |
+
self.h_pad = self.kernel_size[0] // 2
|
| 34 |
+
self.w_pad = self.kernel_size[1] // 2
|
| 35 |
+
|
| 36 |
+
def forward(self, x: torch.Tensor):
|
| 37 |
+
x = nn.functional.pad(x, (0, 0, self.h_pad, self.h_pad))
|
| 38 |
+
return super().forward(cylindrical_conv_pad(x, self.w_pad))
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class CylindricalConvTranspose2D(nn.ConvTranspose2d):
|
| 42 |
+
def __init__(
|
| 43 |
+
self,
|
| 44 |
+
in_channels: int,
|
| 45 |
+
out_channels: int,
|
| 46 |
+
kernel_size: int,
|
| 47 |
+
stride: int,
|
| 48 |
+
):
|
| 49 |
+
super().__init__(
|
| 50 |
+
in_channels=in_channels,
|
| 51 |
+
out_channels=out_channels,
|
| 52 |
+
kernel_size=kernel_size,
|
| 53 |
+
stride=stride,
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
assert self.kernel_size[0] % 2 == 1
|
| 57 |
+
assert self.kernel_size[1] % 2 == 1
|
| 58 |
+
|
| 59 |
+
self.sh, self.sw = self.stride
|
| 60 |
+
self.kh, self.kw = self.kernel_size
|
| 61 |
+
|
| 62 |
+
self.h_pad = math.ceil(((self.sh - 1) + 2 * (self.kh // 2)) / self.sh)
|
| 63 |
+
self.w_pad = math.ceil(((self.sw - 1) + 2 * (self.kw // 2)) / self.sw)
|
| 64 |
+
|
| 65 |
+
self.h0 = self.sh * self.h_pad - (self.sh - 1) + (self.kh // 2)
|
| 66 |
+
self.w0 = self.sw * self.w_pad - (self.sw - 1) + (self.kw // 2)
|
| 67 |
+
|
| 68 |
+
self._bias = nn.Parameter(10**-3 * torch.randn(out_channels))
|
| 69 |
+
|
| 70 |
+
def forward(self, x: torch.Tensor):
|
| 71 |
+
|
| 72 |
+
Nh = x.shape[2] * self.sh
|
| 73 |
+
Nw = x.shape[3] * self.sw
|
| 74 |
+
|
| 75 |
+
x = cylindrical_conv_pad(x, self.w_pad)
|
| 76 |
+
x = nn.functional.pad(x, (0, 0, self.h_pad, self.h_pad))
|
| 77 |
+
|
| 78 |
+
x = super().forward(x)
|
| 79 |
+
|
| 80 |
+
return x[:, :, self.h0 : self.h0 + Nh, self.w0 : self.w0 + Nw]
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
class Down(nn.Module):
|
| 84 |
+
|
| 85 |
+
def __init__(
|
| 86 |
+
self,
|
| 87 |
+
in_channels,
|
| 88 |
+
out_channels,
|
| 89 |
+
p=0,
|
| 90 |
+
film=False,
|
| 91 |
+
down=True,
|
| 92 |
+
attn=False,
|
| 93 |
+
):
|
| 94 |
+
|
| 95 |
+
super().__init__()
|
| 96 |
+
|
| 97 |
+
self.film = film
|
| 98 |
+
self.attn = attn
|
| 99 |
+
|
| 100 |
+
self.conv_1 = CylindricalConv2D(
|
| 101 |
+
in_channels, out_channels, kernel_size=3, stride=1
|
| 102 |
+
)
|
| 103 |
+
if down:
|
| 104 |
+
self.conv_2 = CylindricalConv2D(
|
| 105 |
+
out_channels, out_channels, kernel_size=3, stride=2
|
| 106 |
+
)
|
| 107 |
+
else:
|
| 108 |
+
self.conv_2 = CylindricalConv2D(
|
| 109 |
+
out_channels, out_channels, kernel_size=3, stride=1
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
self.bn_1 = nn.BatchNorm2d(out_channels)
|
| 113 |
+
self.bn_2 = nn.BatchNorm2d(out_channels)
|
| 114 |
+
|
| 115 |
+
self.activation = nn.GELU()
|
| 116 |
+
|
| 117 |
+
if film:
|
| 118 |
+
|
| 119 |
+
self.gamma_1 = torch.nn.Parameter(
|
| 120 |
+
torch.ones(10, out_channels, 1, 1),
|
| 121 |
+
)
|
| 122 |
+
self.gamma_2 = torch.nn.Parameter(
|
| 123 |
+
torch.ones(10, out_channels, 1, 1),
|
| 124 |
+
)
|
| 125 |
+
self.beta_1 = torch.nn.Parameter(
|
| 126 |
+
torch.zeros(10, out_channels, 1, 1),
|
| 127 |
+
)
|
| 128 |
+
self.beta_2 = torch.nn.Parameter(
|
| 129 |
+
torch.zeros(10, out_channels, 1, 1),
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
+
if self.attn:
|
| 133 |
+
self.mha = AttentionBlock(n_channels=out_channels, n_heads=8)
|
| 134 |
+
|
| 135 |
+
def forward(self, xi, film_index=None):
|
| 136 |
+
|
| 137 |
+
film_index = film_index[:, 0].int()
|
| 138 |
+
|
| 139 |
+
x = self.conv_1(xi)
|
| 140 |
+
x = self.bn_1(x)
|
| 141 |
+
if self.film:
|
| 142 |
+
g1 = torch.index_select(self.gamma_1, 0, film_index)
|
| 143 |
+
b1 = torch.index_select(self.beta_1, 0, film_index)
|
| 144 |
+
|
| 145 |
+
x = g1 * x + b1
|
| 146 |
+
|
| 147 |
+
x = self.activation(x)
|
| 148 |
+
x = self.conv_2(x)
|
| 149 |
+
x = self.bn_2(x)
|
| 150 |
+
if self.film:
|
| 151 |
+
g2 = torch.index_select(self.gamma_2, 0, film_index)
|
| 152 |
+
b2 = torch.index_select(self.beta_2, 0, film_index)
|
| 153 |
+
x = g2 * x + b2
|
| 154 |
+
x = self.activation(x)
|
| 155 |
+
|
| 156 |
+
if self.attn:
|
| 157 |
+
x = self.mha(x)
|
| 158 |
+
|
| 159 |
+
return x
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
class Up(nn.Module):
|
| 163 |
+
|
| 164 |
+
def __init__(
|
| 165 |
+
self,
|
| 166 |
+
in_channels,
|
| 167 |
+
out_channels,
|
| 168 |
+
p,
|
| 169 |
+
bilinear=False,
|
| 170 |
+
film=False,
|
| 171 |
+
stride=2,
|
| 172 |
+
attn=False,
|
| 173 |
+
):
|
| 174 |
+
super().__init__()
|
| 175 |
+
|
| 176 |
+
self.film = film
|
| 177 |
+
|
| 178 |
+
if bilinear:
|
| 179 |
+
self.up = nn.Upsample(
|
| 180 |
+
scale_factor=2,
|
| 181 |
+
mode="bilinear",
|
| 182 |
+
align_corners=True,
|
| 183 |
+
)
|
| 184 |
+
else:
|
| 185 |
+
self.up = CylindricalConvTranspose2D(
|
| 186 |
+
in_channels, out_channels, kernel_size=3, stride=stride
|
| 187 |
+
)
|
| 188 |
+
|
| 189 |
+
self.conv = Down(
|
| 190 |
+
out_channels,
|
| 191 |
+
out_channels,
|
| 192 |
+
p=0,
|
| 193 |
+
film=film,
|
| 194 |
+
down=False,
|
| 195 |
+
attn=attn,
|
| 196 |
+
)
|
| 197 |
+
|
| 198 |
+
def forward(self, x1, x2, film_index=None):
|
| 199 |
+
x1 = self.up(x1)
|
| 200 |
+
x1 = self.conv(x1, film_index=film_index)
|
| 201 |
+
|
| 202 |
+
if x1.shape[-1] != x2.shape[-1]:
|
| 203 |
+
x1 = x1[..., :, :-1]
|
| 204 |
+
if x1.shape[-2] != x2.shape[-2]:
|
| 205 |
+
x1 = x1[..., :-1, :]
|
| 206 |
+
|
| 207 |
+
return torch.cat([x2, x1], dim=1)
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
class Unet(nn.Module):
|
| 211 |
+
|
| 212 |
+
def __init__(
|
| 213 |
+
self,
|
| 214 |
+
in_channels,
|
| 215 |
+
out_channels,
|
| 216 |
+
div_factor=1,
|
| 217 |
+
p=0.0,
|
| 218 |
+
context=True,
|
| 219 |
+
film=False,
|
| 220 |
+
film_base=True,
|
| 221 |
+
):
|
| 222 |
+
super(Unet, self).__init__()
|
| 223 |
+
|
| 224 |
+
self.n_channels = in_channels
|
| 225 |
+
self.bilinear = True
|
| 226 |
+
self.fp = nn.Softplus()
|
| 227 |
+
self.variances = nn.Parameter(torch.zeros([out_channels]))
|
| 228 |
+
self.context = context
|
| 229 |
+
self.film = film
|
| 230 |
+
|
| 231 |
+
m = 1
|
| 232 |
+
|
| 233 |
+
self.down1 = Down(
|
| 234 |
+
self.n_channels,
|
| 235 |
+
m * 128 // div_factor,
|
| 236 |
+
p=0,
|
| 237 |
+
film=film,
|
| 238 |
+
attn=False,
|
| 239 |
+
)
|
| 240 |
+
self.down2 = Down(
|
| 241 |
+
m * 128 // div_factor,
|
| 242 |
+
m * 256 // div_factor,
|
| 243 |
+
p=0,
|
| 244 |
+
film=film,
|
| 245 |
+
attn=False,
|
| 246 |
+
)
|
| 247 |
+
self.down3 = Down(
|
| 248 |
+
m * 256 // div_factor,
|
| 249 |
+
m * 512 // div_factor,
|
| 250 |
+
p=0,
|
| 251 |
+
film=film,
|
| 252 |
+
attn=False,
|
| 253 |
+
)
|
| 254 |
+
self.down4 = Down(
|
| 255 |
+
m * 512 // div_factor,
|
| 256 |
+
m * 512 // div_factor,
|
| 257 |
+
p=0,
|
| 258 |
+
film=film,
|
| 259 |
+
attn=False,
|
| 260 |
+
)
|
| 261 |
+
self.up1 = Up(
|
| 262 |
+
m * 512 // div_factor,
|
| 263 |
+
m * 512 // div_factor,
|
| 264 |
+
p=0,
|
| 265 |
+
film=film,
|
| 266 |
+
attn=False,
|
| 267 |
+
)
|
| 268 |
+
self.up2 = Up(
|
| 269 |
+
m * 1024 // div_factor,
|
| 270 |
+
m * 256 // div_factor,
|
| 271 |
+
p=0,
|
| 272 |
+
film=film,
|
| 273 |
+
attn=False,
|
| 274 |
+
)
|
| 275 |
+
self.up3 = Up(
|
| 276 |
+
m * 512 // div_factor,
|
| 277 |
+
m * 128 // div_factor,
|
| 278 |
+
p=0,
|
| 279 |
+
film=film,
|
| 280 |
+
attn=False,
|
| 281 |
+
)
|
| 282 |
+
self.up4 = Up(
|
| 283 |
+
m * 256 // div_factor,
|
| 284 |
+
m * 64 // div_factor,
|
| 285 |
+
p=0,
|
| 286 |
+
film=film,
|
| 287 |
+
attn=False,
|
| 288 |
+
)
|
| 289 |
+
|
| 290 |
+
self.out = nn.Conv2d(
|
| 291 |
+
m * 64 // div_factor + in_channels,
|
| 292 |
+
out_channels,
|
| 293 |
+
kernel_size=1,
|
| 294 |
+
bias=False,
|
| 295 |
+
)
|
| 296 |
+
|
| 297 |
+
def forward(self, x, film_index=None):
|
| 298 |
+
|
| 299 |
+
x1 = x.contiguous()
|
| 300 |
+
x2 = self.down1(x1, film_index=film_index)
|
| 301 |
+
x3 = self.down2(x2, film_index=film_index)
|
| 302 |
+
x4 = self.down3(x3, film_index=film_index)
|
| 303 |
+
x5 = self.down4(x4, film_index=film_index)
|
| 304 |
+
x = self.up1(x5, x4, film_index=film_index)
|
| 305 |
+
x = self.up2(x, x3, film_index=film_index)
|
| 306 |
+
x = self.up3(x, x2, film_index=film_index)
|
| 307 |
+
x = self.up4(x, x1, film_index=film_index)
|
| 308 |
+
x = self.out(x)
|
| 309 |
+
|
| 310 |
+
return x.permute(0, 2, 3, 1)
|
official-src/aardvark/utils.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections import defaultdict
|
| 2 |
+
import numpy as np
|
| 3 |
+
from torch.nn.utils.rnn import pad_sequence
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def channels_to_2nd_dim(x):
|
| 7 |
+
return x.permute(*([0, x.dim() - 1] + list(range(1, x.dim() - 1))))
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def channels_to_final_dim(x):
|
| 11 |
+
return x.permute(*([0] + list(range(2, x.dim())) + [1]))
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def collate(tensor_list):
|
| 15 |
+
out_dict = defaultdict()
|
| 16 |
+
for k in tensor_list[0].keys():
|
| 17 |
+
out_dict[k] = [t[k] for t in tensor_list]
|
| 18 |
+
out_dict[k] = pad_sequence(
|
| 19 |
+
out_dict[k],
|
| 20 |
+
padding_value=np.nan,
|
| 21 |
+
batch_first=True,
|
| 22 |
+
)
|
| 23 |
+
return out_dict
|
official-src/aardvark/vit.py
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
This file is adapted from Nguyen, Tung, et al. "ClimaX: A foundation model
|
| 3 |
+
for weather and climate." arXiv preprint arXiv:2301.10343 (2023).
|
| 4 |
+
Code from this project is available at https://github.com/microsoft/ClimaX
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from functools import lru_cache
|
| 8 |
+
|
| 9 |
+
import numpy as np
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn as nn
|
| 12 |
+
from timm.models.vision_transformer import Block, PatchEmbed, trunc_normal_
|
| 13 |
+
|
| 14 |
+
from architectures import MLP
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def get_2d_sincos_pos_embed(embed_dim, grid_size_h, grid_size_w, cls_token=False):
|
| 18 |
+
|
| 19 |
+
grid_h = np.arange(grid_size_h, dtype=np.float32)
|
| 20 |
+
grid_w = np.arange(grid_size_w, dtype=np.float32)
|
| 21 |
+
grid = np.meshgrid(grid_w, grid_h)
|
| 22 |
+
grid = np.stack(grid, axis=0)
|
| 23 |
+
|
| 24 |
+
grid = grid.reshape([2, 1, grid_size_h, grid_size_w])
|
| 25 |
+
pos_embed = get_2d_sincos_pos_embed_from_grid(embed_dim, grid)
|
| 26 |
+
if cls_token:
|
| 27 |
+
pos_embed = np.concatenate([np.zeros([1, embed_dim]), pos_embed], axis=0)
|
| 28 |
+
return pos_embed
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def get_2d_sincos_pos_embed_from_grid(embed_dim, grid):
|
| 32 |
+
assert embed_dim % 2 == 0
|
| 33 |
+
|
| 34 |
+
emb_h = get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid[0])
|
| 35 |
+
emb_w = get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid[1])
|
| 36 |
+
|
| 37 |
+
emb = np.concatenate([emb_h, emb_w], axis=1)
|
| 38 |
+
return emb
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def get_1d_sincos_pos_embed_from_grid(embed_dim, pos):
|
| 42 |
+
assert embed_dim % 2 == 0
|
| 43 |
+
omega = np.arange(embed_dim // 2, dtype=np.float32)
|
| 44 |
+
omega /= embed_dim / 2.0
|
| 45 |
+
omega = 1.0 / 10000**omega
|
| 46 |
+
|
| 47 |
+
pos = pos.reshape(-1)
|
| 48 |
+
out = np.einsum("m,d->md", pos, omega)
|
| 49 |
+
|
| 50 |
+
emb_sin = np.sin(out)
|
| 51 |
+
emb_cos = np.cos(out)
|
| 52 |
+
|
| 53 |
+
emb = np.concatenate([emb_sin, emb_cos], axis=1)
|
| 54 |
+
return emb
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def interpolate_pos_embed(model, checkpoint_model, new_size=(64, 128)):
|
| 58 |
+
if "net.pos_embed" in checkpoint_model:
|
| 59 |
+
pos_embed_checkpoint = checkpoint_model["net.pos_embed"]
|
| 60 |
+
embedding_size = pos_embed_checkpoint.shape[-1]
|
| 61 |
+
orig_num_patches = pos_embed_checkpoint.shape[-2]
|
| 62 |
+
patch_size = model.patch_size
|
| 63 |
+
w_h_ratio = 2
|
| 64 |
+
orig_h = int((orig_num_patches // w_h_ratio) ** 0.5)
|
| 65 |
+
orig_w = w_h_ratio * orig_h
|
| 66 |
+
orig_size = (orig_h, orig_w)
|
| 67 |
+
new_size = (new_size[0] // patch_size, new_size[1] // patch_size)
|
| 68 |
+
|
| 69 |
+
if orig_size[0] != new_size[0]:
|
| 70 |
+
print(
|
| 71 |
+
"Interpolate PEs from %dx%d to %dx%d"
|
| 72 |
+
% (orig_size[0], orig_size[1], new_size[0], new_size[1])
|
| 73 |
+
)
|
| 74 |
+
pos_tokens = pos_embed_checkpoint.reshape(
|
| 75 |
+
-1, orig_size[0], orig_size[1], embedding_size
|
| 76 |
+
).permute(0, 3, 1, 2)
|
| 77 |
+
new_pos_tokens = torch.nn.functional.interpolate(
|
| 78 |
+
pos_tokens,
|
| 79 |
+
size=(new_size[0], new_size[1]),
|
| 80 |
+
mode="bicubic",
|
| 81 |
+
align_corners=False,
|
| 82 |
+
)
|
| 83 |
+
new_pos_tokens = new_pos_tokens.permute(0, 2, 3, 1).flatten(1, 2)
|
| 84 |
+
checkpoint_model["net.pos_embed"] = new_pos_tokens
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def interpolate_channel_embed(checkpoint_model, new_len):
|
| 88 |
+
if "net.channel_embed" in checkpoint_model:
|
| 89 |
+
channel_embed_checkpoint = checkpoint_model["net.channel_embed"]
|
| 90 |
+
old_len = channel_embed_checkpoint.shape[1]
|
| 91 |
+
if new_len <= old_len:
|
| 92 |
+
checkpoint_model["net.channel_embed"] = channel_embed_checkpoint[
|
| 93 |
+
:, :new_len
|
| 94 |
+
]
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
class ViT(nn.Module):
|
| 98 |
+
|
| 99 |
+
def __init__(
|
| 100 |
+
self,
|
| 101 |
+
in_channels,
|
| 102 |
+
out_channels,
|
| 103 |
+
h_channels,
|
| 104 |
+
img_size=[256, 128],
|
| 105 |
+
patch_size=8,
|
| 106 |
+
depth=24,
|
| 107 |
+
decoder_depth=4,
|
| 108 |
+
num_heads=16,
|
| 109 |
+
mlp_ratio=4.0,
|
| 110 |
+
drop_path=0.0,
|
| 111 |
+
drop_rate=0.0,
|
| 112 |
+
per_var_embedding=True,
|
| 113 |
+
):
|
| 114 |
+
super().__init__()
|
| 115 |
+
|
| 116 |
+
self.img_size = img_size
|
| 117 |
+
self.patch_size = patch_size
|
| 118 |
+
default_vars = [str(i) for i in range(in_channels)]
|
| 119 |
+
self.default_vars = default_vars
|
| 120 |
+
embed_dim = h_channels
|
| 121 |
+
self.per_var_embedding = per_var_embedding
|
| 122 |
+
|
| 123 |
+
if self.per_var_embedding:
|
| 124 |
+
self.token_embeds = nn.ModuleList(
|
| 125 |
+
[
|
| 126 |
+
PatchEmbed(img_size, patch_size, 1, embed_dim)
|
| 127 |
+
for i in range(len(default_vars))
|
| 128 |
+
]
|
| 129 |
+
)
|
| 130 |
+
else:
|
| 131 |
+
self.token_embeds = nn.ModuleList(
|
| 132 |
+
[PatchEmbed(img_size, patch_size, in_channels, embed_dim)]
|
| 133 |
+
)
|
| 134 |
+
self.num_patches = self.token_embeds[0].num_patches
|
| 135 |
+
|
| 136 |
+
self.var_embed, self.var_map = self.create_var_embedding(embed_dim)
|
| 137 |
+
self.var_query = nn.Parameter(torch.zeros(1, 1, embed_dim), requires_grad=True)
|
| 138 |
+
self.var_agg = nn.MultiheadAttention(embed_dim, num_heads, batch_first=True)
|
| 139 |
+
|
| 140 |
+
self.pos_embed = nn.Parameter(
|
| 141 |
+
torch.zeros(1, self.num_patches, embed_dim), requires_grad=True
|
| 142 |
+
)
|
| 143 |
+
self.lead_time_embed = nn.Linear(1, embed_dim)
|
| 144 |
+
|
| 145 |
+
self.out_dim = out_channels
|
| 146 |
+
self.pos_drop = nn.Dropout(p=drop_rate)
|
| 147 |
+
dpr = [x.item() for x in torch.linspace(0, drop_path, depth)]
|
| 148 |
+
self.blocks = nn.ModuleList(
|
| 149 |
+
[
|
| 150 |
+
Block(
|
| 151 |
+
embed_dim,
|
| 152 |
+
num_heads,
|
| 153 |
+
mlp_ratio,
|
| 154 |
+
qkv_bias=True,
|
| 155 |
+
drop_path=dpr[i],
|
| 156 |
+
norm_layer=nn.LayerNorm,
|
| 157 |
+
drop=drop_rate,
|
| 158 |
+
)
|
| 159 |
+
for i in range(depth)
|
| 160 |
+
]
|
| 161 |
+
)
|
| 162 |
+
self.norm = nn.LayerNorm(embed_dim)
|
| 163 |
+
|
| 164 |
+
self.head = nn.ModuleList()
|
| 165 |
+
for _ in range(decoder_depth):
|
| 166 |
+
self.head.append(nn.Linear(embed_dim, embed_dim))
|
| 167 |
+
self.head.append(nn.GELU())
|
| 168 |
+
self.head.append(nn.Linear(embed_dim, self.out_dim * patch_size**2))
|
| 169 |
+
self.head = nn.Sequential(*self.head)
|
| 170 |
+
|
| 171 |
+
self.initialize_weights()
|
| 172 |
+
if not self.per_var_embedding:
|
| 173 |
+
self.mlp = MLP(in_channels=277, out_channels=256)
|
| 174 |
+
|
| 175 |
+
def initialize_weights(self):
|
| 176 |
+
pos_embed = get_2d_sincos_pos_embed(
|
| 177 |
+
self.pos_embed.shape[-1],
|
| 178 |
+
int(self.img_size[0] / self.patch_size),
|
| 179 |
+
int(self.img_size[1] / self.patch_size),
|
| 180 |
+
cls_token=False,
|
| 181 |
+
)
|
| 182 |
+
self.pos_embed.data.copy_(torch.from_numpy(pos_embed).float().unsqueeze(0))
|
| 183 |
+
|
| 184 |
+
var_embed = get_1d_sincos_pos_embed_from_grid(
|
| 185 |
+
self.var_embed.shape[-1], np.arange(len(self.default_vars))
|
| 186 |
+
)
|
| 187 |
+
self.var_embed.data.copy_(torch.from_numpy(var_embed).float().unsqueeze(0))
|
| 188 |
+
|
| 189 |
+
for i in range(len(self.token_embeds)):
|
| 190 |
+
w = self.token_embeds[i].proj.weight.data
|
| 191 |
+
trunc_normal_(w.view([w.shape[0], -1]), std=0.02)
|
| 192 |
+
|
| 193 |
+
self.apply(self._init_weights)
|
| 194 |
+
|
| 195 |
+
def _init_weights(self, m):
|
| 196 |
+
if isinstance(m, nn.Linear):
|
| 197 |
+
trunc_normal_(m.weight, std=0.02)
|
| 198 |
+
if m.bias is not None:
|
| 199 |
+
nn.init.constant_(m.bias, 0)
|
| 200 |
+
elif isinstance(m, nn.LayerNorm):
|
| 201 |
+
nn.init.constant_(m.bias, 0)
|
| 202 |
+
nn.init.constant_(m.weight, 1.0)
|
| 203 |
+
|
| 204 |
+
def create_var_embedding(self, dim):
|
| 205 |
+
var_embed = nn.Parameter(
|
| 206 |
+
torch.zeros(1, len(self.default_vars), dim), requires_grad=True
|
| 207 |
+
)
|
| 208 |
+
var_map = {}
|
| 209 |
+
idx = 0
|
| 210 |
+
for var in self.default_vars:
|
| 211 |
+
var_map[var] = idx
|
| 212 |
+
idx += 1
|
| 213 |
+
return var_embed, var_map
|
| 214 |
+
|
| 215 |
+
@lru_cache(maxsize=None)
|
| 216 |
+
def get_var_ids(self, vars, device):
|
| 217 |
+
ids = np.array([self.var_map[var] for var in vars])
|
| 218 |
+
return torch.from_numpy(ids).to(device)
|
| 219 |
+
|
| 220 |
+
def get_var_emb(self, var_emb, vars):
|
| 221 |
+
ids = self.get_var_ids(vars, var_emb.device)
|
| 222 |
+
return var_emb[:, ids, :]
|
| 223 |
+
|
| 224 |
+
def unpatchify(self, x: torch.Tensor, h=None, w=None):
|
| 225 |
+
p = self.patch_size
|
| 226 |
+
c = self.out_dim
|
| 227 |
+
h = self.img_size[0] // p if h is None else h // p
|
| 228 |
+
w = self.img_size[1] // p if w is None else w // p
|
| 229 |
+
assert h * w == x.shape[1]
|
| 230 |
+
|
| 231 |
+
x = x.reshape(shape=(x.shape[0], h, w, p, p, c))
|
| 232 |
+
x = torch.einsum("nhwpqc->nchpwq", x)
|
| 233 |
+
imgs = x.reshape(shape=(x.shape[0], c, h * p, w * p))
|
| 234 |
+
return imgs
|
| 235 |
+
|
| 236 |
+
def aggregate_variables(self, x: torch.Tensor):
|
| 237 |
+
b, _, l, _ = x.shape
|
| 238 |
+
x = torch.einsum("bvld->blvd", x)
|
| 239 |
+
x = x.flatten(0, 1)
|
| 240 |
+
|
| 241 |
+
var_query = self.var_query.repeat_interleave(x.shape[0], dim=0)
|
| 242 |
+
x, _ = self.var_agg(var_query, x, x)
|
| 243 |
+
x = x.squeeze()
|
| 244 |
+
|
| 245 |
+
x = x.unflatten(dim=0, sizes=(b, l))
|
| 246 |
+
return x
|
| 247 |
+
|
| 248 |
+
def mlp_embedding(self, x):
|
| 249 |
+
|
| 250 |
+
return
|
| 251 |
+
|
| 252 |
+
def forward_encoder(self, x, lead_times, variables):
|
| 253 |
+
|
| 254 |
+
if isinstance(variables, list):
|
| 255 |
+
variables = tuple(variables)
|
| 256 |
+
|
| 257 |
+
if self.per_var_embedding:
|
| 258 |
+
embeds = []
|
| 259 |
+
var_ids = self.get_var_ids(variables, x.device)
|
| 260 |
+
for i in range(len(var_ids)):
|
| 261 |
+
id = var_ids[i]
|
| 262 |
+
embeds.append(self.token_embeds[id](x[:, i : i + 1]))
|
| 263 |
+
x = torch.stack(embeds, dim=1)
|
| 264 |
+
|
| 265 |
+
var_embed = self.get_var_emb(self.var_embed, variables)
|
| 266 |
+
x = x + var_embed.unsqueeze(2)
|
| 267 |
+
|
| 268 |
+
x = self.aggregate_variables(x)
|
| 269 |
+
|
| 270 |
+
else:
|
| 271 |
+
x = self.mlp(x.permute(0, 2, 3, 1)).permute(0, 3, 1, 2)
|
| 272 |
+
x = self.token_embeds[0](x)
|
| 273 |
+
|
| 274 |
+
x = x + self.pos_embed
|
| 275 |
+
|
| 276 |
+
lead_time_emb = self.lead_time_embed(lead_times.unsqueeze(-1))
|
| 277 |
+
lead_time_emb = lead_time_emb.unsqueeze(1)
|
| 278 |
+
|
| 279 |
+
x = x + lead_time_emb
|
| 280 |
+
|
| 281 |
+
x = self.pos_drop(x)
|
| 282 |
+
|
| 283 |
+
for blk in self.blocks:
|
| 284 |
+
x = blk(x)
|
| 285 |
+
x = self.norm(x)
|
| 286 |
+
|
| 287 |
+
return x
|
| 288 |
+
|
| 289 |
+
def forward(self, x, lead_times=None, film_index=None):
|
| 290 |
+
|
| 291 |
+
if lead_times is None:
|
| 292 |
+
lead_times = torch.ones(x.shape[0]).float().cuda().unsqueeze(-1)
|
| 293 |
+
|
| 294 |
+
out_transformers = self.forward_encoder(x, lead_times[:, 0], self.default_vars)
|
| 295 |
+
preds = self.head(out_transformers)
|
| 296 |
+
preds = self.unpatchify(preds)
|
| 297 |
+
|
| 298 |
+
return preds.permute(0, 2, 3, 1)
|
official-src/data/grid_lon_lat/era5_x_1.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:00907116398e8f25089efe261536bdc76937c3d17aa592914cb71852c18c92d9
|
| 3 |
+
size 2048
|
official-src/data/grid_lon_lat/era5_y_1.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e09ac41c355484d2c361c8b4ff1dff34b104efcb7b6f1532a75ff422bd5dce35
|
| 3 |
+
size 1096
|
official-src/data/norm_factors/mean_4u_1.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:40376a60ad7d920f9d1c2ab08f4f783aee2f2aa07f5c6ad6e00fadce5c134be1
|
| 3 |
+
size 224
|
official-src/data/norm_factors/mean_diff_4u_1.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:79c0582e23a3a54afbc13c4249d5af7256c0b10f34da6bf8fd9e1c2daf2c2d06
|
| 3 |
+
size 224
|
official-src/data/norm_factors/mean_hadisd_tas.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8048980d8fe7ec5f4ba74ae5c6bbadb59d2e846f4be12226e9b958e8a4b3a24e
|
| 3 |
+
size 132
|
official-src/data/norm_factors/mean_hadisd_ws.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c97ebf30fa59a35582fda96a16a4b98833aae0e8df7fe36db641944398cbe092
|
| 3 |
+
size 136
|
official-src/data/norm_factors/std_4u_1.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:acad322c1b4ecdcf71d63f48272aaee0d4ab3a89b4ea490e968721b10621ca6d
|
| 3 |
+
size 224
|
official-src/data/norm_factors/std_diff_4u_1.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:50ee4e57a7d460f03b21b1261743fe1667a254e3019f3b3f8c829012d22d873a
|
| 3 |
+
size 224
|
official-src/data/norm_factors/std_hadisd_tas.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7ae2e4568519a0da1bcfdf795dbdccdca6f22feec1a83a18b6665ac1f9a045bc
|
| 3 |
+
size 132
|
official-src/data/norm_factors/std_hadisd_ws.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9181e9fdaff3adf74ccc2a3816690f367435dc945287ebea2728f0c2b8820bfe
|
| 3 |
+
size 136
|
official-src/data/sample_data_final.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:536459e078220d7a8e5f6d3caa3846c930c6b759dbb21660df73473cbaf01d0c
|
| 3 |
+
size 53759995
|
official-src/environment.yml
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: npw
|
| 2 |
+
channels:
|
| 3 |
+
- pytorch
|
| 4 |
+
- nvidia
|
| 5 |
+
- conda-forge
|
| 6 |
+
- defaults
|
| 7 |
+
dependencies:
|
| 8 |
+
- _libgcc_mutex=0.1=conda_forge
|
| 9 |
+
- _openmp_mutex=4.5=2_gnu
|
| 10 |
+
- anyio=4.2.0=py38h06a4308_0
|
| 11 |
+
- argon2-cffi=21.3.0=pyhd3eb1b0_0
|
| 12 |
+
- argon2-cffi-bindings=21.2.0=py38h7f8727e_0
|
| 13 |
+
- asttokens=2.0.5=pyhd3eb1b0_0
|
| 14 |
+
- async-lru=2.0.4=py38h06a4308_0
|
| 15 |
+
- attrs=23.1.0=py38h06a4308_0
|
| 16 |
+
- babel=2.11.0=py38h06a4308_0
|
| 17 |
+
- backcall=0.2.0=pyhd3eb1b0_0
|
| 18 |
+
- beautifulsoup4=4.12.2=py38h06a4308_0
|
| 19 |
+
- blas=1.0=mkl
|
| 20 |
+
- bleach=4.1.0=pyhd3eb1b0_0
|
| 21 |
+
- blosc=1.21.5=hc2324a3_1
|
| 22 |
+
- bottleneck=1.3.7=py38ha9d4c09_0
|
| 23 |
+
- brotli=1.0.9=h5eee18b_7
|
| 24 |
+
- brotli-bin=1.0.9=h5eee18b_7
|
| 25 |
+
- brotli-python=1.0.9=py38h6a678d5_7
|
| 26 |
+
- bzip2=1.0.8=h5eee18b_5
|
| 27 |
+
- c-ares=1.19.1=h5eee18b_0
|
| 28 |
+
- ca-certificates=2024.6.2=hbcca054_0
|
| 29 |
+
- cartopy=0.21.1=py38h1176785_0
|
| 30 |
+
- certifi=2024.6.2=pyhd8ed1ab_0
|
| 31 |
+
- cffi=1.16.0=py38h5eee18b_0
|
| 32 |
+
- charset-normalizer=2.0.4=pyhd3eb1b0_0
|
| 33 |
+
- comm=0.2.1=py38h06a4308_0
|
| 34 |
+
- contourpy=1.0.5=py38hdb19cb5_0
|
| 35 |
+
- cuda-cudart=12.1.105=0
|
| 36 |
+
- cuda-cupti=12.1.105=0
|
| 37 |
+
- cuda-libraries=12.1.0=0
|
| 38 |
+
- cuda-nvrtc=12.1.105=0
|
| 39 |
+
- cuda-nvtx=12.1.105=0
|
| 40 |
+
- cuda-opencl=12.4.99=0
|
| 41 |
+
- cuda-runtime=12.1.0=0
|
| 42 |
+
- cycler=0.11.0=pyhd3eb1b0_0
|
| 43 |
+
- cyrus-sasl=2.1.28=h52b45da_1
|
| 44 |
+
- dbus=1.13.18=hb2f20db_0
|
| 45 |
+
- debugpy=1.6.7=py38h6a678d5_0
|
| 46 |
+
- decorator=5.1.1=pyhd3eb1b0_0
|
| 47 |
+
- defusedxml=0.7.1=pyhd3eb1b0_0
|
| 48 |
+
- eccodes=2.35.0=he84ddb8_0
|
| 49 |
+
- exceptiongroup=1.2.0=py38h06a4308_0
|
| 50 |
+
- executing=0.8.3=pyhd3eb1b0_0
|
| 51 |
+
- expat=2.5.0=h6a678d5_0
|
| 52 |
+
- ffmpeg=4.3=hf484d3e_0
|
| 53 |
+
- filelock=3.13.1=py38h06a4308_0
|
| 54 |
+
- findlibs=0.0.5=pyhd8ed1ab_0
|
| 55 |
+
- fontconfig=2.14.2=h14ed4e7_0
|
| 56 |
+
- fonttools=4.25.0=pyhd3eb1b0_0
|
| 57 |
+
- freeglut=3.4.0=h6a678d5_0
|
| 58 |
+
- freetype=2.12.1=h4a9f257_0
|
| 59 |
+
- geos=3.8.0=he6710b0_0
|
| 60 |
+
- glib=2.78.4=h6a678d5_0
|
| 61 |
+
- glib-tools=2.78.4=h6a678d5_0
|
| 62 |
+
- gmp=6.2.1=h295c915_3
|
| 63 |
+
- gmpy2=2.1.2=py38heeb90bb_0
|
| 64 |
+
- gnutls=3.6.15=he1e5248_0
|
| 65 |
+
- gst-plugins-base=1.14.1=h6a678d5_1
|
| 66 |
+
- gstreamer=1.14.1=h5eee18b_1
|
| 67 |
+
- hdf4=4.2.15=h9772cbc_5
|
| 68 |
+
- hdf5=1.14.3=nompi_h4f84152_100
|
| 69 |
+
- icu=73.2=h59595ed_0
|
| 70 |
+
- idna=3.4=py38h06a4308_0
|
| 71 |
+
- importlib-metadata=7.0.1=py38h06a4308_0
|
| 72 |
+
- importlib_metadata=7.0.1=hd3eb1b0_0
|
| 73 |
+
- importlib_resources=6.1.1=py38h06a4308_1
|
| 74 |
+
- intel-openmp=2023.1.0=hdb19cb5_46306
|
| 75 |
+
- ipykernel=6.28.0=py38h06a4308_0
|
| 76 |
+
- ipython=8.12.2=py38h06a4308_0
|
| 77 |
+
- jasper=4.2.4=he283c48_0
|
| 78 |
+
- jedi=0.18.1=py38h06a4308_1
|
| 79 |
+
- jinja2=3.1.3=py38h06a4308_0
|
| 80 |
+
- jpeg=9e=h5eee18b_1
|
| 81 |
+
- json5=0.9.6=pyhd3eb1b0_0
|
| 82 |
+
- jsonschema=4.19.2=py38h06a4308_0
|
| 83 |
+
- jsonschema-specifications=2023.7.1=py38h06a4308_0
|
| 84 |
+
- jupyter-lsp=2.2.0=py38h06a4308_0
|
| 85 |
+
- jupyter_client=8.6.0=py38h06a4308_0
|
| 86 |
+
- jupyter_core=5.5.0=py38h06a4308_0
|
| 87 |
+
- jupyter_events=0.8.0=py38h06a4308_0
|
| 88 |
+
- jupyter_server=2.10.0=py38h06a4308_0
|
| 89 |
+
- jupyter_server_terminals=0.4.4=py38h06a4308_1
|
| 90 |
+
- jupyterlab=4.0.11=py38h06a4308_0
|
| 91 |
+
- jupyterlab_pygments=0.1.2=py_0
|
| 92 |
+
- jupyterlab_server=2.25.1=py38h06a4308_0
|
| 93 |
+
- kiwisolver=1.4.4=py38h6a678d5_0
|
| 94 |
+
- krb5=1.20.1=h143b758_1
|
| 95 |
+
- lame=3.100=h7b6447c_0
|
| 96 |
+
- lcms2=2.12=h3be6417_0
|
| 97 |
+
- ld_impl_linux-64=2.38=h1181459_1
|
| 98 |
+
- lerc=3.0=h295c915_0
|
| 99 |
+
- libaec=1.1.3=h59595ed_0
|
| 100 |
+
- libbrotlicommon=1.0.9=h5eee18b_7
|
| 101 |
+
- libbrotlidec=1.0.9=h5eee18b_7
|
| 102 |
+
- libbrotlienc=1.0.9=h5eee18b_7
|
| 103 |
+
- libclang=14.0.6=default_hc6dbbc7_1
|
| 104 |
+
- libclang13=14.0.6=default_he11475f_1
|
| 105 |
+
- libcublas=12.1.0.26=0
|
| 106 |
+
- libcufft=11.0.2.4=0
|
| 107 |
+
- libcufile=1.9.0.20=0
|
| 108 |
+
- libcups=2.4.2=h2d74bed_1
|
| 109 |
+
- libcurand=10.3.5.119=0
|
| 110 |
+
- libcurl=8.5.0=h251f7ec_0
|
| 111 |
+
- libcusolver=11.4.4.55=0
|
| 112 |
+
- libcusparse=12.0.2.55=0
|
| 113 |
+
- libdeflate=1.17=h5eee18b_1
|
| 114 |
+
- libedit=3.1.20230828=h5eee18b_0
|
| 115 |
+
- libev=4.33=h7f8727e_1
|
| 116 |
+
- libffi=3.4.4=h6a678d5_0
|
| 117 |
+
- libgcc-ng=13.2.0=h77fa898_7
|
| 118 |
+
- libgfortran-ng=13.2.0=h69a702a_7
|
| 119 |
+
- libgfortran5=13.2.0=hca663fb_7
|
| 120 |
+
- libglib=2.78.4=hdc74915_0
|
| 121 |
+
- libglu=9.0.0=hac7e632_1003
|
| 122 |
+
- libgomp=13.2.0=h77fa898_7
|
| 123 |
+
- libiconv=1.17=hd590300_2
|
| 124 |
+
- libidn2=2.3.4=h5eee18b_0
|
| 125 |
+
- libjpeg-turbo=2.0.0=h9bf148f_0
|
| 126 |
+
- libllvm14=14.0.6=hdb19cb5_3
|
| 127 |
+
- libnetcdf=4.9.2=nompi_h9612171_113
|
| 128 |
+
- libnghttp2=1.57.0=h2d74bed_0
|
| 129 |
+
- libnpp=12.0.2.50=0
|
| 130 |
+
- libnvjitlink=12.1.105=0
|
| 131 |
+
- libnvjpeg=12.1.1.14=0
|
| 132 |
+
- libpng=1.6.43=h2797004_0
|
| 133 |
+
- libpq=12.17=hdbd6064_0
|
| 134 |
+
- libsodium=1.0.18=h7b6447c_0
|
| 135 |
+
- libssh2=1.10.0=hdbd6064_2
|
| 136 |
+
- libstdcxx-ng=13.2.0=hc0a3c3a_7
|
| 137 |
+
- libtasn1=4.19.0=h5eee18b_0
|
| 138 |
+
- libtiff=4.5.1=h6a678d5_0
|
| 139 |
+
- libunistring=0.9.10=h27cfd23_0
|
| 140 |
+
- libuuid=2.38.1=h0b41bf4_0
|
| 141 |
+
- libwebp-base=1.3.2=h5eee18b_0
|
| 142 |
+
- libxcb=1.15=h7f8727e_0
|
| 143 |
+
- libxkbcommon=1.7.0=h662e7e4_0
|
| 144 |
+
- libxml2=2.12.7=hc051c1a_1
|
| 145 |
+
- libzip=1.10.1=h2629f0a_3
|
| 146 |
+
- libzlib=1.2.13=h4ab18f5_6
|
| 147 |
+
- llvm-openmp=14.0.6=h9e868ea_0
|
| 148 |
+
- lz4-c=1.9.4=h6a678d5_0
|
| 149 |
+
- markupsafe=2.1.3=py38h5eee18b_0
|
| 150 |
+
- matplotlib=3.7.2=py38h06a4308_0
|
| 151 |
+
- matplotlib-base=3.7.2=py38h1128e8f_0
|
| 152 |
+
- matplotlib-inline=0.1.6=py38h06a4308_0
|
| 153 |
+
- mistune=2.0.4=py38h06a4308_0
|
| 154 |
+
- mkl=2023.1.0=h213fc3f_46344
|
| 155 |
+
- mkl-service=2.4.0=py38h5eee18b_1
|
| 156 |
+
- mkl_fft=1.3.8=py38h5eee18b_0
|
| 157 |
+
- mkl_random=1.2.4=py38hdb19cb5_0
|
| 158 |
+
- mpc=1.1.0=h10f8cd9_1
|
| 159 |
+
- mpfr=4.0.2=hb69a4c5_1
|
| 160 |
+
- mpmath=1.3.0=py38h06a4308_0
|
| 161 |
+
- munkres=1.1.4=py_0
|
| 162 |
+
- mysql=5.7.24=h721c034_2
|
| 163 |
+
- nbclient=0.8.0=py38h06a4308_0
|
| 164 |
+
- nbconvert=7.10.0=py38h06a4308_0
|
| 165 |
+
- nbformat=5.9.2=py38h06a4308_0
|
| 166 |
+
- ncurses=6.4=h6a678d5_0
|
| 167 |
+
- nest-asyncio=1.6.0=py38h06a4308_0
|
| 168 |
+
- nettle=3.7.3=hbbd107a_1
|
| 169 |
+
- networkx=3.1=py38h06a4308_0
|
| 170 |
+
- notebook-shim=0.2.3=py38h06a4308_0
|
| 171 |
+
- numexpr=2.8.4=py38hc78ab66_1
|
| 172 |
+
- numpy=1.24.3=py38hf6e8229_1
|
| 173 |
+
- numpy-base=1.24.3=py38h060ed82_1
|
| 174 |
+
- openh264=2.1.1=h4ff587b_0
|
| 175 |
+
- openjpeg=2.4.0=h3ad879b_0
|
| 176 |
+
- openssl=3.3.1=h4ab18f5_0
|
| 177 |
+
- overrides=7.4.0=py38h06a4308_0
|
| 178 |
+
- packaging=23.1=py38h06a4308_0
|
| 179 |
+
- pandas=2.0.3=py38h1128e8f_0
|
| 180 |
+
- pandocfilters=1.5.0=pyhd3eb1b0_0
|
| 181 |
+
- parso=0.8.3=pyhd3eb1b0_0
|
| 182 |
+
- pcre2=10.42=hebb0a14_0
|
| 183 |
+
- pexpect=4.8.0=pyhd3eb1b0_3
|
| 184 |
+
- pickleshare=0.7.5=pyhd3eb1b0_1003
|
| 185 |
+
- pillow=10.2.0=py38h5eee18b_0
|
| 186 |
+
- pip=23.3.1=py38h06a4308_0
|
| 187 |
+
- pkgutil-resolve-name=1.3.10=py38h06a4308_1
|
| 188 |
+
- platformdirs=3.10.0=py38h06a4308_0
|
| 189 |
+
- ply=3.11=py38_0
|
| 190 |
+
- pooch=1.7.0=py38h06a4308_0
|
| 191 |
+
- proj=8.2.1=ha227179_0
|
| 192 |
+
- prometheus_client=0.14.1=py38h06a4308_0
|
| 193 |
+
- prompt-toolkit=3.0.43=py38h06a4308_0
|
| 194 |
+
- psutil=5.9.0=py38h5eee18b_0
|
| 195 |
+
- ptyprocess=0.7.0=pyhd3eb1b0_2
|
| 196 |
+
- pure_eval=0.2.2=pyhd3eb1b0_0
|
| 197 |
+
- pycparser=2.21=pyhd3eb1b0_0
|
| 198 |
+
- pygments=2.15.1=py38h06a4308_1
|
| 199 |
+
- pyparsing=3.0.9=py38h06a4308_0
|
| 200 |
+
- pyproj=3.4.1=py38hb83c990_0
|
| 201 |
+
- pyqt=5.15.10=py38h6a678d5_0
|
| 202 |
+
- pyqt5-sip=12.13.0=py38h5eee18b_0
|
| 203 |
+
- pyshp=2.3.1=py38h06a4308_0
|
| 204 |
+
- pysocks=1.7.1=py38h06a4308_0
|
| 205 |
+
- python=3.8.18=h955ad1f_0
|
| 206 |
+
- python-dateutil=2.8.2=pyhd3eb1b0_0
|
| 207 |
+
- python-eccodes=1.7.0=py38he82f83a_2
|
| 208 |
+
- python-fastjsonschema=2.16.2=py38h06a4308_0
|
| 209 |
+
- python-json-logger=2.0.7=py38h06a4308_0
|
| 210 |
+
- python-tzdata=2023.3=pyhd3eb1b0_0
|
| 211 |
+
- python_abi=3.8=2_cp38
|
| 212 |
+
- pytorch=2.3.0=py3.8_cuda12.1_cudnn8.9.2_0
|
| 213 |
+
- pytorch-cuda=12.1=ha16c6d3_5
|
| 214 |
+
- pytorch-mutex=1.0=cuda
|
| 215 |
+
- pytz=2023.3.post1=py38h06a4308_0
|
| 216 |
+
- pyyaml=6.0.1=py38h5eee18b_0
|
| 217 |
+
- pyzmq=25.1.2=py38h6a678d5_0
|
| 218 |
+
- qt-main=5.15.2=h53bd1ea_10
|
| 219 |
+
- readline=8.2=h5eee18b_0
|
| 220 |
+
- referencing=0.30.2=py38h06a4308_0
|
| 221 |
+
- requests=2.31.0=py38h06a4308_1
|
| 222 |
+
- rfc3339-validator=0.1.4=py38h06a4308_0
|
| 223 |
+
- rfc3986-validator=0.1.1=py38h06a4308_0
|
| 224 |
+
- rpds-py=0.10.6=py38hb02cf49_0
|
| 225 |
+
- scipy=1.10.1=py38hf6e8229_1
|
| 226 |
+
- send2trash=1.8.2=py38h06a4308_0
|
| 227 |
+
- setuptools=68.2.2=py38h06a4308_0
|
| 228 |
+
- shapely=2.0.1=py38h0fbb895_0
|
| 229 |
+
- sip=6.7.12=py38h6a678d5_0
|
| 230 |
+
- six=1.16.0=pyhd3eb1b0_1
|
| 231 |
+
- snappy=1.2.0=hdb0a2a9_1
|
| 232 |
+
- sniffio=1.3.0=py38h06a4308_0
|
| 233 |
+
- soupsieve=2.5=py38h06a4308_0
|
| 234 |
+
- sqlite=3.41.2=h5eee18b_0
|
| 235 |
+
- stack_data=0.2.0=pyhd3eb1b0_0
|
| 236 |
+
- sympy=1.12=py38h06a4308_0
|
| 237 |
+
- tbb=2021.8.0=hdb19cb5_0
|
| 238 |
+
- terminado=0.17.1=py38h06a4308_0
|
| 239 |
+
- tinycss2=1.2.1=py38h06a4308_0
|
| 240 |
+
- tk=8.6.12=h1ccaba5_0
|
| 241 |
+
- tomli=2.0.1=py38h06a4308_0
|
| 242 |
+
- torchaudio=2.3.0=py38_cu121
|
| 243 |
+
- torchtriton=2.3.0=py38
|
| 244 |
+
- torchvision=0.18.0=py38_cu121
|
| 245 |
+
- tornado=6.3.3=py38h5eee18b_0
|
| 246 |
+
- tqdm=4.65.0=py38hb070fc8_0
|
| 247 |
+
- traitlets=5.7.1=py38h06a4308_0
|
| 248 |
+
- typing-extensions=4.9.0=py38h06a4308_1
|
| 249 |
+
- typing_extensions=4.9.0=py38h06a4308_1
|
| 250 |
+
- urllib3=2.1.0=py38h06a4308_1
|
| 251 |
+
- wcwidth=0.2.5=pyhd3eb1b0_0
|
| 252 |
+
- webencodings=0.5.1=py38_1
|
| 253 |
+
- websocket-client=0.58.0=py38h06a4308_4
|
| 254 |
+
- wheel=0.41.2=py38h06a4308_0
|
| 255 |
+
- xarray=2022.11.0=py38h06a4308_0
|
| 256 |
+
- xkeyboard-config=2.42=h4ab18f5_0
|
| 257 |
+
- xorg-kbproto=1.0.7=h7f98852_1002
|
| 258 |
+
- xorg-libx11=1.8.9=h8ee46fc_0
|
| 259 |
+
- xorg-libxau=1.0.11=hd590300_0
|
| 260 |
+
- xorg-libxext=1.3.4=h0b41bf4_2
|
| 261 |
+
- xorg-xextproto=7.3.0=h0b41bf4_1003
|
| 262 |
+
- xorg-xproto=7.0.31=h7f98852_1007
|
| 263 |
+
- xz=5.4.6=h5eee18b_0
|
| 264 |
+
- yaml=0.2.5=h7b6447c_0
|
| 265 |
+
- zeromq=4.3.5=h6a678d5_0
|
| 266 |
+
- zipp=3.17.0=py38h06a4308_0
|
| 267 |
+
- zlib=1.2.13=h4ab18f5_6
|
| 268 |
+
- zstd=1.5.5=hc292b87_0
|
| 269 |
+
- pip:
|
| 270 |
+
- aiohttp==3.9.3
|
| 271 |
+
- aiosignal==1.3.1
|
| 272 |
+
- appdirs==1.4.4
|
| 273 |
+
- asciitree==0.3.3
|
| 274 |
+
- async-timeout==4.0.3
|
| 275 |
+
- black==24.4.2
|
| 276 |
+
- cachetools==5.3.3
|
| 277 |
+
- cfgrib==0.9.12.0
|
| 278 |
+
- cftime==1.6.3
|
| 279 |
+
- click==8.1.7
|
| 280 |
+
- click-plugins==1.1.1
|
| 281 |
+
- cligj==0.7.2
|
| 282 |
+
- docker-pycreds==0.4.0
|
| 283 |
+
- eumdac==2.2.2
|
| 284 |
+
- fasteners==0.19
|
| 285 |
+
- fiona==1.9.6
|
| 286 |
+
- frozenlist==1.4.1
|
| 287 |
+
- fsspec==2024.3.1
|
| 288 |
+
- gcsfs==2024.3.1
|
| 289 |
+
- geographiclib==2.0
|
| 290 |
+
- geopandas==0.13.2
|
| 291 |
+
- geopy==2.4.1
|
| 292 |
+
- gitdb==4.0.11
|
| 293 |
+
- gitpython==3.1.42
|
| 294 |
+
- google-api-core==2.17.1
|
| 295 |
+
- google-auth==2.28.2
|
| 296 |
+
- google-auth-oauthlib==1.2.0
|
| 297 |
+
- google-cloud-core==2.4.1
|
| 298 |
+
- google-cloud-storage==2.16.0
|
| 299 |
+
- google-crc32c==1.5.0
|
| 300 |
+
- google-resumable-media==2.7.0
|
| 301 |
+
- googleapis-common-protos==1.63.0
|
| 302 |
+
- h5py==3.10.0
|
| 303 |
+
- huggingface-hub==0.22.2
|
| 304 |
+
- multidict==6.0.5
|
| 305 |
+
- mypy-extensions==1.0.0
|
| 306 |
+
- netcdf4==1.6.5
|
| 307 |
+
- numcodecs==0.12.1
|
| 308 |
+
- oauthlib==3.2.2
|
| 309 |
+
- pathspec==0.12.1
|
| 310 |
+
- plotly==5.20.0
|
| 311 |
+
- protobuf==4.25.3
|
| 312 |
+
- pyasn1==0.5.1
|
| 313 |
+
- pyasn1-modules==0.3.0
|
| 314 |
+
- ratelimiter==1.2.0.post0
|
| 315 |
+
- requests-oauthlib==1.4.0
|
| 316 |
+
- rsa==4.9
|
| 317 |
+
- safetensors==0.4.3
|
| 318 |
+
- scienceplots==2.1.1
|
| 319 |
+
- sentry-sdk==1.42.0
|
| 320 |
+
- setproctitle==1.3.3
|
| 321 |
+
- smmap==5.0.1
|
| 322 |
+
- tenacity==8.2.3
|
| 323 |
+
- timm==0.6.13
|
| 324 |
+
- tokenize-rt==5.2.0
|
| 325 |
+
- wandb==0.16.4
|
| 326 |
+
- yarl==1.9.4
|
| 327 |
+
- zarr==2.16.1
|
| 328 |
+
prefix: /home/azureuser/miniconda3/envs/npw
|
official-src/notebooks/data_demo.ipynb
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"id": "f329e4f1-6dd9-4f2e-b5b3-0e8454832eee",
|
| 6 |
+
"metadata": {},
|
| 7 |
+
"source": [
|
| 8 |
+
"# Dataset\n",
|
| 9 |
+
"This notebook provides an example of the data utilised to generate a forecast using Aardvark Weather. We explore a single timeslice containing all the observations required to generate a forecast. This sample data is the output of the loader `WeatherDatasetE2E` in `../aardvark/loaders.py`."
|
| 10 |
+
]
|
| 11 |
+
},
|
| 12 |
+
{
|
| 13 |
+
"cell_type": "code",
|
| 14 |
+
"execution_count": 1,
|
| 15 |
+
"id": "c17595b2-a624-440b-a2be-6af8d3802f87",
|
| 16 |
+
"metadata": {},
|
| 17 |
+
"outputs": [],
|
| 18 |
+
"source": [
|
| 19 |
+
"import numpy as np\n",
|
| 20 |
+
"import pickle"
|
| 21 |
+
]
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"cell_type": "code",
|
| 25 |
+
"execution_count": 2,
|
| 26 |
+
"id": "bc8b869d-33d4-45e5-b3ef-15d4e521e3d5",
|
| 27 |
+
"metadata": {},
|
| 28 |
+
"outputs": [],
|
| 29 |
+
"source": [
|
| 30 |
+
"import matplotlib\n",
|
| 31 |
+
"from matplotlib import pyplot as plt\n",
|
| 32 |
+
"\n",
|
| 33 |
+
"matplotlib.rcParams['mathtext.fontset'] = 'stix'\n",
|
| 34 |
+
"matplotlib.rcParams['font.family'] = 'STIXGeneral'\n"
|
| 35 |
+
]
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"cell_type": "markdown",
|
| 39 |
+
"id": "6de4a11f-4677-4552-8630-6b395c27012e",
|
| 40 |
+
"metadata": {},
|
| 41 |
+
"source": [
|
| 42 |
+
"## Data\n",
|
| 43 |
+
"Open a sample of data required to produce a forecast."
|
| 44 |
+
]
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"cell_type": "code",
|
| 48 |
+
"execution_count": 3,
|
| 49 |
+
"id": "0483c01d-2e08-469c-b914-42532cc3099b",
|
| 50 |
+
"metadata": {},
|
| 51 |
+
"outputs": [],
|
| 52 |
+
"source": [
|
| 53 |
+
"with open('../data/sample_data_final.pkl', 'rb') as fp:\n",
|
| 54 |
+
" data = pickle.load(fp)"
|
| 55 |
+
]
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"cell_type": "markdown",
|
| 59 |
+
"id": "83b7c966-fab5-47ce-a91c-d5419acaee40",
|
| 60 |
+
"metadata": {},
|
| 61 |
+
"source": [
|
| 62 |
+
"Multiple different datasets are utilised as input to create a forecast, each with multiple channels including observations and metadata. Example channels for each of these are plotted below. The plot_channel variable in each cell can be adjusted to visualise different channels. "
|
| 63 |
+
]
|
| 64 |
+
},
|
| 65 |
+
{
|
| 66 |
+
"cell_type": "code",
|
| 67 |
+
"execution_count": 4,
|
| 68 |
+
"id": "693aa212-c3ab-4eda-b07a-a6b262da627d",
|
| 69 |
+
"metadata": {},
|
| 70 |
+
"outputs": [],
|
| 71 |
+
"source": [
|
| 72 |
+
"# Set up longitude and latitude for plotting\n",
|
| 73 |
+
"lon = np.linspace(0,359,360)\n",
|
| 74 |
+
"lat = np.linspace(90,-90,181)\n",
|
| 75 |
+
"\n",
|
| 76 |
+
"lon_1p5 = np.linspace(0,359,240)\n",
|
| 77 |
+
"lat_1p5 = np.linspace(90,-90,121)"
|
| 78 |
+
]
|
| 79 |
+
},
|
| 80 |
+
{
|
| 81 |
+
"cell_type": "markdown",
|
| 82 |
+
"id": "42a2524f-108a-4ed7-bc89-2fb91638a7aa",
|
| 83 |
+
"metadata": {},
|
| 84 |
+
"source": [
|
| 85 |
+
"## Satellite\n",
|
| 86 |
+
"First visualise the satellite data from HIRS, AMSU-A, AMSU-B, IASI and ASCAT"
|
| 87 |
+
]
|
| 88 |
+
},
|
| 89 |
+
{
|
| 90 |
+
"cell_type": "markdown",
|
| 91 |
+
"id": "1243f422-351a-458c-aaa9-4bf72a959678",
|
| 92 |
+
"metadata": {},
|
| 93 |
+
"source": [
|
| 94 |
+
"### HIRS"
|
| 95 |
+
]
|
| 96 |
+
},
|
| 97 |
+
{
|
| 98 |
+
"cell_type": "code",
|
| 99 |
+
"execution_count": null,
|
| 100 |
+
"id": "0d750dcc-cfef-4ca5-8f14-a7bae45980f4",
|
| 101 |
+
"metadata": {},
|
| 102 |
+
"outputs": [],
|
| 103 |
+
"source": [
|
| 104 |
+
"fig = plt.figure()\n",
|
| 105 |
+
"plot_channel = 11\n",
|
| 106 |
+
"p = plt.contourf(\n",
|
| 107 |
+
" lon,\n",
|
| 108 |
+
" lat,\n",
|
| 109 |
+
" data[\"assimilation\"][\"hirs_current\"][0,...,plot_channel].cpu().T, \n",
|
| 110 |
+
" levels=100,\n",
|
| 111 |
+
" cmap=\"magma\")\n",
|
| 112 |
+
"cbar = fig.colorbar(p)\n",
|
| 113 |
+
"cbar.set_label('Normalised radiance') \n",
|
| 114 |
+
"plt.xlabel(\"Longitude\")\n",
|
| 115 |
+
"plt.ylabel(\"Latitude\")\n",
|
| 116 |
+
"plt.title(f\"HIRS channel {plot_channel}\")\n",
|
| 117 |
+
"plt.show()"
|
| 118 |
+
]
|
| 119 |
+
},
|
| 120 |
+
{
|
| 121 |
+
"cell_type": "markdown",
|
| 122 |
+
"id": "d5c5e48e-3bab-461f-8425-9efa10c415e0",
|
| 123 |
+
"metadata": {},
|
| 124 |
+
"source": [
|
| 125 |
+
"### AMSU-A"
|
| 126 |
+
]
|
| 127 |
+
},
|
| 128 |
+
{
|
| 129 |
+
"cell_type": "code",
|
| 130 |
+
"execution_count": null,
|
| 131 |
+
"id": "79deb284-7ed8-47d2-9006-a13a06d2b835",
|
| 132 |
+
"metadata": {},
|
| 133 |
+
"outputs": [],
|
| 134 |
+
"source": [
|
| 135 |
+
"fig = plt.figure()\n",
|
| 136 |
+
"plot_channel = 8\n",
|
| 137 |
+
"p = plt.contourf(\n",
|
| 138 |
+
" lon, \n",
|
| 139 |
+
" lat[:-1],\n",
|
| 140 |
+
" data[\"assimilation\"][\"amsua_current\"][0,...,plot_channel].cpu(), \n",
|
| 141 |
+
" levels=100,\n",
|
| 142 |
+
" cmap=\"magma\")\n",
|
| 143 |
+
"cbar = fig.colorbar(p)\n",
|
| 144 |
+
"cbar.set_label('Normalised radiance') \n",
|
| 145 |
+
"plt.xlabel(\"Longitude\")\n",
|
| 146 |
+
"plt.ylabel(\"Latitude\")\n",
|
| 147 |
+
"plt.title(f\"AMSU-A channel {plot_channel}\")\n",
|
| 148 |
+
"plt.show()"
|
| 149 |
+
]
|
| 150 |
+
},
|
| 151 |
+
{
|
| 152 |
+
"cell_type": "markdown",
|
| 153 |
+
"id": "9b481a5c-97ad-4016-826d-93771531ad2d",
|
| 154 |
+
"metadata": {},
|
| 155 |
+
"source": [
|
| 156 |
+
"### AMSU-B"
|
| 157 |
+
]
|
| 158 |
+
},
|
| 159 |
+
{
|
| 160 |
+
"cell_type": "code",
|
| 161 |
+
"execution_count": null,
|
| 162 |
+
"id": "466434e7-7296-4454-b14c-6ab40bfbe0d1",
|
| 163 |
+
"metadata": {},
|
| 164 |
+
"outputs": [],
|
| 165 |
+
"source": [
|
| 166 |
+
"fig = plt.figure()\n",
|
| 167 |
+
"plot_channel = 10\n",
|
| 168 |
+
"p = plt.contourf(\n",
|
| 169 |
+
" lon,\n",
|
| 170 |
+
" lat,\n",
|
| 171 |
+
" data[\"assimilation\"][\"amsub_current\"][0,...,plot_channel].T.cpu(), \n",
|
| 172 |
+
" levels=100,\n",
|
| 173 |
+
" cmap=\"magma\")\n",
|
| 174 |
+
"cbar = fig.colorbar(p)\n",
|
| 175 |
+
"cbar.set_label('Normalised radiance') \n",
|
| 176 |
+
"plt.xlabel(\"Longitude\")\n",
|
| 177 |
+
"plt.ylabel(\"Latitude\")\n",
|
| 178 |
+
"plt.title(f\"AMSU-A channel {plot_channel}\")\n",
|
| 179 |
+
"plt.show()"
|
| 180 |
+
]
|
| 181 |
+
},
|
| 182 |
+
{
|
| 183 |
+
"cell_type": "markdown",
|
| 184 |
+
"id": "44de7c3f-6a25-47eb-bcfe-ab7bc82a1aac",
|
| 185 |
+
"metadata": {},
|
| 186 |
+
"source": [
|
| 187 |
+
"### IASI"
|
| 188 |
+
]
|
| 189 |
+
},
|
| 190 |
+
{
|
| 191 |
+
"cell_type": "code",
|
| 192 |
+
"execution_count": null,
|
| 193 |
+
"id": "d5a5caf9-ecc4-403d-97ff-6c96dbc18f13",
|
| 194 |
+
"metadata": {},
|
| 195 |
+
"outputs": [],
|
| 196 |
+
"source": [
|
| 197 |
+
"fig = plt.figure()\n",
|
| 198 |
+
"plot_channel = 10\n",
|
| 199 |
+
"p = plt.contourf(\n",
|
| 200 |
+
" lon,\n",
|
| 201 |
+
" lat,\n",
|
| 202 |
+
" data[\"assimilation\"][\"iasi_current\"][0,...,plot_channel].T.cpu(), \n",
|
| 203 |
+
" levels=100,\n",
|
| 204 |
+
" cmap=\"magma\")\n",
|
| 205 |
+
"cbar = fig.colorbar(p)\n",
|
| 206 |
+
"cbar.set_label('Normalised radiance') \n",
|
| 207 |
+
"plt.xlabel(\"Longitude\")\n",
|
| 208 |
+
"plt.ylabel(\"Latitude\")\n",
|
| 209 |
+
"plt.title(f\"IASI channel {plot_channel}\")\n",
|
| 210 |
+
"plt.show()"
|
| 211 |
+
]
|
| 212 |
+
},
|
| 213 |
+
{
|
| 214 |
+
"cell_type": "markdown",
|
| 215 |
+
"id": "ea0ac772-4150-4bfd-96de-004ac7b8b6b4",
|
| 216 |
+
"metadata": {},
|
| 217 |
+
"source": [
|
| 218 |
+
"### ASCAT"
|
| 219 |
+
]
|
| 220 |
+
},
|
| 221 |
+
{
|
| 222 |
+
"cell_type": "code",
|
| 223 |
+
"execution_count": null,
|
| 224 |
+
"id": "9981b9c6-2d88-47a1-b135-f70f1335df78",
|
| 225 |
+
"metadata": {},
|
| 226 |
+
"outputs": [],
|
| 227 |
+
"source": [
|
| 228 |
+
"fig = plt.figure()\n",
|
| 229 |
+
"plot_channel = 5\n",
|
| 230 |
+
"p = plt.contourf(\n",
|
| 231 |
+
" lon,\n",
|
| 232 |
+
" lat,\n",
|
| 233 |
+
" data[\"assimilation\"][\"ascat_current\"][0,...,plot_channel].T.cpu(), \n",
|
| 234 |
+
" levels=100,\n",
|
| 235 |
+
" cmap=\"magma\")\n",
|
| 236 |
+
"cbar = fig.colorbar(p)\n",
|
| 237 |
+
"cbar.set_label('Normalised radiance') \n",
|
| 238 |
+
"plt.xlabel(\"Longitude\")\n",
|
| 239 |
+
"plt.ylabel(\"Latitude\")\n",
|
| 240 |
+
"plt.title(f\"ASCAT channel {plot_channel}\")\n",
|
| 241 |
+
"plt.show()"
|
| 242 |
+
]
|
| 243 |
+
},
|
| 244 |
+
{
|
| 245 |
+
"cell_type": "markdown",
|
| 246 |
+
"id": "1b6849d1-c6fd-413f-a72e-3de111228a78",
|
| 247 |
+
"metadata": {},
|
| 248 |
+
"source": [
|
| 249 |
+
"## SYNOPS\n",
|
| 250 |
+
"We next visualise the SYNOPS data from land stations, marine platforms and radiosonde profiles"
|
| 251 |
+
]
|
| 252 |
+
},
|
| 253 |
+
{
|
| 254 |
+
"cell_type": "markdown",
|
| 255 |
+
"id": "c392b951-b446-4fde-aacb-02be7759bc97",
|
| 256 |
+
"metadata": {},
|
| 257 |
+
"source": [
|
| 258 |
+
"### HadISD"
|
| 259 |
+
]
|
| 260 |
+
},
|
| 261 |
+
{
|
| 262 |
+
"cell_type": "code",
|
| 263 |
+
"execution_count": null,
|
| 264 |
+
"id": "611eed9e-8c4a-4ac8-82f4-9969a6ef8eb7",
|
| 265 |
+
"metadata": {},
|
| 266 |
+
"outputs": [],
|
| 267 |
+
"source": [
|
| 268 |
+
"fig = plt.figure()\n",
|
| 269 |
+
"plot_channel = 0\n",
|
| 270 |
+
"p = plt.scatter(\n",
|
| 271 |
+
" data[\"assimilation\"][\"x_context_hadisd_current\"][plot_channel][0,0,:].cpu(),\n",
|
| 272 |
+
" data[\"assimilation\"][\"x_context_hadisd_current\"][plot_channel][0,1,:].cpu(),\n",
|
| 273 |
+
" c = data[\"assimilation\"][\"y_context_hadisd_current\"][plot_channel][0].T.cpu(), \n",
|
| 274 |
+
" cmap=\"magma\")\n",
|
| 275 |
+
"cbar = fig.colorbar(p)\n",
|
| 276 |
+
"cbar.set_label('Normalised value') \n",
|
| 277 |
+
"plt.xlabel(\"Longitude\")\n",
|
| 278 |
+
"plt.ylabel(\"Latitude\")\n",
|
| 279 |
+
"plt.title(f\"HadISD channel {plot_channel}\")\n",
|
| 280 |
+
"plt.show()"
|
| 281 |
+
]
|
| 282 |
+
},
|
| 283 |
+
{
|
| 284 |
+
"cell_type": "markdown",
|
| 285 |
+
"id": "b2ae8c40-28bd-49ad-8a2f-b9f7df706834",
|
| 286 |
+
"metadata": {},
|
| 287 |
+
"source": [
|
| 288 |
+
"### ICOADS"
|
| 289 |
+
]
|
| 290 |
+
},
|
| 291 |
+
{
|
| 292 |
+
"cell_type": "code",
|
| 293 |
+
"execution_count": null,
|
| 294 |
+
"id": "1e425818-5a30-4641-abc7-08cfd4c6b4fe",
|
| 295 |
+
"metadata": {},
|
| 296 |
+
"outputs": [],
|
| 297 |
+
"source": [
|
| 298 |
+
"plot_channel = 1\n",
|
| 299 |
+
"fig = plt.figure()\n",
|
| 300 |
+
"p = plt.scatter(\n",
|
| 301 |
+
" data[\"assimilation\"][\"icoads_x_current\"][0][0,:].cpu(),\n",
|
| 302 |
+
" data[\"assimilation\"][\"icoads_x_current\"][1][0,:].cpu(),\n",
|
| 303 |
+
" c = data[\"assimilation\"][\"icoads_current\"][0,plot_channel,:].T.cpu(), \n",
|
| 304 |
+
" cmap=\"magma\")\n",
|
| 305 |
+
"cbar = fig.colorbar(p)\n",
|
| 306 |
+
"cbar.set_label('Normalised value') \n",
|
| 307 |
+
"plt.xlabel(\"Longitude\")\n",
|
| 308 |
+
"plt.ylabel(\"Latitude\")\n",
|
| 309 |
+
"plt.title(f\"ICOADS channel {plot_channel}\")\n",
|
| 310 |
+
"plt.show()"
|
| 311 |
+
]
|
| 312 |
+
},
|
| 313 |
+
{
|
| 314 |
+
"cell_type": "markdown",
|
| 315 |
+
"id": "14d103a9-053c-4576-b17f-35ebae3fa44a",
|
| 316 |
+
"metadata": {},
|
| 317 |
+
"source": [
|
| 318 |
+
"### IGRA"
|
| 319 |
+
]
|
| 320 |
+
},
|
| 321 |
+
{
|
| 322 |
+
"cell_type": "code",
|
| 323 |
+
"execution_count": null,
|
| 324 |
+
"id": "11d5f5de-22ea-4d6e-a590-c1ce0a842a53",
|
| 325 |
+
"metadata": {},
|
| 326 |
+
"outputs": [],
|
| 327 |
+
"source": [
|
| 328 |
+
"plot_channel = 1\n",
|
| 329 |
+
"fig = plt.figure()\n",
|
| 330 |
+
"p = plt.scatter(\n",
|
| 331 |
+
" data[\"assimilation\"][\"igra_x_current\"][0][0,:].cpu(),\n",
|
| 332 |
+
" data[\"assimilation\"][\"igra_x_current\"][1][0,:].cpu(),\n",
|
| 333 |
+
" c = data[\"assimilation\"][\"igra_current\"][0,plot_channel,:].T.cpu(), \n",
|
| 334 |
+
" cmap=\"magma\")\n",
|
| 335 |
+
"cbar = fig.colorbar(p)\n",
|
| 336 |
+
"cbar.set_label('Normalised value') \n",
|
| 337 |
+
"plt.xlabel(\"Longitude\")\n",
|
| 338 |
+
"plt.ylabel(\"Latitude\")\n",
|
| 339 |
+
"plt.title(f\"IGRA channel {plot_channel}\")\n",
|
| 340 |
+
"plt.show()"
|
| 341 |
+
]
|
| 342 |
+
},
|
| 343 |
+
{
|
| 344 |
+
"cell_type": "code",
|
| 345 |
+
"execution_count": null,
|
| 346 |
+
"id": "2b893a77-4c30-4f18-a7fb-b007a676d849",
|
| 347 |
+
"metadata": {},
|
| 348 |
+
"outputs": [],
|
| 349 |
+
"source": []
|
| 350 |
+
},
|
| 351 |
+
{
|
| 352 |
+
"cell_type": "code",
|
| 353 |
+
"execution_count": null,
|
| 354 |
+
"id": "521c93b6-babf-41fe-8dc6-c9a6eaef5b6f",
|
| 355 |
+
"metadata": {},
|
| 356 |
+
"outputs": [],
|
| 357 |
+
"source": []
|
| 358 |
+
}
|
| 359 |
+
],
|
| 360 |
+
"metadata": {
|
| 361 |
+
"kernelspec": {
|
| 362 |
+
"display_name": "Python 3 (ipykernel)",
|
| 363 |
+
"language": "python",
|
| 364 |
+
"name": "python3"
|
| 365 |
+
},
|
| 366 |
+
"language_info": {
|
| 367 |
+
"codemirror_mode": {
|
| 368 |
+
"name": "ipython",
|
| 369 |
+
"version": 3
|
| 370 |
+
},
|
| 371 |
+
"file_extension": ".py",
|
| 372 |
+
"mimetype": "text/x-python",
|
| 373 |
+
"name": "python",
|
| 374 |
+
"nbconvert_exporter": "python",
|
| 375 |
+
"pygments_lexer": "ipython3",
|
| 376 |
+
"version": "3.8.18"
|
| 377 |
+
},
|
| 378 |
+
"vscode": {
|
| 379 |
+
"interpreter": {
|
| 380 |
+
"hash": "ff379d8b57ea10bc9abbe40475203c855480f57b5b4821ba344a2e4e1a8a455a"
|
| 381 |
+
}
|
| 382 |
+
}
|
| 383 |
+
},
|
| 384 |
+
"nbformat": 4,
|
| 385 |
+
"nbformat_minor": 5
|
| 386 |
+
}
|
official-src/notebooks/e2e_finetune_demo.ipynb
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
official-src/notebooks/forecast_demo.ipynb
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
official-src/training/downscaling_run_pipeline.sh
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
python3 train_module.py \
|
| 4 |
+
--output_dir $3 \
|
| 5 |
+
--master_port 22348 \
|
| 6 |
+
--decoder base \
|
| 7 |
+
--loss downscaling_rmse \
|
| 8 |
+
--diff 0 \
|
| 9 |
+
--in_channels 36 \
|
| 10 |
+
--out_channels 1 \
|
| 11 |
+
--int_channels 24 \
|
| 12 |
+
--mode downscaling \
|
| 13 |
+
--lr 5e-4 \
|
| 14 |
+
--batch_size 64 \
|
| 15 |
+
--start_ind 0 \
|
| 16 |
+
--end_ind 24 \
|
| 17 |
+
--epoch 20 \
|
| 18 |
+
--weight_decay 1e-6 \
|
| 19 |
+
--downscaling_context aardvark \
|
| 20 |
+
--downscaling_train_start_date 2007-01-02 \
|
| 21 |
+
--downscaling_train_end_date 2017-12-31 \
|
| 22 |
+
--lead_time $1 \
|
| 23 |
+
--var $2
|
official-src/training/finetune.sh
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
python3 ../aardvark/generate_initial_condition_single.py \
|
| 2 |
+
--assimilation_model_path ENCODER_PATH
|
| 3 |
+
|
| 4 |
+
python3 ../aardvark/finetune.py \
|
| 5 |
+
--assimilation_model_path ENCODER_PATH \
|
| 6 |
+
--forecast_model_path FORECAST_PATH \
|
| 7 |
+
--output_dir FINETUNE_PATH \
|
| 8 |
+
--lr 5e-5 \
|
| 9 |
+
--finetune_epochs 1
|
official-src/training/test/config.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:565f5a5ba9b564b2a63bcb30d20bd2f74045ef537ebf12a594a49eed577a5ace
|
| 3 |
+
size 387
|
official-src/training/train_decoder.sh
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
python3 ../aardvark/train_module.py \
|
| 2 |
+
--output_dir DECODER/TAS \
|
| 3 |
+
--master_port 22348 \
|
| 4 |
+
--decoder base \
|
| 5 |
+
--loss downscaling_rmse \
|
| 6 |
+
--diff 0 \
|
| 7 |
+
--in_channels 36 \
|
| 8 |
+
--out_channels 1 \
|
| 9 |
+
--int_channels 24 \
|
| 10 |
+
--mode downscaling \
|
| 11 |
+
--lr 5e-4 \
|
| 12 |
+
--batch_size 64 \
|
| 13 |
+
--start_ind 0 \
|
| 14 |
+
--end_ind 24 \
|
| 15 |
+
--epoch 20 \
|
| 16 |
+
--weight_decay 1e-6 \
|
| 17 |
+
--downscaling_context aardvark \
|
| 18 |
+
--downscaling_train_start_date 2007-01-02 \
|
| 19 |
+
--downscaling_train_end_date 2017-12-31 \
|
| 20 |
+
--lead_time 4 \
|
| 21 |
+
--var tas
|
| 22 |
+
|
| 23 |
+
python3 ../aardvark/train_module.py \
|
| 24 |
+
--output_dir DECODER/WS \
|
| 25 |
+
--master_port 22348 \
|
| 26 |
+
--decoder base \
|
| 27 |
+
--loss downscaling_rmse \
|
| 28 |
+
--diff 0 \
|
| 29 |
+
--in_channels 36 \
|
| 30 |
+
--out_channels 1 \
|
| 31 |
+
--int_channels 24 \
|
| 32 |
+
--mode downscaling \
|
| 33 |
+
--lr 5e-4 \
|
| 34 |
+
--batch_size 64 \
|
| 35 |
+
--start_ind 0 \
|
| 36 |
+
--end_ind 24 \
|
| 37 |
+
--epoch 20 \
|
| 38 |
+
--weight_decay 1e-6 \
|
| 39 |
+
--downscaling_context aardvark \
|
| 40 |
+
--downscaling_train_start_date 2007-01-02 \
|
| 41 |
+
--downscaling_train_end_date 2017-12-31 \
|
| 42 |
+
--lead_time 4 \
|
| 43 |
+
--var ws
|
official-src/training/train_e2e.sh
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Command for training with wind speed variable (ws)
|
| 2 |
+
python3 ../aardvark/e2e_train.py \
|
| 3 |
+
--output_dir E2E/WS \
|
| 4 |
+
--loss downscaling_rmse \
|
| 5 |
+
--region global \
|
| 6 |
+
--lead_time 1 \
|
| 7 |
+
--sf_model_path DECODER_PATH \
|
| 8 |
+
--se_model_path ENCODER_PATH \
|
| 9 |
+
--forecast_model_path PROCESSOR_PATH \
|
| 10 |
+
--var ws
|
| 11 |
+
|
| 12 |
+
# Command for training with temperature variable (tas)
|
| 13 |
+
python3 ../aardvark/e2e_train.py \
|
| 14 |
+
--output_dir E2E/TAS \
|
| 15 |
+
--loss downscaling_rmse \
|
| 16 |
+
--region global \
|
| 17 |
+
--lead_time 1 \
|
| 18 |
+
--sf_model_path DECODER_PATH \
|
| 19 |
+
--se_model_path ENCODER_PATH \
|
| 20 |
+
--forecast_model_path PROCESSOR_PATH \
|
| 21 |
+
--var tas
|
official-src/training/train_encoder.sh
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
python3 ../aardvark/train_module.py \
|
| 2 |
+
--output_dir ENCODER_PATH \
|
| 3 |
+
--master_port 12348 \
|
| 4 |
+
--decoder vit_assimilation \
|
| 5 |
+
--loss lw_rmse \
|
| 6 |
+
--diff 0 \
|
| 7 |
+
--in_channels 277 \
|
| 8 |
+
--out_channels 24 \
|
| 9 |
+
--int_channels 24 \
|
| 10 |
+
--mode assimilation \
|
| 11 |
+
--lr 5e-4 \
|
| 12 |
+
--batch_size 6 \
|
| 13 |
+
--start_ind 0 \
|
| 14 |
+
--end_ind 24 \
|
| 15 |
+
--epoch 100 \
|
| 16 |
+
--weight_per_variable 1
|
official-src/training/train_processor.sh
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
python3 ../aardvark/train_module.py \
|
| 2 |
+
--output_dir FORECAST_PATH/ \
|
| 3 |
+
--lead_time 1 \
|
| 4 |
+
--loss lw_rmse_pressure_weighted \
|
| 5 |
+
--diff 1 \
|
| 6 |
+
--mode forecast \
|
| 7 |
+
--in_channels 35 \
|
| 8 |
+
--out_channels 24 \
|
| 9 |
+
--int_channels 24 \
|
| 10 |
+
--decoder vit \
|
| 11 |
+
--ic era5 \
|
| 12 |
+
--film 0 \
|
| 13 |
+
--epoch 200 \
|
| 14 |
+
--weight_per_variable 0 \
|
| 15 |
+
--batch_size 24 \
|
| 16 |
+
--lr 5e-4 \
|
| 17 |
+
--era5_mode 4u
|
scripts/inference.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""Run the one-day tas forecast through the official Aardvark modules."""
|
| 3 |
+
|
| 4 |
+
import argparse
|
| 5 |
+
import json
|
| 6 |
+
import sys
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
|
| 9 |
+
ROOT = Path(__file__).resolve().parents[1]
|
| 10 |
+
sys.path.insert(0, str(ROOT))
|
| 11 |
+
|
| 12 |
+
import copy
|
| 13 |
+
import torch
|
| 14 |
+
|
| 15 |
+
from model.aardvark_adapter import build_one_day_model, load_sample
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def main() -> None:
|
| 19 |
+
parser = argparse.ArgumentParser()
|
| 20 |
+
parser.add_argument("--root", type=Path, default=ROOT)
|
| 21 |
+
parser.add_argument("--device", default="cuda")
|
| 22 |
+
parser.add_argument("--checkpoint", type=Path, help="Optional checkpoint produced by scripts/train.py")
|
| 23 |
+
parser.add_argument("--output", type=Path, default=ROOT / "result" / "inference_one_day.json")
|
| 24 |
+
args = parser.parse_args()
|
| 25 |
+
root = args.root.resolve()
|
| 26 |
+
sample_path = root / "weights/sample_data/sample_data_final.pkl"
|
| 27 |
+
sample = load_sample(sample_path)
|
| 28 |
+
model = build_one_day_model(root / "weights", root / "official-src", args.device)
|
| 29 |
+
if args.checkpoint:
|
| 30 |
+
tuned = args.checkpoint.resolve()
|
| 31 |
+
payload = torch.load(tuned, map_location=args.device, weights_only=False)
|
| 32 |
+
target_model = model if payload["train_modules"] == "all" else model.sf_model
|
| 33 |
+
target_model.load_state_dict(payload["model"])
|
| 34 |
+
model.eval()
|
| 35 |
+
target = sample["y_target"].cpu()
|
| 36 |
+
with torch.inference_mode():
|
| 37 |
+
station, global_forecast, initial_state = model(copy.deepcopy(sample))
|
| 38 |
+
result_dir = root / "result"
|
| 39 |
+
result_dir.mkdir(parents=True, exist_ok=True)
|
| 40 |
+
torch.save(station.cpu(), result_dir / "prediction.pt")
|
| 41 |
+
torch.save(target, result_dir / "target.pt")
|
| 42 |
+
report = {
|
| 43 |
+
"device": args.device,
|
| 44 |
+
"lead_time_days": 1,
|
| 45 |
+
"station_tas_shape": list(station.shape),
|
| 46 |
+
"global_forecast_shape": list(global_forecast.shape),
|
| 47 |
+
"initial_state_shape": list(initial_state.shape),
|
| 48 |
+
"finite_outputs": bool(torch.isfinite(station).all()),
|
| 49 |
+
"weights": str(tuned) if args.checkpoint else "official",
|
| 50 |
+
}
|
| 51 |
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
| 52 |
+
args.output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n")
|
| 53 |
+
print(json.dumps(report, indent=2, sort_keys=True))
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
if __name__ == "__main__":
|
| 57 |
+
main()
|