Source code for neural_lam.models.forecasters.autoregressive

"""Forecaster that uses an auto-regressive strategy to unroll a forecast."""

# Standard library

# Third-party
import torch

# Local
from ...datastore import BaseDatastore
from ..step_predictors.base import StepPredictor
from .base import Forecaster


[docs] class ARForecaster(Forecaster): """ Subclass of Forecaster that uses an auto-regressive strategy to unroll a forecast. Makes use of a StepPredictor at each AR step. """ boundary_mask: torch.Tensor interior_mask: torch.Tensor def __init__( self, predictor: StepPredictor, datastore: BaseDatastore ) -> None: """ Initialize the ARForecaster. Parameters ---------- predictor : StepPredictor The predictor to use for each step. datastore : BaseDatastore The datastore providing grid metadata and boundary masks. """ super().__init__() self.predictor = predictor # Register boundary/interior masks on the forecaster, not the predictor boundary_mask = ( torch.tensor(datastore.boundary_mask.values, dtype=torch.float32) .unsqueeze(0) .unsqueeze(-1) ) self.register_buffer("boundary_mask", boundary_mask, persistent=False) self.register_buffer( "interior_mask", 1.0 - self.boundary_mask, persistent=False ) @property def predicts_std(self) -> bool: """ Whether the forecaster predicts standard deviation. Returns ------- bool ``True`` if the forecaster predicts standard deviation, ``False`` otherwise. """ return self.predictor.predicts_std
[docs] def forward( self, init_states: torch.Tensor, forcing_features: torch.Tensor, boundary_states: torch.Tensor, ) -> tuple[torch.Tensor, torch.Tensor | None]: """ Unroll the autoregressive model: at each step ``i`` call ``self.predictor`` to produce the next state, then overwrite boundary nodes with the true value from ``boundary_states[:, i]``. Parameters ---------- init_states : torch.Tensor Shape ``(B, 2, num_grid_nodes, num_state_vars)``. The two initial states ``[X_{t-1}, X_t]`` used to start the rollout from. Dims: ``B`` is batch size, ``2`` initial time steps (``X_{t-1}, X_t``), ``num_grid_nodes`` is the number of spatial nodes, and ``num_state_vars`` is the number of state variables. forcing_features : torch.Tensor Shape ``(B, pred_steps, num_grid_nodes, num_forcing_vars)``. Forcing features for each predicted step; ``pred_steps`` defines the rollout length. Dims: ``B`` is batch size, ``pred_steps`` is the number of predicted steps, ``num_grid_nodes`` is the number of spatial nodes, and ``num_forcing_vars`` is the number of forcing variables (already concatenated past/current/future windows). boundary_states : torch.Tensor Shape ``(B, pred_steps, num_grid_nodes, num_state_vars)``. True state values used ONLY to overwrite boundary nodes at each AR step. The interior prediction at step ``i`` must not depend on ``boundary_states[:, i]`` in any other way. Dims: ``B`` is batch size, ``pred_steps`` is the number of predicted steps, ``num_grid_nodes`` is the number of spatial nodes, and ``num_state_vars`` is the state feature dimension. Returns ------- prediction : torch.Tensor Shape ``(B, pred_steps, num_grid_nodes, num_state_vars)``. Stacked per-step forecasts (with boundary overwritten by the true value). Dims: same as ``boundary_states``. pred_std : torch.Tensor or None Shape ``(B, pred_steps, num_grid_nodes, num_state_vars)`` when the wrapped predictor outputs an std, otherwise ``None`` (in which case ``ForecasterModule`` substitutes the constant per-variable std). Dims: same as ``prediction``. """ prev_prev_state = init_states[:, 0] prev_state = init_states[:, 1] prediction_list = [] pred_std_list = [] pred_steps = forcing_features.shape[1] for i in range(pred_steps): forcing = forcing_features[:, i] boundary_state = boundary_states[:, i] pred_state, pred_std = self.predictor( prev_state, prev_prev_state, forcing ) # Overwrite boundary with true state using ARForecaster's mask new_state = ( self.boundary_mask * boundary_state + self.interior_mask * pred_state ) prediction_list.append(new_state) if pred_std is not None: pred_std_list.append(pred_std) # Update conditioning states prev_prev_state = prev_state prev_state = new_state prediction = torch.stack(prediction_list, dim=1) # If predictor outputs std, stack it; otherwise return None so # ForecasterModule can substitute the constant per_var_std if pred_std_list: pred_std = torch.stack(pred_std_list, dim=1) else: pred_std = None return prediction, pred_std