class_docstr: A customizable graph neural network (GNN) block.
  
  :param in_keys: One key identifying the input tensors.
  :param out_keys: One key identifying the output tensors.
  :param in_shapes: List of input shapes.
  :param edges: List of graph edges required for message passing (aggregation).
  :param aggregate: The aggregation function to use (max, mean, sum).
  :param hidden_units: List containing the number of hidden units for hidden layers.
  :param non_lin: The non-linearity to apply after each layer.
  :param with_layer_norm: If True layer normalization is applied.
  :param node2node_aggr: If True node to node message passing is applied.
  :param edge2node_aggr: If True edge to node message passing is applied.
  :param node2edge_aggr: If True node to edge message passing is applied.
  :param edge2edge_aggr: If True edge to edge message passing is applied.
  :param with_node_embedding: If True the node embedding is computed.
  :param with_edge_embedding: If True the edge embedding is computed.
docstr: Computes the embeddings of node and edge feature matrices.
  
  :param n: The initial node feature matrix.
  :param e: The initial edge feature matrix.
  :return: Tuple holding the node and edge embedding tensors.
docstr_tok: ["Computes","the","embeddings","of","node","and","edge","feature","matrices",".",":","param","n",":","The","initial","node","feature","matrix",".",":","param","e",":","The","initial","edge","feature","matrix",".",":","return",":","Tuple","holding","the","node","and","edge","embedding","tensors","."]
code: def _compute_embeddings(
      self, n: torch.Tensor, e: torch.Tensor
  ) -> Tuple[torch.Tensor, torch.Tensor]:
      """Computes the embeddings of node and edge feature matrices.
  
      :param n: The initial node feature matrix.
      :param e: The initial edge feature matrix.
      :return: Tuple holding the node and edge embedding tensors.
      """
  
      # iterate layers
      prev_n, prev_e = None, None
      for i in range(self.n_layers):
          first_layer = i == 0
          last_layer = i == self.n_layers - 1
  
          if (
              self.with_node_embedding
              or self.node2edge_aggr
              or self.edge2node_aggr
          ):
              # message passing: node -> node
              if not first_layer:
                  if self.node2node_aggr:
                      n2n = self.n2n_aggregation(prev_n)
                      n = torch.cat((n, n2n), dim=-1)
  
                  if self.edge2node_aggr:
                      e2n = self.e2n_aggregation(prev_e)
                      n = torch.cat((n, e2n), dim=-1)
  
              # embed nodes
              if self.with_node_embedding or not last_layer:
                  n = self.node_layers[i](n)
  
          if (
              self.with_edge_embedding
              or self.edge2node_aggr
              or self.node2edge_aggr
          ):
              if not first_layer:
                  if self.edge2edge_aggr:
                      e2e = self.e2e_aggregation(prev_e)
                      e = torch.cat((e, e2e), dim=-1)
  
                  if self.node2edge_aggr:
                      n2e = self.n2e_aggregation(prev_n)
                      e = torch.cat((e, n2e), dim=-1)
  
              # embed edges
              if self.with_edge_embedding or not last_layer:
                  e = self.edge_layers[i](e)
  
          prev_n = n
          prev_e = e
  
      return n, e
  
imports: from typing import Any, List, Tuple, Union, Sequence, Dict
  import numpy
  import torch
  from torch import nn
  from torch_scatter import scatter
  from maze.core.annotations import override
  from maze.core.utils.factory import Factory
  from maze.perception.blocks.shape_normalization import ShapeNormalizationBlock
label_desc: Use image node_id 3 for calling the GNNBlock obj's underlying member method code with example usage: obj._compute_embeddings(n, e) and returns: n,  e