code: def to_heterogeneous(
      G,
      ntypes,
      etypes,
      ntype_field=NTYPE,
      etype_field=ETYPE,
      metagraph=None,
  ):
      """Convert a homogeneous graph to a heterogeneous graph and return.
  
      The input graph should have only one type of nodes and edges. Each node and edge
      stores an integer feature as its type ID
      (specified by :attr:`ntype_field` and :attr:`etype_field`).
      DGL uses it to retrieve the type names stored in the given
      :attr:`ntypes` and :attr:`etypes` arguments.
  
      The function will automatically distinguish edge types that have the same given
      type IDs but different src and dst type IDs. For example, it allows both edges A and B
      to have the same type ID 0, but one has (0, 1) and the other as (2, 3) as the
      (src, dst) type IDs. In this case, the function will "split" edge type 0 into two types:
      (0, ty_A, 1) and (2, ty_B, 3). In another word, these two edges share the same edge
      type name, but can be distinguished by an edge type triplet.
  
      The function stores the node and edge IDs in the input graph using the ``dgl.NID``
      and ``dgl.EID`` names in the ``ndata`` and ``edata`` of the resulting graph.
      It also copies any node/edge features from :attr:`G` to the returned heterogeneous
      graph, except for reserved fields for storing type IDs (``dgl.NTYPE`` and ``dgl.ETYPE``)
      and node/edge IDs (``dgl.NID`` and ``dgl.EID``).
  
      Parameters
      ----------
      G : DGLGraph
          The homogeneous graph.
      ntypes : list[str]
          The node type names.
      etypes : list[str]
          The edge type names.
      ntype_field : str, optional
          The feature field used to store node type. (Default: ``dgl.NTYPE``)
      etype_field : str, optional
          The feature field used to store edge type. (Default: ``dgl.ETYPE``)
      metagraph : networkx MultiDiGraph, optional
          Metagraph of the returned heterograph.
          If provided, DGL assumes that G can indeed be described with the given metagraph.
          If None, DGL will infer the metagraph from the given inputs, which could be
          costly for large graphs.
  
      Returns
      -------
      DGLGraph
          A heterogeneous graph.
  
      Notes
      -----
      * The returned node and edge types may not necessarily be in the same order as
        ``ntypes`` and ``etypes``.
      * Calling :func:`~dgl.to_homogeneous` then calling :func:`~dgl.to_heterogeneous` again
        yields the same result.
  
      Examples
      --------
  
      The following example uses PyTorch backend.
  
      >>> import dgl
      >>> import torch
  
      >>> hg = dgl.heterograph({
      ...     ('user', 'develops', 'activity'): (torch.tensor([0, 1]), torch.tensor([1, 2])),
      ...     ('developer', 'develops', 'game'): (torch.tensor([0, 1]), torch.tensor([0, 1]))
      ... })
      >>> print(hg)
      Graph(num_nodes={'activity': 3, 'developer': 2, 'game': 2, 'user': 2},
            num_edges={('developer', 'develops', 'game'): 2, ('user', 'develops', 'activity'): 2},
            metagraph=[('developer', 'game', 'develops'), ('user', 'activity', 'develops')])
  
      We first convert the heterogeneous graph to a homogeneous graph.
  
      >>> g = dgl.to_homogeneous(hg)
      >>> print(g)
      Graph(num_nodes=9, num_edges=4,
            ndata_schemes={'_TYPE': Scheme(shape=(), dtype=torch.int64),
                           '_ID': Scheme(shape=(), dtype=torch.int64)}
            edata_schemes={'_TYPE': Scheme(shape=(), dtype=torch.int64),
                           '_ID': Scheme(shape=(), dtype=torch.int64)})
      >>> g.ndata
      {'_TYPE': tensor([0, 0, 0, 1, 1, 2, 2, 3, 3]), '_ID': tensor([0, 1, 2, 0, 1, 0, 1, 0, 1])}
      Nodes 0, 1, 2 for 'activity', 3, 4 for 'developer', 5, 6 for 'game', 7, 8 for 'user'
      >>> g.edata
      {'_TYPE': tensor([0, 0, 1, 1]), '_ID': tensor([0, 1, 0, 1])}
      Edges 0, 1 for ('developer', 'develops', 'game'), 2, 3 for ('user', 'develops', 'activity')
  
      Now convert the homogeneous graph back to a heterogeneous graph.
  
      >>> hg_2 = dgl.to_heterogeneous(g, hg.ntypes, hg.etypes)
      >>> print(hg_2)
      Graph(num_nodes={'activity': 3, 'developer': 2, 'game': 2, 'user': 2},
            num_edges={('developer', 'develops', 'game'): 2, ('user', 'develops', 'activity'): 2},
            metagraph=[('developer', 'game', 'develops'), ('user', 'activity', 'develops')])
  
      Retrieve the original node/edge IDs.
  
      >>> hg_2.ndata[dgl.NID]
      {'activity': tensor([0, 1, 2]),
       'developer': tensor([3, 4]),
       'game': tensor([5, 6]),
       'user': tensor([7, 8])}
      >>> hg_2.edata[dgl.EID]
      {('developer', 'develops', 'game'): tensor([0, 1]),
       ('user', 'develops', 'activity'): tensor([2, 3])}
  
      See Also
      --------
      to_homogeneous
      """
      if (
          hasattr(G, "ntypes")
          and len(G.ntypes) > 1
          or hasattr(G, "etypes")
          and len(G.etypes) > 1
      ):
          raise DGLError(
              "The input graph should be homogeneous and have only one "
              " type of nodes and edges."
          )
  
      num_ntypes = len(ntypes)
      idtype = G.idtype
      device = G.device
  
      ntype_ids = F.asnumpy(G.ndata[ntype_field])
      etype_ids = F.asnumpy(G.edata[etype_field])
  
      # relabel nodes to per-type local IDs
      ntype_count = np.bincount(ntype_ids, minlength=num_ntypes)
      ntype_offset = np.insert(np.cumsum(ntype_count), 0, 0)
      ntype_ids_sortidx = np.argsort(ntype_ids, kind="stable")
      ntype_local_ids = np.zeros_like(ntype_ids)
      node_groups = []
      for i in range(num_ntypes):
          node_group = ntype_ids_sortidx[
              ntype_offset[i] : ntype_offset[i + 1]
          ]
          node_groups.append(node_group)
          ntype_local_ids[node_group] = np.arange(ntype_count[i])
  
      src, dst = G.all_edges(order="eid")
      src = F.asnumpy(src)
      dst = F.asnumpy(dst)
      src_local = ntype_local_ids[src]
      dst_local = ntype_local_ids[dst]
      # a 2D tensor of shape (E, 3). Each row represents the (stid, etid, dtid) tuple.
      edge_ctids = np.stack(
          [ntype_ids[src], etype_ids, ntype_ids[dst]], 1
      )
  
      # infer metagraph and canonical edge types
      # No matter which branch it takes, the code will generate a 2D tensor of shape (E_m, 3),
      # E_m is the set of all possible canonical edge tuples. Each row represents the
      # (stid, dtid, dtid) tuple. We then compute a 2D tensor of shape (E, E_m) using the
      # above ``edge_ctids`` matrix. Each element i,j indicates whether the edge i is of the
      # canonical edge type j. We can then group the edges of the same type together.
      if metagraph is None:
          canonical_etids, _, etype_remapped = utils.make_invmap(
              list(tuple(_) for _ in edge_ctids), False
          )
          etype_mask = (
              etype_remapped[None, :]
              == np.arange(len(canonical_etids))[:, None]
          )
      else:
          ntypes_invmap = {nt: i for i, nt in enumerate(ntypes)}
          etypes_invmap = {et: i for i, et in enumerate(etypes)}
          canonical_etids = []
          for i, (srctype, dsttype, etype) in enumerate(
              metagraph.edges(keys=True)
          ):
              srctype_id = ntypes_invmap[srctype]
              etype_id = etypes_invmap[etype]
              dsttype_id = ntypes_invmap[dsttype]
              canonical_etids.append((srctype_id, etype_id, dsttype_id))
          canonical_etids = np.asarray(canonical_etids)
          etype_mask = (
              edge_ctids[None, :] == canonical_etids[:, None]
          ).all(2)
      edge_groups = [
          etype_mask[i].nonzero()[0]
          for i in range(len(canonical_etids))
      ]
  
      data_dict = dict()
      canonical_etypes = []
      for i, (stid, etid, dtid) in enumerate(canonical_etids):
          src_of_etype = src_local[edge_groups[i]]
          dst_of_etype = dst_local[edge_groups[i]]
          canonical_etypes.append(
              (ntypes[stid], etypes[etid], ntypes[dtid])
          )
          data_dict[canonical_etypes[-1]] = (src_of_etype, dst_of_etype)
      hg = heterograph(
          data_dict,
          dict(zip(ntypes, ntype_count)),
          idtype=idtype,
          device=device,
      )
  
      ntype2ngrp = {
          ntype: node_groups[ntid] for ntid, ntype in enumerate(ntypes)
      }
  
      # features
      for key, data in G.ndata.items():
          if key in [ntype_field, NID]:
              continue
          for ntid, ntype in enumerate(hg.ntypes):
              rows = F.copy_to(
                  F.tensor(ntype2ngrp[ntype]), F.context(data)
              )
              hg._node_frames[ntid][key] = F.gather_row(data, rows)
  
      for key, data in G.edata.items():
          if key in [etype_field, EID]:
              continue
          for etid in range(len(hg.canonical_etypes)):
              rows = F.copy_to(
                  F.tensor(edge_groups[etid]), F.context(data)
              )
              hg._edge_frames[hg.get_etype_id(canonical_etypes[etid])][
                  key
              ] = F.gather_row(data, rows)
  
      # Record the original IDs of the nodes/edges
      for ntid, ntype in enumerate(hg.ntypes):
          hg._node_frames[ntid][NID] = F.copy_to(
              F.tensor(ntype2ngrp[ntype]), device
          )
      for etid in range(len(hg.canonical_etypes)):
          hg._edge_frames[hg.get_etype_id(canonical_etypes[etid])][
              EID
          ] = F.copy_to(F.tensor(edge_groups[etid]), device)
  
      return hg
  
imports: from collections import defaultdict
  from collections.abc import Mapping
  import networkx
  import numpy
  from scipy.sparse import spmatrix
  from .None import backend, graph_index, heterograph_index, utils
  from .base import DGLError, EID, ETYPE, NID, NTYPE
  from .heterograph import combine_frames, DGLBlock, DGLGraph
label_desc: Use image node_id 6 for calling a global function with example usage: to_heterogeneous(G, ntypes, etypes, ntype_field, etype_field, metagraph) and returns: hg