code: def get_in_nodes(node_list, target_ops, input_names):
      """Create a dictionary mapping from op_name nodes or multi-input
      nodes to closest input ancestors.
  
      Parameters
      ----------
      node_list : list of dict of str to object
          List of all nodes in a graph.
  
      target_ops: List of str
          List of target relay op
  
      input_names : list of str
          Names of graph input nodes.
  
      Returns
      -------
      out : dict of int to list of int
          Dictionary maps node index to closest input ancestors.
      """
  
      visited_dict = {}
      in_node_dict = {}
      for i, node in enumerate(node_list):
          if is_boundary_node(node, input_names) or is_skipped_node(
              node
          ):
              continue
          get_direct_ancestor(
              node_list, visited_dict, target_ops, i, input_names
          )
      for key, val in visited_dict.items():
          node = node_list[key]
          is_multiple_inputs = has_multiple_inputs(
              node_list, key, input_names, OPT_OUT_OP
          )
          if node["op"] in target_ops or is_multiple_inputs:
              in_node_dict[key] = val
  
      # Reduce boundary nodes
      out_node_dict = get_out_nodes(in_node_dict)
      has_reduced_node = True
      while has_reduced_node:
          boundary_nodes = []
          for key, val in in_node_dict.items():
              node = node_list[key]
              is_boundary = True
              # Target ops can't be boundary nodes
              if node["op"] not in target_ops:
                  for input_idx in val:
                      in_node = node_list[input_idx]
                      if (
                          not is_boundary_node(in_node, input_names)
                          and input_idx in in_node_dict
                      ):
                          is_boundary = False
                      else:
                          val.remove(input_idx)
                      if is_boundary:
                          boundary_nodes.append(key)
          if boundary_nodes:
              for idx in boundary_nodes:
                  if idx in in_node_dict:
                      del in_node_dict[idx]
          else:
              has_reduced_node = False
  
      # Remove empty nodes to ignore pre-computed sub-graph
      has_empty_node = True
      while has_empty_node:
          empty_nodes = []
          for key, val in in_node_dict.items():
              if not val:
                  empty_nodes.append(key)
          if empty_nodes:
              has_empty_node = True
              for node in empty_nodes:
                  del in_node_dict[node]
                  if node in out_node_dict:
                      for out_node in out_node_dict[node]:
                          in_node_dict[out_node].remove(node)
          else:
              has_empty_node = False
  
      return in_node_dict
  
file_path: tvm/python/tvm/autotvm/graph_tuner/utils/traverse_graph.py
imports: import threading
  import re
  import tvm
  from tvm import relay, autotvm
  from tvm.relay import transform
  from tvm.relay.expr import Call, TupleGetItem, Var, Constant, Tuple
  from tvm.relay.function import Function
  from tvm.relay.ty import TupleType, TensorType
  from tvm.autotvm.task import TaskExtractEnv
  from .utils import has_multiple_inputs, is_boundary_node, is_skipped_node
  from .._base import OPT_OUT_OP
label_desc: Use image node_id 6 for calling a global function with example usage: get_in_nodes(node_list, target_ops, input_names) and returns: in_node_dict