array-api-jit 0.0.0__tar.gz → 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: array-api-jit
3
- Version: 0.0.0
3
+ Version: 1.0.0
4
4
  Summary: JIT decorator supporting multiple array API compatible libraries
5
5
  Author-email: 34j <34j.95a2p@simplelogin.com>
6
6
  License: MIT
@@ -12,15 +12,15 @@ Classifier: Development Status :: 2 - Pre-Alpha
12
12
  Classifier: Intended Audience :: Developers
13
13
  Classifier: Natural Language :: English
14
14
  Classifier: Operating System :: OS Independent
15
- Classifier: Programming Language :: Python :: 3.9
16
15
  Classifier: Programming Language :: Python :: 3.10
17
16
  Classifier: Programming Language :: Python :: 3.11
18
17
  Classifier: Programming Language :: Python :: 3.12
19
18
  Classifier: Programming Language :: Python :: 3.13
20
19
  Classifier: Topic :: Software Development :: Libraries
21
- Requires-Python: >=3.9
20
+ Requires-Python: >=3.10
22
21
  Description-Content-Type: text/markdown
23
22
  License-File: LICENSE
23
+ Requires-Dist: array-api-compat>=1.11.2
24
24
  Dynamic: license-file
25
25
 
26
26
  # array API JIT
@@ -69,7 +69,49 @@ JIT decorator supporting multiple array API compatible libraries
69
69
 
70
70
  Install this via pip (or your favourite package manager):
71
71
 
72
- `pip install array-api-jit`
72
+ ```shell
73
+ pip install array-api-jit
74
+ ```
75
+
76
+ ## Usage
77
+
78
+ Simply decorate your function with `@jit()`:
79
+
80
+ ```python
81
+ from array_api_jit import jit
82
+
83
+
84
+ @jit()
85
+ def my_function(x: Any) -> Any:
86
+ xp = array_namespace(x)
87
+ return xp.sin(x) + xp.cos(x)
88
+ ```
89
+
90
+ ## Advanced Usage
91
+
92
+ You can specify the decorator, arguments, and keyword arguments for each library.
93
+
94
+ ```python
95
+ from array_api_jit import jit
96
+ from array_api_compat import array_namespace
97
+ from typing import Any
98
+ import numba
99
+
100
+
101
+ @jit(
102
+ {"numpy": numba.jit()}, # numba.jit is not used by default because it may not succeed
103
+ decorator_kwargs={
104
+ "jax": {"static_argnames": ["n"]}
105
+ }, # jax requires for-loop variable to be "static_argnames"
106
+ # fail_on_error: bool = False, # do not raise an error if the decorator fails (Default)
107
+ # rerun_on_error: bool = True, # re-run the original function if the wrapped function fails (NOT Default)
108
+ )
109
+ def sin_n_times(x: Any, n: int) -> Any:
110
+ xp = array_namespace(x)
111
+ for i in range(n):
112
+ x = xp.sin(x)
113
+ return x
114
+ ```
73
115
 
74
116
  ## Contributors ✨
75
117
 
@@ -44,7 +44,49 @@ JIT decorator supporting multiple array API compatible libraries
44
44
 
45
45
  Install this via pip (or your favourite package manager):
46
46
 
47
- `pip install array-api-jit`
47
+ ```shell
48
+ pip install array-api-jit
49
+ ```
50
+
51
+ ## Usage
52
+
53
+ Simply decorate your function with `@jit()`:
54
+
55
+ ```python
56
+ from array_api_jit import jit
57
+
58
+
59
+ @jit()
60
+ def my_function(x: Any) -> Any:
61
+ xp = array_namespace(x)
62
+ return xp.sin(x) + xp.cos(x)
63
+ ```
64
+
65
+ ## Advanced Usage
66
+
67
+ You can specify the decorator, arguments, and keyword arguments for each library.
68
+
69
+ ```python
70
+ from array_api_jit import jit
71
+ from array_api_compat import array_namespace
72
+ from typing import Any
73
+ import numba
74
+
75
+
76
+ @jit(
77
+ {"numpy": numba.jit()}, # numba.jit is not used by default because it may not succeed
78
+ decorator_kwargs={
79
+ "jax": {"static_argnames": ["n"]}
80
+ }, # jax requires for-loop variable to be "static_argnames"
81
+ # fail_on_error: bool = False, # do not raise an error if the decorator fails (Default)
82
+ # rerun_on_error: bool = True, # re-run the original function if the wrapped function fails (NOT Default)
83
+ )
84
+ def sin_n_times(x: Any, n: int) -> Any:
85
+ xp = array_namespace(x)
86
+ for i in range(n):
87
+ x = xp.sin(x)
88
+ return x
89
+ ```
48
90
 
