class_docstr: Apply `Bidirectional Separate GNN mechanism <https://arxiv.org/abs/1808.07624>`__
  to `Graph Attention Network <https://arxiv.org/abs/1710.10903>`__ over an
  input signal. Compute node embeddings for incoming and outgoing directions
  separately, and then concatenate the two output node embeddings
  after the final layer.
  
  .. math::
      h_i^{(l+1)} = \sum_{j\in \mathcal{N}(i)} \alpha_{i,j} W^{(l)} h_j^{(l)}
  where :math:`\alpha_{ij}` is the attention score bewteen node :math:`i` and
  node :math:`j`:
  
  .. math::
      \alpha_{ij}^{l} & = \mathrm{softmax_i} (e_{ij}^{l})
      e_{ij}^{l} & = \mathrm{LeakyReLU}\left(\vec{a}^T [W h_{i} \| W h_{j}]\right)
  
  Parameters
  ----------
  input_size : int, or pair of ints
      Input feature size.
      If the layer is to be applied to a unidirectional bipartite graph, ``input_size``
      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.
  output_size : int
      Output feature size.
  num_heads : int
      Number of heads in Multi-Head Attention.
  feat_drop : float, optional
      Dropout rate on feature, default: ``0``.
  attn_drop : float, optional
      Dropout rate on attention weight, default: ``0``.
  negative_slope : float, optional
      LeakyReLU angle of negative slope, default: ``0.2``.
  residual : bool, optional
      If True, use residual connection.
      Default: ``False``.
  activation : callable activation function/layer or None, optional.
      If not None, applies an activation function to the updated node features.
      Default: ``None``.
  allow_zero_in_degree : bool, optional
      If there are 0-in-degree nodes in the graph, output for those nodes will be invalid
      since no message will be passed to those nodes. This is harmful for some applications
      causing silent performance regression. This module will raise a DGLError if it detects
      0-in-degree nodes in input graph. By setting ``True``, it will suppress the check
      and let the users handle it by themselves. Defaults: ``False``.
code: def __init__(
      self,
      input_size,
      output_size,
      num_heads,
      feat_drop=0.0,
      attn_drop=0.0,
      negative_slope=0.2,
      residual=False,
      activation=None,
      allow_zero_in_degree=False,
  ):
      super(BiSepGATLayerConv, self).__init__()
      self._num_heads = num_heads
      self._in_src_feats, self._in_dst_feats = expand_as_pair(
          input_size
      )
      self._out_feats = output_size
      self._allow_zero_in_degree = allow_zero_in_degree
      if isinstance(input_size, tuple):
          self.fc_src_fw = nn.Linear(
              self._in_src_feats, output_size * num_heads, bias=False
          )
          self.fc_dst_fw = nn.Linear(
              self._in_dst_feats, output_size * num_heads, bias=False
          )
  
          self.fc_src_bw = nn.Linear(
              self._in_src_feats, output_size * num_heads, bias=False
          )
          self.fc_dst_bw = nn.Linear(
              self._in_dst_feats, output_size * num_heads, bias=False
          )
      else:
          self.fc_fw = nn.Linear(
              self._in_src_feats, output_size * num_heads, bias=False
          )
          self.fc_bw = nn.Linear(
              self._in_src_feats, output_size * num_heads, bias=False
          )
  
      self.attn_l_fw = nn.Parameter(
          torch.FloatTensor(size=(1, num_heads, output_size))
      )
      self.attn_l_bw = nn.Parameter(
          torch.FloatTensor(size=(1, num_heads, output_size))
      )
      self.attn_r_fw = nn.Parameter(
          torch.FloatTensor(size=(1, num_heads, output_size))
      )
      self.attn_r_bw = nn.Parameter(
          torch.FloatTensor(size=(1, num_heads, output_size))
      )
      self.feat_drop = nn.Dropout(feat_drop)
      self.attn_drop = nn.Dropout(attn_drop)
      self.leaky_relu_fw = nn.LeakyReLU(negative_slope)
      self.leaky_relu_bw = nn.LeakyReLU(negative_slope)
      if residual:
          if self._in_dst_feats != output_size:
              self.res_fc_fw = nn.Linear(
                  self._in_dst_feats,
                  num_heads * output_size,
                  bias=False,
              )
              self.res_fc_bw = nn.Linear(
                  self._in_dst_feats,
                  num_heads * output_size,
                  bias=False,
              )
          else:
              self.res_fc_fw = self.res_fc_bw = Identity()
      else:
          self.register_buffer("res_fc_fw", None)
          self.register_buffer("res_fc_bw", None)
      self.reset_parameters()
      self.activation = activation
  
imports: import dgl.function
  import torch
  import torch.nn
  from dgl.base import DGLError
  from dgl.nn.pytorch import GATConv
  from dgl.nn.pytorch.softmax import edge_softmax
  from dgl.utils import expand_as_pair
  from ..utils.generic_utils import Identity
  from .base import GNNBase, GNNLayerBase
label_desc: Use image node_id 1 to create a new BiSepGATLayerConv object from inherited base classes: GNNLayerBase with example: obj = BiSepGATLayerConv(input_size, output_size, num_heads, feat_drop, attn_drop, negative_slope, residual, activation, allow_zero_in_degree)