code: def forward(self, graph, feat_src, feat_edge=None):
      with graph.local_scope():
          if not self._allow_zero_in_degree:
              if (graph.in_degrees() == 0).any():
                  assert False
  
          if graph.is_block:
              feat_dst = feat_src[: graph.number_of_dst_nodes()]
          else:
              feat_dst = feat_src
  
          if self._use_symmetric_norm:
              degs = graph.srcdata["deg"]
              # degs = graph.out_degrees().float().clamp(min=1)
              norm = torch.pow(degs, -0.5)
              shp = norm.shape + (1,) * (feat_src.dim() - 1)
              norm = torch.reshape(norm, shp)
              feat_src = feat_src * norm
  
          feat_src_fc = self.src_fc(feat_src).view(
              -1, self._n_heads, self._out_feats
          )
          feat_dst_fc = self.dst_fc(feat_dst).view(
              -1, self._n_heads, self._out_feats
          )
          attn_src = self.attn_src_fc(feat_src).view(
              -1, self._n_heads, 1
          )
  
          # NOTE: GAT paper uses "first concatenation then linear projection"
          # to compute attention scores, while ours is "first projection then
          # addition", the two approaches are mathematically equivalent:
          # We decompose the weight vector a mentioned in the paper into
          # [a_l || a_r], then
          # a^T [Wh_i || Wh_j] = a_l Wh_i + a_r Wh_j
          # Our implementation is much efficient because we do not need to
          # save [Wh_i || Wh_j] on edges, which is not memory-efficient. Plus,
          # addition could be optimized with DGL's built-in function u_add_v,
          # which further speeds up computation and saves memory footprint.
          graph.srcdata.update(
              {"feat_src_fc": feat_src_fc, "attn_src": attn_src}
          )
  
          if self.attn_dst_fc is not None:
              attn_dst = self.attn_dst_fc(feat_dst).view(
                  -1, self._n_heads, 1
              )
              graph.dstdata.update({"attn_dst": attn_dst})
              graph.apply_edges(
                  fn.u_add_v("attn_src", "attn_dst", "attn_node")
              )
          else:
              graph.apply_edges(fn.copy_u("attn_src", "attn_node"))
  
          e = graph.edata["attn_node"]
          if feat_edge is not None:
              attn_edge = self.attn_edge_fc(feat_edge).view(
                  -1, self._n_heads, 1
              )
              graph.edata.update({"attn_edge": attn_edge})
              e += graph.edata["attn_edge"]
          e = self.leaky_relu(e)
  
          if self.training and self.edge_drop > 0:
              perm = torch.randperm(graph.num_edges(), device=e.device)
              bound = int(graph.num_edges() * self.edge_drop)
              eids = perm[bound:]
              graph.edata["a"] = torch.zeros_like(e)
              graph.edata["a"][eids] = self.attn_drop(
                  edge_softmax(graph, e[eids], eids=eids)
              )
          else:
              graph.edata["a"] = self.attn_drop(edge_softmax(graph, e))
  
          # message passing
          graph.update_all(
              fn.u_mul_e("feat_src_fc", "a", "m"),
              fn.sum("m", "feat_src_fc"),
          )
  
          rst = graph.dstdata["feat_src_fc"]
  
          if self._use_symmetric_norm:
              degs = graph.dstdata["deg"]
              # degs = graph.in_degrees().float().clamp(min=1)
              norm = torch.pow(degs, 0.5)
              shp = norm.shape + (1,) * (feat_dst.dim())
              norm = torch.reshape(norm, shp)
              rst = rst * norm
  
          # residual
          if self.dst_fc is not None:
              rst += feat_dst_fc
          else:
              rst += self.bias
  
          # activation
          if self.activation is not None:
              rst = self.activation(rst, inplace=True)
  
          return rst
  
imports: import math
  from functools import partial
  import dgl.function
  import dgl.nn.pytorch
  import torch
  import torch.nn
  import torch.nn.functional
  from dgl import function
  from dgl._ffi.base import DGLError
  from dgl.base import ALL
  from dgl.nn.pytorch.utils import Identity
  from dgl.ops import edge_softmax
  from dgl.utils import expand_as_pair
  from torch.nn import init
  from torch.utils.checkpoint import checkpoint
label_desc: Use image node_id 4 for calling the GATConv obj's underlying member method code with example usage: obj.forward(graph, feat_src, feat_edge) and returns: rst