class_docstr: Graph attention layer that handles edge features from `Rossmann-Toolbox
  <https://pubmed.ncbi.nlm.nih.gov/34571541/>`__ (see supplementary data)
  
  The difference lies in how unnormalized attention scores :math:`e_{ij}` are obtained:
  
  .. math::
      e_{ij} &= \vec{F} (f_{ij}^{\prime})
  
      f_{ij}^{\prime} &= \mathrm{LeakyReLU}\left(A [ h_{i} \| f_{ij} \| h_{j}]\right)
  
  where :math:`f_{ij}^{\prime}` are edge features, :math:`\mathrm{A}` is weight matrix and
  :math:`\vec{F}` is weight vector. After that, resulting node features
  :math:`h_{i}^{\prime}` are updated in the same way as in regular GAT.
  
  Parameters
  ----------
  in_node_feats : int, or pair of ints
      Input feature size; i.e, the number of dimensions of :math:`h_{i}`.
      EGATConv can be applied on homogeneous graph and unidirectional
      `bipartite graph <https://docs.dgl.ai/generated/dgl.bipartite.html?highlight=bipartite>`__.
      If the layer is to be applied to a unidirectional bipartite graph, ``in_feats``
      specifies the input feature size on both the source and destination nodes.  If
      a scalar is given, the source and destination node feature size would take the
      same value.
  in_edge_feats : int
      Input edge feature size :math:`f_{ij}`.
  out_node_feats : int
      Output node feature size.
  out_edge_feats : int
      Output edge feature size :math:`f_{ij}^{\prime}`.
  num_heads : int
      Number of attention heads.
  bias : bool, optional
      If True, add bias term to :math:`f_{ij}^{\prime}`. Defaults: ``True``.
  
  Examples
  ----------
  >>> import dgl
  >>> import torch as th
  >>> from dgl.nn import EGATConv
  
  >>> # Case 1: Homogeneous graph
  >>> num_nodes, num_edges = 8, 30
  >>> # generate a graph
  >>> graph = dgl.rand_graph(num_nodes,num_edges)
  >>> node_feats = th.rand((num_nodes, 20))
  >>> edge_feats = th.rand((num_edges, 12))
  >>> egat = EGATConv(in_node_feats=20,
  ...                 in_edge_feats=12,
  ...                 out_node_feats=15,
  ...                 out_edge_feats=10,
  ...                 num_heads=3)
  >>> #forward pass
  >>> new_node_feats, new_edge_feats = egat(graph, node_feats, edge_feats)
  >>> new_node_feats.shape, new_edge_feats.shape
  torch.Size([8, 3, 15]) torch.Size([30, 3, 10])
  
  >>> # Case 2: Unidirectional bipartite graph
  >>> u = [0, 1, 0, 0, 1]
  >>> v = [0, 1, 2, 3, 2]
  >>> g = dgl.heterograph({('A', 'r', 'B'): (u, v)})
  >>> u_feat = th.tensor(np.random.rand(2, 25).astype(np.float32))
  >>> v_feat = th.tensor(np.random.rand(4, 30).astype(np.float32))
  >>> nfeats = (u_feat,v_feat)
  >>> efeats = th.tensor(np.random.rand(5, 15).astype(np.float32))
  >>> in_node_feats = (25,30)
  >>> in_edge_feats = 15
  >>> out_node_feats = 10
  >>> out_edge_feats = 5
  >>> num_heads = 3
  >>> egat_model =  EGATConv(in_node_feats,
  ...                        in_edge_feats,
  ...                        out_node_feats,
  ...                        out_edge_feats,
  ...                        num_heads,
  ...                        bias=True)
  >>> #forward pass
  >>> new_node_feats,
  >>> new_edge_feats,
  >>> attentions = egat_model(g, nfeats, efeats, get_attention=True)
  >>> new_node_feats.shape, new_edge_feats.shape, attentions.shape
  (torch.Size([4, 3, 10]), torch.Size([5, 3, 5]), torch.Size([5, 3, 1]))
docstr: Compute new node and edge features.
  
  Parameters
  ----------
  graph : DGLGraph
      The graph.
  nfeat : torch.Tensor or pair of torch.Tensor
      If a torch.Tensor is given, the input feature of shape :math:`(N, D_{in})`
      where:
          :math:`D_{in}` is size of input node feature,
          :math:`N` is the number of nodes.
      If a pair of torch.Tensor is given, the pair must contain two tensors of shape
          :math:`(N_{in}, D_{in_{src}})` and
          :math:`(N_{out}, D_{in_{dst}})`.
  efeats: torch.Tensor
       The input edge feature of shape :math:`(E, F_{in})`
       where:
           :math:`F_{in}` is size of input node feature,
           :math:`E` is the number of edges.
  edge_weight : torch.Tensor, optional
      A 1D tensor of edge weight values.  Shape: :math:`(|E|,)`.
  get_attention : bool, optional
          Whether to return the attention values. Default to False.
  
  Returns
  -------
  pair of torch.Tensor
      node output features followed by edge output features.
      The node output feature is of shape :math:`(N, H, D_{out})`
      The edge output feature is of shape :math:`(F, H, F_{out})`
      where:
          :math:`H` is the number of heads,
          :math:`D_{out}` is size of output node feature,
          :math:`F_{out}` is size of output edge feature.
  torch.Tensor, optional
      The attention values of shape :math:`(E, H, 1)`.
      This is returned only when :attr:`get_attention` is ``True``.
