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,561 @@
1
+ from abc import ABC, abstractmethod
2
+ from dataclasses import dataclass
3
+ from typing import Any
4
+
5
+ from lark import Lark, Tree
6
+
7
+
8
+ class EinsumExpr(ABC):
9
+ @abstractmethod
10
+ def get_loops(self) -> set[str]:
11
+ pass
12
+
13
+ @abstractmethod
14
+ def run(self, xp, loops, kwargs):
15
+ pass
16
+
17
+
18
+ nary_ops = {
19
+ "+": "add",
20
+ "add": "add",
21
+ "-": "subtract",
22
+ "sub": "subtract",
23
+ "subtract": "subtract",
24
+ "*": "multiply",
25
+ "mul": "multiply",
26
+ "multiply": "multiply",
27
+ "/": "divide",
28
+ "div": "divide",
29
+ "divide": "divide",
30
+ "//": "floor_divide",
31
+ "fld": "floor_divide",
32
+ "floor_divide": "floor_divide",
33
+ "%": "remainder",
34
+ "mod": "remainder",
35
+ "remainder": "remainder",
36
+ "**": "power",
37
+ "pow": "power",
38
+ "power": "power",
39
+ "==": "equal",
40
+ "eq": "equal",
41
+ "equal": "equal",
42
+ "!=": "not_equal",
43
+ "ne": "not_equal",
44
+ "not_equal": "not_equal",
45
+ "<": "less",
46
+ "lt": "less",
47
+ "less": "less",
48
+ "<=": "less_equal",
49
+ "le": "less_equal",
50
+ "less_equal": "less_equal",
51
+ ">": "greater",
52
+ "gt": "greater",
53
+ "greater": "greater",
54
+ ">=": "greater_equal",
55
+ "ge": "greater_equal",
56
+ "greater_equal": "greater_equal",
57
+ "&": "bitwise_and",
58
+ "bitwise_and": "bitwise_and",
59
+ "|": "bitwise_or",
60
+ "bitwise_or": "bitwise_or",
61
+ "^": "bitwise_xor",
62
+ "bitwise_xor": "bitwise_xor",
63
+ "<<": "bitwise_left_shift",
64
+ "lshift": "bitwise_left_shift",
65
+ "bitwise_left_shift": "bitwise_left_shift",
66
+ ">>": "bitwise_right_shift",
67
+ "rshift": "bitwise_right_shift",
68
+ "bitwise_right_shift": "bitwise_right_shift",
69
+ "and": "logical_and",
70
+ "or": "logical_or",
71
+ "not": "logical_not",
72
+ "min": "minimum",
73
+ "max": "maximum",
74
+ "logaddexp": "logaddexp",
75
+ }
76
+
77
+
78
+ unary_ops = {
79
+ "+": "positive",
80
+ "pos": "positive",
81
+ "positive": "positive",
82
+ "-": "negative",
83
+ "neg": "negative",
84
+ "negative": "negative",
85
+ "~": "bitwise_invert",
86
+ "invert": "bitwise_invert",
87
+ "bitwise_invert": "bitwise_invert",
88
+ "not": "logical_not",
89
+ "logical_not": "logical_not",
90
+ "abs": "absolute",
91
+ "absolute": "absolute",
92
+ "sqrt": "sqrt",
93
+ "exp": "exp",
94
+ "log": "log",
95
+ "log1p": "log1p",
96
+ "log10": "log10",
97
+ "log2": "log2",
98
+ "sin": "sin",
99
+ "cos": "cos",
100
+ "tan": "tan",
101
+ "sinh": "sinh",
102
+ "cosh": "cosh",
103
+ "tanh": "tanh",
104
+ "asin": "arcsin",
105
+ "acos": "arccos",
106
+ "atan": "arctan",
107
+ "asinh": "arcsinh",
108
+ "acosh": "arccosh",
109
+ "atanh": "arctanh",
110
+ }
111
+
112
+
113
+ reduction_ops = {
114
+ "+": "sum",
115
+ "add": "sum",
116
+ "sum": "sum",
117
+ "*": "prod",
118
+ "mul": "prod",
119
+ "prod": "prod",
120
+ "and": "all",
121
+ "or": "any",
122
+ "min": "min",
123
+ "max": "max",
124
+ "argmin": "argmin",
125
+ "argmax": "argmax",
126
+ "mean": "mean",
127
+ "std": "std",
128
+ "var": "var",
129
+ "count_nonzero": "count_nonzero",
130
+ # "&": "bitwise_and",
131
+ # "|": "bitwise_or",
132
+ # "^": "bitwise_xor",
133
+ }
134
+
135
+
136
+ @dataclass
137
+ class Access(EinsumExpr):
138
+ tns: str
139
+ idxs: list[str]
140
+
141
+ def get_loops(self) -> set[str]:
142
+ return set(self.idxs)
143
+
144
+ def run(self, xp, loops, kwargs):
145
+ assert len(self.idxs) == len(set(self.idxs))
146
+ perm = [self.idxs.index(idx) for idx in loops if idx in self.idxs]
147
+ tns = kwargs[self.tns]
148
+ tns = xp.permute_dims(tns, perm)
149
+ return xp.expand_dims(
150
+ tns, [i for i in range(len(loops)) if loops[i] not in self.idxs]
151
+ )
152
+
153
+
154
+ @dataclass
155
+ class Literal(EinsumExpr):
156
+ value: bool | int | float | complex
157
+
158
+ def get_loops(self) -> set[str]:
159
+ return set()
160
+
161
+ def run(self, xp, loops, kwargs):
162
+ # Create a scalar array with the same shape as needed
163
+ shape = [1] * len(loops)
164
+ return xp.full(shape, self.value, format="dense")
165
+
166
+
167
+ @dataclass
168
+ class Call(EinsumExpr):
169
+ func: str
170
+ args: list[EinsumExpr]
171
+
172
+ def get_loops(self) -> set[str]:
173
+ return set().union(*[arg.get_loops() for arg in self.args])
174
+
175
+ def run(self, xp, loops, kwargs):
176
+ if len(self.args) == 1:
177
+ func = getattr(xp, unary_ops[self.func])
178
+ else:
179
+ func = getattr(xp, nary_ops[self.func])
180
+ vals = [arg.run(xp, loops, kwargs) for arg in self.args]
181
+ return func(*vals)
182
+
183
+
184
+ @dataclass
185
+ class Einsum:
186
+ arg: EinsumExpr
187
+ op: str | None
188
+ tns: str
189
+ idxs: list[str]
190
+
191
+ def run(self, xp, kwargs):
192
+ # This is the main entry point for einsum execution
193
+ loops = self.arg.get_loops()
194
+ assert set(self.idxs).issubset(loops)
195
+ loops = sorted(loops)
196
+ arg = self.arg.run(xp, loops, kwargs)
197
+ axis = tuple(i for i in range(len(loops)) if loops[i] not in self.idxs)
198
+ if self.op is not None:
199
+ op = getattr(xp, reduction_ops.get(self.op))
200
+ val = op(arg, axis=axis)
201
+ else:
202
+ assert set(self.idxs) == set(loops)
203
+ val = arg
204
+ dropped = [idx for idx in loops if idx in self.idxs]
205
+ axis = [dropped.index(idx) for idx in self.idxs]
206
+ return xp.permute_dims(val, axis)
207
+
208
+
209
+ lark_parser = Lark("""
210
+ %import common.CNAME
211
+ %import common.SIGNED_INT
212
+ %import common.SIGNED_FLOAT
213
+ %ignore " " // Disregard spaces in text
214
+
215
+ start: increment | assign
216
+ increment: access (OP | FUNC_NAME) "=" expr
217
+ assign: access "=" expr
218
+
219
+ // Python operator precedence (lowest to highest)
220
+ expr: or_expr
221
+ or_expr: and_expr (OR and_expr)*
222
+ and_expr: not_expr (AND not_expr)*
223
+ not_expr: NOT not_expr | comparison_expr
224
+ comparison_expr: bitwise_or_expr ((EQ | NE | LT | LE | GT | GE) bitwise_or_expr)*
225
+ bitwise_or_expr: bitwise_xor_expr (PIPE bitwise_xor_expr)*
226
+ bitwise_xor_expr: bitwise_and_expr (CARET bitwise_and_expr)*
227
+ bitwise_and_expr: shift_expr (AMPERSAND shift_expr)*
228
+ shift_expr: add_expr ((LSHIFT | RSHIFT) add_expr)*
229
+ add_expr: mul_expr ((PLUS | MINUS) mul_expr)*
230
+ mul_expr: unary_expr ((MUL | DIV | FLOORDIV | MOD) unary_expr)*
231
+ unary_expr: (PLUS | MINUS | TILDE) unary_expr | power_expr
232
+ power_expr: primary (POW unary_expr)?
233
+ primary: call_func | access | literal | "(" expr ")"
234
+
235
+ OR: "or"
236
+ AND: "and"
237
+ NOT: "not"
238
+ EQ: "=="
239
+ NE: "!="
240
+ LT: "<"
241
+ LE: "<="
242
+ GT: ">"
243
+ GE: ">="
244
+ PIPE: "|"
245
+ CARET: "^"
246
+ AMPERSAND: "&"
247
+ LSHIFT: "<<"
248
+ RSHIFT: ">>"
249
+ PLUS: "+"
250
+ MINUS: "-"
251
+ MUL: "*"
252
+ DIV: "/"
253
+ FLOORDIV: "//"
254
+ MOD: "%"
255
+ POW: "**"
256
+ TILDE: "~"
257
+
258
+ OP: "+" | "-" | "*" | "or" | "and" | "|" | "&" | "^" | "<<" | ">>"
259
+ | "//" | "/" | "%" | "**" | ">" | "<" | ">=" | "<=" | "==" | "!="
260
+
261
+ access: TNS "[" (IDX ",")* IDX? "]"
262
+ call_func: (FUNC_NAME "(" (expr ",")* expr? ")")
263
+ literal: bool_literal | complex_literal | float_literal | int_literal
264
+ bool_literal: BOOL
265
+ int_literal: SIGNED_INT
266
+ float_literal: SIGNED_FLOAT
267
+ complex_literal: COMPLEX
268
+
269
+ BOOL: "True" | "False"
270
+ COMPLEX: (SIGNED_FLOAT | SIGNED_INT) ("j" | "J")
271
+ IDX: CNAME
272
+ TNS: CNAME
273
+ FUNC_NAME: CNAME
274
+ """)
275
+
276
+
277
+ def _parse_einop_expr(t: Tree) -> EinsumExpr:
278
+ match t:
279
+ case Tree(
280
+ "start"
281
+ | "expr"
282
+ | "or_expr"
283
+ | "and_expr"
284
+ | "not_expr"
285
+ | "comparison_expr"
286
+ | "bitwise_or_expr"
287
+ | "bitwise_xor_expr"
288
+ | "bitwise_and_expr"
289
+ | "shift_expr"
290
+ | "add_expr"
291
+ | "mul_expr"
292
+ | "unary_expr"
293
+ | "power_expr"
294
+ | "primary"
295
+ | "literal",
296
+ [child],
297
+ ):
298
+ return _parse_einop_expr(child)
299
+ case Tree(
300
+ "or_expr"
301
+ | "and_expr"
302
+ | "bitwise_or_expr"
303
+ | "bitwise_and_expr"
304
+ | "bitwise_xor_expr"
305
+ | "shift_expr"
306
+ | "add_expr"
307
+ | "mul_expr",
308
+ args,
309
+ ) if len(args) > 1:
310
+ expr = _parse_einop_expr(args[0])
311
+ for i in range(1, len(args), 2):
312
+ arg = _parse_einop_expr(args[i + 1])
313
+ expr = Call(args[i].value, [expr, arg]) # type: ignore[union-attr]
314
+ return expr
315
+ case Tree("comparison_expr", args) if len(args) > 1:
316
+ # Handle Python's comparison chaining: a < b < c becomes (a < b) and (b < c)
317
+ left = _parse_einop_expr(args[0])
318
+ right = _parse_einop_expr(args[2])
319
+ expr = Call(args[1].value, [left, right]) # type: ignore[union-attr]
320
+ for i in range(2, len(args) - 2, 2):
321
+ left = _parse_einop_expr(args[i])
322
+ right = _parse_einop_expr(args[i + 2])
323
+ expr = Call("and", [expr, Call(args[i + 1].value, [left, right])]) # type: ignore[union-attr]
324
+ return expr
325
+ case Tree("power_expr", args) if len(args) > 1:
326
+ left = _parse_einop_expr(args[0])
327
+ right = _parse_einop_expr(args[2])
328
+ return Call(args[1].value, [left, right]) # type: ignore[union-attr]
329
+ case Tree("unary_expr" | "not_expr", [op, arg]):
330
+ return Call(op.value, [_parse_einop_expr(arg)]) # type: ignore[union-attr]
331
+ case Tree("access", [tns, *idxs]):
332
+ return Access(tns.value, [idx.value for idx in idxs]) # type: ignore[union-attr]
333
+ case Tree("bool_literal", [val]):
334
+ return Literal(val.value == "True") # type: ignore[union-attr]
335
+ case Tree("int_literal", [val]):
336
+ return Literal(int(val.value)) # type: ignore[union-attr]
337
+ case Tree("float_literal", [val]):
338
+ return Literal(float(val.value)) # type: ignore[union-attr]
339
+ case Tree("complex_literal", [val]):
340
+ return Literal(complex(val.value)) # type: ignore[union-attr]
341
+ case Tree("call_func", [func, *args]):
342
+ return Call(func.value, [_parse_einop_expr(arg) for arg in args]) # type: ignore[union-attr]
343
+ case _:
344
+ raise ValueError(f"Unknown tree structure: {t}")
345
+
346
+
347
+ def parse_einop(expr: str) -> Einsum:
348
+ tree = lark_parser.parse(expr)
349
+
350
+ match tree:
351
+ case Tree(
352
+ "start", [Tree("increment", [Tree("access", [tns, *idxs]), op, expr_node])]
353
+ ):
354
+ input_expr = _parse_einop_expr(expr_node) # type: ignore[arg-type]
355
+ return Einsum(input_expr, op.value, tns.value, [idx.value for idx in idxs]) # type: ignore[union-attr]
356
+
357
+ case Tree("start", [Tree("assign", [Tree("access", [tns, *idxs]), expr_node])]):
358
+ input_expr = _parse_einop_expr(expr_node) # type: ignore[arg-type]
359
+ return Einsum(input_expr, None, tns.value, [idx.value for idx in idxs]) # type: ignore[union-attr]
360
+
361
+ case _:
362
+ raise ValueError(
363
+ f"Expected top-level assignment or increment, got {tree.data}"
364
+ )
365
+
366
+
367
+ def einop_impl(xp, prgm, **kwargs):
368
+ """Execute an einsum expression using the specified array framework.
369
+
370
+ This function parses and executes einsum-like expressions with extended syntax
371
+ that supports various operations beyond traditional Einstein summation notation.
372
+
373
+ Args:
374
+ xp: Array framework module (e.g., numpy, cupy, or other array library)
375
+ that provides the underlying array operations.
376
+ prgm (str): Einsum program string specifying the computation. The syntax
377
+ supports:
378
+ - Assignment: "C[i,j] = A[i,j] + B[j,i]"
379
+ - Increment: "C[i,j] += A[i,k] * B[k,j]"
380
+ - Reductions: "C[i] += A[i,j]", "C[i] max= A[i,j]", "C[i] &= A[i,j]"
381
+ - Arithmetic operations: +, -, *, /, //, %, **
382
+ - Comparison operations: ==, !=, <, <=, >, >=
383
+ - Logical operations: and, or, not
384
+ - Bitwise operations: &, |, ^, <<, >>
385
+ - Function calls and complex expressions with parentheses
386
+ - Mathematical functions: abs, sqrt, exp, log, sin, cos, tan, etc.
387
+ - Literal values: integers, floats, booleans, and complex numbers
388
+ - Python operator precedence and parentheses for grouping
389
+ **kwargs: Named arrays referenced in the einsum expression. The keys
390
+ should match the tensor names used in the program string.
391
+
392
+ Returns:
393
+ The result array from executing the einsum expression.
394
+
395
+ Examples:
396
+ >>> import numpy as np
397
+ >>> A = np.random.rand(3, 4)
398
+ >>> B = np.random.rand(4, 3)
399
+ >>> # Matrix addition with transpose
400
+ >>> C = einop_impl(np, "C[i,j] = A[i,j] + B[j,i]", A=A, B=B)
401
+ >>> # Matrix multiplication
402
+ >>> D = einop_impl(np, "D[i,j] += A[i,k] * B[k,j]", A=A, B=B)
403
+ >>> # Min-Plus multiplication with shift
404
+ >>> E = einop_impl(np, "E[i] min= A[i,k] + D[k,j] << 1", A=A, D=D)
405
+ """
406
+ prgm = parse_einop(prgm)
407
+ kwargs = {var: xp.Tensor(tns) for var, tns in kwargs.items()}
408
+ res = prgm.run(xp, {var: xp.lazy(tns) for var, tns in kwargs.items()})
409
+ if all(tns.is_computed() for tns in kwargs.values()):
410
+ return xp.compute(res)
411
+ return res
412
+
413
+
414
+ def parse_einsum(*args_) -> tuple[Einsum, dict[str, Any]]:
415
+ args = list(args_)
416
+ if len(args) < 2:
417
+ raise ValueError("Expected at least a subscript string and one operand.")
418
+ bc = "none"
419
+ if isinstance(args[0], str):
420
+ subscripts = args[0]
421
+ operands = args[1:]
422
+ if subscripts.count("->") > 1:
423
+ raise ValueError("Subscripts can only contain one '->' symbol.")
424
+ if subscripts.count("->") == 1:
425
+ subscripts, output_sub = subscripts.split("->")
426
+ output_sub = output_sub.strip()
427
+ else:
428
+ output_sub = None
429
+ input_subs = [s.strip() for s in subscripts.split(",")]
430
+ # Check for ellipses in input subscripts
431
+ if any("..." in sub for sub in input_subs):
432
+ if all(sub.startswith("...") for sub in input_subs):
433
+ bc = "prefix"
434
+ input_subs = [sub[3:] for sub in input_subs]
435
+ if output_sub is not None:
436
+ assert output_sub.startswith("...")
437
+ output_sub = output_sub[3:]
438
+ elif all(sub.endswith("...") for sub in input_subs):
439
+ bc = "suffix"
440
+ input_subs = [sub[:-3] for sub in input_subs]
441
+ if output_sub is not None:
442
+ assert output_sub.endswith("...")
443
+ output_sub = output_sub[:-3]
444
+ else:
445
+ raise ValueError(
446
+ "Ellipses must be at the start or end of all subscripts."
447
+ )
448
+ input_idxs = [list(sub) for sub in input_subs]
449
+ output_idxs = None if output_sub is None else list(output_sub)
450
+ else:
451
+ # Alternative syntax: einsum(operand0, subscript0, operand1, subscript1, ...)
452
+ # Check if the last element is the output subscript
453
+ if len(args) % 2 == 1:
454
+ operands = args[0:-2:2]
455
+ input_idxs = args[1::2]
456
+ output_idxs = list(args[-1])
457
+ output_idxs = [f"j_{j}" for j in output_idxs]
458
+ else:
459
+ operands = args[0::2]
460
+ input_idxs = args[1::2]
461
+ output_idxs = None
462
+ input_idxs = [[f"j_{j}" for j in idx] for idx in input_idxs]
463
+ if any(Ellipsis in idx for idx in input_idxs):
464
+ if all(idx[0] == Ellipsis for idx in input_idxs):
465
+ bc = "prefix"
466
+ input_idxs = [idx[1:] for idx in input_idxs]
467
+ if output_idxs is not None:
468
+ assert output_idxs[0] == Ellipsis
469
+ output_idxs = output_idxs[1:]
470
+ elif all(idx[-1] == Ellipsis for idx in input_idxs):
471
+ bc = "suffix"
472
+ input_idxs = [idx[:-1] for idx in input_idxs]
473
+ if output_idxs is not None:
474
+ assert output_idxs[-1] == Ellipsis
475
+ output_idxs = output_idxs[:-1]
476
+ else:
477
+ raise ValueError(
478
+ "Ellipses must be at the start or end of all subscripts."
479
+ )
480
+
481
+ all_idxs = set().union(*input_idxs)
482
+
483
+ if output_idxs is None:
484
+ output_idx_set = set()
485
+ for idx in all_idxs:
486
+ if sum(idx in sub for sub in input_idxs) == 1:
487
+ output_idx_set.add(idx)
488
+ output_idxs = sorted(output_idx_set)
489
+
490
+ def ndim(tns):
491
+ if hasattr(tns, "ndim"):
492
+ return tns.ndim
493
+ return 0
494
+
495
+ if bc == "prefix":
496
+ max_ell_len = max(
497
+ ndim(op) - len(sub) for op, sub in zip(operands, input_idxs, strict=False)
498
+ )
499
+ for i in range(len(operands)):
500
+ ell_idxs = [
501
+ f"i_{j}"
502
+ for j in range(
503
+ max_ell_len - (ndim(operands[i]) - len(input_idxs[i])), max_ell_len
504
+ )
505
+ ]
506
+ input_idxs[i] = ell_idxs + input_idxs[i]
507
+ ell_idxs = [f"i_{j}" for j in range(max_ell_len)]
508
+ output_idxs = [f"i_{j}" for j in range(max_ell_len)] + output_idxs
509
+ elif bc == "suffix":
510
+ max_ell_len = max(
511
+ ndim(op) - len(sub) for op, sub in zip(operands, input_idxs, strict=False)
512
+ )
513
+ for i in range(len(operands)):
514
+ ell_idxs = [f"k_{j}" for j in range(ndim(operands[i]) - len(input_idxs[i]))]
515
+ input_idxs[i] = input_idxs[i] + ell_idxs
516
+ output_idxs = output_idxs + [f"k_{j}" for j in range(max_ell_len)]
517
+
518
+ all_idxs = set().union(*input_idxs)
519
+
520
+ if len(input_idxs) != len(operands):
521
+ raise ValueError("Number of input subscripts must match number of operands.")
522
+ assert set(output_idxs).issubset(all_idxs), (
523
+ "Output indices must be a subset of input indices."
524
+ )
525
+ tag = 0
526
+
527
+ def freshen(x):
528
+ nonlocal tag
529
+ tag += 1
530
+ return f"{x}_{tag}"
531
+
532
+ for j in all_idxs:
533
+ freshen(j)
534
+ op = None if output_idxs == all_idxs else "add"
535
+ out_tns = freshen("B")
536
+ idxs = tuple(output_idxs)
537
+ in_tnss = [freshen("A") for _ in operands]
538
+ arg = Access(in_tnss[0], input_idxs[0])
539
+ for i in range(1, len(operands)):
540
+ arg = Call(
541
+ "mul",
542
+ (arg, Access(in_tnss[i], input_idxs[i])),
543
+ ) # type: ignore[assignment]
544
+ return (
545
+ Einsum(
546
+ arg,
547
+ op,
548
+ out_tns,
549
+ idxs,
550
+ ),
551
+ {in_tnss[i]: operands[i] for i in range(len(operands))},
552
+ )
553
+
554
+
555
+ def einsum_impl(xp, *args):
556
+ prgm, kwargs = parse_einsum(*args)
557
+ kwargs = {var: xp.Tensor(tns) for var, tns in kwargs.items()}
558
+ res = prgm.run(xp, {var: xp.lazy(tns) for var, tns in kwargs.items()})
559
+ if all(tns.is_computed() for tns in kwargs.values()):
560
+ return xp.compute(res)
561
+ return res
@@ -0,0 +1,2 @@
1
+ class PerformanceWarning(Warning):
2
+ pass
@@ -0,0 +1,33 @@
1
+ from collections.abc import Callable
2
+ from typing import Any
3
+
4
+ from finch.finch_fused import jit as _finch_jit
5
+
6
+ from .compiled import compute, lazy
7
+ from .tensor import Tensor
8
+
9
+
10
+ def _defer(value: Any) -> Any:
11
+ if isinstance(value, tuple):
12
+ return tuple(_defer(item) for item in value)
13
+ return lazy(value) if isinstance(value, Tensor) else value
14
+
15
+
16
+ def _compute(value: Any) -> Any:
17
+ if isinstance(value, tuple):
18
+ return tuple(_compute(item) for item in value)
19
+ return compute(value) if isinstance(value, Tensor) else value
20
+
21
+
22
+ def _maybedefer(values: tuple[Any, ...]) -> tuple[Any, ...]:
23
+ return tuple(_defer(value) for value in values)
24
+
25
+
26
+ def jit(f: Callable[..., Any], /, ctx: Any = None) -> Callable[..., Any]:
27
+ transformed = _finch_jit(f, ctx=ctx)
28
+ transformed.__globals__.update(
29
+ defer=_defer,
30
+ compute=_compute,
31
+ maybedefer=_maybedefer,
32
+ )
33
+ return transformed
galley_jl_python/io.py ADDED
@@ -0,0 +1,15 @@
1
+ from pathlib import Path
2
+
3
+ from .julia import jl
4
+ from .tensor import Tensor
5
+
6
+
7
+ def read(filename: Path | str) -> Tensor:
8
+ fn = str(filename)
9
+ julia_obj = jl.fread(fn)
10
+ return Tensor(julia_obj)
11
+
12
+
13
+ def write(filename: Path | str, tns: Tensor) -> None:
14
+ fn = str(filename)
15
+ jl.fwrite(fn, tns._obj)
@@ -0,0 +1,37 @@
1
+ import ssl # noqa: I001
2
+
3
+ # Julia 1.12, which galley pins, bundles OpenSSL 3.5, and Python and Julia share
4
+ # one process, so juliapkg only allows Julia 1.12 with a Python linked against
5
+ # OpenSSL 3.5 or newer. Without this check the failure is juliapkg finding no
6
+ # compatible Julia version.
7
+ if ssl.OPENSSL_VERSION_INFO[:2] < (3, 5):
8
+ raise ImportError(
9
+ "galley-jl-python needs a Python linked against OpenSSL 3.5 or newer, but "
10
+ f"this one uses {ssl.OPENSSL_VERSION}. Galley runs Julia 1.12, which "
11
+ "bundles OpenSSL 3.5 and shares this process with Python. Use a Python "
12
+ "from conda-forge, e.g. through pixi or conda, which ships a current "
13
+ "OpenSSL."
14
+ )
15
+
16
+ import juliapkg # noqa: E402, F401
17
+
18
+ from . import _sysimage # noqa: E402
19
+
20
+ _sysimage.configure()
21
+
22
+ # To change the version of Finch used, see the documentation for pyjuliapkg here: https://github.com/JuliaPy/pyjuliapkg
23
+ # Use pyjuliapkg to modify the `juliapkg.json` file in the root of this repo.
24
+ # You can also run `develop.py` to quickly use a local copy of Finch.jl.
25
+ # An example development json is found in `juliapkg_dev.json`
26
+ # Julia and all packages are pinned so the prebuilt sysimage matches; update the
27
+ # pins with `scripts/sysimage/pin_julia_deps.py`. Finch is pinned to a git
28
+ # commit (`rev`), which that script leaves alone.
29
+ import juliacall as jc # noqa: E402, F401
30
+ from juliacall import Main as jl # noqa: E402, F401
31
+
32
+ jl.seval("using Finch")
33
+ jl.seval("using HDF5")
34
+ jl.seval("using NPZ")
35
+ jl.seval("using TensorMarket")
36
+ jl.seval("using Random")
37
+ jl.seval("using Statistics")