# Path: opt_einsum/parser.py
# def convert_to_valid_einsum_chars(einsum_str: str) -> str:
#     """Convert the str ``einsum_str`` to contain only the alphabetic characters
#     valid for numpy einsum. If there are too many symbols, let the backend
#     throw an error.
# 
#     Examples
#     --------
#     >>> oe.parser.convert_to_valid_einsum_chars("Ĥěļļö")
#     'cbdda'
#     """
#     symbols = sorted(set(einsum_str) - set(",->"))
#     replacer = {x: get_symbol(i) for i, x in enumerate(symbols)}
#     return "".join(replacer.get(x, x) for x in einsum_str)
# 
# Path: opt_einsum/sharing.py
# def to_backend_cache_wrap(to_backend: Any = None, constants: Any = False) -> Any:
#     """Decorates an ``to_backend()`` implementation to be memoized inside a
#     :func:`shared_intermediates` context (e.g. ``to_cupy``, ``to_torch``).
#     """
#     # manage the case that decorator is called with args
#     if to_backend is None:
#         return functools.partial(to_backend_cache_wrap, constants=constants)
# 
#     if constants:
# 
#         @functools.wraps(to_backend)
#         def cached_to_backend(array, constant=False):
#             if not currently_sharing():
#                 return to_backend(array, constant=constant)
# 
#             # hash by id
#             key = to_backend.__name__, id(array), constant
#             return _memoize(key, to_backend, array, constant=constant)
# 
#     else:
# 
#         @functools.wraps(to_backend)
#         def cached_to_backend(array):
#             if not currently_sharing():
#                 return to_backend(array)
# 
#             # hash by id
#             key = to_backend.__name__, id(array)
#             return _memoize(key, to_backend, array)
# 
#     return cached_to_backend


import numpy as np
        import torch
from ..parser import convert_to_valid_einsum_chars
from ..sharing import to_backend_cache_wrap
]

_TORCH_DEVICE = None
_TORCH_HAS_TENSORDOT = None

_torch_symbols_base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"


def _get_torch_and_device():
    global _TORCH_DEVICE
    global _TORCH_HAS_TENSORDOT

    if _TORCH_DEVICE is None:

        device = "cuda" if torch.cuda.is_available() else "cpu"
        _TORCH_DEVICE = torch, device
        _TORCH_HAS_TENSORDOT = hasattr(torch, "tensordot")

    return _TORCH_DEVICE


def transpose(a, axes):
    """Normal torch transpose is only valid for 2D matrices."""
    return a.permute(*axes)


def einsum(equation, *operands):
    """Variadic version of torch.einsum to match numpy api."""
    # rename symbols to support PyTorch 0.4.1 and earlier,
    # which allow only symbols a-z.

# Path: opt_einsum/parser.py
# def convert_to_valid_einsum_chars(einsum_str: str) -> str:
#     """Convert the str ``einsum_str`` to contain only the alphabetic characters
#     valid for numpy einsum. If there are too many symbols, let the backend
#     throw an error.
# 
#     Examples
#     --------
#     >>> oe.parser.convert_to_valid_einsum_chars("Ĥěļļö")
#     'cbdda'
#     """
#     symbols = sorted(set(einsum_str) - set(",->"))
#     replacer = {x: get_symbol(i) for i, x in enumerate(symbols)}
#     return "".join(replacer.get(x, x) for x in einsum_str)
# 
# Path: opt_einsum/sharing.py
# def to_backend_cache_wrap(to_backend: Any = None, constants: Any = False) -> Any:
#     """Decorates an ``to_backend()`` implementation to be memoized inside a
#     :func:`shared_intermediates` context (e.g. ``to_cupy``, ``to_torch``).
#     """
#     # manage the case that decorator is called with args
#     if to_backend is None:
#         return functools.partial(to_backend_cache_wrap, constants=constants)
# 
#     if constants:
# 
#         @functools.wraps(to_backend)
#         def cached_to_backend(array, constant=False):
#             if not currently_sharing():
#                 return to_backend(array, constant=constant)
# 
#             # hash by id
#             key = to_backend.__name__, id(array), constant
#             return _memoize(key, to_backend, array, constant=constant)
# 
#     else:
# 
#         @functools.wraps(to_backend)
#         def cached_to_backend(array):
#             if not currently_sharing():
#                 return to_backend(array)
# 
#             # hash by id
#             key = to_backend.__name__, id(array)
#             return _memoize(key, to_backend, array)
# 
#     return cached_to_backend


# Path: opt_einsum/backends/torch.py
import numpy as np
        import torch
from ..parser import convert_to_valid_einsum_chars
from ..sharing import to_backend_cache_wrap

]

_TORCH_DEVICE = None
_TORCH_HAS_TENSORDOT = None

_torch_symbols_base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"


def _get_torch_and_device():
    global _TORCH_DEVICE
    global _TORCH_HAS_TENSORDOT

    if _TORCH_DEVICE is None:

        device = "cuda" if torch.cuda.is_available() else "cpu"
        _TORCH_DEVICE = torch, device
        _TORCH_HAS_TENSORDOT = hasattr(torch, "tensordot")

    return _TORCH_DEVICE


def transpose(a, axes):
    """Normal torch transpose is only valid for 2D matrices."""
    return a.permute(*axes)


def einsum(equation, *operands):
    """Variadic version of torch.einsum to match numpy api."""
    # rename symbols to support PyTorch 0.4.1 and earlier,
    # which allow only symbols a-z.

    equation = convert_to_valid_einsum_chars(equation)