code: def add_reverse_edges(
      g,
      readonly=None,
      copy_ndata=True,
      copy_edata=False,
      ignore_bipartite=False,
      exclude_self=True,
  ):
      r"""Add a reversed edge for each edge in the input graph and return a new graph.
  
      For a graph with edges :math:`(i_1, j_1), \cdots, (i_n, j_n)`, this
      function creates a new graph with edges
      :math:`(i_1, j_1), \cdots, (i_n, j_n), (j_1, i_1), \cdots, (j_n, i_n)`.
  
      The returned graph may have duplicate edges. To create a bidirected graph without
      duplicate edges, use :func:`to_bidirected`.
  
      The operation only works for edges whose two endpoints belong to the same node type.
      DGL will raise error if the input graph is heterogeneous and contains edges
      with different types of endpoints. If :attr:`ignore_bipartite` is true, DGL will
      ignore those edges instead.
  
      Parameters
      ----------
      g : DGLGraph
          The input graph.
      readonly : bool, default to be True
          Deprecated. There will be no difference between readonly and non-readonly
      copy_ndata: bool, optional
          If True, the node features of the new graph are copied from
          the original graph. If False, the new graph will not have any
          node features.
  
          (Default: True)
      copy_edata: bool, optional
          If True, the features of the reversed edges will be identical to
          the original ones.
  
          If False, the new graph will not have any edge features.
  
          (Default: False)
      ignore_bipartite: bool, optional
          If True, unidirectional bipartite graphs are ignored and
          no error is raised. If False, an error will be raised if
          an edge type of the input heterogeneous graph is for a unidirectional
          bipartite graph.
      exclude_self: bool, optional
          If True, it does not add reverse edges for self-loops, which is likely
          meaningless in most cases.
  
      Returns
      -------
      DGLGraph
          The graph with reversed edges added.
  
      Notes
      -----
      If :attr:`copy_ndata` is True, the resulting graph will share the node feature
      tensors with the input graph. Hence, users should try to avoid in-place operations
      which will be visible to both graphs. On the contrary, the two graphs do not share
      the same edge feature storage.
  
      This function discards the batch information. Please use
      :func:`dgl.DGLGraph.set_batch_num_nodes`
      and :func:`dgl.DGLGraph.set_batch_num_edges` on the transformed graph
      to maintain the information.
  
      Examples
      --------
      **Homogeneous graphs**
  
      >>> g = dgl.graph((th.tensor([0, 0]), th.tensor([0, 1])))
      >>> bg1 = dgl.add_reverse_edges(g)
      >>> bg1.edges()
      (tensor([0, 0, 0, 1]), tensor([0, 1, 0, 0]))
  
      **Heterogeneous graphs**
  
      >>> g = dgl.heterograph({
      >>>     ('user', 'wins', 'user'): (th.tensor([0, 2, 0, 2, 2]), th.tensor([1, 1, 2, 1, 0])),
      >>>     ('user', 'plays', 'game'): (th.tensor([1, 2, 1]), th.tensor([2, 1, 1])),
      >>>     ('user', 'follows', 'user'): (th.tensor([1, 2, 1), th.tensor([0, 0, 0]))
      >>> })
      >>> g.nodes['game'].data['hv'] = th.ones(3, 1)
      >>> g.edges['wins'].data['h'] = th.tensor([0, 1, 2, 3, 4])
  
      The :func:`add_reverse_edges` operation is applied to the edge type
      ``('user', 'wins', 'user')`` and the edge type ``('user', 'follows', 'user')``.
      The edge type ``('user', 'plays', 'game')`` is ignored.  Both the node features and
      edge features are shared.
  
      >>> bg = dgl.add_reverse_edges(g, copy_ndata=True,
                                 copy_edata=True, ignore_bipartite=True)
      >>> bg.edges(('user', 'wins', 'user'))
      (tensor([0, 2, 0, 2, 2, 1, 1, 2, 1, 0]), tensor([1, 1, 2, 1, 0, 0, 2, 0, 2, 2]))
      >>> bg.edges(('user', 'follows', 'user'))
      (tensor([1, 2, 1, 0, 0, 0]), tensor([0, 0, 0, 1, 2, 1]))
      >>> bg.edges(('user', 'plays', 'game'))
      (th.tensor([1, 2, 1]), th.tensor([2, 1, 1]))
      >>> bg.nodes['game'].data['hv']
      tensor([0, 0, 0])
      >>> bg.edges[('user', 'wins', 'user')].data['h']
      th.tensor([0, 1, 2, 3, 4, 0, 1, 2, 3, 4])
      """
      if readonly is not None:
          dgl_warning(
              "Parameter readonly is deprecated"
              "There will be no difference between readonly and non-readonly DGLGraph"
          )
  
      # get node cnt for each ntype
      num_nodes_dict = {}
      for ntype in g.ntypes:
          num_nodes_dict[ntype] = g.num_nodes(ntype)
  
      canonical_etypes = g.canonical_etypes
      num_nodes_dict = {ntype: g.num_nodes(ntype) for ntype in g.ntypes}
      subgs = {}
      rev_eids = {}
  
      def add_for_etype(etype):
          u, v = g.edges(form="uv", order="eid", etype=etype)
          rev_u, rev_v = v, u
          eid = F.copy_to(F.arange(0, g.num_edges(etype)), g.device)
          if exclude_self:
              self_loop_mask = F.equal(rev_u, rev_v)
              non_self_loop_mask = F.logical_not(self_loop_mask)
              rev_u = F.boolean_mask(rev_u, non_self_loop_mask)
              rev_v = F.boolean_mask(rev_v, non_self_loop_mask)
              non_self_loop_eid = F.boolean_mask(
                  eid, non_self_loop_mask
              )
              rev_eids[etype] = F.cat([eid, non_self_loop_eid], 0)
          else:
              rev_eids[etype] = F.cat([eid, eid], 0)
          subgs[etype] = (
              F.cat([u, rev_u], dim=0),
              F.cat([v, rev_v], dim=0),
          )
  
      # fast path
      if ignore_bipartite is False:
          for c_etype in canonical_etypes:
              if c_etype[0] != c_etype[2]:
                  assert False, (
                      "add_reverse_edges is not well defined for "
                      "unidirectional bipartite graphs"
                      ", but {} is unidirectional bipartite".format(
                          c_etype
                      )
                  )
              add_for_etype(c_etype)
  
          new_g = convert.heterograph(
              subgs, num_nodes_dict=num_nodes_dict
          )
      else:
          for c_etype in canonical_etypes:
              if c_etype[0] != c_etype[2]:
                  u, v = g.edges(form="uv", order="eid", etype=c_etype)
                  subgs[c_etype] = (u, v)
              else:
                  add_for_etype(c_etype)
  
          new_g = convert.heterograph(
              subgs, num_nodes_dict=num_nodes_dict
          )
  
      # handle features
      if copy_ndata:
          node_frames = utils.extract_node_subframes(g, None)
          utils.set_new_frames(new_g, node_frames=node_frames)
  
      if copy_edata:
          # find indices
          eids = []
          for c_etype in canonical_etypes:
              if c_etype[0] != c_etype[2]:
                  eids.append(
                      F.copy_to(
                          F.arange(0, g.num_edges(c_etype)),
                          new_g.device,
                      )
                  )
              else:
                  eids.append(rev_eids[c_etype])
  
          edge_frames = utils.extract_edge_subframes(g, eids)
          utils.set_new_frames(new_g, edge_frames=edge_frames)
  
      return new_g
  
imports: import copy
  from collections.abc import Iterable, Mapping
  import numpy
  import scipy.sparse
  import scipy.sparse.linalg
  from ..utils import version
  from ..None import backend, batch, convert, function, ndarray, subgraph, utils
  from .._ffi.function import _init_api
  from ..base import dgl_warning, DGLError, EID, NID
  from ..frame import Frame
  from ..heterograph import DGLGraph
  from ..heterograph_index import create_heterograph_from_relations, create_metagraph_index
  from ..partition import metis_partition, metis_partition_assignment, partition_graph_with_halo
  from ..sampling.neighbor import sample_neighbors
label_desc: Use image node_id 9 for calling a global function with example usage: add_reverse_edges(g, readonly, copy_ndata, copy_edata, ignore_bipartite, exclude_) and returns: new_g