Source code for neural_lam.models.latent.graph_decoder

"""Latent decoder for flat (non-hierarchical) graphs."""

# Standard library
from typing import Any

# Third-party
import torch

# First-party
from neural_lam import utils
from neural_lam.gnn_layers import get_gnn_class

# Local
from .base_decoder import BaseGraphLatentDecoder


[docs] class GraphLatentDecoder(BaseGraphLatentDecoder): """ Latent decoder for a flat (non-hierarchical) graph. Encodes grid into mesh with a g2m GNN (type set by ``g2m_gnn_type``), processes on mesh, and reads back out to grid with an m2g GNN (type set by ``m2g_gnn_type``). The grid representation also goes through a residual MLP that is added back to the mesh-to-grid output. """ def __init__( self, g2m_edge_index: torch.Tensor, m2m_edge_index: torch.Tensor, m2g_edge_index: torch.Tensor, hidden_dim: int, latent_dim: int, num_state_vars: int, m2m_layers: int, hidden_layers: int = 1, g2m_gnn_type: str = "InteractionNet", m2g_gnn_type: str = "InteractionNet", output_std: bool = True, ) -> None: """ Set up the g2m, on-mesh and m2g GNNs. Parameters ---------- g2m_edge_index : torch.Tensor Shape ``(2, M_g2m)``. Edge index of grid-to-mesh edges. m2m_edge_index : torch.Tensor Shape ``(2, M_m2m)``. Edge index of mesh-to-mesh edges. m2g_edge_index : torch.Tensor Shape ``(2, M_m2g)``. Edge index of mesh-to-grid edges. hidden_dim : int Dimensionality of internal node and edge representations. latent_dim : int Dimensionality of the latent variable at each mesh node. num_state_vars : int Number of state variables predicted at each grid node. m2m_layers : int Number of on-mesh (m2m) GNN layers; 0 disables on-mesh processing. hidden_layers : int Number of hidden layers in internal MLPs. g2m_gnn_type : str GNN type for the grid-to-mesh step (key in ``gnn_layers.GNN_TYPES``). m2g_gnn_type : str GNN type for the mesh-to-grid step (key in ``gnn_layers.GNN_TYPES``). output_std : bool If True, the decoder outputs both mean and std of the next-state distribution; if False, only the mean. """ super().__init__( hidden_dim, latent_dim, num_state_vars, hidden_layers, output_std ) self.g2m_gnn = get_gnn_class(g2m_gnn_type)( g2m_edge_index, hidden_dim, hidden_layers=hidden_layers, update_edges=False, ) # None if m2m_layers == 0, in which case no on-mesh processing is # done in combine_with_latent self.m2m_gnns = ( utils.make_gnn_seq( m2m_edge_index, m2m_layers, hidden_layers, hidden_dim ) if m2m_layers > 0 else None ) self.m2g_gnn = get_gnn_class(m2g_gnn_type)( m2g_edge_index, hidden_dim, hidden_layers=hidden_layers, update_edges=False, )
[docs] def combine_with_latent( self, original_grid_rep: torch.Tensor, latent_rep: torch.Tensor, residual_grid_rep: torch.Tensor, graph_emb: dict[str, Any], ) -> torch.Tensor: """ Fuse grid and latent reps via g2m -> m2m -> m2g. Parameters ---------- original_grid_rep : torch.Tensor Shape ``(B, num_grid_nodes, d_h)``. Embedded grid input features, used as the sender representation in the grid-to-mesh step. latent_rep : torch.Tensor Shape ``(B, num_mesh_nodes, d_h)``. Latent sample embedded to ``d_h``, used as the initial mesh node representation (the receiver in the grid-to-mesh step), so all mesh processing starts from the latent. residual_grid_rep : torch.Tensor Shape ``(B, num_grid_nodes, d_h)``. Residually updated grid representation, used as the receiver representation in the mesh-to-grid step. graph_emb : dict Embedded static graph node and edge features, with at least the entries ``g2m``: ``(B, M_g2m, d_h)``, ``m2m``: ``(B, M_m2m, d_h)`` and ``m2g``: ``(B, M_m2g, d_h)``. Returns ------- torch.Tensor Shape ``(B, num_grid_nodes, d_h)``. Combined grid representation, incorporating both the grid input and the latent sample. """ mesh_rep = self.g2m_gnn(original_grid_rep, latent_rep, graph_emb["g2m"]) if self.m2m_gnns is not None: mesh_rep, _ = self.m2m_gnns(mesh_rep, graph_emb["m2m"]) grid_rep = self.m2g_gnn(mesh_rep, residual_grid_rep, graph_emb["m2g"]) return grid_rep