Source code for neural_lam.models.step_predictors.graph.hi_lam

"""
Hierarchical graph-based LAM model.
"""

# Standard library
from typing import cast

# Third-party
import torch
from torch import nn

# Local
from ....datastore import BaseDatastore
from ....gnn_layers import InteractionNet, get_gnn_class
from .hierarchical import BaseHiGraphModel


[docs] class HiLAM(BaseHiGraphModel): """ Hierarchical graph model with message passing that goes sequentially down and up the hierarchy during processing. The Hi-LAM model from Oskarsson et al. (2023) """ def __init__( self, datastore: BaseDatastore, graph_name: str = "multiscale", hidden_dim: int = 64, hidden_layers: int = 1, processor_layers: int = 4, mesh_aggr: str = "sum", num_past_forcing_steps: int = 1, num_future_forcing_steps: int = 1, output_std: bool = False, output_clamping_lower: dict[str, float] | None = None, output_clamping_upper: dict[str, float] | None = None, g2m_gnn_type: str = "InteractionNet", m2g_gnn_type: str = "InteractionNet", mesh_up_gnn_type: str = "InteractionNet", mesh_down_gnn_type: str = "InteractionNet", ) -> None: """ Initialize the HiLAM model. Parameters ---------- datastore : BaseDatastore The datastore providing grid metadata and data access. graph_name : str, default "multiscale" The name of the graph to load. hidden_dim : int, default 64 The dimension of the hidden representations. hidden_layers : int, default 1 The number of hidden layers in the MLPs. processor_layers : int, default 4 The number of processor layers in the GNN. mesh_aggr : str, default "sum" The aggregation method for mesh nodes. num_past_forcing_steps : int, default 1 The number of past forcing steps to include. num_future_forcing_steps : int, default 1 The number of future forcing steps to include. output_std : bool, default False Whether to output a predicted standard deviation. output_clamping_lower : dict, optional Lower clamping limits for state variables. output_clamping_upper : dict, optional Upper clamping limits for state variables. """ super().__init__( datastore=datastore, graph_name=graph_name, hidden_dim=hidden_dim, hidden_layers=hidden_layers, processor_layers=processor_layers, mesh_aggr=mesh_aggr, num_past_forcing_steps=num_past_forcing_steps, num_future_forcing_steps=num_future_forcing_steps, output_std=output_std, output_clamping_lower=output_clamping_lower, output_clamping_upper=output_clamping_upper, g2m_gnn_type=g2m_gnn_type, m2g_gnn_type=m2g_gnn_type, mesh_up_gnn_type=mesh_up_gnn_type, mesh_down_gnn_type=mesh_down_gnn_type, ) # Make down GNNs, both for down edges and same level self.mesh_down_gnns = nn.ModuleList( [self.make_down_gnns() for _ in range(processor_layers)] ) # Nested lists (proc_steps, num_levels-1) self.mesh_down_same_gnns = nn.ModuleList( [self.make_same_gnns() for _ in range(processor_layers)] ) # Nested lists (proc_steps, num_levels) # Make up GNNs, both for up edges and same level self.mesh_up_gnns = nn.ModuleList( [self.make_up_gnns() for _ in range(processor_layers)] ) # Nested lists (proc_steps, num_levels-1) self.mesh_up_same_gnns = nn.ModuleList( [self.make_same_gnns() for _ in range(processor_layers)] ) # Nested lists (proc_steps, num_levels)
[docs] def make_same_gnns(self) -> nn.ModuleList: """ Make intra-level GNNs. Returns ------- nn.ModuleList List of GNNs for each level. """ return nn.ModuleList( [ InteractionNet( edge_index, self.hidden_dim, hidden_layers=self.hidden_layers, ) for edge_index in self.m2m_edge_index ] )
[docs] def make_up_gnns(self) -> nn.ModuleList: """ Make GNNs for processing steps up through the hierarchy. Returns ------- nn.ModuleList List of GNNs for each inter-level gap (upwards). """ gnn_class = get_gnn_class(self.mesh_up_gnn_type) return nn.ModuleList( [ gnn_class( edge_index, self.hidden_dim, hidden_layers=self.hidden_layers, ) for edge_index in self.mesh_up_edge_index ] )
[docs] def make_down_gnns(self) -> nn.ModuleList: """ Make GNNs for processing steps down through the hierarchy. Returns ------- nn.ModuleList List of GNNs for each inter-level gap (downwards). """ gnn_class = get_gnn_class(self.mesh_down_gnn_type) return nn.ModuleList( [ gnn_class( edge_index, self.hidden_dim, hidden_layers=self.hidden_layers, ) for edge_index in self.mesh_down_edge_index ] )
[docs] def mesh_down_step( self, mesh_rep_levels: list[torch.Tensor], mesh_same_rep: list[torch.Tensor], mesh_down_rep: list[torch.Tensor], down_gnns: nn.ModuleList, same_gnns: nn.ModuleList, ) -> tuple[list[torch.Tensor], list[torch.Tensor], list[torch.Tensor]]: """ Run the downward pass of vertical processing, alternating between down-edges and same-level edges from the top to the bottom level. Parameters ---------- mesh_rep_levels : list of torch.Tensor One tensor per level, each of shape ``(B, num_mesh_nodes[l], hidden_dim)``. Node representations at each hierarchy level. Dims: ``B`` is batch size, ``num_mesh_nodes[l]`` is the node count at level ``l``, and ``hidden_dim`` is the hidden dimension. mesh_same_rep : list of torch.Tensor One tensor per level, each of shape ``(B, num_edges[l], hidden_dim)``. Same-level edge representations. mesh_down_rep : list of torch.Tensor One tensor per inter-level gap, each of shape ``(B, num_edges[l], hidden_dim)``. Downward edge representations from level ``l+1`` to ``l``. down_gnns : nn.ModuleList GNNs for downward edges, one per inter-level gap. same_gnns : nn.ModuleList GNNs for same-level edges, one per level. Returns ------- tuple of (list, list, list) Updated ``(mesh_rep_levels, mesh_same_rep, mesh_down_rep)``. """ # Run same level processing on level L mesh_rep_levels[-1], mesh_same_rep[-1] = same_gnns[-1]( mesh_rep_levels[-1], mesh_rep_levels[-1], mesh_same_rep[-1], ) # Let level_l go from L-1 to 0 for level_l, down_gnn, same_gnn in zip( range(self.num_levels - 2, -1, -1), reversed(down_gnns), reversed(same_gnns[:-1]), ): # Extract representations send_node_rep = mesh_rep_levels[level_l + 1] rec_node_rep = mesh_rep_levels[level_l] down_edge_rep = mesh_down_rep[level_l] same_edge_rep = mesh_same_rep[level_l] # Apply down GNN new_node_rep, mesh_down_rep[level_l] = down_gnn( send_node_rep, rec_node_rep, down_edge_rep, ) # Run same level processing on level l mesh_rep_levels[level_l], mesh_same_rep[level_l] = same_gnn( new_node_rep, new_node_rep, same_edge_rep ) return mesh_rep_levels, mesh_same_rep, mesh_down_rep
[docs] def mesh_up_step( self, mesh_rep_levels: list[torch.Tensor], mesh_same_rep: list[torch.Tensor], mesh_up_rep: list[torch.Tensor], up_gnns: nn.ModuleList, same_gnns: nn.ModuleList, ) -> tuple[list[torch.Tensor], list[torch.Tensor], list[torch.Tensor]]: """ Run the upward pass of vertical processing, alternating between up-edges and same-level edges from the bottom to the top level. Parameters ---------- mesh_rep_levels : list of torch.Tensor One tensor per level, each of shape ``(B, num_mesh_nodes[l], hidden_dim)``. Node representations at each hierarchy level. Dims: ``B`` is batch size, ``num_mesh_nodes[l]`` is the node count at level ``l``, and ``hidden_dim`` is the hidden dimension. mesh_same_rep : list of torch.Tensor One tensor per level, each of shape ``(B, num_edges[l], hidden_dim)``. Same-level edge representations. mesh_up_rep : list of torch.Tensor One tensor per inter-level gap, each of shape ``(B, num_edges[l], hidden_dim)``. Upward edge representations from level ``l`` to ``l+1``. up_gnns : nn.ModuleList GNNs for upward edges, one per inter-level gap. same_gnns : nn.ModuleList GNNs for same-level edges, one per level. Returns ------- tuple of (list, list, list) Updated ``(mesh_rep_levels, mesh_same_rep, mesh_up_rep)``. """ # Run same level processing on level 0 mesh_rep_levels[0], mesh_same_rep[0] = same_gnns[0]( mesh_rep_levels[0], mesh_rep_levels[0], mesh_same_rep[0], ) # Let level_l go from 1 to L for level_l, (up_gnn, same_gnn) in enumerate( zip(up_gnns, same_gnns[1:]), start=1, ): # Extract representations send_node_rep = mesh_rep_levels[level_l - 1] rec_node_rep = mesh_rep_levels[level_l] up_edge_rep = mesh_up_rep[level_l - 1] same_edge_rep = mesh_same_rep[level_l] # Apply up GNN new_node_rep, mesh_up_rep[level_l - 1] = up_gnn( send_node_rep, rec_node_rep, up_edge_rep, ) # Run same level processing on level l mesh_rep_levels[level_l], mesh_same_rep[level_l] = same_gnn( new_node_rep, new_node_rep, same_edge_rep ) return mesh_rep_levels, mesh_same_rep, mesh_up_rep
[docs] def hi_processor_step( self, mesh_rep_levels: list[torch.Tensor], mesh_same_rep: list[torch.Tensor], mesh_up_rep: list[torch.Tensor], mesh_down_rep: list[torch.Tensor], ) -> tuple[ list[torch.Tensor], list[torch.Tensor], list[torch.Tensor], list[torch.Tensor], ]: """ Run all processor steps (down then up at each layer depth) over the hierarchical mesh. Parameters ---------- mesh_rep_levels : list of torch.Tensor One tensor per level, each of shape ``(B, num_mesh_nodes[l], hidden_dim)``. Node representations at each hierarchy level. Dims: ``B`` is batch size, ``num_mesh_nodes[l]`` is the node count at level ``l``, and ``hidden_dim`` is the hidden dimension. mesh_same_rep : list of torch.Tensor One tensor per level, each of shape ``(B, num_edges[l], hidden_dim)``. Same-level edge representations. mesh_up_rep : list of torch.Tensor One tensor per inter-level gap, each of shape ``(B, num_edges[l], hidden_dim)``. Upward edge representations. mesh_down_rep : list of torch.Tensor One tensor per inter-level gap, each of shape ``(B, num_edges[l], hidden_dim)``. Downward edge representations. Returns ------- tuple of (list, list, list, list) Updated ``(mesh_rep_levels, mesh_same_rep, mesh_up_rep, mesh_down_rep)``. """ for down_gnns, down_same_gnns, up_gnns, up_same_gnns in zip( self.mesh_down_gnns, self.mesh_down_same_gnns, self.mesh_up_gnns, self.mesh_up_same_gnns, ): # Down mesh_rep_levels, mesh_same_rep, mesh_down_rep = self.mesh_down_step( mesh_rep_levels, mesh_same_rep, mesh_down_rep, cast("nn.ModuleList", down_gnns), cast("nn.ModuleList", down_same_gnns), ) # Up mesh_rep_levels, mesh_same_rep, mesh_up_rep = self.mesh_up_step( mesh_rep_levels, mesh_same_rep, mesh_up_rep, cast("nn.ModuleList", up_gnns), cast("nn.ModuleList", up_same_gnns), ) # NOTE: We return all, even though only down edges really are used # later return mesh_rep_levels, mesh_same_rep, mesh_up_rep, mesh_down_rep