Source code for neural_lam.models.latent.graph_encoder
"""Latent encoder 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_encoder import BaseLatentEncoder
[docs]
class GraphLatentEncoder(BaseLatentEncoder):
"""
Latent encoder that maps grid features to mesh and outputs a Gaussian
distribution over a latent variable on mesh nodes. Uses a flat
(non-hierarchical) graph: one g2m GNN (type set by ``g2m_gnn_type``)
followed by a stack of on-mesh (m2m) InteractionNet layers.
"""
def __init__(
self,
latent_dim: int,
g2m_edge_index: torch.Tensor,
m2m_edge_index: torch.Tensor,
hidden_dim: int,
m2m_layers: int,
hidden_layers: int = 1,
g2m_gnn_type: str = "InteractionNet",
output_dist: str = "isotropic",
) -> None:
"""
Set up the g2m GNN, on-mesh processing stack and latent param map.
Parameters
----------
latent_dim : int
Dimensionality of the latent variable at each mesh node.
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.
hidden_dim : int
Dimensionality of internal node and edge representations.
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``).
output_dist : str
Type of output distribution: ``"isotropic"`` or ``"diagonal"``.
"""
super().__init__(latent_dim, output_dist)
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 compute_dist_params
self.m2m_gnns = (
utils.make_gnn_seq(
m2m_edge_index, m2m_layers, hidden_layers, hidden_dim
)
if m2m_layers > 0
else None
)
self.latent_param_map = utils.make_mlp(
[hidden_dim] * (hidden_layers + 1) + [self.output_dim],
layer_norm=False,
)
# pylint: disable-next=arguments-differ
[docs]
def compute_dist_params( # type: ignore[override]
self,
grid_rep: torch.Tensor,
graph_emb: dict[str, Any],
**kwargs: Any,
) -> torch.Tensor:
"""
Compute distribution parameters on mesh from grid features.
Parameters
----------
grid_rep : torch.Tensor
Shape ``(B, num_grid_nodes, d_h)``. Grid input representation.
graph_emb : dict
Embedded graph node and edge features, with at least entries
``mesh``: ``(B, num_mesh_nodes, d_h)``,
``g2m``: ``(B, M_g2m, d_h)`` and ``m2m``: ``(B, M_m2m, d_h)``.
**kwargs
Ignored.
Returns
-------
torch.Tensor
Shape ``(B, num_mesh_nodes, output_dim)``. Raw parameters of
the latent distribution.
"""
mesh_rep = self.g2m_gnn(grid_rep, graph_emb["mesh"], graph_emb["g2m"])
if self.m2m_gnns is not None:
mesh_rep, _ = self.m2m_gnns(mesh_rep, graph_emb["m2m"])
return self.latent_param_map(mesh_rep)