array-api-jit 0.0.0__tar.gz → 0.1.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: 0.1.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,17 @@ 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
+ Requires-Dist: cm-time>=0.1.2
25
+ Requires-Dist: frozendict>=2.4.6
24
26
  Dynamic: license-file
25
27
 
26
28
  # array API JIT
@@ -69,7 +71,49 @@ JIT decorator supporting multiple array API compatible libraries
69
71
 
70
72
  Install this via pip (or your favourite package manager):
71
73
 
72
- `pip install array-api-jit`
74
+ ```shell
75
+ pip install array-api-jit
76
+ ```
77
+
78
+ ## Usage
79
+
80
+ Simply decorate your function with `@jit()`:
81
+
82
+ ```python
83
+ from array_api_jit import jit
84
+
85
+
86
+ @jit()
87
+ def my_function(x: Any) -> Any:
88
+ xp = array_namespace(x)
89
+ return xp.sin(x) + xp.cos(x)
90
+ ```
91
+
92
+ ## Advanced Usage
93
+
94
+ You can specify the decorator, arguments, and keyword arguments for each library.
95
+
96
+ ```python
97
+ from array_api_jit import jit
98
+ from array_api_compat import array_namespace
99
+ from typing import Any
100
+ import numba
101
+
102
+
103
+ @jit(
104
+ {"numpy": numba.jit()}, # numba.jit is not used by default because it may not succeed
105
+ decorator_kwargs={
106
+ "jax": {"static_argnames": ["n"]}
107
+ }, # jax requires for-loop variable to be "static_argnames"
108
+ # fail_on_error: bool = False, # do not raise an error if the decorator fails (Default)
109
+ # rerun_on_error: bool = True, # re-run the original function if the wrapped function fails (NOT Default)
110
+ )
111
+ def sin_n_times(x: Any, n: int) -> Any:
112
+ xp = array_namespace(x)
113
+ for i in range(n):
114
+ x = xp.sin(x)
115
+ return x
116
+ ```
73
117
 
74
118
  ## Contributors ✨
75
119
 
@@ -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 = "0.1.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,9 @@ classifiers = [
26
25
  ]
27
26
 
28
27
  dependencies = [
28
+ "array-api-compat>=1.11.2",
29
+ "cm-time>=0.1.2",
30
+ "frozendict>=2.4.6",
29
31
  ]
30
32
  urls."Bug Tracker" = "https://github.com/34j/array-api-jit/issues"
31
33
  urls.Changelog = "https://github.com/34j/array-api-jit/blob/main/CHANGELOG.md"
@@ -34,8 +36,11 @@ urls.repository = "https://github.com/34j/array-api-jit"
34
36
 
35
37
  [dependency-groups]
36
38
  dev = [
39
+ "jax>=0.4.30",
40
+ "numba>=0.60.0",
37
41
  "pytest>=8,<9",
38
42
  "pytest-cov>=6,<7",
43
+ "torch>=2.7.1",
39
44
  ]
