class_docstr: Fuse aggregated embeddings from both incoming and outgoing
  directions before updating node embeddings.
  .. math::
     h_{i}^{0} = [ x_i \| \mathbf{0} ]
     a_{i, \vdash}^{t} = \sum_{j\in\mathcal{N}_{\vdash }(i)}
     W_{\vdash} h_{j}^{t}
     a_{i, \dashv}^{t} = \sum_{j\in\mathcal{N}_{\dashv }(i)}
     W_{\dashv} h_{j}^{t}
     e_{i}^{t} &= \sigma (W_{f}[a_{i, \vdash}^{t};a_{i, \dashv}^{t};
     a_{i, \vdash}^{t}*a_{i, \dashv}^{t};
     a_{i, \vdash}^{t}-a_{i, \dashv}^{t}])
     h_{i}^{t+1} = \mathrm{GRU}(e_{i}^{t}, h_{i}^{t})
  Attributes
  ----------
  input_size: int
      Input feature size.
  output_size: int
      Output feature size.
  n_etypes: int
      Number of edge types. Default: 1.
  bias: bool
      If True, adds a learnable bias to the output. Default: True.
  Attributes
  ----------
  input_size: int
      Input feature size.
  output_size: int
      Output feature size.
  n_etypes: int
      Number of edge types. Default: 1.
  bias: bool
      If True, adds a learnable bias to the output. Default: True.
docstr: Parameters
  ----------
  graph: dgl.DGLGraph
  node_feats: torch.Tensor
      The shape of node_feats is :math:`(N, D_{in})`.
  edge_weight: torch.Tensor
      The shape of edge_weight is :math:`(N_E, 1)`. N_E is the number of edges in graph.
  Returns
  -------
  torch.Tensor
      The output feature of shape :math:`(N, D_{out})` where
      :math:`D_{out}` is size of output feature.
docstr_tok: ["Parameters","--","--","--","--","--","graph",":","dgl.DGLGraph","node_feats",":","torch.Tensor","The","shape","of","node_feats","is",":","math",":","`","(","N",",","D_","{","in","}",")","`",".","edge_weight",":","torch.Tensor","The","shape","of","edge_weight","is",":","math",":","`","(","N_E",",","1",")","`",".","N_E","is","the","number","of","edges","in","graph",".","Returns","--","--","--","-","torch.Tensor","The","output","feature","of","shape",":","math",":","`","(","N",",","D_","{","out","}",")","`","where",":","math",":","`","D_","{","out","}","`","is","size","of","output","feature","."]
code: def forward(self, graph, node_feats, etypes=None, edge_weight=None):
      """
      Parameters
      ----------
      graph: dgl.DGLGraph
      node_feats: torch.Tensor
          The shape of node_feats is :math:`(N, D_{in})`.
      edge_weight: torch.Tensor
          The shape of edge_weight is :math:`(N_E, 1)`. N_E is the number of edges in graph.
      Returns
      -------
      torch.Tensor
          The output feature of shape :math:`(N, D_{out})` where
          :math:`D_{out}` is size of output feature.
      """
      feat_in, feat_out = node_feats  # feat_in == feat_out
  
      # forward aggregation
      graph_in = graph
      graph_in = graph_in.local_var()
      graph_in.ndata["h"] = feat_in
      for i in range(self._n_etypes):
          eids = (etypes == i).nonzero(as_tuple=False).view(-1)
          if len(eids) > 0:
              if edge_weight is None:
                  graph_in.apply_edges(
                      lambda edges: {
                          "W_e*h": self.linears_in[i](edges.src["h"])
                      },
                      eids,
                  )
              else:
                  assert isinstance(edge_weight, tuple)
                  graph_in.apply_edges(
                      lambda edges: {
                          "W_e*h": self.linears_in[i](edges.src["h"])
                          * edge_weight[0].view(-1).unsqueeze(1)
                      },
                      eids,
                  )
      graph_in.update_all(fn.copy_e("W_e*h", "m"), fn.sum("m", "a"))
      agg_in = graph_in.ndata.pop("a")  # (N, D)
  
      # backward aggregation
      graph_out = graph.reverse()
      graph_out = graph_out.local_var()
      graph_out.ndata["h"] = feat_out
      for i in range(self._n_etypes):
          eids = (etypes == i).nonzero(as_tuple=False).view(-1)
          if len(eids) > 0:
              if edge_weight is None:
                  graph_out.apply_edges(
                      lambda edges: {
                          "W_e*h": self.linears_out[i](edges.src["h"])
                      },
                      eids,
                  )
              else:
                  assert isinstance(edge_weight, tuple)
                  graph_out.apply_edges(
                      lambda edges: {
                          "W_e*h": self.linears_out[i](edges.src["h"])
                          * edge_weight[1].view(-1).unsqueeze(1)
                      },
                      eids,
                  )
      graph_out.update_all(fn.copy_e("W_e*h", "m"), fn.sum("m", "a"))
      agg_out = graph_out.ndata.pop("a")  # (N, D)
  
      # fuse
      fuse_vector = torch.cat(
          [agg_in, agg_out, agg_in * agg_out, agg_in - agg_out], dim=-1
      )
      fuse_gate_vector = torch.sigmoid(self.fuse_linear(fuse_vector))
      emb_fused = (
          fuse_gate_vector * agg_in + (1 - fuse_gate_vector) * agg_out
      )
  
      # update
      rst = self.gru(emb_fused, feat_in)
  
      return [rst, rst]
  
imports: import dgl
  import dgl.function
  import torch
  import torch.nn
  from torch.nn import init
  from ...data.data import GraphData
  from .base import GNNBase, GNNLayerBase
label_desc: Use image node_id 3 for calling the BiFuseGGNNLayerConv obj's underlying member method code with example usage: obj.forward(graph, node_feats, etypes, edge_weight) and returns: list