mirror of
https://github.com/wassname/jaxtyping.git
synced 2026-09-11 12:21:38 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c25da278a | ||
|
|
e2f004afd4 | ||
|
|
81c56052e5 | ||
|
|
d911ebb99c | ||
|
|
f30b7d1546 | ||
|
|
4b3f834e12 |
@@ -26,7 +26,7 @@ jobs:
|
|||||||
run-tests:
|
run-tests:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [ 3.7, 3.8, 3.9 ]
|
python-version: [ 3.8, 3.9 ]
|
||||||
os: [ ubuntu-latest ]
|
os: [ ubuntu-latest ]
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|||||||
@@ -23,13 +23,13 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
- repo: https://github.com/nbQA-dev/nbQA
|
- repo: https://github.com/nbQA-dev/nbQA
|
||||||
rev: 1.2.3
|
rev: 1.6.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: nbqa-black
|
- id: nbqa-black
|
||||||
- id: nbqa-isort
|
- id: nbqa-isort
|
||||||
- id: nbqa-flake8
|
- id: nbqa-flake8
|
||||||
- repo: https://github.com/PyCQA/isort
|
- repo: https://github.com/PyCQA/isort
|
||||||
rev: 5.10.1
|
rev: 5.12.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: isort
|
||||||
- repo: https://github.com/pycqa/flake8
|
- repo: https://github.com/pycqa/flake8
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ Float32[Array, "some_shape"]
|
|||||||
|
|
||||||
The array should typically be a `jaxtyping.Array`, which is an alias for `jax.numpy.ndarray`.
|
The array should typically be a `jaxtyping.Array`, which is an alias for `jax.numpy.ndarray`.
|
||||||
|
|
||||||
|
`jaxtyping.ArrayLike` is also available, which is an alias for `jax.typing.ArrayLike`. This is a union over JAX arrays and the builtin `bool`/`int`/`float`/`complex`.
|
||||||
|
|
||||||
But you can use other types as well. `jaxtyping` has support for JAX, NumPy, TensorFlow, and PyTorch, e.g.:
|
But you can use other types as well. `jaxtyping` has support for JAX, NumPy, TensorFlow, and PyTorch, e.g.:
|
||||||
```python
|
```python
|
||||||
Float[np.ndarray, "..."]
|
Float[np.ndarray, "..."]
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
Type annotations **and runtime checking** for:
|
Type annotations **and runtime checking** for:
|
||||||
|
|
||||||
1. shape and dtype of [JAX](https://github.com/google/jax) arrays;
|
1. shape and dtype of [JAX](https://github.com/google/jax) arrays; *(Now also supports PyTorch, NumPy, and TensorFlow!)*
|
||||||
2. [PyTrees](https://jax.readthedocs.io/en/latest/pytrees.html).
|
2. [PyTrees](https://jax.readthedocs.io/en/latest/pytrees.html).
|
||||||
|
|
||||||
|
|
||||||
**For example:**
|
**For example:**
|
||||||
```python
|
```python
|
||||||
from jaxtyping import Array, Float, PyTree
|
from jaxtyping import Array, Float, PyTree
|
||||||
@@ -28,6 +29,10 @@ def accepts_pytree_of_arrays(x: PyTree[Float[Array, "batch c1 c2"]]):
|
|||||||
pip install jaxtyping
|
pip install jaxtyping
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Requires Python 3.8+.
|
||||||
|
|
||||||
|
JAX is an optional dependency, required for `jaxtyping.{Array, ArrayLike, PyTree}`. If JAX is not installed then these types will not be available, but you may still use jaxtyping alongside PyTorch/NumPy/etc.
|
||||||
|
|
||||||
Also install your favourite runtime type-checking package. The two most popular are [typeguard](https://github.com/agronholm/typeguard) (which exhaustively checks every argument) and [beartype](https://github.com/beartype/beartype) (which checks random pieces of arguments).
|
Also install your favourite runtime type-checking package. The two most popular are [typeguard](https://github.com/agronholm/typeguard) (which exhaustively checks every argument) and [beartype](https://github.com/beartype/beartype) (which checks random pieces of arguments).
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
@@ -48,12 +53,6 @@ Computer vision models: [Eqxvision](https://github.com/paganpasta/eqxvision).
|
|||||||
|
|
||||||
SymPy<->JAX conversion; train symbolic expressions via gradient descent: [sympy2jax](https://github.com/google/sympy2jax).
|
SymPy<->JAX conversion; train symbolic expressions via gradient descent: [sympy2jax](https://github.com/google/sympy2jax).
|
||||||
|
|
||||||
### Acknowledgements
|
|
||||||
|
|
||||||
Shape annotations + runtime type checking is inspired by [TorchTyping](https://github.com/patrick-kidger/torchtyping).
|
|
||||||
|
|
||||||
The concise syntax is partially inspired by [etils.array_types](https://github.com/google/etils/tree/main/etils/array_types).
|
|
||||||
|
|
||||||
### Disclaimer
|
### Disclaimer
|
||||||
|
|
||||||
This is not an official Google product.
|
This is not an official Google product.
|
||||||
|
|||||||
+12
-2
@@ -18,7 +18,6 @@
|
|||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
import typing
|
import typing
|
||||||
import typing_extensions
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -35,6 +34,7 @@ if typing.TYPE_CHECKING:
|
|||||||
# For imports, we need to explicitly `import X as X` in order for Pyright to see
|
# For imports, we need to explicitly `import X as X` in order for Pyright to see
|
||||||
# them as public. See discussion at https://github.com/microsoft/pyright/issues/2277
|
# them as public. See discussion at https://github.com/microsoft/pyright/issues/2277
|
||||||
from jax import Array as Array
|
from jax import Array as Array
|
||||||
|
from jax.typing import ArrayLike as ArrayLike
|
||||||
elif has_jax:
|
elif has_jax:
|
||||||
if getattr(typing, "GENERATING_DOCUMENTATION", False):
|
if getattr(typing, "GENERATING_DOCUMENTATION", False):
|
||||||
|
|
||||||
@@ -42,9 +42,19 @@ elif has_jax:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
Array.__module__ = "builtins"
|
Array.__module__ = "builtins"
|
||||||
|
|
||||||
|
class ArrayLike:
|
||||||
|
pass
|
||||||
|
|
||||||
|
ArrayLike.__module__ = "builtins"
|
||||||
else:
|
else:
|
||||||
from jax import Array as Array
|
from jax import Array as Array
|
||||||
|
|
||||||
|
try:
|
||||||
|
from jax.typing import ArrayLike as ArrayLike
|
||||||
|
except (ModuleNotFoundError, ImportError):
|
||||||
|
pass
|
||||||
|
|
||||||
from .array_types import (
|
from .array_types import (
|
||||||
AbstractArray as AbstractArray,
|
AbstractArray as AbstractArray,
|
||||||
AbstractDtype as AbstractDtype,
|
AbstractDtype as AbstractDtype,
|
||||||
@@ -102,4 +112,4 @@ elif has_jax:
|
|||||||
|
|
||||||
del has_jax
|
del has_jax
|
||||||
|
|
||||||
__version__ = "0.2.11"
|
__version__ = "0.2.13"
|
||||||
|
|||||||
+149
-29
@@ -20,8 +20,17 @@
|
|||||||
import enum
|
import enum
|
||||||
import functools as ft
|
import functools as ft
|
||||||
import typing
|
import typing
|
||||||
from typing import Any, Dict, List, NoReturn, Optional, Tuple, TYPE_CHECKING, Union
|
from typing import (
|
||||||
from typing_extensions import Literal
|
Any,
|
||||||
|
Dict,
|
||||||
|
List,
|
||||||
|
Literal,
|
||||||
|
NoReturn,
|
||||||
|
Optional,
|
||||||
|
Tuple,
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Union,
|
||||||
|
)
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
@@ -58,24 +67,72 @@ class _NamedDim:
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.broadcastable = broadcastable
|
self.broadcastable = broadcastable
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if type(self) is not type(other):
|
||||||
|
return False
|
||||||
|
if self.name != other.name:
|
||||||
|
return False
|
||||||
|
if self.broadcastable != other.broadcastable:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((self.name, self.broadcastable))
|
||||||
|
|
||||||
|
|
||||||
class _NamedVariadicDim:
|
class _NamedVariadicDim:
|
||||||
def __init__(self, name, broadcastable):
|
def __init__(self, name, broadcastable):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.broadcastable = broadcastable
|
self.broadcastable = broadcastable
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if type(self) is not type(other):
|
||||||
|
return False
|
||||||
|
if self.name != other.name:
|
||||||
|
return False
|
||||||
|
if self.broadcastable != other.broadcastable:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((self.name, self.broadcastable))
|
||||||
|
|
||||||
|
|
||||||
class _FixedDim:
|
class _FixedDim:
|
||||||
def __init__(self, size, broadcastable):
|
def __init__(self, size, broadcastable):
|
||||||
self.size = size
|
self.size = size
|
||||||
self.broadcastable = broadcastable
|
self.broadcastable = broadcastable
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if type(self) is not type(other):
|
||||||
|
return False
|
||||||
|
if self.size != other.size:
|
||||||
|
return False
|
||||||
|
if self.broadcastable != other.broadcastable:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((self.size, self.broadcastable))
|
||||||
|
|
||||||
|
|
||||||
class _SymbolicDim:
|
class _SymbolicDim:
|
||||||
def __init__(self, expr, broadcastable):
|
def __init__(self, expr, broadcastable):
|
||||||
self.expr = expr
|
self.expr = expr
|
||||||
self.broadcastable = broadcastable
|
self.broadcastable = broadcastable
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if type(self) is not type(other):
|
||||||
|
return False
|
||||||
|
if self.expr != other.expr:
|
||||||
|
return False
|
||||||
|
if self.broadcastable != other.broadcastable:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((self.expr, self.broadcastable))
|
||||||
|
|
||||||
|
|
||||||
_AbstractDimOrVariadicDim = Union[
|
_AbstractDimOrVariadicDim = Union[
|
||||||
Literal[_anonymous_dim],
|
Literal[_anonymous_dim],
|
||||||
@@ -127,6 +184,22 @@ def _check_dims(
|
|||||||
|
|
||||||
|
|
||||||
class _MetaAbstractArray(type):
|
class _MetaAbstractArray(type):
|
||||||
|
def __eq__(self, other):
|
||||||
|
if type(self) is not type(other):
|
||||||
|
return False
|
||||||
|
if self.array_type is not other.array_type:
|
||||||
|
return False
|
||||||
|
if self.dtypes != other.dtypes:
|
||||||
|
return False
|
||||||
|
if self.dims != other.dims:
|
||||||
|
return False
|
||||||
|
if self.index_variadic != other.index_variadic:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((self.array_type, self.dtypes, self.dims, self.index_variadic))
|
||||||
|
|
||||||
def __instancecheck__(cls, obj):
|
def __instancecheck__(cls, obj):
|
||||||
if not isinstance(obj, cls.array_type):
|
if not isinstance(obj, cls.array_type):
|
||||||
return False
|
return False
|
||||||
@@ -234,6 +307,12 @@ class _MetaAbstractArray(type):
|
|||||||
assert False
|
assert False
|
||||||
|
|
||||||
|
|
||||||
|
def _check_scalar(dtype, dtypes, dims):
|
||||||
|
if len(dims) != 0:
|
||||||
|
return False
|
||||||
|
return (_any_dtype is dtypes) or any(d.startswith(dtype) for d in dtypes)
|
||||||
|
|
||||||
|
|
||||||
class AbstractArray(metaclass=_MetaAbstractArray):
|
class AbstractArray(metaclass=_MetaAbstractArray):
|
||||||
array_type: Any
|
array_type: Any
|
||||||
dtypes: List[str]
|
dtypes: List[str]
|
||||||
@@ -383,31 +462,68 @@ class _MetaAbstractDtype(type):
|
|||||||
elem = compile(elem, "<string>", "eval")
|
elem = compile(elem, "<string>", "eval")
|
||||||
elem = _SymbolicDim(elem, broadcastable)
|
elem = _SymbolicDim(elem, broadcastable)
|
||||||
dims.append(elem)
|
dims.append(elem)
|
||||||
# In python 3.8, e.g., typing.Union lacks `__name__`.
|
dims = tuple(dims)
|
||||||
try:
|
|
||||||
type_str = array_type.__name__
|
_not_made = object()
|
||||||
except AttributeError:
|
|
||||||
type_str = repr(array_type)
|
def _make(x):
|
||||||
if _array_name_format == "dtype_and_shape":
|
# Allow Python built-in numeric types.
|
||||||
name = f"{cls.__name__}[{type_str}, '{dim_str}']"
|
# TODO: do something more generic than this? Should we _make all types
|
||||||
elif _array_name_format == "array":
|
# that have `shape` and `dtype` attributes or something?
|
||||||
name = type_str
|
if x is bool:
|
||||||
|
if _check_scalar("bool", cls.dtypes, dims):
|
||||||
|
return x
|
||||||
|
else:
|
||||||
|
return _not_made
|
||||||
|
elif x is int:
|
||||||
|
if _check_scalar("int", cls.dtypes, dims):
|
||||||
|
return x
|
||||||
|
else:
|
||||||
|
return _not_made
|
||||||
|
elif x is float:
|
||||||
|
if _check_scalar("float", cls.dtypes, dims):
|
||||||
|
return x
|
||||||
|
else:
|
||||||
|
return _not_made
|
||||||
|
elif x is complex:
|
||||||
|
if _check_scalar("complex", cls.dtypes, dims):
|
||||||
|
return x
|
||||||
|
else:
|
||||||
|
return _not_made
|
||||||
|
try:
|
||||||
|
type_str = x.__name__
|
||||||
|
except AttributeError:
|
||||||
|
type_str = repr(x)
|
||||||
|
if _array_name_format == "dtype_and_shape":
|
||||||
|
name = f"{cls.__name__}[{type_str}, '{dim_str}']"
|
||||||
|
elif _array_name_format == "array":
|
||||||
|
name = type_str
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
f"array_name_format {_array_name_format} not recognised"
|
||||||
|
)
|
||||||
|
out = _MetaAbstractArray(
|
||||||
|
name,
|
||||||
|
(AbstractArray,),
|
||||||
|
dict(
|
||||||
|
array_type=x,
|
||||||
|
dtypes=cls.dtypes,
|
||||||
|
dims=dims,
|
||||||
|
index_variadic=index_variadic,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if getattr(typing, "GENERATING_DOCUMENTATION", False):
|
||||||
|
out.__module__ = "builtins"
|
||||||
|
else:
|
||||||
|
out.__module__ = "jaxtyping"
|
||||||
|
return out
|
||||||
|
|
||||||
|
if typing.get_origin(array_type) is typing.Union:
|
||||||
|
out = [_make(x) for x in typing.get_args(array_type)]
|
||||||
|
out = tuple(x for x in out if x is not _not_made)
|
||||||
|
out = Union[out]
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"array_name_format {_array_name_format} not recognised")
|
out = _make(array_type)
|
||||||
out = _MetaAbstractArray(
|
|
||||||
name,
|
|
||||||
(AbstractArray,),
|
|
||||||
dict(
|
|
||||||
array_type=array_type,
|
|
||||||
dtypes=cls.dtypes,
|
|
||||||
dims=dims,
|
|
||||||
index_variadic=index_variadic,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if getattr(typing, "GENERATING_DOCUMENTATION", False):
|
|
||||||
out.__module__ = "builtins"
|
|
||||||
else:
|
|
||||||
out.__module__ = "jaxtyping"
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
@@ -425,7 +541,9 @@ class AbstractDtype(metaclass=_MetaAbstractDtype):
|
|||||||
|
|
||||||
dtypes: Union[Literal[_any_dtype], str, List[str]] = cls.dtypes
|
dtypes: Union[Literal[_any_dtype], str, List[str]] = cls.dtypes
|
||||||
if isinstance(dtypes, str):
|
if isinstance(dtypes, str):
|
||||||
dtypes = [dtypes]
|
dtypes = (dtypes,)
|
||||||
|
elif dtypes is not _any_dtype:
|
||||||
|
dtypes = tuple(dtypes)
|
||||||
cls.dtypes = dtypes
|
cls.dtypes = dtypes
|
||||||
|
|
||||||
|
|
||||||
@@ -459,7 +577,8 @@ if TYPE_CHECKING:
|
|||||||
Annotated as UInt64,
|
Annotated as UInt64,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
_bool = "bool_"
|
_bool = "bool"
|
||||||
|
_bool_ = "bool_"
|
||||||
_uint8 = "uint8"
|
_uint8 = "uint8"
|
||||||
_uint16 = "uint16"
|
_uint16 = "uint16"
|
||||||
_uint32 = "uint32"
|
_uint32 = "uint32"
|
||||||
@@ -502,6 +621,7 @@ else:
|
|||||||
Complex64 = _make_dtype(_complex64, "Complex64")
|
Complex64 = _make_dtype(_complex64, "Complex64")
|
||||||
Complex128 = _make_dtype(_complex128, "Complex128")
|
Complex128 = _make_dtype(_complex128, "Complex128")
|
||||||
|
|
||||||
|
bools = [_bool, _bool_]
|
||||||
uints = [_uint8, _uint16, _uint32, _uint64]
|
uints = [_uint8, _uint16, _uint32, _uint64]
|
||||||
ints = [_int8, _int16, _int32, _int64]
|
ints = [_int8, _int16, _int32, _int64]
|
||||||
floats = [_bfloat16, _float16, _float32, _float64]
|
floats = [_bfloat16, _float16, _float32, _float64]
|
||||||
@@ -510,7 +630,7 @@ else:
|
|||||||
# We match NumPy's type hierarachy in what types to provide. See the diagram at
|
# We match NumPy's type hierarachy in what types to provide. See the diagram at
|
||||||
# https://numpy.org/doc/stable/reference/arrays.scalars.html#scalars
|
# https://numpy.org/doc/stable/reference/arrays.scalars.html#scalars
|
||||||
|
|
||||||
Bool = _make_dtype(_bool, "Bool")
|
Bool = _make_dtype(bools, "Bool")
|
||||||
UInt = _make_dtype(uints, "UInt")
|
UInt = _make_dtype(uints, "UInt")
|
||||||
Int = _make_dtype(ints, "Int")
|
Int = _make_dtype(ints, "Int")
|
||||||
Integer = _make_dtype(uints + ints, "Integer")
|
Integer = _make_dtype(uints + ints, "Integer")
|
||||||
|
|||||||
@@ -66,9 +66,11 @@ def _call_with_frames_removed(f, *args, **kwargs):
|
|||||||
|
|
||||||
def _optimized_cache_from_source(path, debug_override=None):
|
def _optimized_cache_from_source(path, debug_override=None):
|
||||||
# Version 2: change the position of the `@jaxtyped` decorator, so need a
|
# Version 2: change the position of the `@jaxtyped` decorator, so need a
|
||||||
# different name to avoid hitting old __pycache__
|
# different name to avoid hitting old __pycache__.
|
||||||
# Version 3: now also annotating classes.
|
# Version 3: now also annotating classes.
|
||||||
return cache_from_source(path, debug_override, optimization="jaxtyping3")
|
# Version 4: I'm honestly not sure, but bumping this fixed some kind of odd error.
|
||||||
|
# Maybe I changed something with hte classes part way through version 3?
|
||||||
|
return cache_from_source(path, debug_override, optimization="jaxtyping4")
|
||||||
|
|
||||||
|
|
||||||
def _dot_lookup(*elements):
|
def _dot_lookup(*elements):
|
||||||
@@ -111,7 +113,7 @@ class _JaxtypingTransformer(ast.NodeVisitor):
|
|||||||
args = [ast.Constant(None)]
|
args = [ast.Constant(None)]
|
||||||
else:
|
else:
|
||||||
args = [_dot_lookup(*self._typechecker)]
|
args = [_dot_lookup(*self._typechecker)]
|
||||||
node.decorator_list.append(ast.Call(func, args, keywords=[]))
|
node.decorator_list.insert(0, ast.Call(func, args, keywords=[]))
|
||||||
self._parents.append(node)
|
self._parents.append(node)
|
||||||
self.generic_visit(node)
|
self.generic_visit(node)
|
||||||
self._parents.pop()
|
self._parents.pop()
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ classifiers = [
|
|||||||
"Topic :: Scientific/Engineering :: Mathematics",
|
"Topic :: Scientific/Engineering :: Mathematics",
|
||||||
]
|
]
|
||||||
|
|
||||||
python_requires = "~=3.7"
|
python_requires = "~=3.8"
|
||||||
|
|
||||||
# We use typeguard internally (in a fairly minimal way), but it's not required that
|
# We use typeguard internally (in a fairly minimal way), but it's not required that
|
||||||
# end users make the same choice.
|
# end users make the same choice.
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
import dataclasses
|
||||||
|
|
||||||
import equinox as eqx
|
import equinox as eqx
|
||||||
import jax.numpy as jnp
|
import jax.numpy as jnp
|
||||||
import pytest
|
import pytest
|
||||||
@@ -45,3 +47,16 @@ with pytest.raises(ParamError):
|
|||||||
M(1.0, jnp.array([1.0]))
|
M(1.0, jnp.array([1.0]))
|
||||||
with pytest.raises(ParamError):
|
with pytest.raises(ParamError):
|
||||||
M(1, jnp.array(1.0))
|
M(1, jnp.array(1.0))
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class D:
|
||||||
|
foo: int
|
||||||
|
bar: Float32[jnp.ndarray, " a"]
|
||||||
|
|
||||||
|
|
||||||
|
D(1, jnp.array([1.0]))
|
||||||
|
with pytest.raises(ParamError):
|
||||||
|
D(1.0, jnp.array([1.0]))
|
||||||
|
with pytest.raises(ParamError):
|
||||||
|
D(1, jnp.array(1.0))
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
import dataclasses
|
||||||
|
|
||||||
import equinox as eqx
|
import equinox as eqx
|
||||||
import jax.numpy as jnp
|
import jax.numpy as jnp
|
||||||
import pytest
|
import pytest
|
||||||
@@ -45,3 +47,16 @@ with pytest.raises(ParamError):
|
|||||||
M(1.0, jnp.array([1.0]))
|
M(1.0, jnp.array([1.0]))
|
||||||
with pytest.raises(ParamError):
|
with pytest.raises(ParamError):
|
||||||
M(1, jnp.array(1.0))
|
M(1, jnp.array(1.0))
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class D:
|
||||||
|
foo: int
|
||||||
|
bar: Float32[jnp.ndarray, " a"]
|
||||||
|
|
||||||
|
|
||||||
|
D(1, jnp.array([1.0]))
|
||||||
|
with pytest.raises(ParamError):
|
||||||
|
D(1.0, jnp.array([1.0]))
|
||||||
|
with pytest.raises(ParamError):
|
||||||
|
D(1, jnp.array(1.0))
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
import dataclasses
|
||||||
|
|
||||||
import equinox as eqx
|
import equinox as eqx
|
||||||
import jax.numpy as jnp
|
import jax.numpy as jnp
|
||||||
import pytest
|
import pytest
|
||||||
@@ -45,3 +47,16 @@ with pytest.raises(ParamError):
|
|||||||
M(1.0, jnp.array([1.0]))
|
M(1.0, jnp.array([1.0]))
|
||||||
with pytest.raises(ParamError):
|
with pytest.raises(ParamError):
|
||||||
M(1, jnp.array(1.0))
|
M(1, jnp.array(1.0))
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class D:
|
||||||
|
foo: int
|
||||||
|
bar: Float32[jnp.ndarray, " a"]
|
||||||
|
|
||||||
|
|
||||||
|
D(1, jnp.array([1.0]))
|
||||||
|
with pytest.raises(ParamError):
|
||||||
|
D(1.0, jnp.array([1.0]))
|
||||||
|
with pytest.raises(ParamError):
|
||||||
|
D(1, jnp.array(1.0))
|
||||||
|
|||||||
+55
-1
@@ -17,11 +17,14 @@
|
|||||||
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
from typing import get_args, get_origin, Union
|
||||||
|
|
||||||
import jax.numpy as jnp
|
import jax.numpy as jnp
|
||||||
import jax.random as jr
|
import jax.random as jr
|
||||||
|
import numpy as np
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from jaxtyping import AbstractDtype, Array, Float, Float32, jaxtyped, Shaped
|
from jaxtyping import AbstractDtype, Array, ArrayLike, Float, Float32, jaxtyped, Shaped
|
||||||
|
|
||||||
from .helpers import ParamError, ReturnError
|
from .helpers import ParamError, ReturnError
|
||||||
|
|
||||||
@@ -409,3 +412,54 @@ def test_incomplete_symbolic(typecheck, getkey):
|
|||||||
x = jr.normal(getkey(), (4,))
|
x = jr.normal(getkey(), (4,))
|
||||||
with pytest.raises(NameError):
|
with pytest.raises(NameError):
|
||||||
foo(x)
|
foo(x)
|
||||||
|
|
||||||
|
|
||||||
|
def test_arraylike(typecheck, getkey):
|
||||||
|
floatlike1 = Float32[ArrayLike, ""]
|
||||||
|
floatlike2 = Float[ArrayLike, ""]
|
||||||
|
floatlike3 = Float32[ArrayLike, "4"]
|
||||||
|
|
||||||
|
assert get_origin(floatlike1) is Union
|
||||||
|
assert get_origin(floatlike2) is Union
|
||||||
|
assert get_origin(floatlike3) is Union
|
||||||
|
assert set(get_args(floatlike1)) == {
|
||||||
|
Float32[Array, ""],
|
||||||
|
Float32[np.ndarray, ""],
|
||||||
|
Float32[np.bool_, ""],
|
||||||
|
Float32[np.number, ""],
|
||||||
|
float,
|
||||||
|
}
|
||||||
|
assert set(get_args(floatlike2)) == {
|
||||||
|
Float[Array, ""],
|
||||||
|
Float[np.ndarray, ""],
|
||||||
|
Float[np.bool_, ""],
|
||||||
|
Float[np.number, ""],
|
||||||
|
float,
|
||||||
|
}
|
||||||
|
assert set(get_args(floatlike3)) == {
|
||||||
|
Float32[Array, "4"],
|
||||||
|
Float32[np.ndarray, "4"],
|
||||||
|
Float32[np.bool_, "4"],
|
||||||
|
Float32[np.number, "4"],
|
||||||
|
}
|
||||||
|
|
||||||
|
shaped1 = Shaped[ArrayLike, ""]
|
||||||
|
shaped2 = Shaped[ArrayLike, "4"]
|
||||||
|
assert get_origin(shaped1) is Union
|
||||||
|
assert get_origin(shaped2) is Union
|
||||||
|
assert set(get_args(shaped1)) == {
|
||||||
|
Shaped[Array, ""],
|
||||||
|
Shaped[np.ndarray, ""],
|
||||||
|
Shaped[np.bool_, ""],
|
||||||
|
Shaped[np.number, ""],
|
||||||
|
bool,
|
||||||
|
int,
|
||||||
|
float,
|
||||||
|
complex,
|
||||||
|
}
|
||||||
|
assert set(get_args(shaped2)) == {
|
||||||
|
Shaped[Array, "4"],
|
||||||
|
Shaped[np.ndarray, "4"],
|
||||||
|
Shaped[np.bool_, "4"],
|
||||||
|
Shaped[np.number, "4"],
|
||||||
|
}
|
||||||
|
|||||||
+15
-2
@@ -1,13 +1,26 @@
|
|||||||
|
import abc
|
||||||
|
|
||||||
from jaxtyping import jaxtyped
|
from jaxtyping import jaxtyped
|
||||||
|
|
||||||
|
|
||||||
class M:
|
class M(metaclass=abc.ABCMeta):
|
||||||
@jaxtyped
|
@jaxtyped
|
||||||
@classmethod
|
@classmethod
|
||||||
def f(cls):
|
def f(cls):
|
||||||
return 3
|
return 3
|
||||||
|
|
||||||
|
@jaxtyped
|
||||||
|
@abc.abstractmethod
|
||||||
|
def g(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
# Check that the @jaxtyped decorator doesn't blat the __get__ of @classmethod
|
# Check that the @jaxtyped decorator doesn't blat the __get__ of @classmethod
|
||||||
def test_decorator():
|
def test_classmethod():
|
||||||
assert M.f() == 3
|
assert M.f() == 3
|
||||||
|
|
||||||
|
|
||||||
|
# Check that the @jaxtyped decorator doesn't blat the __isabstractmethod__ of
|
||||||
|
# @abstractmethod
|
||||||
|
def test_abstractmethod():
|
||||||
|
assert M.g.__isabstractmethod__
|
||||||
|
|||||||
Reference in New Issue
Block a user