class_docstr: An implementation of the Spatial Attention Module (i.e compute spatial attention scores). For details see this paper:
  `"Attention Based Spatial-Temporal Graph Convolutional Networks for Traffic Flow
  Forecasting." <https://ojs.aaai.org/index.php/AAAI/article/view/3881>`_
  
  Args:
      in_channels (int): Number of input features.
      num_of_vertices (int): Number of vertices in the graph.
      num_of_timesteps (int): Number of time lags.
docstr: Making a forward pass of the spatial attention layer.
  
  Arg types:
      * **X** (PyTorch FloatTensor) - Node features for T time periods, with shape (B, N_nodes, F_in, T_in).
  
  Return types:
      * **S** (PyTorch FloatTensor) - Spatial attention score matrices, with shape (B, N_nodes, N_nodes).
code: def forward(self, X: torch.FloatTensor) -> torch.FloatTensor:
      """
      Making a forward pass of the spatial attention layer.
  
      Arg types:
          * **X** (PyTorch FloatTensor) - Node features for T time periods, with shape (B, N_nodes, F_in, T_in).
  
      Return types:
          * **S** (PyTorch FloatTensor) - Spatial attention score matrices, with shape (B, N_nodes, N_nodes).
      """
      # lhs = left hand side embedding;
      # to calculcate it :
      # multiply with W1 (B, N, F_in, T)(T) -> (B,N,F_in)
      # multiply with W2 (B,N,F_in)(F_in,T)->(B,N,T)
      # for example (32, 307, 1, 12) * (12) -> (32, 307, 1) * (1, 12) -> (32, 307, 12)
      LHS = torch.matmul(torch.matmul(X, self._W1), self._W2)
  
      # rhs = right hand side embedding
      # to calculcate it :
      # mutliple W3 with X (F)(B,N,F,T)->(B, N, T)
      # transpose  (B, N, T)  -> (B, T, N)
      # for example (1)(32, 307, 1, 12) -> (32, 307, 12) -transpose-> (32, 12, 307)
      RHS = torch.matmul(self._W3, X).transpose(-1, -2)
  
      # Then, we multiply LHS with RHS :
      # (B,N,T)(B,T, N)->(B,N,N)
      # for example (32, 307, 12) * (32, 12, 307) -> (32, 307, 307)
      # Then multiply Vs(N,N) with the output
      # (N,N)(B, N, N)->(B,N,N) (32, 307, 307)
      # for example (307, 307) *  (32, 307, 307) ->   (32, 307, 307)
      S = torch.matmul(
          self._Vs, torch.sigmoid(torch.matmul(LHS, RHS) + self._bs)
      )
      S = F.softmax(S, dim=1)
      return S
  
imports: import math
  from typing import Optional, List, Union
  import torch
  import torch.nn
  from torch.nn import Parameter
  import torch.nn.functional
  from torch_geometric.data import Data
  from torch_geometric.typing import OptTensor
  from torch_geometric.nn.conv import MessagePassing
  from torch_geometric.transforms import LaplacianLambdaMax
  from torch_geometric.utils import remove_self_loops, add_self_loops, get_laplacian
label_desc: Use image node_id 3 for calling the SpatialAttention obj's underlying member method code with example usage: obj.forward(X) and returns: S