code: def sample_etype_neighbors(
      g,
      nodes,
      etype_offset,
      fanout,
      edge_dir="in",
      prob=None,
      replace=False,
      copy_ndata=True,
      copy_edata=True,
      etype_sorted=False,
      _dist_training=False,
      output_device=None,
  ):
      """Sample neighboring edges of the given nodes and return the induced subgraph.
  
      For each node, a number of inbound (or outbound when ``edge_dir == 'out'``) edges
      will be randomly chosen.  The graph returned will then contain all the nodes in the
      original graph, but only the sampled edges.
  
      Node/edge features are not preserved. The original IDs of
      the sampled edges are stored as the `dgl.EID` feature in the returned graph.
  
      Parameters
      ----------
      g : DGLGraph
          The graph.  Can only be in CPU. Should only have one node type and one edge type.
      nodes : tensor or dict
          Node IDs to sample neighbors from.
  
          This argument can take a single ID tensor or a dictionary of node types and ID tensors.
          If a single tensor is given, the graph must only have one type of nodes.
      etype_offset : list[int]
          The offset of each edge type ID.
      fanout : Tensor
          The number of edges to be sampled for each node per edge type.  Must be a
          1D tensor with the number of elements same as the number of edge types.
  
          If -1 is given, all of the neighbors with non-zero probability will be selected.
      edge_dir : str, optional
          Determines whether to sample inbound or outbound edges.
  
          Can take either ``in`` for inbound edges or ``out`` for outbound edges.
      prob : list[Tensor], optional
          The (unnormalized) probabilities associated with each neighboring edge of
          a node.
  
          The features must be non-negative floats or boolean.  Otherwise, the
          result will be undefined.
      replace : bool, optional
          If True, sample with replacement.
      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 edge features of the new graph are copied from
          the original graph.  If False, the new graph will not have any
          edge features.
  
          (Default: True)
      _dist_training : bool, optional
          Internal argument.  Do not use.
  
          (Default: False)
      etype_sorted: bool, optional
          A hint telling whether the etypes are already sorted.
  
          (Default: False)
      output_device : Framework-specific device context object, optional
          The output device.  Default is the same as the input graph.
  
      Returns
      -------
      DGLGraph
          A sampled subgraph containing only the sampled neighboring edges, with the
          same device as the input graph.
  
      Notes
      -----
      If :attr:`copy_ndata` or :attr:`copy_edata` is True, same tensors are used as
      the node or edge features of the original graph and the new graph.
      As a result, users should avoid performing in-place operations
      on the node features of the new graph to avoid feature corruption.
      """
      if g.device != F.cpu():
          raise DGLError("The graph should be in cpu.")
      # (BarclayII) because the homogenized graph no longer contains the *name* of edge
      # types, the fanout argument can no longer be a dict of etypes and ints, as opposed
      # to sample_neighbors.
      if not F.is_tensor(fanout):
          raise DGLError("The fanout should be a tensor")
      if isinstance(nodes, dict):
          assert (
              len(nodes) == 1
          ), "The input graph should not have node types"
          nodes = list(nodes.values())[0]
  
      nodes = utils.prepare_tensor(g, nodes, "nodes")
      device = utils.context_of(nodes)
      nodes = F.to_dgl_nd(nodes)
      # treat etypes as int32, it is much cheaper than int64
      # TODO(xiangsx): int8 can be a better choice.
      fanout = F.to_dgl_nd(fanout)
  
      prob_array = _prepare_edge_arrays(g, prob)
  
      subgidx = _CAPI_DGLSampleNeighborsEType(
          g._graph,
          nodes,
          etype_offset,
          fanout,
          edge_dir,
          prob_array,
          replace,
          etype_sorted,
      )
      induced_edges = subgidx.induced_edges
      ret = DGLGraph(subgidx.graph, g.ntypes, g.etypes)
  
      # handle features
      # (TODO) (BarclayII) DGL distributed fails with bus error, freezes, or other
      # incomprehensible errors with lazy feature copy.
      # So in distributed training context, we fall back to old behavior where we
      # only set the edge IDs.
      if not _dist_training:
          if copy_ndata:
              node_frames = utils.extract_node_subframes(g, device)
              utils.set_new_frames(ret, node_frames=node_frames)
  
          if copy_edata:
              edge_frames = utils.extract_edge_subframes(
                  g, induced_edges
              )
              utils.set_new_frames(ret, edge_frames=edge_frames)
      else:
          for i, etype in enumerate(ret.canonical_etypes):
              ret.edges[etype].data[EID] = induced_edges[i]
  
      return ret if output_device is None else ret.to(output_device)
  
file_path: dgl/python/dgl/sampling/neighbor.py
imports: import os
  import torch
  from ..None import backend, ndarray, utils
  from .._ffi.function import _init_api
  from ..base import DGLError, EID
  from ..heterograph import DGLBlock, DGLGraph
  from .utils import EidExcluder
label_desc: Use image node_id 2 for calling a global function with example usage: sample_etype_neighbors(g, nodes, etype_offset, fanout, edge_dir, prob, replace, copy_ndata, copy_edata, etype_sorted, _dist_training, output_device) and returns: unknown