docs: def _prepare_transformation_recipe(pattern: str, reduction: str, axes_lengths: Tuple) -> TransformRecipe:
      """ Perform initial parsing of pattern and provided supplementary info
      axes_lengths is a tuple of tuples (axis_name, axis_length)
      """
      left, right = pattern.split('->')
      identifiers_left, composite_axes_left = parse_expression(left)
      identifiers_rght, composite_axes_rght = parse_expression(right)
  
      # checking that both have similar letters
      if reduction == 'rearrange':
          difference = set.symmetric_difference(identifiers_left, identifiers_rght)
          if len(difference) > 0:
              raise EinopsError('Identifiers only on one side of expression (should be on both): {}'.format(difference))
      elif reduction in _reductions:
          difference = set.difference(identifiers_rght, identifiers_left)
          if len(difference) > 0:
              raise EinopsError('Unexpected identifiers on the right side of expression: {}'.format(difference))
      else:
          raise EinopsError('Unknown reduction {}'.format(reduction))
  
      # parsing all dimensions to find out lengths
      known_lengths = OrderedDict()
      position_lookup = {}
      position_lookup_after_reduction = {}
      reduced_axes = []
      for composite_axis in composite_axes_left:
          for axis in composite_axis:
              position_lookup[axis] = len(position_lookup)
              if axis in identifiers_rght:
                  position_lookup_after_reduction[axis] = len(position_lookup_after_reduction)
              else:
                  reduced_axes.append(len(known_lengths))
              known_lengths[axis] = None
  
      def update_axis_length(axis_name, axis_length):
          if known_lengths[axis_name] is not None:
              # check is not performed for symbols
              if isinstance(axis_length, int) and isinstance(known_lengths[axis_name], int):
                  if axis_length != known_lengths[axis_name]:
                      raise RuntimeError('Inferred length for {} is {} not {}'.format(
                          axis_name, axis_length, known_lengths[axis_name]))
          else:
              known_lengths[axis_name] = axis_length
  
      for elementary_axis, axis_length in axes_lengths:
          if not _check_elementary_axis_name(elementary_axis):
              raise EinopsError('Invalid name for an axis', elementary_axis)
          if elementary_axis not in known_lengths:
              raise EinopsError('Axis {} is not used in transform'.format(elementary_axis))
          update_axis_length(elementary_axis, axis_length)
  
      input_axes_known_unknown = []
      # inferring rest of sizes from arguments
      for composite_axis in composite_axes_left:
          known = {axis for axis in composite_axis if known_lengths[axis] is not None}
          unknown = {axis for axis in composite_axis if known_lengths[axis] is None}
          lookup = dict(zip(list(known_lengths), range(len(known_lengths))))
          if len(unknown) > 1:
              raise EinopsError('Could not infer sizes for {}'.format(unknown))
          assert len(unknown) + len(known) == len(composite_axis)
          input_axes_known_unknown.append(([lookup[axis] for axis in known], [lookup[axis] for axis in unknown]))
  
      result_axes_grouping = [[position_lookup_after_reduction[axis] for axis in composite_axis]
                              for composite_axis in composite_axes_rght]
  
      ellipsis_left = math.inf if _ellipsis not in composite_axes_left else composite_axes_left.index(_ellipsis)
      ellipsis_rght = math.inf if _ellipsis not in composite_axes_rght else composite_axes_rght.index(_ellipsis)
  
      return TransformRecipe(elementary_axes_lengths=list(known_lengths.values()),
                             input_composite_axes=input_axes_known_unknown,
                             output_composite_axes=result_axes_grouping,
                             reduction_type=reduction,
                             reduced_elementary_axes=tuple(reduced_axes),
                             ellipsis_positions=(ellipsis_left, ellipsis_rght)
                             )