Skip to content

Commit

Permalink
misc
Browse files Browse the repository at this point in the history
  • Loading branch information
dgasmith committed Jun 25, 2024
1 parent 9b9fe5a commit 7a99d78
Show file tree
Hide file tree
Showing 10 changed files with 115 additions and 288 deletions.
2 changes: 0 additions & 2 deletions devtools/conda-envs/min-deps-environment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ channels:
dependencies:
# Base depends
- python >=3.9
- numpy >=1.23
- nomkl

# Testing
- autoflake
Expand Down
2 changes: 2 additions & 0 deletions opt_einsum/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from opt_einsum.paths import BranchBound, DynamicProgramming
from opt_einsum.sharing import shared_intermediates

__all__ = ["blas", "helpers", "path_random", "paths", "contract", "contract_expression", "contract_path", "get_symbol", "RadnomGreedy", "BranchBound", "DynamicProgramming", "shared_intermediates"]

# Handle versioneer
from opt_einsum._version import get_versions # isort:skip

Expand Down
9 changes: 3 additions & 6 deletions opt_einsum/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
Contains the primary optimization and contraction routines.
"""

from collections import namedtuple
from decimal import Decimal
from functools import lru_cache
from typing import Any, Collection, Dict, Iterable, List, Literal, Optional, Sequence, Tuple, Union, overload

from opt_einsum import backends, blas, helpers, parser, paths, sharing
from opt_einsum.typing import (
ArrayIndexType,
ArrayShaped,
ArrayType,
BackendType,
ContractionListType,
Expand Down Expand Up @@ -957,14 +957,11 @@ def __str__(self) -> str:
return "".join(s)


Shaped = namedtuple("Shaped", ["shape"])


def shape_only(shape: TensorShapeType) -> Shaped:
def shape_only(shape: TensorShapeType) -> ArrayShaped:
"""Dummy ``numpy.ndarray`` which has a shape only - for generating
contract expressions.
"""
return Shaped(shape)
return ArrayShaped(shape)


# Overlaod for contract(einsum_string, *operands)
Expand Down
176 changes: 2 additions & 174 deletions opt_einsum/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
Contains helper functions for opt_einsum testing scripts
"""

from typing import Any, Collection, Dict, FrozenSet, Iterable, List, Literal, Optional, Tuple, Union, overload
from typing import Any, Collection, Dict, FrozenSet, Iterable, List, Tuple, overload

from opt_einsum.parser import get_symbol
from opt_einsum.typing import ArrayIndexType, ArrayType, PathType
from opt_einsum.typing import ArrayIndexType, ArrayType

__all__ = ["build_views", "compute_size_by_dict", "find_contraction", "flop_count"]

Expand All @@ -14,41 +13,6 @@
_default_dim_dict = {c: s for c, s in zip(_valid_chars, _sizes)}


def build_views(string: str, dimension_dict: Optional[Dict[str, int]] = None) -> List[np.ndarray]:
"""
Builds random numpy arrays for testing.
Parameters
----------
string : str
List of tensor strings to build
dimension_dict : dictionary
Dictionary of index _sizes
Returns
-------
ret : list of np.ndarry's
The resulting views.
Examples
--------
>>> view = build_views('abbc', {'a': 2, 'b':3, 'c':5})
>>> view[0].shape
(2, 3, 3, 5)
"""

if dimension_dict is None:
dimension_dict = _default_dim_dict

views = []
terms = string.split("->")[0].split(",")
for term in terms:
dims = [dimension_dict[x] for x in term]
views.append(np.random.rand(*dims))
return views


@overload
def compute_size_by_dict(indices: Iterable[int], idx_dict: List[int]) -> int: ...

Expand Down Expand Up @@ -194,139 +158,3 @@ def has_array_interface(array: ArrayType) -> ArrayType:
return True
else:
return False


