|
|
"""Testing routines for opt_einsum.""" |
|
|
|
|
|
import random |
|
|
from typing import Any, Dict, List, Literal, Optional, Tuple, Union, overload |
|
|
|
|
|
import pytest |
|
|
|
|
|
from opt_einsum.parser import get_symbol |
|
|
from opt_einsum.typing import ArrayType, PathType, TensorShapeType |
|
|
|
|
|
_valid_chars = "abcdefghijklmopqABC" |
|
|
_sizes = [2, 3, 4, 5, 4, 3, 2, 6, 5, 4, 3, 2, 5, 7, 4, 3, 2, 3, 4] |
|
|
_default_dim_dict = dict(zip(_valid_chars, _sizes)) |
|
|
|
|
|
|
|
|
def build_shapes(string: str, dimension_dict: Optional[Dict[str, int]] = None) -> Tuple[TensorShapeType, ...]: |
|
|
"""Builds random tensor shapes for testing. |
|
|
|
|
|
Parameters: |
|
|
string: List of tensor strings to build |
|
|
dimension_dict: Dictionary of index sizes, defaults to indices size of 2-7 |
|
|
|
|
|
Returns: |
|
|
The resulting shapes. |
|
|
|
|
|
Examples: |
|
|
```python |
|
|
>>> shapes = build_shapes('abbc', {'a': 2, 'b':3, 'c':5}) |
|
|
>>> shapes |
|
|
[(2, 3), (3, 3, 5), (5,)] |
|
|
``` |
|
|
|
|
|
""" |
|
|
if dimension_dict is None: |
|
|
dimension_dict = _default_dim_dict |
|
|
|
|
|
shapes = [] |
|
|
terms = string.split("->")[0].split(",") |
|
|
for term in terms: |
|
|
dims = [dimension_dict[x] for x in term] |
|
|
shapes.append(tuple(dims)) |
|
|
return tuple(shapes) |
|
|
|
|
|
|
|
|
def build_views( |
|
|
string: str, dimension_dict: Optional[Dict[str, int]] = None, array_function: Optional[Any] = None |
|
|
) -> Tuple[ArrayType]: |
|
|
"""Builds random numpy arrays for testing. |
|
|
|
|
|
Parameters: |
|
|
string: List of tensor strings to build |
|
|
dimension_dict: Dictionary of index _sizes |
|
|
array_function: Function to build the arrays, defaults to np.random.rand |
|
|
|
|
|
Returns: |
|
|
The resulting views. |
|
|
|
|
|
Examples: |
|
|
```python |
|
|
>>> view = build_views('abbc', {'a': 2, 'b':3, 'c':5}) |
|
|
>>> view[0].shape |
|
|
(2, 3, 3, 5) |
|
|
``` |
|
|
|
|
|
""" |
|
|
if array_function is None: |
|
|
np = pytest.importorskip("numpy") |
|
|
array_function = np.random.rand |
|
|
|
|
|
views = [] |
|
|
for shape in build_shapes(string, dimension_dict=dimension_dict): |
|
|
if shape: |
|
|
views.append(array_function(*shape)) |
|
|
else: |
|
|
views.append(random.random()) |
|
|
return tuple(views) |
|
|
|
|
|
|
|
|
@overload |
|
|
def rand_equation( |
|
|
n: int, |
|
|
regularity: int, |
|
|
n_out: int = ..., |
|
|
d_min: int = ..., |
|
|
d_max: int = ..., |
|
|
seed: Optional[int] = ..., |
|
|
global_dim: bool = ..., |
|
|
*, |
|
|
return_size_dict: Literal[True], |
|
|
) -> Tuple[str, PathType, Dict[str, int]]: ... |
|
|
|
|
|
|
|
|
@overload |
|
|
def rand_equation( |
|
|
n: int, |
|
|
regularity: int, |
|
|
n_out: int = ..., |
|
|
d_min: int = ..., |
|
|
d_max: int = ..., |
|
|
seed: Optional[int] = ..., |
|
|
global_dim: bool = ..., |
|
|
return_size_dict: Literal[False] = ..., |
|
|
) -> Tuple[str, PathType]: ... |
|
|
|
|
|
|
|
|
def rand_equation( |
|
|
n: int, |
|
|
regularity: int, |
|
|
n_out: int = 0, |
|
|
d_min: int = 2, |
|
|
d_max: int = 9, |
|
|
seed: Optional[int] = None, |
|
|
global_dim: bool = False, |
|
|
return_size_dict: bool = False, |
|
|
) -> Union[Tuple[str, PathType, Dict[str, int]], Tuple[str, PathType]]: |
|
|
"""Generate a random contraction and shapes. |
|
|
|
|
|
Parameters: |
|
|
n: Number of array arguments. |
|
|
regularity: 'Regularity' of the contraction graph. This essentially determines how |
|
|
many indices each tensor shares with others on average. |
|
|
n_out: Number of output indices (i.e. the number of non-contracted indices). |
|
|
Defaults to 0, i.e., a contraction resulting in a scalar. |
|
|
d_min: Minimum dimension size. |
|
|
d_max: Maximum dimension size. |
|
|
seed: If not None, seed numpy's random generator with this. |
|
|
global_dim: Add a global, 'broadcast', dimension to every operand. |
|
|
return_size_dict: Return the mapping of indices to sizes. |
|
|
|
|
|
Returns: |
|
|
eq: The equation string. |
|
|
shapes: The array shapes. |
|
|
size_dict: The dict of index sizes, only returned if ``return_size_dict=True``. |
|
|
|
|
|
Examples: |
|
|
```python |
|
|
>>> eq, shapes = rand_equation(n=10, regularity=4, n_out=5, seed=42) |
|
|
>>> eq |
|
|
'oyeqn,tmaq,skpo,vg,hxui,n,fwxmr,hitplcj,kudlgfv,rywjsb->cebda' |
|
|
|
|
|
>>> shapes |
|
|
[(9, 5, 4, 5, 4), |
|
|
(4, 4, 8, 5), |
|
|
(9, 4, 6, 9), |
|
|
(6, 6), |
|
|
(6, 9, 7, 8), |
|
|
(4,), |
|
|
(9, 3, 9, 4, 9), |
|
|
(6, 8, 4, 6, 8, 6, 3), |
|
|
(4, 7, 8, 8, 6, 9, 6), |
|
|
(9, 5, 3, 3, 9, 5)] |
|
|
``` |
|
|
""" |
|
|
np = pytest.importorskip("numpy") |
|
|
if seed is not None: |
|
|
np.random.seed(seed) |
|
|
|
|
|
|
|
|
num_inds = n * regularity // 2 + n_out |
|
|
inputs = ["" for _ in range(n)] |
|
|
output = [] |
|
|
|
|
|
size_dict = {get_symbol(i): np.random.randint(d_min, d_max + 1) for i in range(num_inds)} |
|
|
|
|
|
|
|
|
def gen(): |
|
|
for i, ix in enumerate(size_dict): |
|
|
|
|
|
if i < n_out: |
|
|
output.append(ix) |
|
|
yield ix |
|
|
|
|
|
else: |
|
|
yield ix |
|
|
yield ix |
|
|
|
|
|
|
|
|
for i, ix in enumerate(np.random.permutation(list(gen()))): |
|
|
|
|
|
if i < n: |
|
|
inputs[i] += ix |
|
|
else: |
|
|
|
|
|
where = np.random.randint(0, n) |
|
|
while ix in inputs[where]: |
|
|
where = np.random.randint(0, n) |
|
|
|
|
|
inputs[where] += ix |
|
|
|
|
|
|
|
|
if global_dim: |
|
|
gdim = get_symbol(num_inds) |
|
|
size_dict[gdim] = np.random.randint(d_min, d_max + 1) |
|
|
for i in range(n): |
|
|
inputs[i] += gdim |
|
|
output += gdim |
|
|
|
|
|
|
|
|
output = "".join(np.random.permutation(output)) |
|
|
eq = "{}->{}".format(",".join(inputs), output) |
|
|
|
|
|
|
|
|
shapes = [tuple(size_dict[ix] for ix in op) for op in inputs] |
|
|
|
|
|
ret = (eq, shapes) |
|
|
|
|
|
if return_size_dict: |
|
|
return ret + (size_dict,) |
|
|
else: |
|
|
return ret |
|
|
|
|
|
|
|
|
def build_arrays_from_tuples(path: PathType) -> List[Any]: |
|
|
"""Build random numpy arrays from a path. |
|
|
|
|
|
Parameters: |
|
|
path: The path to build arrays from. |
|
|
|
|
|
Returns: |
|
|
The resulting arrays. |
|
|
""" |
|
|
np = pytest.importorskip("numpy") |
|
|
|
|
|
return [np.random.rand(*x) for x in path] |
|
|
|