code: def negative_sampling(
      edge_index: Tensor,
      num_nodes: Optional[Union[int, Tuple[int, int]]] = None,
      num_neg_samples: Optional[int] = None,
      method: str = "sparse",
      force_undirected: bool = False,
  ) -> Tensor:
      r"""Samples random negative edges of a graph given by :attr:`edge_index`.
  
      Args:
          edge_index (LongTensor): The edge indices.
          num_nodes (int or Tuple[int, int], optional): The number of nodes,
              *i.e.* :obj:`max_val + 1` of :attr:`edge_index`.
              If given as a tuple, then :obj:`edge_index` is interpreted as a
              bipartite graph with shape :obj:`(num_src_nodes, num_dst_nodes)`.
              (default: :obj:`None`)
          num_neg_samples (int, optional): The (approximate) number of negative
              samples to return.
              If set to :obj:`None`, will try to return a negative edge for every
              positive edge. (default: :obj:`None`)
          method (str, optional): The method to use for negative sampling,
              *i.e.* :obj:`"sparse"` or :obj:`"dense"`.
              This is a memory/runtime trade-off.
              :obj:`"sparse"` will work on any graph of any size, while
              :obj:`"dense"` can perform faster true-negative checks.
              (default: :obj:`"sparse"`)
          force_undirected (bool, optional): If set to :obj:`True`, sampled
              negative edges will be undirected. (default: :obj:`False`)
  
      :rtype: LongTensor
  
      Examples:
          >>> # Standard usage
          >>> edge_index = torch.as_tensor([[0, 0, 1, 2],
          ...                               [0, 1, 2, 3]])
          >>> negative_sampling(edge_index)
          tensor([[3, 0, 0, 3],
                  [2, 3, 2, 1]])
  
          >>> # For bipartite graph
          >>> negative_sampling(edge_index, num_nodes=(3, 4))
          tensor([[0, 2, 2, 1],
                  [2, 2, 1, 3]])
      """
      assert method in ["sparse", "dense"]
  
      if num_nodes is None:
          num_nodes = maybe_num_nodes(edge_index, num_nodes)
  
      if isinstance(num_nodes, int):
          size = (num_nodes, num_nodes)
          bipartite = False
      else:
          size = num_nodes
          bipartite = True
          force_undirected = False
  
      idx, population = edge_index_to_vector(
          edge_index, size, bipartite, force_undirected
      )
  
      if idx.numel() >= population:
          return edge_index.new_empty((2, 0))
  
      if num_neg_samples is None:
          num_neg_samples = edge_index.size(1)
      if force_undirected:
          num_neg_samples = num_neg_samples // 2
  
      prob = (
          1.0 - idx.numel() / population
      )  # Probability to sample a negative.
      sample_size = int(
          1.1 * num_neg_samples / prob
      )  # (Over)-sample size.
  
      neg_idx: Optional[Tensor] = None
      if method == "dense":
          # The dense version creates a mask of shape `population` to check for
          # invalid samples.
          mask = idx.new_ones(population, dtype=torch.bool)
          mask[idx] = False
          for _ in range(
              3
          ):  # Number of tries to sample negative indices.
              rnd = sample(population, sample_size, idx.device)
              rnd = rnd[mask[rnd]]  # Filter true negatives.
              neg_idx = (
                  rnd if neg_idx is None else torch.cat([neg_idx, rnd])
              )
              if neg_idx.numel() >= num_neg_samples:
                  neg_idx = neg_idx[:num_neg_samples]
                  break
              mask[neg_idx] = False
  
      else:  # 'sparse'
          # The sparse version checks for invalid samples via `np.isin`.
          idx = idx.to("cpu")
          for _ in range(
              3
          ):  # Number of tries to sample negative indices.
              rnd = sample(population, sample_size, device="cpu")
              mask = np.isin(rnd.numpy(), idx.numpy())  # type: ignore
              if neg_idx is not None:
                  mask |= np.isin(rnd, neg_idx.to("cpu"))
              mask = torch.from_numpy(mask).to(torch.bool)
              rnd = rnd[~mask].to(edge_index.device)
              neg_idx = (
                  rnd if neg_idx is None else torch.cat([neg_idx, rnd])
              )
              if neg_idx.numel() >= num_neg_samples:
                  neg_idx = neg_idx[:num_neg_samples]
                  break
  
      assert neg_idx is not None
      return vector_to_edge_index(
          neg_idx, size, bipartite, force_undirected
      )
  
file_path: pytorch_geometric/torch_geometric/utils/_negative_sampling.py
imports: import random
  from typing import Optional, Tuple, Union
  import numpy
  import torch
  from torch import Tensor
  from torch_geometric.utils import coalesce, cumsum, degree, remove_self_loops
  from torch_geometric.utils.num_nodes import maybe_num_nodes
label_desc: Use image node_id 1 for calling a global function with example usage: negative_sampling(edge_index, num_nodes, num_neg_samples, method, force_undirected) and returns: vector_to_edge_index, edge_index