@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)]
```
"""

if seed is not None:
np.random.seed(seed)

# total number of indices
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)}

# generate a list of indices to place either once or twice
def gen():
for i, ix in enumerate(size_dict):
# generate an outer index
if i < n_out:
output.append(ix)
yield ix
# generate a bond
else:
yield ix
yield ix

# add the indices randomly to the inputs
for i, ix in enumerate(np.random.permutation(list(gen()))):
# make sure all inputs have at least one index
if i < n:
inputs[i] += ix
else:
# don't add any traces on same op
where = np.random.randint(0, n)
while ix in inputs[where]:
where = np.random.randint(0, n)

inputs[where] += ix

# possibly add the same global dim to every arg
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

# randomly transpose the output indices and form equation
output = "".join(np.random.permutation(output)) # type: ignore
eq = "{}->{}".format(",".join(inputs), output)

# make the shapes
shapes = [tuple(size_dict[ix] for ix in op) for op in inputs]

if return_size_dict:
return (
eq,
shapes,
size_dict,
)
else:
return (eq, shapes)
45 changes: 21 additions & 24 deletions opt_einsum/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,32 +265,29 @@ def parse_einsum_input(operands: Any, shapes: bool = False) -> Tuple[str, str, L
"""
A reproduction of einsum c side einsum parsing in python.
**Parameters:**
Intakes the same inputs as `contract_path`, but NOT the keyword args. The only
supported keyword argument is:
- **shapes** - *(bool, optional)* Whether ``parse_einsum_input`` should assume arrays (the default) or
array shapes have been supplied.
Parameters:
operands: Intakes the same inputs as `contract_path`, but NOT the keyword args. The only
supported keyword argument is:
shapes: Whether ``parse_einsum_input`` should assume arrays (the default) or
array shapes have been supplied.
Returns
-------
input_strings : str
Parsed input strings
output_string : str
Parsed output string
operands : list of array_like
The operands to use in the numpy contraction
Examples
--------
The operand list is simplified to reduce printing:
>>> a = np.random.rand(4, 4)
>>> b = np.random.rand(4, 4, 4)
>>> parse_einsum_input(('...a,...a->...', a, b))
('za,xza', 'xz', [a, b])
>>> parse_einsum_input((a, [Ellipsis, 0], b, [Ellipsis, 0]))
('za,xza', 'xz', [a, b])
input_strings: Parsed input strings
output_string: Parsed output string
operands: The operands to use in the numpy contraction
Examples:
The operand list is simplified to reduce printing:
```python
>>> a = np.random.rand(4, 4)
>>> b = np.random.rand(4, 4, 4)
>>> parse_einsum_input(('...a,...a->...', a, b))
('za,xza', 'xz', [a, b])
>>> parse_einsum_input((a, [Ellipsis, 0], b, [Ellipsis, 0]))
('za,xza', 'xz', [a, b])
```
"""

if len(operands) == 0:
Expand Down
16 changes: 5 additions & 11 deletions opt_einsum/paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
from typing import Counter as CounterType
from typing import Dict, FrozenSet, Generator, List, Optional, Sequence, Set, Tuple, Union

import numpy as np

from opt_einsum.helpers import compute_size_by_dict, flop_count
from opt_einsum.typing import ArrayIndexType, PathSearchFunctionType, PathType, TensorShapeType

Expand All @@ -39,17 +37,13 @@ class PathOptimizer:
Subclassed optimizers should define a call method with signature:
```python
def __call__(self, inputs, output, size_dict, memory_limit=None):
def __call__(self, inputs: List[ArrayIndexType], output: ArrayIndexType, size_dict: dict[str, int], memory_limit: int | None = None) -> list[tuple[int, ...]]:
\"\"\"
Parameters:
inputs : list[set[str]]
The indices of each input array.
outputs : set[str]
The output indices
size_dict : dict[str, int]
The size of each index
memory_limit : int, optional
If given, the maximum allowed memory.
inputs: The indices of each input array.
outputs: The output indices
size_dict: The size of each index
memory_limit: If given, the maximum allowed memory.
\"\"\"
# ... compute path here ...
return path
Expand Down
Loading

0 comments on commit 7a99d78

Please sign in to comment.