class_docstr: A stack followed by a reshape layer can be replaced by a ``concat`` if the reshape
  simply removes the new axis and doubles the size of one of the axes next to it.
  
  If the new axis is reshaped to the "right" (that is, the axis just after it is
  doubled), then we can use a ``concat``. If it is reshaped to the "left" (the axis
  just before it is doubled), then the ``concat`` needs to set the ``interleaved`` flag.
  
  Examples:
  
  .. code-block::
  
      Given:
          %1 = tensor(1, 5, 3, 4)
          %2 = tensor(1, 5, 3, 4)
          %3 = stack((%1,%2), axis=2) # shape = (1, 5, 2, 3, 4)
          %4 = reshape(%3, shape=[1, 10, 3, 4])
  
      Result:
          %1 = tensor(1, 5, 3, 4)
          %2 = tensor(1, 5, 3, 4)
          %4 = concat((%1,%2), axis=1, interleave=True) # shape = (1, 10, 3, 4)
  
      Given:
          %1 = tensor(1, 5, 3, 4)
          %2 = tensor(1, 5, 3, 4)
          %3 = stack((%1, %2), axis=1) # shape = (1, 2, 5, 3, 4)
          %4 = reshape(%3, shape=[1, 10, 3, 4])
  
      Result:
          %1 = tensor(1, 5, 3, 4)
          %2 = tensor(1, 5, 3, 4)
          %4 = concat((%1, %2), axis = 1) # shape = (1, 10, 3, 4)
class_docstr_tok: ["A","stack","followed","by","a","reshape","layer","can","be","replaced","by","a","``","concat","``","if","the","reshape","simply","removes","the","new","axis","and","doubles","the","size","of","one","of","the","axes","next","to","it",".","If","the","new","axis","is","reshaped","to","the","``","right","''","(","that","is",",","the","axis","just","after","it","is","doubled",")",",","then","we","can","use","a","``","concat","``",".","If","it","is","reshaped","to","the","``","left","''","(","the","axis","just","before","it","is","doubled",")",",","then","the","``","concat","``","needs","to","set","the","``","interleaved","``","flag",".","Examples",":","..","code-block",":",":","Given",":","%","1","=","tensor","(","1",",","5",",","3",",","4",")","%","2","=","tensor","(","1",",","5",",","3",",","4",")","%","3","=","stack","(","(","%","1",",","%","2",")",",","axis=2",")","#","shape","=","(","1",",","5",",","2",",","3",",","4",")","%","4","=","reshape","(","%","3",",","shape=","[","1",",","10",",","3",",","4","]",")","Result",":","%","1","=","tensor","(","1",",","5",",","3",",","4",")","%","2","=","tensor","(","1",",","5",",","3",",","4",")","%","4","=","concat","(","(","%","1",",","%","2",")",",","axis=1",",","interleave=True",")","#","shape","=","(","1",",","10",",","3",",","4",")","Given",":","%","1","=","tensor","(","1",",","5",",","3",",","4",")","%","2","=","tensor","(","1",",","5",",","3",",","4",")","%","3","=","stack","(","(","%","1",",","%","2",")",",","axis=1",")","#","shape","=","(","1",",","2",",","5",",","3",",","4",")","%","4","=","reshape","(","%","3",",","shape=","[","1",",","10",",","3",",","4","]",")","Result",":","%","1","=","tensor","(","1",",","5",",","3",",","4",")","%","2","=","tensor","(","1",",","5",",","3",",","4",")","%","4","=","concat","(","(","%","1",",","%","2",")",",","axis","=","1",")","#","shape","=","(","1",",","10",",","3",",","4",")"]
code: def _match_operation(stack_op):
      # Identify if this is an op we can transform
      if stack_op.op_type != "stack":
          return None, None
  
      child_ops = stack_op.outputs[0].child_ops
      if len(child_ops) != 1:
          return None, None
  
      if child_ops[0].op_type != "reshape":
          return None, None
  
      stack_axis = stack_op.inputs["axis"]
      if not stack_axis:
          return None, None
      stack_axis_val = stack_axis.val
  
      reshape_op = child_ops[0]
  
      # Now, op is a stack op followed by a reshape op
      # So we need to check that the stack really gets eliminated
      stack_output_rank = len(stack_op.outputs[0].shape)
      reshape_output_rank = len(reshape_op.outputs[0].shape)
  
      if stack_output_rank != (reshape_output_rank + 1):
          return None, None
  
      # Compare the input to stack to the output from reshape
      # These shapes should differ in either the stack_axis_val place (by a factor of 2),
      # or in the stack_axis_val-1 place by the same factor
      input_shape = list(stack_op.inputs["values"][0].shape)
      concat_axis = [
          idx
          for idx, (x, y) in enumerate(
              zip(input_shape, reshape_op.outputs[0].shape)
          )
          if x != y
      ]
      if len(concat_axis) != 1:
          return None, None
  
      concat_axis = concat_axis[0]
  
      if (
          input_shape[concat_axis] * 2
          != reshape_op.outputs[0].shape[concat_axis]
      ):
          return None, None
  
      if (
          concat_axis != stack_axis_val
          and concat_axis != stack_axis_val - 1
      ):
          return None, None
  
      return stack_op, reshape_op
  
imports: import numpy
  from coremltools.converters.mil._deployment_compatibility import AvailableTarget
  from coremltools.converters.mil.frontend._utils import value_at
  from coremltools.converters.mil.mil import Builder
  from coremltools.converters.mil.mil.block import is_current_opset_version_compatible_with
  from coremltools.converters.mil.mil.passes.graph_pass import AbstractGraphPass
  from coremltools.converters.mil.mil.passes.helper import _check_child_op_type, _check_var_scalar_value, block_context_manager
  from coremltools.converters.mil.mil.passes.pass_registry import register_pass
  from coremltools.converters.mil.mil.types.symbolic import any_symbolic
label_desc: Use image node_id 2 for calling the replace_stack_reshape obj's underlying member method code with example usage: obj._match_operation(stack_op) and returns: stack_op,  reshape_op, None,  None, None,  None, None,  None, None,  None, None,  None, None,  None, None,  None, None,  None