code: def forward(
      self, graph, nfeats, efeats, edge_weight=None, get_attention=False
  ):
      r"""
      Compute new node and edge features.
  
      Parameters
      ----------
      graph : DGLGraph
          The graph.
      nfeat : torch.Tensor or pair of torch.Tensor
          If a torch.Tensor is given, the input feature of shape :math:`(N, D_{in})`
          where:
              :math:`D_{in}` is size of input node feature,
              :math:`N` is the number of nodes.
          If a pair of torch.Tensor is given, the pair must contain two tensors of shape
              :math:`(N_{in}, D_{in_{src}})` and
              :math:`(N_{out}, D_{in_{dst}})`.
      efeats: torch.Tensor
           The input edge feature of shape :math:`(E, F_{in})`
           where:
               :math:`F_{in}` is size of input node feature,
               :math:`E` is the number of edges.
      edge_weight : torch.Tensor, optional
          A 1D tensor of edge weight values.  Shape: :math:`(|E|,)`.
      get_attention : bool, optional
              Whether to return the attention values. Default to False.
  
      Returns
      -------
      pair of torch.Tensor
          node output features followed by edge output features.
          The node output feature is of shape :math:`(N, H, D_{out})`
          The edge output feature is of shape :math:`(F, H, F_{out})`
          where:
              :math:`H` is the number of heads,
              :math:`D_{out}` is size of output node feature,
              :math:`F_{out}` is size of output edge feature.
      torch.Tensor, optional
          The attention values of shape :math:`(E, H, 1)`.
          This is returned only when :attr:`get_attention` is ``True``.
      """
  
      with graph.local_scope():
          if (graph.in_degrees() == 0).any():
              raise DGLError(
                  "There are 0-in-degree nodes in the graph, "
                  "output for those nodes will be invalid. "
                  "This is harmful for some applications, "
                  "causing silent performance regression. "
                  "Adding self-loop on the input graph by "
                  "calling `g = dgl.add_self_loop(g)` will resolve "
                  "the issue."
              )
  
          # calc edge attention
          # same trick way as in dgl.nn.pytorch.GATConv, but also includes edge feats
          # https://github.com/dmlc/dgl/blob/master/python/dgl/nn/pytorch/conv/gatconv.py
          if isinstance(nfeats, tuple):
              nfeats_src, nfeats_dst = nfeats
          else:
              nfeats_src = nfeats_dst = nfeats
  
          f_ni = self.fc_ni(nfeats_src)
          f_nj = self.fc_nj(nfeats_dst)
          f_fij = self.fc_fij(efeats)
  
          graph.srcdata.update({"f_ni": f_ni})
          graph.dstdata.update({"f_nj": f_nj})
          # add ni, nj factors
          graph.apply_edges(fn.u_add_v("f_ni", "f_nj", "f_tmp"))
          # add fij to node factor
          f_out = graph.edata.pop("f_tmp") + f_fij
          if self.bias is not None:
              f_out = f_out + self.bias
          f_out = nn.functional.leaky_relu(f_out)
          f_out = f_out.view(-1, self._num_heads, self._out_edge_feats)
          # compute attention factor
          e = (f_out * self.attn).sum(dim=-1).unsqueeze(-1)
          graph.edata["a"] = edge_softmax(graph, e)
          if edge_weight is not None:
              graph.edata["a"] = graph.edata["a"] * edge_weight.tile(
                  1, self._num_heads, 1
              ).transpose(0, 2)
          graph.srcdata["h_out"] = self.fc_node_src(nfeats_src).view(
              -1, self._num_heads, self._out_node_feats
          )
          # calc weighted sum
          graph.update_all(
              fn.u_mul_e("h_out", "a", "m"), fn.sum("m", "h_out")
          )
  
          h_out = graph.dstdata["h_out"].view(
              -1, self._num_heads, self._out_node_feats
          )
          if get_attention:
              return h_out, f_out, graph.edata.pop("a")
          else:
              return h_out, f_out
  
file_path: dgl/python/dgl/nn/pytorch/conv/egatconv.py
imports: import torch
  from torch import nn
  from torch.nn import init
  from ....None import function
  from ....base import DGLError
  from ....utils import expand_as_pair
  from ...functional import edge_softmax
label_desc: Use image node_id 3 for calling the EGATConv obj's underlying member method code with example usage: obj.forward(graph, nfeats, efeats, edge_weight, get_attention) and returns: h_out,  f_out,  graph, h_out,  f_out