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

"""Graph-based LAM model with a flat mesh."""

# Standard library

# Third-party
import torch
import torch_geometric as pyg

# Local
from .... import utils
from ....datastore import BaseDatastore
from ....gnn_layers import InteractionNet
from .base import BaseGraphModel


[docs] class GraphLAM(BaseGraphModel): """ Full graph-based LAM model that can be used with different (non-hierarchical )graphs. Mainly based on GraphCast, but the model from Keisler (2022) is almost identical. Used for GC-LAM and L1-LAM in Oskarsson et al. (2023). """ mesh_static_features: torch.Tensor m2m_features: torch.Tensor m2m_edge_index: torch.Tensor 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", **_kwargs: object, ) -> None: """ Initialize the GraphLAM 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, ) assert not self.hierarchical, ( "GraphLAM does not use a hierarchical mesh graph" ) # grid_dim from data + static + batch_static mesh_dim = self.mesh_static_features.shape[1] m2m_edges, m2m_dim = self.m2m_features.shape utils.log_on_rank_zero( f"Edges in subgraphs: m2m={m2m_edges}, g2m={self.g2m_edges}, " f"m2g={self.m2g_edges}" ) # Define sub-models # Feature embedders for mesh self.mesh_embedder = utils.make_mlp([mesh_dim] + self.mlp_blueprint_end) self.m2m_embedder = utils.make_mlp([m2m_dim] + self.mlp_blueprint_end) # GNNs # processor processor_nets = [ InteractionNet( self.m2m_edge_index, hidden_dim, hidden_layers=hidden_layers, aggr=mesh_aggr, ) for _ in range(processor_layers) ] self.processor = pyg.nn.Sequential( "mesh_rep, edge_rep", [ ( net, "mesh_rep, mesh_rep, edge_rep -> mesh_rep, edge_rep", ) for net in processor_nets ], )
[docs] def get_num_mesh(self) -> tuple[int, int]: """ Compute number of mesh nodes from loaded features, and number of mesh nodes that should be ignored in encoding/decoding. Returns ------- num_mesh_nodes : int The number of mesh nodes. num_ignore_mesh_nodes : int The number of mesh nodes to ignore. """ return self.mesh_static_features.shape[0], 0
[docs] def embedd_mesh_nodes(self) -> torch.Tensor: """ Embed static mesh node features. Returns ------- torch.Tensor Shape ``(num_mesh_nodes, hidden_dim)``. Embedded mesh node representations. Dims: ``num_mesh_nodes`` is the number of mesh nodes and ``hidden_dim`` is the hidden dimension. """ return self.mesh_embedder( self.mesh_static_features ) # (num_mesh_nodes, hidden_dim)
[docs] def process_step(self, mesh_rep: torch.Tensor) -> torch.Tensor: """ Process the mesh representation through the flat message-passing processor (all nodes at a single level). Parameters ---------- mesh_rep : torch.Tensor Shape ``(B, num_mesh_nodes, hidden_dim)``. Current mesh node representations. Dims: ``B`` is batch size, ``num_mesh_nodes`` is the number of mesh nodes, and ``hidden_dim`` is the hidden dimension. Returns ------- torch.Tensor Shape ``(B, num_mesh_nodes, hidden_dim)``. Updated mesh node representations. Dims: same as ``mesh_rep``. """ # Embed m2m here first batch_size = mesh_rep.shape[0] m2m_emb = self.m2m_embedder( self.m2m_features ) # (num_edges, hidden_dim) m2m_emb_expanded = self.expand_to_batch( m2m_emb, batch_size ) # (B, num_edges, hidden_dim) mesh_rep, _ = self.processor( mesh_rep, m2m_emb_expanded ) # (B, num_mesh_nodes, hidden_dim) return mesh_rep