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: Prepares multiplicative pooling matrices for efficient feature aggregation.
  
  :return: Tuple holding the four pooling matrices.
docstr_tok: ["Prepares","multiplicative","pooling","matrices","for","efficient","feature","aggregation",".",":","return",":","Tuple","holding","the","four","pooling","matrices","."]
code: def _prepare_pooling_matrices(
      self,
  ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
      """Prepares multiplicative pooling matrices for efficient feature aggregation.
  
      :return: Tuple holding the four pooling matrices.
      """
  
      # prepare node -> node pooling mask
      n2n_pooling_mask = np.zeros(
          (self.num_nodes, self.num_nodes), dtype=np.float32
      )
      n2n_pooling_mask[:] = np.nan
  
      for i in range(self.num_nodes):
          idxs = self.edge_list[1, self.edge_list[0, :] == i]
          n2n_pooling_mask[i, idxs] = 1.0
          n2n_pooling_mask[idxs, i] = 1.0
  
      n2n_pooling_mask[
          range(self.num_nodes), range(self.num_nodes)
      ] = 1.0
  
      n2n_pooling_mask = (
          torch.from_numpy(n2n_pooling_mask).unsqueeze(-1).unsqueeze(0)
      )
  
      # prepare edge -> node pooling mask
      e2n_pooling_mask = np.zeros(
          (self.num_nodes, self.num_edges), dtype=np.float32
      )
      e2n_pooling_mask[:] = np.nan
  
      for i in range(self.num_edges):
          e2n_pooling_mask[self.edge_list[:, i], i] = 1.0
  
      e2n_pooling_mask = (
          torch.from_numpy(e2n_pooling_mask).unsqueeze(-1).unsqueeze(0)
      )
  
      # prepare node -> edge pooling mask
      n2e_pooling_mask = np.zeros(
          (self.num_edges, self.num_nodes), dtype=np.float32
      )
      n2e_pooling_mask[:] = np.nan
  
      for i in range(self.num_edges):
          n2e_pooling_mask[i, self.edge_list[:, i]] = 1.0
  
      n2e_pooling_mask = (
          torch.from_numpy(n2e_pooling_mask).unsqueeze(-1).unsqueeze(0)
      )
  
      # prepare edge -> edge pooling mask
      e2e_pooling_mask = np.zeros(
          (self.num_edges, self.num_edges), dtype=np.float32
      )
      e2e_pooling_mask[:] = np.nan
  
      for i in range(self.num_edges):
          e = self.edge_list[:, i]
          idxs = (
              (self.edge_list[0, :] == e[0])
              | (self.edge_list[0, :] == e[1])
              | (self.edge_list[1, :] == e[0])
              | (self.edge_list[1, :] == e[1])
          )
          e2e_pooling_mask[i, idxs] = 1.0
          e2e_pooling_mask[idxs, i] = 1.0
  
      e2e_pooling_mask[
          range(self.num_edges), range(self.num_edges)
      ] = 1.0
  
      e2e_pooling_mask = (
          torch.from_numpy(e2e_pooling_mask).unsqueeze(-1).unsqueeze(0)
      )
  
      return (
          n2n_pooling_mask,
          e2n_pooling_mask,
          n2e_pooling_mask,
          e2e_pooling_mask,
      )
  
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 4 for calling the GNNBlock obj's underlying member method code with example usage: obj._prepare_pooling_matrices() and returns: n2n_pooling_mask,  e2n_pooling_mask,  n2e_pooling_mask,  e2e_pooling_mask