class_docstr: Base class for all graph blocks, implementing all methods needed for processing the adjacency matrix
docstr: Transform the adjacency matrix needed for the computation.
  
  Since repeated application of forward computation on the standard adjacency matrix can lead to numerical
  instabilities and exploding/vanishing gradients when used in a deep neural network model. To alleviate this
  problem, the following renormalization trick is used:
  
  A^bar := A + I_n * :param self_importance_scalar
  D^bar_ii := sum_j A^bar_ij
  then: A^hat := D^bar^(-1/2) A^bar D^bar^(-1/2)
  
  :param adj: The standard adjacency matrix.
  :param self_importance_scalar: Determine how important a node is to itself relative to other nodes.
      (default 1, meaning it is just as important as other nodes) - could be treated as a trainable parameter.
  :return: The normalized adjacency matrix A^hat.
docstr_tok: ["Transform","the","adjacency","matrix","needed","for","the","computation",".","Since","repeated","application","of","forward","computation","on","the","standard","adjacency","matrix","can","lead","to","numerical","instabilities","and","exploding\/vanishing","gradients","when","used","in","a","deep","neural","network","model",".","To","alleviate","this","problem",",","the","following","renormalization","trick","is","used",":","A^bar",":","=","A","+","I_n","*",":","param","self_importance_scalar","D^bar_ii",":","=","sum_j","A^bar_ij","then",":","A^hat",":","=","D^bar^","(","-1\/2",")","A^bar","D^bar^","(","-1\/2",")",":","param","adj",":","The","standard","adjacency","matrix",".",":","param","self_importance_scalar",":","Determine","how","important","a","node","is","to","itself","relative","to","other","nodes",".","(","default","1",",","meaning","it","is","just","as","important","as","other","nodes",")","-","could","be","treated","as","a","trainable","parameter",".",":","return",":","The","normalized","adjacency","matrix","A^hat","."]
code: def preprocess_adj_to_adj_hat(
      cls,
      adj: torch.Tensor,
      self_importance_scalar: Optional[torch.Tensor] = torch.tensor(1),
  ) -> Union[torch.Tensor, np.ndarray]:
      """Transform the adjacency matrix needed for the computation.
  
      Since repeated application of forward computation on the standard adjacency matrix can lead to numerical
      instabilities and exploding/vanishing gradients when used in a deep neural network model. To alleviate this
      problem, the following renormalization trick is used:
  
      A^bar := A + I_n * :param self_importance_scalar
      D^bar_ii := sum_j A^bar_ij
      then: A^hat := D^bar^(-1/2) A^bar D^bar^(-1/2)
  
      :param adj: The standard adjacency matrix.
      :param self_importance_scalar: Determine how important a node is to itself relative to other nodes.
          (default 1, meaning it is just as important as other nodes) - could be treated as a trainable parameter.
      :return: The normalized adjacency matrix A^hat.
      """
  
      batch_dim = 0
      if len(adj.shape) > 2:
          # If a batch of adjacency matrices is given compute only once and repeat
          batch_dim = adj.shape[0]
      else:
          adj = adj.unsqueeze(0)
      # Our framework currently does not naturally support edge features  and  is  limited  to  undirected  graphs  (
      # weighted  or  unweighted). Results  on  NELL  however show that it is possible to handle both directed edges
      # and edge features by representing the original directed graph as an undirected bipartite graph with additional
      # nodes that represent edges in the original graph (see Section 5.1 for details).
      # - https://arxiv.org/abs/1609.02907 THUS --> adjacency matrix has to be symmetric (-> thus square)
      assert (
          adj.shape[-1] == adj.shape[-2]
      ), "The adj matrix should be a square matrix"
  
      # Add the feature of the node itself. For example, the first row of the result matrix should contain features of
      # node A too.
      adj_bar = adj + self_importance_scalar * torch.eye(
          adj.shape[-2], device=adj.device
      ).repeat(adj.shape[0], 1, 1)
  
      # Row-normalize sparse matrix, so that the adjacency matrix is scaled by both rows and columns.
      rowsum = adj_bar.sum(-1)
      r_inv_sqrt = torch.pow(rowsum, -0.5)
      r_inv_sqrt[torch.isinf(r_inv_sqrt)] = 0.0
  
      r_mat_inv_sqrt = torch.diag_embed(r_inv_sqrt)
      row_norm = torch.matmul(adj_bar, r_mat_inv_sqrt)
      adj_hat = torch.matmul(
          torch.transpose(row_norm, dim0=-2, dim1=-1), r_mat_inv_sqrt
      )
  
      if batch_dim == 0:
          adj_hat = adj_hat.squeeze(0)
  
      return adj_hat
  
imports: from collections import OrderedDict
  from typing import Union, List, Sequence, Dict, Optional, Callable
  import numpy
  import torch
  from torch import nn
  from torch.nn.parameter import Parameter
  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 GraphAdjacencyMethods obj's underlying member method code with example usage: obj.preprocess_adj_to_adj_hat(cls, adj, _importance_scalar) and returns: adj_hat