Compare commits

..
5 Commits
8 changed files with 218 additions and 24 deletions
+3 -4
View File
@@ -95,16 +95,15 @@ BatchImage = Float[Array, "batch channels height width"]
Note that `jaxtyping.{Array, ArrayLike}` are only available if JAX has been installed.
## Scalars, PRNGKeys
## Scalars, PRNG keys
For convenience, jaxtyping also includes `jaxtyping.Scalar`, `jaxtyping.ScalarLike`, and `jaxtyping.PRNGKeyArray`, defined as:
```python
Scalar = Shaped[Array, ""]
ScalarLike = Shaped[ArrayLike, ""]
# Depending on the value of `JAX_ENABLE_CUSTOM_PRNG`:
PRNGKeyArray = Key[Array, ""]
PRNGKeyArray = UInt32[Array, "2"]
# Left: new-style typed keys; right: old-style keys. See JEP 9263.
PRNGKeyArray = Union[Key[Array, ""], UInt32[Array, "2"]]
```
Recalling that shape-and-dtype specified jaxtyping arrays can be nested, this means that e.g. you can annotate the output of `jax.random.split` with `Shaped[PRNGKeyArray, "2"]`, or e.g. an integer scalar with `Int[Scalar, ""]`.
+12
View File
@@ -20,3 +20,15 @@ It can be a lot of effort to add `@jaxtyped` decorators all over your codebase.
The easier option is usually to use the import hook.
::: jaxtyping.install_import_hook
---
#### IPython extension
If you are running in an IPython environment (for example a Jupyter or Colab notebook), then the jaxtyping hook can be automatically ran via a custom magic:
```python
import jaxtyping
%load_ext jaxtyping
%jaxtyping.typechecker beartype.beartype # or any other runtime type checker
```
Place this at the start of your notebook -- everything that is directly defined in the notebook, after this magic is run, will be hook'd.
+2 -9
View File
@@ -20,24 +20,17 @@
import importlib.metadata
import typing
try:
import jax
except ImportError:
has_jax = False
else:
has_jax = True
del jax
# First import some things as normal
from ._array_types import (
AbstractArray as AbstractArray,
AbstractDtype as AbstractDtype,
get_array_name_format as get_array_name_format,
has_jax,
set_array_name_format as set_array_name_format,
)
from ._decorator import jaxtyped as jaxtyped
from ._import_hook import install_import_hook as install_import_hook
from ._ipython_extension import load_ipython_extension as load_ipython_extension
# Now import Array and ArrayLike
+13 -10
View File
@@ -32,7 +32,11 @@ from ._decorator import storage
try:
import jax
except ImportError:
except (ImportError, RuntimeError, AttributeError):
# We catch `RuntimeError` as JAX will throw this if it's present, but unable to run
# on the current machine. This fails with this error.
# We catch `AttributeError` as the above then leaves the module in a partially
# initialised state, which causes subsequent imports to fail with this error.
has_jax = False
else:
has_jax = True
@@ -521,8 +525,9 @@ class _MetaAbstractDtype(type):
def __getitem__(cls, item: tuple[Any, str]):
if not isinstance(item, tuple) or len(item) != 2:
raise ValueError(
"As of jaxtyping v0.2.0, type annotations must now include an explicit "
"array type. For example `jaxtyping.Float32[jax.Array, 'foo bar']`."
"As of jaxtyping v0.2.0, type annotations must now include both an "
"array type and a shape. For example `Float[Array, 'foo bar']`.\n"
"Ellipsis can be used to accept any shape: `Float[Array, '...']`."
)
array_type, dim_str = item
del item
@@ -657,12 +662,10 @@ Num = _make_dtype(uints + ints + floats + complexes, "Num")
Shaped = _make_dtype(_any_dtype, "Shaped")
if has_jax:
if jax.config.jax_enable_custom_prng:
_key_regex = re.compile(r"^key<\w+>$")
Key = _make_dtype(_key_regex, "Key")
PRNGKeyArray = Key[jax.Array, ""]
else:
Key = UInt32
PRNGKeyArray = Key[jax.Array, "2"]
_key_regex = re.compile(r"^key<\w+>$")
Key = _make_dtype(_key_regex, "Key")
# New-style `jax.random.key` have scalar shape and dtype `key<foo>`.
# Old-style `jax.random.PRNGKey` have shape `(2,)` and dtype `uint32`.
PRNGKeyArray = Union[Key[jax.Array, ""], UInt32[jax.Array, "2"]]
Scalar = Shaped[jax.Array, ""]
ScalarLike = Shaped[jax.typing.ArrayLike, ""]
+35
View File
@@ -0,0 +1,35 @@
from ._import_hook import _JaxtypingTransformer
try:
from IPython.core.magic import line_magic, Magics, magics_class
@magics_class
class ChooseTypecheckerMagics(Magics):
@line_magic("jaxtyping.typechecker")
def typechecker(self, typechecker):
# remove old _JaxtypingTransformer, if present
self.shell.ast_transformers = list(
filter(
lambda x: not isinstance(x, _JaxtypingTransformer),
self.shell.ast_transformers,
)
)
# add new one
self.shell.ast_transformers.append(
_JaxtypingTransformer(typechecker=typechecker)
)
except ImportError:
pass
def load_ipython_extension(ipython):
try:
ipython.register_magics(ChooseTypecheckerMagics)
except NameError:
raise NameError(
"ChooseTypecheckerMagics is not defined.\n\n"
+ "You may be trying to use IPython extension without IPython installed."
)
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "jaxtyping"
version = "0.2.21"
version = "0.2.22"
description = "Type annotations and runtime checking for shape and dtype of JAX arrays, and PyTrees."
readme = "README.md"
requires-python ="~=3.9"
+1
View File
@@ -4,3 +4,4 @@ equinox
jaxlib
pytest
typeguard<3
IPython
+151
View File
@@ -0,0 +1,151 @@
import pytest
from IPython.testing.globalipapp import start_ipython
from .helpers import ParamError
@pytest.fixture(scope="session")
def session_ip():
yield start_ipython()
@pytest.fixture(scope="function")
def ip(session_ip):
session_ip.run_cell(raw_cell="import jaxtyping")
session_ip.run_line_magic(magic_name="load_ext", line="jaxtyping")
session_ip.run_line_magic(
magic_name="jaxtyping.typechecker", line="beartype.beartype"
)
yield session_ip
def test_that_ipython_works(ip):
ip.run_cell(raw_cell="x = 1").raise_error()
assert ip.user_global_ns["x"] == 1
def test_function_beartype(ip):
ip.run_cell(
raw_cell="""
def f(x: int):
pass
"""
).raise_error()
ip.run_cell(raw_cell="f(1)").raise_error()
with pytest.raises(ParamError):
ip.run_cell(raw_cell='f("x")').raise_error()
def test_function_none(ip):
ip.run_cell(
raw_cell="""
def f(a,b,c):
pass
"""
).raise_error()
ip.run_cell(raw_cell='f(1,2,"k")').raise_error()
def test_function_jaxtyped(ip):
ip.run_cell(
raw_cell="""
from jaxtyping import Float, Array, Int
import jax
def g(x: Float[Array, "1"]):
return x + 1
"""
).raise_error()
ip.run_cell(raw_cell="g(jax.numpy.array([1.0]))").raise_error()
with pytest.raises(ParamError):
ip.run_cell(raw_cell="g(jax.numpy.array(1.0))").raise_error()
with pytest.raises(ParamError):
ip.run_cell(raw_cell="g(jax.numpy.array([1]))").raise_error()
with pytest.raises(ParamError):
ip.run_cell(raw_cell="g(jax.numpy.array([2, 3]))").raise_error()
with pytest.raises(ParamError):
ip.run_cell(raw_cell='g("string")').raise_error()
def test_function_jaxtyped_and_jitted(ip):
ip.run_cell(
raw_cell="""
from jaxtyping import Float, Array, Int
import jax
@jax.jit
def g(x: Float[Array, "1"]):
return x + 1
"""
).raise_error()
ip.run_cell(raw_cell="g(jax.numpy.array([1.0]))").raise_error()
with pytest.raises(ParamError):
ip.run_cell(raw_cell="g(jax.numpy.array(1.0))").raise_error()
with pytest.raises(ParamError):
ip.run_cell(raw_cell="g(jax.numpy.array([1]))").raise_error()
with pytest.raises(ParamError):
ip.run_cell(raw_cell="g(jax.numpy.array([2, 3]))").raise_error()
with pytest.raises(ParamError):
ip.run_cell(raw_cell='g("string")').raise_error()
def test_class_jaxtyped(ip):
ip.run_cell(
raw_cell="""
from jaxtyping import Float, Array, Int
import equinox as eqx
import jax
class A(eqx.Module):
x: Float[Array, "2"]
def do_something(self, y: Int[Array, ""]):
return self.x + y
"""
).raise_error()
ip.run_cell(raw_cell="a = A(jax.numpy.array([1.0, 2.0]))").raise_error()
ip.run_cell(raw_cell="a.do_something(jax.numpy.array(2))").raise_error()
with pytest.raises(ParamError):
ip.run_cell(raw_cell="A(jax.numpy.array([1.0]))").raise_error()
with pytest.raises(ParamError):
ip.run_cell(
raw_cell="a.do_something(jax.numpy.array([2.0, 3.0]))"
).raise_error()
def test_class_not_dataclass(ip):
ip.run_cell(
raw_cell="""
from jaxtyping import Float, Array, Int
import equinox as eqx
import jax
class A:
def __init__(self, x):
self.x = x
def do_something(self, y):
return x + y
"""
).raise_error()
ip.run_cell(raw_cell="a = A(jax.numpy.array([1.0, 2.0]))").raise_error()
ip.run_cell(raw_cell="a.do_something(jax.numpy.array(2))").raise_error()
ip.run_cell(raw_cell="A(jax.numpy.array([1.0]))").raise_error()
ip.run_cell(raw_cell="a.do_something(jax.numpy.array([2.0, 3.0]))").raise_error()