class_docstr: An implementation of the Temporal Attention Module( i.e. compute temporal 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 temporal attention layer.
  
  Arg types:
      * **X** (PyTorch FloatTensor) - Node features for T time periods, with shape (B, N_nodes, F_in, T_in).
  
  Return types:
      * **E** (PyTorch FloatTensor) - Temporal attention score matrices, with shape (B, T_in, T_in).
code: def forward(self, X: torch.FloatTensor) -> torch.FloatTensor:
      """
      Making a forward pass of the temporal attention layer.
  
      Arg types:
          * **X** (PyTorch FloatTensor) - Node features for T time periods, with shape (B, N_nodes, F_in, T_in).
  
      Return types:
          * **E** (PyTorch FloatTensor) - Temporal attention score matrices, with shape (B, T_in, T_in).
      """
      # lhs = left hand side embedding;
      # to calculcate it :
      # permute x:(B, N, F_in, T) -> (B, T, F_in, N)
      # multiply with U1 (B, T, F_in, N)(N) -> (B,T,F_in)
      # multiply with U2 (B,T,F_in)(F_in,N)->(B,T,N)
      # for example (32, 307, 1, 12) -premute-> (32, 12, 1, 307) * (307) -> (32, 12, 1) * (1, 307) -> (32, 12, 307)
      LHS = torch.matmul(
          torch.matmul(X.permute(0, 3, 2, 1), self._U1), self._U2
      )  # (32, 12, 307)
  
      # rhs = right hand side embedding
      # to calculcate it :
      # mutliple U3 with X (F)(B,N,F,T)->(B, N, T)
      # for example (1)(32, 307, 1, 12) -> (32, 307, 12)
      RHS = torch.matmul(self._U3, X)  # (32, 307, 12)
  
      # Them we multiply LHS with RHS :
      # (B,T,N)(B,N,T)->(B,T,T)
      # for example (32, 12, 307) * (32, 307, 12) -> (32, 12, 12)
      # Then multiply Ve(T,T) with the output
      # (T,T)(B, T, T)->(B,T,T)
      # for example (12, 12) *  (32, 12, 12) ->   (32, 12, 12)
      E = torch.matmul(
          self._Ve, torch.sigmoid(torch.matmul(LHS, RHS) + self._be)
      )
      E = F.softmax(E, dim=1)  #  (B, T, T)  for example (32, 12, 12)
      return E
  
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 TemporalAttention obj's underlying member method code with example usage: obj.forward(X) and returns: E