class_docstr: If there are multiple ops with "identical" inputs, then they are redundant and all but one of them can be removed.
  This pass checks and removes such ops.
  
  Since all inputs to ops in MIL are named, two ops with same ``op_types`` can be compared by comparing their
  correspondingly named inputs. Inputs are treated as identical if one of the following is true:
  
  - The input is a constant var, in which case its value should have the same dtype and numerical value.
  - The input is a non constant var, in which case it should be the same var object.
  
  This pass iterates over the ops, takes its first output var, and then builds a candidate op list from the child
  ops of this var.
  This candidate ops list contains ops of the same ``op_type``, arranged in topological order.
  From each of these candidate ops in the list, the second, third, and subsequent ops are pairwise compared with the first op,
  and if identical to it, they are removed. For example:
  
  .. code-block::
  
      Input:
          %0 = op0(...)
          %1 = op1(...)
          %2 = const(val=4.5)
          %3 = const(val=4.5)
          %4 = op2(%1, %0, %2)
          %5 = op3(%1, %0, %3)
  
      Output:
          %0 = op0(...)
          %1 = op1(...)
          %2 = const(val=4.5)
          %3 = const(val=4.5) # this will get removed later by dead code elimination pass
          %4 = op2(%1, %0, %2)
  
  In the example above, ``op3`` is removed and all uses of ``%5`` is replaced by ``%4``.
  For more examples, see "TestRemoveRedundantOpsPass".
docstr: Return True, if all inputs of op1 and op2 are identical.
  non-constant inputs must refer to the same object.
  
  For constant inputs, we only compare arrays with small size.
  Large size const ops are already deduplicated in the const_deduplication pass so we
  can compare the pointers.
docstr_tok: ["Return","True",",","if","all","inputs","of","op1","and","op2","are","identical",".","non-constant","inputs","must","refer","to","the","same","object",".","For","constant","inputs",",","we","only","compare","arrays","with","small","size",".","Large","size","const","ops","are","already","deduplicated","in","the","const_deduplication","pass","so","we","can","compare","the","pointers","."]
code: def _are_ops_identical(op1, op2):
      """
      Return True, if all inputs of op1 and op2 are identical.
      non-constant inputs must refer to the same object.
  
      For constant inputs, we only compare arrays with small size.
      Large size const ops are already deduplicated in the const_deduplication pass so we
      can compare the pointers.
      """
  
      def _are_values_identical(val1, val2):
          if not isinstance(val1, np.ndarray) or not isinstance(
              val2, np.ndarray
          ):
              return np.array_equal(np.array(val1), np.array(val2))
          if val1.size != val2.size:
              return False
          if val1.size < 100:
              return np.array_equal(val1, val2)
          return False
  
      def _are_vars_identical(var1, var2):
          if var1 is var2:
              return True
          if var1.val is None and var2.val is None:
              if var1 != var2:
                  return False
          elif var1.val is not None and var2.val is not None:
              if var1.dtype != var2.dtype:
                  return False
              if not _are_values_identical(var1.val, var2.val):
                  return False
          else:
              return False
          return True
  
      if op1 == op2:
          return True
      if op1.op_type != op2.op_type:
          return False
      if len(op1.inputs) != len(op2.inputs):
          return False
  
      for key, value1 in op1.inputs.items():
          if key not in op2.inputs:
              return False
          value2 = op2.inputs[key]
          if isinstance(value1, Var) and isinstance(value2, Var):
              if not _are_vars_identical(value1, value2):
                  return False
          elif isinstance(value1, (list, tuple)) and isinstance(
              value2, (list, tuple)
          ):
              if len(value1) != len(value2):
                  return False
              else:
                  for i, v in enumerate(value1):
                      if not _are_vars_identical(v, value2[i]):
                          return False
          else:
              return False
  
      assert (
          len(op1.blocks) == 0
      ), "this method does not handle ops that have blocks in it"
      assert (
          len(op2.blocks) == 0
      ), "this method does not handle ops that have blocks in it"
      return True
  
file_path: coremltools/coremltools/converters/mil/mil/passes/defs/cleanup/remove_redundant_ops.py
imports: import collections
  import numpy
  from coremltools.converters.mil.mil import Var
  from coremltools.converters.mil.mil.passes.graph_pass import AbstractGraphPass
  from coremltools.converters.mil.mil.passes.helper import block_context_manager
  from coremltools.converters.mil.mil.passes.pass_registry import register_pass
label_desc: Use image node_id 5 for calling the remove_redundant_ops obj's underlying member method code with example usage: obj._are_ops_identical(op1, op2) and returns: True, False, True, True, False, False, np, False, np, True, False, False, False, False, False, False, False, False, False