class_docstr: A plain python object modeling a heterogeneous graph with various
  attributes (Node types in :class:`HeteroGraph` require string type).
  
  Args:
      G (:class:`networkx.classes.graph`): A NetworkX graph object which
          contains features and labels for each node type and edge type.
      **kwargs: Keyworded argument list with keys such
          as :obj:`"node_feature"`, :obj:`"node_label"` and
          corresponding attributes.
class_docstr_tok: ["A","plain","python","object","modeling","a","heterogeneous","graph","with","various","attributes","(","Node","types","in",":","class",":","`","HeteroGraph","`","require","string","type",")",".","Args",":","G","(",":","class",":","`","networkx.classes.graph","`",")",":","A","NetworkX","graph","object","which","contains","features","and","labels","for","each","node","type","and","edge","type",".","*","*","kwargs",":","Keyworded","argument","list","with","keys","such","as",":","obj",":","`","``","node_feature","''","`",",",":","obj",":","`","``","node_label","''","`","and","corresponding","attributes","."]
docstr: Samples random negative edges for a heterogeneous graph given
  by :attr:`edge_index`.
  
  Args:
      edge_index (LongTensor): The indices for edges.
      num_nodes (dict, optional): A dictionary of number of nodes.
      num_neg_samples (dict, optional): The number of negative samples to
          return. If set to :obj:`None`, will try to return a negative
          edge for every positive edge.
  
  Returns:
      :class:`torch.LongTensor`: The :attr:`edge_index` tensor 
      for negative edges.
docstr_tok: ["Samples","random","negative","edges","for","a","heterogeneous","graph","given","by",":","attr",":","`","edge_index","`",".","Args",":","edge_index","(","LongTensor",")",":","The","indices","for","edges",".","num_nodes","(","dict",",","optional",")",":","A","dictionary","of","number","of","nodes",".","num_neg_samples","(","dict",",","optional",")",":","The","number","of","negative","samples","to","return",".","If","set","to",":","obj",":","`","None","`",",","will","try","to","return","a","negative","edge","for","every","positive","edge",".","Returns",":",":","class",":","`","torch.LongTensor","`",":","The",":","attr",":","`","edge_index","`","tensor","for","negative","edges","."]
code: def negative_sampling(
      edge_index: Dict[str, torch.tensor],
      num_nodes: Dict[str, int] = None,
      num_neg_samples: Dict[str, int] = None,
  ):
      r"""
      Samples random negative edges for a heterogeneous graph given
      by :attr:`edge_index`.
  
      Args:
          edge_index (LongTensor): The indices for edges.
          num_nodes (dict, optional): A dictionary of number of nodes.
          num_neg_samples (dict, optional): The number of negative samples to
              return. If set to :obj:`None`, will try to return a negative
              edge for every positive edge.
  
      Returns:
          :class:`torch.LongTensor`: The :attr:`edge_index` tensor
          for negative edges.
  
      """
      num_neg_samples_available = {}
      for message_type in edge_index:
          head_type = message_type[0]
          tail_type = message_type[2]
          num_neg_samples_available[message_type] = min(
              num_neg_samples[message_type],
              num_nodes[head_type] * num_nodes[tail_type]
              - edge_index[message_type].shape[1],
          )
          if num_neg_samples_available[message_type] == 0:
              raise ValueError(
                  "No negative samples could be generated for a "
                  f"complete graph in message_type: {message_type}."
              )
  
      rng = {}
      for message_type in edge_index:
          head_type = message_type[0]
          tail_type = message_type[2]
          rng[message_type] = range(
              num_nodes[head_type] * num_nodes[tail_type]
          )
  
      idx = {}
      for message_type in edge_index:
          head_type = message_type[0]
          tail_type = message_type[2]
          if num_nodes[head_type] >= num_nodes[tail_type]:
              idx[message_type] = (
                  edge_index[message_type][0] * num_nodes[tail_type]
                  + edge_index[message_type][1]
              )
          else:
              idx[message_type] = (
                  edge_index[message_type][1] * num_nodes[head_type]
                  + edge_index[message_type][0]
              )
  
      perm = {}
      for message_type in edge_index:
          samples = random.sample(
              rng[message_type], num_neg_samples_available[message_type]
          )
          perm[message_type] = torch.tensor(samples)
  
      mask = {
          message_type: torch.from_numpy(
              np.isin(perm[message_type], idx[message_type])
          ).to(torch.bool)
          for message_type in edge_index
      }
      rest = {
          message_type: torch.nonzero(mask[message_type]).view(-1)
          for message_type in edge_index
      }
  
      for message_type in edge_index:
          while rest[message_type].numel() > 0:
              tmp = torch.tensor(
                  random.sample(
                      rng[message_type], rest[message_type].size(0)
                  )
              )
              mask = torch.from_numpy(
                  np.isin(tmp, idx[message_type])
              ).to(torch.bool)
              perm[message_type][rest[message_type]] = tmp
              rest[message_type] = rest[message_type][
                  torch.nonzero(mask).view(-1)
              ]
  
      row, col = {}, {}
      for message_type in perm:
          head_type = message_type[0]
          tail_type = message_type[2]
  
          if num_nodes[head_type] >= num_nodes[tail_type]:
              row[message_type] = (
                  perm[message_type] // num_nodes[tail_type]
              )
              col[message_type] = (
                  perm[message_type] % num_nodes[tail_type]
              )
          else:
              row[message_type] = (
                  perm[message_type] % num_nodes[head_type]
              )
              col[message_type] = (
                  perm[message_type] // num_nodes[head_type]
              )
  
      neg_edge_index = {
          message_type: torch.stack(
              [row[message_type], col[message_type]], dim=0
          ).long()
          for message_type in edge_index
      }
      for message_type in edge_index:
          if (
              num_neg_samples_available[message_type]
              < num_neg_samples[message_type]
          ):
              multiplicity = math.ceil(
                  num_neg_samples[message_type]
                  / num_neg_samples_available[message_type]
              )
              neg_edge_index[message_type] = torch.cat(
                  [neg_edge_index[message_type]] * multiplicity, dim=1
              )
              neg_edge_index[message_type] = neg_edge_index[
                  message_type
              ][:, : num_neg_samples[message_type]]
  
      for message_type in edge_index:
          neg_edge_index[message_type].to(
              neg_edge_index[message_type].device
          )
      return neg_edge_index
  
imports: import math
  import copy
  import random
  import torch
  import networkx
  import numpy
  from deepsnap.graph import Graph
  from typing import Dict, List, Union
  import warnings
label_desc: Use image node_id 39 for calling the HeteroGraph obj's underlying member method code with example usage: obj.negative_sampling(edge_index, num_nodes, num_neg_samples) and returns: neg_edge_index