galley-jl-python 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,171 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import abstractmethod
4
+ from collections.abc import Callable, Iterator
5
+ from dataclasses import dataclass
6
+ from functools import wraps
7
+ from typing import TYPE_CHECKING, Any
8
+
9
+ from .julia import jl
10
+ from .typing import JuliaObj
11
+
12
+ if TYPE_CHECKING:
13
+ from .tensor import Tensor
14
+
15
+ IterObj = tuple | list | dict | Any
16
+
17
+
18
+ def _recurse(x: IterObj, /, *, f: Callable[[Any], Any]) -> IterObj:
19
+ if isinstance(x, tuple | list):
20
+ return type(x)(_recurse(xi, f=f) for xi in x)
21
+ if isinstance(x, dict):
22
+ ret = {k: _recurse(v, f=f) for k, v in x.items()}
23
+ if type(x) is not dict:
24
+ ret = type(x)(ret)
25
+ return ret
26
+ return f(x)
27
+
28
+
29
+ def _recurse_iter(x: IterObj, /) -> Iterator[Any]:
30
+ if isinstance(x, tuple | list):
31
+ for xi in x:
32
+ yield from _recurse_iter(xi)
33
+ return
34
+ if isinstance(x, dict):
35
+ for xi in x.values():
36
+ yield from _recurse_iter(xi)
37
+ return
38
+ yield x
39
+
40
+
41
+ def _to_lazy_tensor(x: Tensor | Any, /) -> Tensor | Any:
42
+ from .tensor import Tensor
43
+
44
+ return x if not isinstance(x, Tensor) else lazy(x)
45
+
46
+
47
+ @dataclass
48
+ class _ArgumentIndexer:
49
+ _idx: int = 0
50
+
51
+ def index(self, _) -> int:
52
+ ret = self._idx
53
+ self._idx += 1
54
+ return ret
55
+
56
+
57
+ def _recurse_iter_compute(x: IterObj, /, *, compute_kwargs: dict[str, Any]) -> IterObj:
58
+ from .tensor import Tensor
59
+
60
+ # Make a recursive iterator of indices.
61
+ idx_obj = _recurse(x, f=_ArgumentIndexer().index)
62
+ jl_computed = []
63
+ py_computed = []
64
+
65
+ # Collect lazy tensors; use placeholder
66
+ _placeholder = object()
67
+ for xi in _recurse_iter(x):
68
+ if isinstance(xi, Tensor) and not xi.is_computed():
69
+ jl_computed.append(xi._obj)
70
+ py_computed.append(_placeholder)
71
+ else:
72
+ py_computed.append(xi)
73
+ jl_len = len(jl_computed)
74
+ # This doesn't return an iterable of arrays -- only a single array
75
+ # for `len(jl_computed) == 1`
76
+ jl_computed = jl.Finch.compute(*jl_computed, **compute_kwargs)
77
+ if jl_len == 1:
78
+ jl_computed = (jl_computed,)
79
+
80
+ # Replace placeholders with computed tensors.
81
+ jl_computed_iter = iter(jl_computed)
82
+ for i in range(len(py_computed)):
83
+ if py_computed[i] is _placeholder:
84
+ py_computed[i] = Tensor(next(jl_computed_iter))
85
+
86
+ # Replace recursive indices by actual computed objects
87
+ return _recurse(idx_obj, f=lambda idx: py_computed[idx])
88
+
89
+
90
+ def compiled(opt=None, *, force_materialization=False, tag: int | None = None):
91
+ def inner(func):
92
+ @wraps(func)
93
+ def wrapper_func(*args, **kwargs):
94
+ from .tensor import Tensor
95
+
96
+ args = tuple(args)
97
+ kwargs = dict(kwargs)
98
+ compute_at_end = force_materialization or all(
99
+ t.is_computed()
100
+ for t in _recurse_iter((args, kwargs))
101
+ if isinstance(t, Tensor)
102
+ )
103
+ args = _recurse(args, f=_to_lazy_tensor)
104
+ kwargs = _recurse(kwargs, f=_to_lazy_tensor)
105
+ result = func(*args, **kwargs)
106
+ if not compute_at_end:
107
+ return result
108
+ compute_kwargs = (
109
+ {"ctx": opt.get_julia_scheduler()} if opt is not None else {}
110
+ )
111
+ if tag is not None:
112
+ compute_kwargs["tag"] = tag
113
+
114
+ return _recurse_iter_compute(result, compute_kwargs=compute_kwargs)
115
+
116
+ return wrapper_func
117
+
118
+ return inner
119
+
120
+
121
+ class AbstractScheduler:
122
+ def __init__(self, verbose: bool = False):
123
+ self.verbose = verbose
124
+
125
+ @abstractmethod
126
+ def get_julia_scheduler(self) -> JuliaObj:
127
+ pass
128
+
129
+
130
+ class GalleyScheduler(AbstractScheduler):
131
+ def get_julia_scheduler(self) -> JuliaObj:
132
+ return jl.Finch.galley_scheduler(verbose=self.verbose)
133
+
134
+
135
+ class DefaultScheduler(AbstractScheduler):
136
+ def get_julia_scheduler(self) -> JuliaObj:
137
+ return jl.Finch.default_scheduler(verbose=self.verbose)
138
+
139
+
140
+ def set_optimizer(opt: AbstractScheduler) -> None:
141
+ jl.Finch.set_scheduler_b(opt.get_julia_scheduler())
142
+
143
+
144
+ set_optimizer(GalleyScheduler())
145
+
146
+
147
+ def lazy(tensor: Tensor) -> Tensor:
148
+ from .tensor import Tensor
149
+
150
+ if tensor.is_computed():
151
+ return Tensor(jl.Finch.LazyTensor(tensor._obj))
152
+ return tensor
153
+
154
+
155
+ def compute(
156
+ tensor: Tensor, *, opt: AbstractScheduler | None = None, tag: int = -1
157
+ ) -> Tensor:
158
+ from .tensor import Tensor
159
+
160
+ if not tensor.is_computed():
161
+ if opt is None:
162
+ return Tensor(jl.Finch.compute(tensor._obj, tag=tag))
163
+ return Tensor(
164
+ jl.Finch.compute(
165
+ tensor._obj,
166
+ verbose=opt.verbose,
167
+ ctx=opt.get_julia_scheduler(),
168
+ tag=tag,
169
+ )
170
+ )
171
+ return tensor
@@ -0,0 +1,61 @@
1
+ import builtins
2
+
3
+ import numpy as np
4
+
5
+ from .julia import jl
6
+
7
+ int_: jl.DataType = jl.Int
8
+ int8: jl.DataType = jl.Int8
9
+ int16: jl.DataType = jl.Int16
10
+ int32: jl.DataType = jl.Int32
11
+ int64: jl.DataType = jl.Int64
12
+ uint: jl.DataType = jl.UInt
13
+ uint8: jl.DataType = jl.UInt8
14
+ uint16: jl.DataType = jl.UInt16
15
+ uint32: jl.DataType = jl.UInt32
16
+ uint64: jl.DataType = jl.UInt64
17
+ float16: jl.DataType = jl.Float16
18
+ float32: jl.DataType = jl.Float32
19
+ float64: jl.DataType = jl.Float64
20
+ complex64: jl.DataType = jl.ComplexF32
21
+ complex128: jl.DataType = jl.ComplexF64
22
+ bool: jl.DataType = jl.Bool
23
+
24
+ number: jl.DataType = jl.Number
25
+ complex: jl.DataType = jl.Complex
26
+ integer: jl.DataType = jl.Integer
27
+ abstract_float: jl.DataType = jl.AbstractFloat
28
+
29
+ jl_to_np_dtype = {
30
+ int_: np.int_,
31
+ int8: np.int8,
32
+ int16: np.int16,
33
+ int32: np.int32,
34
+ int64: np.int64,
35
+ uint: np.uint,
36
+ uint8: np.uint8,
37
+ uint16: np.uint16,
38
+ uint32: np.uint32,
39
+ uint64: np.uint64,
40
+ float16: np.float16,
41
+ float32: np.float32,
42
+ float64: np.float64,
43
+ complex64: np.complex64,
44
+ complex128: np.complex128,
45
+ bool: builtins.bool,
46
+ None: None,
47
+ }
48
+
49
+
50
+ def finfo(dtype):
51
+ return np.finfo(jl_to_np_dtype[dtype])
52
+
53
+
54
+ def iinfo(dtype):
55
+ return np.iinfo(jl_to_np_dtype[dtype])
56
+
57
+
58
+ def can_cast(from_, to, /) -> builtins.bool:
59
+ if hasattr(from_, "dtype"):
60
+ from_ = from_.dtype
61
+ return np.can_cast(jl_to_np_dtype[from_], jl_to_np_dtype[to])