Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 18 additions & 15 deletions einops/einops.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,22 @@
import string
import typing
from collections import OrderedDict
from typing import Any, Protocol, TypeAlias, TypeVar, cast
from collections.abc import Sequence
from typing import TYPE_CHECKING, Protocol, TypeAlias, TypeVar, cast

from . import EinopsError
from ._backends import get_backend
from .parsing import AnonymousAxis, ParsedExpression, _ellipsis

if TYPE_CHECKING:
import numpy as np


# typing helper, allows not using overloads
class _TensorLike(Protocol):
@property
def shape(self, /) -> typing.Any: ...
def __getitem__(self, arg) -> typing.Any: ...
def __getitem__(self, arg, /) -> typing.Any: ...


Tensor = TypeVar("Tensor", bound=_TensorLike)
Expand All @@ -26,7 +30,7 @@ def __call__(self, tensor: Tensor, axes: tuple[int, ...], /) -> Tensor: ...
def __hash__(self) -> int: ...


Reduction = str | ReductionCallable
Reduction: TypeAlias = str | ReductionCallable
Size: TypeAlias = typing.Any


Expand Down Expand Up @@ -299,6 +303,9 @@ def _apply_recipe_array_api(
return tensor


_Axis: TypeAlias = str | AnonymousAxis


@functools.lru_cache(256)
def _prepare_transformation_recipe(
pattern: str,
Expand Down Expand Up @@ -347,21 +354,21 @@ def _prepare_transformation_recipe(
raise EinopsError(f"Wrong shape: expected >={n_other_dims} dims. Received {ndim}-dim tensor.")
ellipsis_ndim = ndim - n_other_dims
ell_axes = [_ellipsis + str(i) for i in range(ellipsis_ndim)]
left_composition = []
left_composition: list[Sequence[_Axis]] = []
for composite_axis in left.composition:
if composite_axis == _ellipsis:
for axis in ell_axes:
left_composition.append([axis])
else:
left_composition.append(composite_axis)

rght_composition = []
rght_composition: list[Sequence[_Axis]] = []
for composite_axis in rght.composition:
if composite_axis == _ellipsis:
for axis in ell_axes:
rght_composition.append([axis])
else:
group = []
group: list[_Axis] = []
for axis in composite_axis:
if axis == _ellipsis:
group.extend(ell_axes)
Expand All @@ -381,7 +388,7 @@ def _prepare_transformation_recipe(
rght_composition = rght.composition

# parsing all dimensions to find out lengths
axis_name2known_length: OrderedDict[str | AnonymousAxis, int] = OrderedDict()
axis_name2known_length: OrderedDict[_Axis, int] = OrderedDict()
for composite_axis in left_composition:
for axis_name in composite_axis:
if isinstance(axis_name, AnonymousAxis):
Expand Down Expand Up @@ -413,16 +420,16 @@ def _prepare_transformation_recipe(
input_axes_known_unknown = []
# some shapes are inferred later - all information is prepared for faster inference
for composite_axis in left_composition:
known: set[str] = {axis for axis in composite_axis if axis_name2known_length[axis] != _unknown_axis_length}
unknown: set[str] = {axis for axis in composite_axis if axis_name2known_length[axis] == _unknown_axis_length}
known = {axis for axis in composite_axis if axis_name2known_length[axis] != _unknown_axis_length}
unknown = {axis for axis in composite_axis if axis_name2known_length[axis] == _unknown_axis_length}
if len(unknown) > 1:
raise EinopsError(f"Could not infer sizes for {unknown}")
assert len(unknown) + len(known) == len(composite_axis)
input_axes_known_unknown.append(
([axis_name2position[axis] for axis in known], [axis_name2position[axis] for axis in unknown])
)

axis_position_after_reduction: dict[str, int] = {}
axis_position_after_reduction: dict[_Axis, int] = {}
for axis_name in itertools.chain(*left_composition):
if axis_name in rght.identifiers:
axis_position_after_reduction[axis_name] = len(axis_position_after_reduction)
Expand Down Expand Up @@ -748,11 +755,7 @@ def _enumerate_directions(x):
return result


# to avoid importing numpy
np_ndarray = Any


def asnumpy(tensor: Tensor) -> np_ndarray:
def asnumpy(tensor: Tensor) -> "np.ndarray":
"""
Convert a tensor of an imperative framework (i.e. numpy/cupy/torch/jax/etc.) to `numpy.ndarray`

Expand Down
Loading