class_docstr: Fold ``mul`` + ``add`` into a ``batchnorm``
  if the ``const`` feeding into the ``mul``/``add`` is of shape ``(1,C,1,1)`` or ``(C,1,1)``
  and input to ``mul`` is of rank 4.
  
  .. code-block::
  
      Given:
               [Const]   [Const]
                  |         |
                  V         V
      [...] --> [Mul] --> [Add] --> [...]
  
      That is,
  
          %2 = op1(%1)
          %3 = mul(%2, constant)
          %4 = add(%3, constant)
          %5 = op2(%4)
          ...
  
      Result:
  
      [...] --> [BatchNorm] --> [...]
  
      That is,
          %2 = op1(%1)
          %4 = batchnorm(%2)
          %5 = op2(%4)
          ...
code: def _try_to_transform(mul_op, add_op, block):
      def _find_const_input_val(op):
          if op.x.val is not None:
              return op.x.val
          if op.y.val is not None:
              return op.y.val
          return None
  
      def _check_shape(arr):
          """
          return True if shape is of form
          (1,C,1,1) or (C,1,1)
          """
          rank = len(arr.shape)
          if not (rank == 3 or rank == 4):
              return False
          C = arr.shape[-3]
          if not (arr.shape == (1, C, 1, 1) or arr.shape == (C, 1, 1)):
              return False
          return True
  
      non_const_input_mul = (
          mul_op.x if mul_op.x.val is None else mul_op.y
      )
      if non_const_input_mul.rank != 4:
          return False
  
      gamma = _find_const_input_val(mul_op)
      beta = _find_const_input_val(add_op)
      if gamma is None or beta is None:
          return False
  
      if not (
          isinstance(gamma, np.ndarray) and isinstance(beta, np.ndarray)
      ):
          return False
  
      # check that gamma and beta have shape (1,C,1,1) or (C,1,1)
      # that is they are doing vector addition on the axis=-3, which is what the
      # batchnorm layer does (batchnorm layer only works on rank 4 input tensors)
      if not (_check_shape(gamma) and _check_shape(beta)):
          return False
  
      C = gamma.shape[-3]
      if C == 1:
          return False
  
      out_name = add_op.outputs[0].name
      x = mb.batch_norm(
          x=non_const_input_mul,
          mean=np.zeros((C,), np.float32),
          variance=np.ones((C,), np.float32),
          gamma=np.squeeze(gamma),
          beta=np.squeeze(beta),
          name=out_name,
          before_op=mul_op,
      )
  
      add_op.enclosing_block.replace_uses_of_var_after_op(
          anchor_op=add_op, old_var=add_op.outputs[0], new_var=x
      )
      # Remove all the ops at once
      block.remove_ops([mul_op, add_op])
      return True
  
imports: import numpy
  from coremltools.converters.mil.mil import Block
  from coremltools.converters.mil.mil import Builder
  from coremltools.converters.mil.mil import Operation, Var
  from coremltools.converters.mil.mil import types
  from coremltools.converters.mil.mil.ops.defs._utils import broadcast_shapes
  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 3 for calling the fuse_elementwise_to_batchnorm obj's underlying member method code with example usage: obj._try_to_transform(mul_op, add_op, block) and returns: True, None, True, False, False, False, False, False, op, op, False, False