40
45
  docs = [
41
46
  "furo>=2023.5.20; python_version>='3.11'",
@@ -45,7 +50,8 @@ docs = [
45
50
  ]
46
51
 
47
52
  [tool.ruff]
48
- line-length = 88
53
+ format.docstring-code-format = true
54
+ line-length = 100
49
55
  lint.select = [
50
56
  "B", # flake8-bugbear
51
57
  "D", # flake8-docstrings
@@ -83,9 +89,6 @@ lint.isort.known-first-party = [ "array_api_jit", "tests" ]
83
89
  addopts = """\
84
90
  -v
85
91
  -Wdefault
86
- --cov=array_api_jit
87
- --cov-report=term
88
- --cov-report=xml
89
92
  """
90
93
  pythonpath = [ "src" ]
91
94
 
@@ -0,0 +1,4 @@
1
+ __version__ = "0.1.0"
2
+ from ._main import jit
3
+
4
+ __all__ = ["jit"]
@@ -0,0 +1,178 @@
1
+ # https://github.com/search?q=gumerov+translation+language%3APython&type=code&l=Python
2
+ import importlib.util
3
+ import warnings
4
+ from collections.abc import Callable, Mapping, Sequence
5
+ from functools import cache, wraps
6
+ from types import ModuleType
7
+ from typing import Any, ParamSpec, TypeVar
8
+
9
+ from array_api_compat import (
10
+ array_namespace,
11
+ is_cupy_namespace,
12
+ is_dask_namespace,
13
+ is_jax_namespace,
14
+ is_numpy_namespace,
15
+ is_torch_namespace,
16
+ )
17
+ from frozendict import frozendict
18
+
19
+ if importlib.util.find_spec("numba"):
20
+ import numpy as np
21
+ from numba.extending import overload
22
+
23
+ @overload(array_namespace)
24
+ def _array_namespace_overload(*args: Any) -> Any:
25
+ def inner(*args: Any) -> Any:
26
+ return np
27
+
28
+ return inner
29
+
30
+
31
+ P = ParamSpec("P")
32
+ T = TypeVar("T")
33
+ Pin = ParamSpec("Pin")
34
+ Tin = TypeVar("Tin")
35
+ Pinner = ParamSpec("Pinner")
36
+ Tinner = TypeVar("Tinner")
37
+ STR_TO_IS_NAMESPACE = {
38
+ "numpy": is_numpy_namespace,
39
+ "jax": is_jax_namespace,
40
+ "cupy": is_cupy_namespace,
41
+ "torch": is_torch_namespace,
42
+ "dask": is_dask_namespace,
43
+ }
44
+
45
+
46
+ def _default_decorator(
47
+ module: ModuleType,
48
+ /,
49
+ ) -> Callable[[Callable[P, T]], Callable[P, T]]:
50
+ if is_jax_namespace(module):
51
+ import jax
52
+
53
+ return jax.jit
54
+ elif is_numpy_namespace(module) or is_cupy_namespace(module):
55
+ # import numba
56
+
57
+ # return numba.jit()
58
+ # The success rate of numba.jit is low
59
+ return lambda x: x
60
+ elif is_torch_namespace(module):
61
+ import torch
62
+
63
+ return torch.compile
64
+ elif is_dask_namespace(module):
65
+ return lambda x: x
66
+ else:
67
+ return getattr(module, "jit", lambda x: x)
68
+
69
+
70
+ Decorator = Callable[[Callable[Pin, Tin]], Callable[Pin, Tin]]
71
+
72
+
73
+ def jit(
74
+ decorator: Mapping[str, Decorator[..., Any]] | None = None,
75
+ /,
76
+ *,
77
+ fail_on_error: bool = False,
78
+ rerun_on_error: bool = False,
79
+ decorator_args: Mapping[str, Sequence[Any]] | None = None,
80
+ decorator_kwargs: Mapping[str, Mapping[str, Any]] | None = None,
81
+ ) -> Callable[[Callable[P, T]], Callable[P, T]]:
82
+ """
83
+ Just-in-time compilation decorator with multiple backends.
84
+
85
+ Parameters
86
+ ----------
87
+ decorator : Mapping[str, Callable[[Callable[P, T]], Callable[P, T]]] | None, optional
88
+ The JIT decorator to use for each array namespace, by default None
89
+ fail_on_error : bool, optional
90
+ If True, raise an error if the JIT decorator fails to apply.
91
+ If False, just warn and return the original function, by default False
92
+ rerun_on_error : bool, optional
93
+ If True, rerun the function without JIT if the function
94
+ with JIT applied fails, by default False
95
+ decorator_args : Mapping[str, Sequence[Any]] | None, optional
96
+ Additional positional arguments to be passed along with the function
97
+ to the decorator for each array namespace, by default None
98
+ decorator_kwargs : Mapping[str, Mapping[str, Any]] | None, optional
99
+ Additional keyword arguments to be passed along with the function
100
+ to the decorator for each array namespace, by default None
101
+
102
+ Returns
103
+ -------
104
+ Callable[[Callable[P, T]], Callable[P, T]]
105
+ The JIT decorator that can be applied to a function.
106
+
107
+ Example
108
+ -------
109
+ >>> from array_api_jit import jit
110
+ >>> from array_api_compat import array_namespace
111
+ >>> from typing import Any
112
+ >>> import numba
113
+ >>> @jit(
114
+ ... {"numpy": numba.jit()}, # numba.jit is not used by default
115
+ ... decorator_kwargs={"jax": {"static_argnames": ["n"]}}, # jax requires static_argnames
116
+ ... )
117
+ ... def sin_n_times(x: Any, n: int) -> Any:
118
+ ... xp = array_namespace(x)
119
+ ... for i in range(n):
120
+ ... x = xp.sin(x)
121
+ ... return x
122
+
123
+ """
124
+
125
+ def new_decorator(f: Callable[Pinner, Tinner]) -> Callable[Pinner, Tinner]:
126
+ decorator_args_ = frozendict(decorator_args or {})
127
+ decorator_kwargs_ = frozendict(decorator_kwargs or {})
128
+ decorator_ = decorator or {}
129
+
130
+ @cache
131
+ def jit_cached(xp: ModuleType) -> Callable[Pinner, Tinner]:
132
+ for name_, is_namespace in STR_TO_IS_NAMESPACE.items():
133
+ if is_namespace(xp):
134
+ name = name_
135
+ else:
136
+ name = xp.__name__.split(".")[0]
137
+ decorator_args__ = decorator_args_.get(name, ())
138
+ decorator_kwargs__ = decorator_kwargs_.get(name, {})
139
+ if name in decorator_:
140
+ decorator_current = decorator_[name]
141
+ else:
142
+ decorator_current = _default_decorator(xp)
143
+ try:
144
+ return decorator_current(f, *decorator_args__, **decorator_kwargs__)
145
+ except Exception as e:
146
+ if fail_on_error:
147
+ raise RuntimeError(f"Failed to apply JIT decorator for {name}") from e
148
+ warnings.warn(
149
+ f"Failed to apply JIT decorator for {name}: {e}",
150
+ RuntimeWarning,
151
+ stacklevel=2,
152
+ )
153
+ return f
154
+
155
+ @wraps(f)
156
+ def inner(*args_inner: Pinner.args, **kwargs_inner: Pinner.kwargs) -> Tinner:
157
+ try:
158
+ xp = array_namespace(*args_inner)
159
+ except TypeError as e:
160
+ if e.args[0] == "Unrecognized array input":
161
+ return f(*args_inner, **kwargs_inner)
162
+ raise
163
+ f_jit = jit_cached(xp)
164
+ try:
165
+ return f_jit(*args_inner, **kwargs_inner)
166
+ except Exception as e:
167
+ if rerun_on_error:
168
+ warnings.warn(
169
+ f"JIT failed for {xp.__name__}: {e}. Rerunning without JIT.",
170
+ RuntimeWarning,
171
+ stacklevel=2,
172
+ )
173
+ return f(*args_inner, **kwargs_inner)
174
+ raise RuntimeError(f"Failed to run JIT function for {xp.__name__}") from e
175
+
176
+ return inner
177
+
178
+ 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: 0.1.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,17 @@ 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
+ Requires-Dist: cm-time>=0.1.2
25
+ Requires-Dist: frozendict>=2.4.6
24
26
  Dynamic: license-file
25
27
 
26
28
  # array API JIT
@@ -69,7 +71,49 @@ JIT decorator supporting multiple array API compatible libraries
69
71
 
70
72
  Install this via pip (or your favourite package manager):
71
73
 
72
- `pip install array-api-jit`
74
+ ```shell
75
+ pip install array-api-jit
76
+ ```
77
+
78
+ ## Usage
79
+
80
+ Simply decorate your function with `@jit()`:
81
+
82
+ ```python
83
+ from array_api_jit import jit
84
+
85
+
86
+ @jit()
87
+ def my_function(x: Any) -> Any:
88
+ xp = array_namespace(x)
89
+ return xp.sin(x) + xp.cos(x)
90
+ ```
91
+
92
+ ## Advanced Usage
93
+
94
+ You can specify the decorator, arguments, and keyword arguments for each library.
95
+
96
+ ```python
97
+ from array_api_jit import jit
98
+ from array_api_compat import array_namespace
99
+ from typing import Any
100
+ import numba
101
+
102
+
103
+ @jit(
104
+ {"numpy": numba.jit()}, # numba.jit is not used by default because it may not succeed
105
+ decorator_kwargs={
106
+ "jax": {"static_argnames": ["n"]}
107
+ }, # jax requires for-loop variable to be "static_argnames"
108
+ # fail_on_error: bool = False, # do not raise an error if the decorator fails (Default)
109
+ # rerun_on_error: bool = True, # re-run the original function if the wrapped function fails (NOT Default)
110
+ )
111
+ def sin_n_times(x: Any, n: int) -> Any:
112
+ xp = array_namespace(x)
113
+ for i in range(n):
114
+ x = xp.sin(x)
115
+ return x
116
+ ```
73
117
 
74
118
  ## Contributors ✨
75
119
 
@@ -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,3 @@
1
+ array-api-compat>=1.11.2
2
+ cm-time>=0.1.2
3
+ frozendict>=2.4.6
@@ -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)
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