49
91
  ## Contributors ✨
50
92
 
@@ -4,20 +4,19 @@ requires = [ "setuptools" ]
4
4
 
5
5
  [project]
6
6
  name = "array-api-jit"
7
- version = "0.0.0"
7
+ version = "1.0.0"
8
8
  description = "JIT decorator supporting multiple array API compatible libraries"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
11
11
  authors = [
12
12
  { name = "34j", email = "34j.95a2p@simplelogin.com" },
13
13
  ]
14
- requires-python = ">=3.9"
14
+ requires-python = ">=3.10"
15
15
  classifiers = [
16
16
  "Development Status :: 2 - Pre-Alpha",
17
17
  "Intended Audience :: Developers",
18
18
  "Natural Language :: English",
19
19
  "Operating System :: OS Independent",
20
- "Programming Language :: Python :: 3.9",
21
20
  "Programming Language :: Python :: 3.10",
22
21
  "Programming Language :: Python :: 3.11",
23
22
  "Programming Language :: Python :: 3.12",
@@ -26,6 +25,7 @@ classifiers = [
26
25
  ]
27
26
 
28
27
  dependencies = [
28
+ "array-api-compat>=1.11.2",
29
29
  ]
30
30
  urls."Bug Tracker" = "https://github.com/34j/array-api-jit/issues"
31
31
  urls.Changelog = "https://github.com/34j/array-api-jit/blob/main/CHANGELOG.md"
@@ -34,8 +34,12 @@ urls.repository = "https://github.com/34j/array-api-jit"
34
34
 
35
35
  [dependency-groups]
36
36
  dev = [
37
+ "cm-time>=0.1.2",
38
+ "jax>=0.4.30",
39
+ "numba>=0.60.0",
37
40
  "pytest>=8,<9",
38
41
  "pytest-cov>=6,<7",
42
+ "torch>=2.7.1",
39
43
  ]
40
44
  docs = [
41
45
  "furo>=2023.5.20; python_version>='3.11'",
@@ -45,7 +49,8 @@ docs = [
45
49
  ]
46
50
 
47
51
  [tool.ruff]
48
- line-length = 88
52
+ format.docstring-code-format = true
53
+ line-length = 100
49
54
  lint.select = [
50
55
  "B", # flake8-bugbear
51
56
  "D", # flake8-docstrings
@@ -83,9 +88,6 @@ lint.isort.known-first-party = [ "array_api_jit", "tests" ]
83
88
  addopts = """\
84
89
  -v
85
90
  -Wdefault
86
- --cov=array_api_jit
87
- --cov-report=term
88
- --cov-report=xml
89
91
  """
90
92
  pythonpath = [ "src" ]
91
93
 
@@ -0,0 +1,4 @@
1
+ __version__ = "1.0.0"
2
+ from ._main import jit
3
+
4
+ __all__ = ["jit"]
@@ -0,0 +1,176 @@
1
+ import importlib.util
2
+ import warnings
3
+ from collections.abc import Callable, Mapping, Sequence
4
+ from functools import cache, wraps
5
+ from types import ModuleType
6
+ from typing import Any, ParamSpec, TypeVar
7
+
8
+ from array_api_compat import (
9
+ array_namespace,
10
+ is_cupy_namespace,
11
+ is_dask_namespace,
12
+ is_jax_namespace,
13
+ is_numpy_namespace,
14
+ is_torch_namespace,
15
+ )
16
+
17
+ if importlib.util.find_spec("numba"):
18
+ import numpy as np
19
+ from numba.extending import overload
20
+
21
+ @overload(array_namespace)
22
+ def _array_namespace_overload(*args: Any) -> Any:
23
+ def inner(*args: Any) -> Any:
24
+ return np
25
+
26
+ return inner
27
+
28
+
29
+ P = ParamSpec("P")
30
+ T = TypeVar("T")
31
+ Pin = ParamSpec("Pin")
32
+ Tin = TypeVar("Tin")
33
+ Pinner = ParamSpec("Pinner")
34
+ Tinner = TypeVar("Tinner")
35
+ STR_TO_IS_NAMESPACE = {
36
+ "numpy": is_numpy_namespace,
37
+ "jax": is_jax_namespace,
38
+ "cupy": is_cupy_namespace,
39
+ "torch": is_torch_namespace,
40
+ "dask": is_dask_namespace,
41
+ }
42
+
43
+
44
+ def _default_decorator(
45
+ module: ModuleType,
46
+ /,
47
+ ) -> Callable[[Callable[P, T]], Callable[P, T]]:
48
+ if is_jax_namespace(module):
49
+ import jax
50
+
51
+ return jax.jit
52
+ elif is_numpy_namespace(module) or is_cupy_namespace(module):
53
+ # import numba
54
+
55
+ # return numba.jit()
56
+ # The success rate of numba.jit is low
57
+ return lambda x: x
58
+ elif is_torch_namespace(module):
59
+ import torch
60
+
61
+ return torch.compile
62
+ elif is_dask_namespace(module):
63
+ return lambda x: x
64
+ else:
65
+ return getattr(module, "jit", lambda x: x)
66
+
67
+
68
+ Decorator = Callable[[Callable[Pin, Tin]], Callable[Pin, Tin]]
69
+
70
+
71
+ def jit(
72
+ decorator: Mapping[str, Decorator[..., Any]] | None = None,
73
+ /,
74
+ *,
75
+ fail_on_error: bool = False,
76
+ rerun_on_error: bool = False,
77
+ decorator_args: Mapping[str, Sequence[Any]] | None = None,
78
+ decorator_kwargs: Mapping[str, Mapping[str, Any]] | None = None,
79
+ ) -> Callable[[Callable[P, T]], Callable[P, T]]:
80
+ """
81
+ Just-in-time compilation decorator with multiple backends.
82
+
83
+ Parameters
84
+ ----------
85
+ decorator : Mapping[str, Callable[[Callable[P, T]], Callable[P, T]]] | None, optional
86
+ The JIT decorator to use for each array namespace, by default None
87
+ fail_on_error : bool, optional
88
+ If True, raise an error if the JIT decorator fails to apply.
89
+ If False, just warn and return the original function, by default False
90
+ rerun_on_error : bool, optional
91
+ If True, rerun the function without JIT if the function
92
+ with JIT applied fails, by default False
93
+ decorator_args : Mapping[str, Sequence[Any]] | None, optional
94
+ Additional positional arguments to be passed along with the function
95
+ to the decorator for each array namespace, by default None
96
+ decorator_kwargs : Mapping[str, Mapping[str, Any]] | None, optional
97
+ Additional keyword arguments to be passed along with the function
98
+ to the decorator for each array namespace, by default None
99
+
100
+ Returns
101
+ -------
102
+ Callable[[Callable[P, T]], Callable[P, T]]
103
+ The JIT decorator that can be applied to a function.
104
+
105
+ Example
106
+ -------
107
+ >>> from array_api_jit import jit
108
+ >>> from array_api_compat import array_namespace
109
+ >>> from typing import Any
110
+ >>> import numba
111
+ >>> @jit(
112
+ ... {"numpy": numba.jit()}, # numba.jit is not used by default
113
+ ... decorator_kwargs={"jax": {"static_argnames": ["n"]}}, # jax requires static_argnames
114
+ ... )
115
+ ... def sin_n_times(x: Any, n: int) -> Any:
116
+ ... xp = array_namespace(x)
117
+ ... for i in range(n):
118
+ ... x = xp.sin(x)
119
+ ... return x
120
+
121
+ """
122
+
123
+ def new_decorator(f: Callable[Pinner, Tinner]) -> Callable[Pinner, Tinner]:
124
+ decorator_args_ = decorator_args or {}
125
+ decorator_kwargs_ = decorator_kwargs or {}
126
+ decorator_ = decorator or {}
127
+
128
+ @cache
129
+ def jit_cached(xp: ModuleType) -> Callable[Pinner, Tinner]:
130
+ for name_, is_namespace in STR_TO_IS_NAMESPACE.items():
131
+ if is_namespace(xp):
132
+ name = name_
133
+ else:
134
+ name = xp.__name__.split(".")[0]
135
+ decorator_args__ = decorator_args_.get(name, ())
136
+ decorator_kwargs__ = decorator_kwargs_.get(name, {})
137
+ if name in decorator_:
138
+ decorator_current = decorator_[name]
139
+ else:
140
+ decorator_current = _default_decorator(xp)
141
+ try:
142
+ return decorator_current(f, *decorator_args__, **decorator_kwargs__)
143
+ except Exception as e:
144
+ if fail_on_error:
145
+ raise RuntimeError(f"Failed to apply JIT decorator for {name}") from e
146
+ warnings.warn(
147
+ f"Failed to apply JIT decorator for {name}: {e}",
148
+ RuntimeWarning,
149
+ stacklevel=2,
150
+ )
151
+ return f
152
+
153
+ @wraps(f)
154
+ def inner(*args_inner: Pinner.args, **kwargs_inner: Pinner.kwargs) -> Tinner:
155
+ try:
156
+ xp = array_namespace(*args_inner)
157
+ except TypeError as e:
158
+ if e.args[0] == "Unrecognized array input":
159
+ return f(*args_inner, **kwargs_inner)
160
+ raise
161
+ f_jit = jit_cached(xp)
162
+ try:
163
+ return f_jit(*args_inner, **kwargs_inner)
164
+ except Exception as e:
165
+ if rerun_on_error:
166
+ warnings.warn(
167
+ f"JIT failed for {xp.__name__}: {e}. Rerunning without JIT.",
168
+ RuntimeWarning,
169
+ stacklevel=2,
170
+ )
171
+ return f(*args_inner, **kwargs_inner)
172
+ raise RuntimeError(f"Failed to run JIT function for {xp.__name__}") from e
173
+
174
+ return inner
175
+
176
+ return new_decorator
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: array-api-jit
3
- Version: 0.0.0
3
+ Version: 1.0.0
4
4
  Summary: JIT decorator supporting multiple array API compatible libraries
5
5
  Author-email: 34j <34j.95a2p@simplelogin.com>
6
6
  License: MIT
@@ -12,15 +12,15 @@ Classifier: Development Status :: 2 - Pre-Alpha
12
12
  Classifier: Intended Audience :: Developers
13
13
  Classifier: Natural Language :: English
14
14
  Classifier: Operating System :: OS Independent
15
- Classifier: Programming Language :: Python :: 3.9
16
15
  Classifier: Programming Language :: Python :: 3.10
17
16
  Classifier: Programming Language :: Python :: 3.11
18
17
  Classifier: Programming Language :: Python :: 3.12
19
18
  Classifier: Programming Language :: Python :: 3.13
20
19
  Classifier: Topic :: Software Development :: Libraries
21
- Requires-Python: >=3.9
20
+ Requires-Python: >=3.10
22
21
  Description-Content-Type: text/markdown
23
22
  License-File: LICENSE
23
+ Requires-Dist: array-api-compat>=1.11.2
24
24
  Dynamic: license-file
25
25
 
26
26
  # array API JIT
@@ -69,7 +69,49 @@ JIT decorator supporting multiple array API compatible libraries
69
69
 
70
70
  Install this via pip (or your favourite package manager):
71
71
 
72
- `pip install array-api-jit`
72
+ ```shell
73
+ pip install array-api-jit
74
+ ```
75
+
76
+ ## Usage
77
+
78
+ Simply decorate your function with `@jit()`:
79
+
80
+ ```python
81
+ from array_api_jit import jit
82
+
83
+
84
+ @jit()
85
+ def my_function(x: Any) -> Any:
86
+ xp = array_namespace(x)
87
+ return xp.sin(x) + xp.cos(x)
88
+ ```
89
+
90
+ ## Advanced Usage
91
+
92
+ You can specify the decorator, arguments, and keyword arguments for each library.
93
+
94
+ ```python
95
+ from array_api_jit import jit
96
+ from array_api_compat import array_namespace
97
+ from typing import Any
98
+ import numba
99
+
100
+
101
+ @jit(
102
+ {"numpy": numba.jit()}, # numba.jit is not used by default because it may not succeed
103
+ decorator_kwargs={
104
+ "jax": {"static_argnames": ["n"]}
105
+ }, # jax requires for-loop variable to be "static_argnames"
106
+ # fail_on_error: bool = False, # do not raise an error if the decorator fails (Default)
107
+ # rerun_on_error: bool = True, # re-run the original function if the wrapped function fails (NOT Default)
108
+ )
109
+ def sin_n_times(x: Any, n: int) -> Any:
110
+ xp = array_namespace(x)
111
+ for i in range(n):
112
+ x = xp.sin(x)
113
+ return x
114
+ ```
73
115
 
74
116
  ## Contributors ✨
75
117
 
@@ -3,10 +3,11 @@ README.md
3
3
  pyproject.toml
4
4
  setup.py
5
5
  src/array_api_jit/__init__.py
6
- src/array_api_jit/main.py
6
+ src/array_api_jit/_main.py
7
7
  src/array_api_jit/py.typed
8
8
  src/array_api_jit.egg-info/PKG-INFO
9
9
  src/array_api_jit.egg-info/SOURCES.txt
10
10
  src/array_api_jit.egg-info/dependency_links.txt
11
+ src/array_api_jit.egg-info/requires.txt
11
12
  src/array_api_jit.egg-info/top_level.txt
12
13
  tests/test_main.py
@@ -0,0 +1 @@
1
+ array-api-compat>=1.11.2
@@ -0,0 +1,133 @@
1
+ from os import environ
2
+ from sys import platform
3
+ from typing import Any
4
+
5
+ import numba
6
+ import numpy as np
7
+ import pytest
8
+ from array_api_compat import array_namespace
9
+ from cm_time import timer
10
+ from numba import prange
11
+ from numba.extending import overload
12
+
13
+ from array_api_jit import jit
14
+
15
+ IS_CI = environ.get("CI", "false").lower() in ("true", "1", "yes")
16
+
17
+
18
+ @overload(np.stack)
19
+ def stack(arrays, axis=0):
20
+ def inner(arrays, axis=0):
21
+ if axis == 0:
22
+ shape = (len(arrays),) + arrays[0].shape # noqa: RUF005
23
+ stacked_array = np.empty(shape, dtype=arrays[0].dtype)
24
+ for j in prange(len(arrays)):
25
+ stacked_array[j] = arrays[j]
26
+ elif axis == -1:
27
+ shape = arrays[0].shape + (len(arrays),) # noqa: RUF005
28
+ stacked_array = np.empty(shape, dtype=arrays[0].dtype)
29
+ for j in prange(len(arrays)):
30
+ stacked_array[..., j] = arrays[j]
31
+ return stacked_array
32
+
33
+ return inner
34
+
35
+
36
+ def legendre(x: Any, n_end: int) -> Any:
37
+ """
38
+ Legendre polynomial of order 0 to n_end-1.
39
+
40
+ Parameters
41
+ ----------
42
+ x : Any
43
+ The points at which to evaluate the polynomial.
44
+ n_end : int
45
+ The number of orders to compute, starting from 0.
46
+
47
+ Returns
48
+ -------
49
+ Any
50
+ Array of shape (*x.shape, n_end),
51
+ where [..., i] is the value of the i-th order polynomial at x.
52
+
53
+ """
54
+ xp = array_namespace(x)
55
+ prevprev = xp.zeros_like(x)
56
+ prev = xp.ones_like(x)
57
+ result = [prevprev, prev]
58
+ for n in range(2, n_end):
59
+ prevprev, prev = prev, (((2 * n - 1) * x * prev - (n - 1) * prevprev) / n)
60
+ result.append(prev)
61
+ return xp.stack(result[:n_end], axis=-1)
62
+
63
+
64
+ def legendre_assign(x: Any, n_end: int) -> Any:
65
+ """
66
+ Legendre polynomial of order 0 to n_end-1.
67
+
68
+ Parameters
69
+ ----------
70
+ x : Any
71
+ The points at which to evaluate the polynomial.
72
+ n_end : int
73
+ The number of orders to compute, starting from 0.
74
+
75
+ Returns
76
+ -------
77
+ Any
78
+ Array of shape (*x.shape, n_end),
79
+ where [..., i] is the value of the i-th order polynomial at x.
80
+
81
+ """
82
+ xp = array_namespace(x)
83
+ prevprev = xp.zeros_like(x)
84
+ prev = xp.ones_like(x)
85
+ result = xp.empty((*x.shape, n_end), dtype=x.dtype)
86
+ if n_end > 0:
87
+ result[..., 0] = prevprev
88
+ if n_end > 1:
89
+ result[..., 1] = prev
90
+ for n in range(2, n_end):
91
+ prevprev, prev = prev, (((2 * n - 1) * x * prev - (n - 1) * prevprev) / n)
92
+ result[..., n] = prev
93
+ return result
94
+
95
+
96
+ legendre_jit = jit(
97
+ {"numpy": numba.jit(nogil=True, parallel=True)},
98
+ decorator_kwargs={"jax": {"static_argnames": ["n_end"]}},
99
+ )(legendre)
100
+ legendre_assign_jit = jit({"numpy": numba.jit(nogil=True, parallel=True)})(legendre_assign)
101
+
102
+
103
+ @pytest.mark.skipif(
104
+ IS_CI and platform != "linux", reason="Compiler not available on GitHub Actions"
105
+ )
106
+ def test_jit(xp: Any) -> None:
107
+ t = {}
108
+ for name, func in [("nojit", legendre), ("jit", legendre_jit)] + (
109
+ [
110
+ ("assign-nojit", legendre_assign),
111
+ ("assign-jit", legendre_assign_jit),
112
+ ]
113
+ if "jax" not in xp.__name__
114
+ else []
115
+ ):
116
+ print(xp.__name__, name)
117
+ for i in range(20):
118
+ with timer() as timer_:
119
+ x = xp.arange(1000000, dtype=xp.float32) / 1000000
120
+ p = func(x, 10)
121
+ assert p.shape == (1000000, 10)
122
+ t[name] = timer_.elapsed
123
+ if i == 0:
124
+ print(f"First call: {timer_.elapsed:g}s")
125
+ print(f"Last call: {timer_.elapsed:g}s")
126
+ if "numpy" not in xp.__name__:
127
+ assert t["jit"] < t["nojit"], (
128
+ f"JIT time {t['jit']} should be less than non-JIT time {t['nojit']}"
129
+ )
130
+ assert t["assign-jit"] < t["assign-nojit"], (
131
+ f"JIT assign time {t['assign-jit']} should be "
132
+ f"less than non-JIT assign time {t['assign-nojit']}"
133
+ )
@@ -1 +0,0 @@
1
- __version__ = "0.0.0"
@@ -1,3 +0,0 @@
1
- def add(n1: int, n2: int) -> int:
2
- """Add the arguments."""
3
- return n1 + n2
@@ -1,6 +0,0 @@
1
- from array_api_jit.main import add
2
-
3
-
4
- def test_add():
5
- """Adding two number works as expected."""
6
- assert add(1, 1) == 2
File without changes
File without changes
File without changes