| from dataclasses import dataclass |
| from typing import Mapping |
|
|
| import torch |
| from torch import nn |
| from torch.nn import functional as F |
|
|
| from .metnet3_blocks import ConvBlock, LongRangeMaxViT |
| from .metnet3_heads import ClassificationHead, RegressionHead |
| from .metnet3_schema import InputSchema, validate_batch |
|
|
|
|
| @dataclass |
| class MetNet3Config(InputSchema): |
| hidden: int = 32 |
| maxvit_blocks: int = 1 |
| condition_dim: int = 16 |
|
|
|
|
| class MetNet3(nn.Module): |
| def __init__(self, config: MetNet3Config | None = None): |
| super().__init__() |
| self.config = config or MetNet3Config() |
| s = self.config |
| input_channels = s.high_channels + s.low_channels + s.omo_channels + s.hrrr_channels + s.goes_channels + s.total_static_channels |
| self.condition = nn.Sequential(nn.Linear(2, s.condition_dim), nn.GELU(), nn.Linear(s.condition_dim, s.condition_dim)) |
| self.enc1 = ConvBlock(input_channels, s.hidden, s.condition_dim) |
| self.down = nn.Conv2d(s.hidden, s.hidden * 2, 3, stride=2, padding=1) |
| self.enc2 = ConvBlock(s.hidden * 2, s.hidden * 2, s.condition_dim) |
| self.long_range = LongRangeMaxViT(s.hidden * 2, blocks=s.maxvit_blocks) |
| self.up = nn.ConvTranspose2d(s.hidden * 2, s.hidden, 2, stride=2) |
| self.dec = ConvBlock(s.hidden * 2, s.hidden, s.condition_dim) |
| self.precipitation = ClassificationHead(s.hidden, 1, s.precipitation_bins) |
| self.ground = ClassificationHead(s.hidden, s.ground_targets, s.ground_bins) |
| self.hrrr = RegressionHead(s.hidden, s.hrrr_channels) |
|
|
| @staticmethod |
| def _last_frame(x: torch.Tensor) -> torch.Tensor: |
| return x[:, -1] |
|
|
| def forward(self, batch: Mapping[str, torch.Tensor]): |
| s = self.config |
| validate_batch(batch, s) |
| high = self._last_frame(batch["mrms_high"]) |
| low = self._last_frame(batch["mrms_low"]) |
| if low.shape[-2:] != high.shape[-2:]: |
| low = F.interpolate(low, size=high.shape[-2:], mode="bilinear", align_corners=False) |
| omo = self._last_frame(batch["omo"]) * batch["omo_input_mask"].float() |
| hrrr = self._last_frame(batch["hrrr"]) |
| goes = self._last_frame(batch["goes"]) |
| static = torch.cat([batch["elevation"], batch["coordinates"], batch["topography_embedding"]], dim=1) |
| x = torch.cat([high, low, omo, hrrr, goes, static], dim=1) |
| condition = self.condition(torch.cat([batch["current_time"], batch["lead_time"]], dim=1).float()) |
| skip = self.enc1(x, condition) |
| deep = self.enc2(self.down(skip), condition) |
| deep = self.long_range(deep) |
| decoded = self.up(deep) |
| decoded = self.dec(torch.cat([decoded, skip], dim=1), condition) |
| return {"precipitation_logits": self.precipitation(decoded), "ground_logits": self.ground(decoded), "hrrr_regression": self.hrrr(decoded)} |
|
|