class_docstr: The PPFNet operator from the `"PPFNet: Global Context Aware Local
  Features for Robust 3D Point Matching" <https://arxiv.org/abs/1802.02669>`_
  paper.
  
  .. math::
      \mathbf{x}^{\prime}_i = \gamma_{\mathbf{\Theta}} \left( \max_{j \in
      \mathcal{N}(i) \cup \{ i \}} h_{\mathbf{\Theta}} ( \mathbf{x}_j, \|
      \mathbf{d_{j,i}} \|, \angle(\mathbf{n}_i, \mathbf{d_{j,i}}),
      \angle(\mathbf{n}_j, \mathbf{d_{j,i}}), \angle(\mathbf{n}_i,
      \mathbf{n}_j) \right)
  
  where :math:`\gamma_{\mathbf{\Theta}}` and :math:`h_{\mathbf{\Theta}}`
  denote neural networks, *.i.e.* MLPs, which takes in node features and
  :class:`torch_geometric.transforms.PointPairFeatures`.
  
  Args:
      local_nn (torch.nn.Module, optional): A neural network
          :math:`h_{\mathbf{\Theta}}` that maps node features :obj:`x` and
          relative spatial coordinates :obj:`pos_j - pos_i` of shape
          :obj:`[-1, in_channels + num_dimensions]` to shape
          :obj:`[-1, out_channels]`, *e.g.*, defined by
          :class:`torch.nn.Sequential`. (default: :obj:`None`)
      global_nn (torch.nn.Module, optional): A neural network
          :math:`\gamma_{\mathbf{\Theta}}` that maps aggregated node features
          of shape :obj:`[-1, out_channels]` to shape :obj:`[-1,
          final_out_channels]`, *e.g.*, defined by
          :class:`torch.nn.Sequential`. (default: :obj:`None`)
      add_self_loops (bool, optional): If set to :obj:`False`, will not add
          self-loops to the input graph. (default: :obj:`True`)
      **kwargs (optional): Additional arguments of
          :class:`torch_geometric.nn.conv.MessagePassing`.
  
  Shapes:
      - **input:**
        node features :math:`(|\mathcal{V}|, F_{in})` or
        :math:`((|\mathcal{V_s}|, F_{s}), (|\mathcal{V_t}|, F_{t}))`
        if bipartite,
        positions :math:`(|\mathcal{V}|, 3)` or
        :math:`((|\mathcal{V_s}|, 3), (|\mathcal{V_t}|, 3))` if bipartite,
        point normals :math:`(|\mathcal{V}, 3)` or
        :math:`((|\mathcal{V_s}|, 3), (|\mathcal{V_t}|, 3))` if bipartite,
        edge indices :math:`(2, |\mathcal{E}|)`
      - **output:** node features :math:`(|\mathcal{V}|, F_{out})` or
        :math:`(|\mathcal{V}_t|, F_{out})` if bipartite
code: def forward(
      self,
      x: Union[OptTensor, PairOptTensor],
      pos: Union[Tensor, PairTensor],
      normal: Union[Tensor, PairTensor],
      edge_index: Adj,
  ) -> Tensor:
      if not isinstance(x, tuple):
          x = (x, None)
  
      if isinstance(pos, Tensor):
          pos = (pos, pos)
  
      if isinstance(normal, Tensor):
          normal = (normal, normal)
  
      if self.add_self_loops:
          if isinstance(edge_index, Tensor):
              edge_index, _ = remove_self_loops(edge_index)
              edge_index, _ = add_self_loops(
                  edge_index, num_nodes=pos[1].size(0)
              )
          elif isinstance(edge_index, SparseTensor):
              edge_index = torch_sparse.set_diag(edge_index)
  
      # propagate_type: (x: PairOptTensor, pos: PairTensor,
      #                  normal: PairTensor)
      out = self.propagate(edge_index, x=x, pos=pos, normal=normal)
  
      if self.global_nn is not None:
          out = self.global_nn(out)
  
      return out
  
file_path: pytorch_geometric/torch_geometric/nn/conv/ppf_conv.py
imports: from typing import Callable, Optional, Union
  import torch
  from torch import Tensor
  from torch_geometric.nn.conv import MessagePassing
  from torch_geometric.nn.inits import reset
  from torch_geometric.typing import Adj, OptTensor, PairOptTensor, PairTensor, SparseTensor, torch_sparse
  from torch_geometric.utils import add_self_loops, remove_self_loops
label_desc: Use image node_id 3 for calling the PPFConv obj's underlying member method code with example usage: obj.forward(x, pos, normal, edge_index) and returns: out