docs: def reconstruct_from_shape(self, shape, optimize=False):
          """
          Shape is a tuple that may contain integers, shape symbols (tf, keras, theano) and UnknownSize (keras, mxnet)
          known axes can be integers or symbols, but not Nones
          """
          axes_lengths = list(self.elementary_axes_lengths)
          if self.ellipsis_positions != (math.inf, math.inf):
              if len(shape) < len(self.input_composite_axes) - 1:
                  raise EinopsError('Expected at least {} dimensions, got {}'.format(
                      len(self.input_composite_axes) - 1, len(shape)))
          else:
              if len(shape) != len(self.input_composite_axes):
                  raise EinopsError('Expected {} dimensions, got {}'.format(len(self.input_composite_axes), len(shape)))
          for input_axis, (known_axes, unknown_axes) in enumerate(self.input_composite_axes):
              before_ellipsis = input_axis
              after_ellipsis = input_axis + len(shape) - len(self.input_composite_axes)
              if input_axis == self.ellipsis_positions[0]:
                  assert len(known_axes) == 0 and len(unknown_axes) == 1
                  unknown_axis, = unknown_axes
                  ellipsis_shape = shape[before_ellipsis:after_ellipsis + 1]
                  if any(d is None for d in ellipsis_shape):
                      raise EinopsError("Couldn't infer shape for one or more axes represented by ellipsis")
                  axes_lengths[unknown_axis] = _product(ellipsis_shape)
              else:
                  if input_axis < self.ellipsis_positions[0]:
                      length = shape[before_ellipsis]
                  else:
                      length = shape[after_ellipsis]
                  known_product = 1
                  for axis in known_axes:
                      known_product *= axes_lengths[axis]
  
                  if len(unknown_axes) == 0:
                      if isinstance(length, int) and isinstance(known_product, int) and length != known_product:
                          raise EinopsError('Shape mismatch, {} != {}'.format(length, known_product))
                  else:
                      if isinstance(length, int) and isinstance(known_product, int) and length % known_product != 0:
                          raise EinopsError("Shape mismatch, can't divide axis of length {} in chunks of {}".format(
                              length, known_product))
  
                      unknown_axis, = unknown_axes
                      axes_lengths[unknown_axis] = length // known_product
  
          init_shapes = axes_lengths
          reduced_axes_lengths = [dim for i, dim in enumerate(axes_lengths) if i not in self.reduced_elementary_axes]
          final_shapes = []
          for output_axis, grouping in enumerate(self.output_composite_axes):
              if output_axis == self.ellipsis_positions[1]:
                  final_shapes.extend(ellipsis_shape)
              else:
                  lengths = [reduced_axes_lengths[elementary_axis] for elementary_axis in grouping]
                  if any(l is None for l in lengths):
                      final_shapes.append(None)
                  else:
                      final_shapes.append(_product(lengths))
          reduced_axes = self.reduced_elementary_axes
          axes_reordering = self.final_axes_grouping_flat
          if optimize:
              return _optimize_transformation(init_shapes, reduced_axes, axes_reordering, final_shapes)
          else:
              return init_shapes, reduced_axes, axes_reordering, final_shapes