# 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

    # initialize empty indices
    x_ix = [None] * xnd
    y_ix = [None] * ynd
    out_ix = []

    # fill in repeated indices
    available_ix = iter(_torch_symbols_base)
    for ax1, ax2 in zip(*axes):
        repeat = next(available_ix)
        x_ix[ax1] = repeat
        y_ix[ax2] = repeat

    # fill in the rest, and maintain output order
    for i in range(xnd):
        if x_ix[i] is None:
            leave = next(available_ix)
            x_ix[i] = leave
            out_ix.append(leave)
    for i in range(ynd):
        if y_ix[i] is None:
            leave = next(available_ix)
            y_ix[i] = leave
            out_ix.append(leave)

    # form full string and contract!
    einsum_str = "{},{}->{}".format(*map("".join, (x_ix, y_ix, out_ix)))
    return einsum(einsum_str, x, y)



# 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


    # initialize empty indices
    x_ix = [None] * xnd
    y_ix = [None] * ynd
    out_ix = []

    # fill in repeated indices
    available_ix = iter(_torch_symbols_base)
    for ax1, ax2 in zip(*axes):
        repeat = next(available_ix)
        x_ix[ax1] = repeat
        y_ix[ax2] = repeat

    # fill in the rest, and maintain output order
    for i in range(xnd):
        if x_ix[i] is None:
            leave = next(available_ix)
            x_ix[i] = leave
            out_ix.append(leave)
    for i in range(ynd):
        if y_ix[i] is None:
            leave = next(available_ix)
            y_ix[i] = leave
            out_ix.append(leave)

    # form full string and contract!
    einsum_str = "{},{}->{}".format(*map("".join, (x_ix, y_ix, out_ix)))
    return einsum(einsum_str, x, y)



@to_backend_cache_wrap