pyopencl 2025.2.7__cp314-cp314-macosx_10_15_x86_64.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.

Potentially problematic release.


This version of pyopencl might be problematic. Click here for more details.

Files changed (46) hide show
  1. pyopencl/__init__.py +1995 -0
  2. pyopencl/_cl.cpython-314-darwin.so +0 -0
  3. pyopencl/_cl.pyi +2009 -0
  4. pyopencl/_cluda.py +57 -0
  5. pyopencl/_monkeypatch.py +1104 -0
  6. pyopencl/_mymako.py +17 -0
  7. pyopencl/algorithm.py +1454 -0
  8. pyopencl/array.py +3530 -0
  9. pyopencl/bitonic_sort.py +245 -0
  10. pyopencl/bitonic_sort_templates.py +597 -0
  11. pyopencl/cache.py +535 -0
  12. pyopencl/capture_call.py +200 -0
  13. pyopencl/characterize/__init__.py +461 -0
  14. pyopencl/characterize/performance.py +240 -0
  15. pyopencl/cl/pyopencl-airy.cl +324 -0
  16. pyopencl/cl/pyopencl-bessel-j-complex.cl +238 -0
  17. pyopencl/cl/pyopencl-bessel-j.cl +1084 -0
  18. pyopencl/cl/pyopencl-bessel-y.cl +435 -0
  19. pyopencl/cl/pyopencl-complex.h +303 -0
  20. pyopencl/cl/pyopencl-eval-tbl.cl +120 -0
  21. pyopencl/cl/pyopencl-hankel-complex.cl +444 -0
  22. pyopencl/cl/pyopencl-random123/array.h +325 -0
  23. pyopencl/cl/pyopencl-random123/openclfeatures.h +93 -0
  24. pyopencl/cl/pyopencl-random123/philox.cl +486 -0
  25. pyopencl/cl/pyopencl-random123/threefry.cl +864 -0
  26. pyopencl/clmath.py +281 -0
  27. pyopencl/clrandom.py +412 -0
  28. pyopencl/cltypes.py +217 -0
  29. pyopencl/compyte/.gitignore +21 -0
  30. pyopencl/compyte/__init__.py +0 -0
  31. pyopencl/compyte/array.py +211 -0
  32. pyopencl/compyte/dtypes.py +314 -0
  33. pyopencl/compyte/pyproject.toml +49 -0
  34. pyopencl/elementwise.py +1288 -0
  35. pyopencl/invoker.py +417 -0
  36. pyopencl/ipython_ext.py +70 -0
  37. pyopencl/py.typed +0 -0
  38. pyopencl/reduction.py +815 -0
  39. pyopencl/scan.py +1921 -0
  40. pyopencl/tools.py +1680 -0
  41. pyopencl/typing.py +61 -0
  42. pyopencl/version.py +11 -0
  43. pyopencl-2025.2.7.dist-info/METADATA +108 -0
  44. pyopencl-2025.2.7.dist-info/RECORD +46 -0
  45. pyopencl-2025.2.7.dist-info/WHEEL +6 -0
  46. pyopencl-2025.2.7.dist-info/licenses/LICENSE +282 -0
pyopencl/cache.py ADDED
@@ -0,0 +1,535 @@
1
+ """PyOpenCL compiler cache."""
2
+ from __future__ import annotations
3
+
4
+
5
+ __copyright__ = "Copyright (C) 2011 Andreas Kloeckner"
6
+
7
+ __license__ = """
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ of this software and associated documentation files (the "Software"), to deal
10
+ in the Software without restriction, including without limitation the rights
11
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ copies of the Software, and to permit persons to whom the Software is
13
+ furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in
16
+ all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ THE SOFTWARE.
25
+ """
26
+
27
+ import logging
28
+ import os
29
+ import re
30
+ import sys
31
+ from dataclasses import dataclass
32
+
33
+ import pyopencl._cl as _cl
34
+
35
+
36
+ logger = logging.getLogger(__name__)
37
+
38
+
39
+ import hashlib
40
+
41
+
42
+ new_hash = hashlib.md5
43
+
44
+
45
+ def _erase_dir(directory):
46
+ from os import listdir, rmdir, unlink
47
+ from os.path import join
48
+
49
+ for name in listdir(directory):
50
+ unlink(join(directory, name))
51
+
52
+ rmdir(directory)
53
+
54
+
55
+ def update_checksum(checksum, obj):
56
+ if isinstance(obj, str):
57
+ checksum.update(obj.encode("utf8"))
58
+ else:
59
+ checksum.update(obj)
60
+
61
+
62
+ # {{{ cleanup
63
+
64
+ class CleanupBase:
65
+ pass
66
+
67
+
68
+ class CleanupManager(CleanupBase):
69
+ def __init__(self):
70
+ self.cleanups = []
71
+
72
+ def register(self, c):
73
+ self.cleanups.insert(0, c)
74
+
75
+ def clean_up(self):
76
+ for c in self.cleanups:
77
+ c.clean_up()
78
+
79
+ def error_clean_up(self):
80
+ for c in self.cleanups:
81
+ c.error_clean_up()
82
+
83
+
84
+ class CacheLockManager(CleanupBase):
85
+ def __init__(self, cleanup_m, cache_dir):
86
+ if cache_dir is not None:
87
+ self.lock_file = os.path.join(cache_dir, "lock")
88
+
89
+ attempts = 0
90
+ while True:
91
+ try:
92
+ self.fd = os.open(self.lock_file,
93
+ os.O_CREAT | os.O_WRONLY | os.O_EXCL)
94
+ break
95
+ except OSError:
96
+ pass
97
+
98
+ # This value was chosen based on the py-filelock package:
99
+ # https://github.com/tox-dev/py-filelock/blob/a6c8fabc4192fa7a4ae19b1875ee842ec5eb4f61/src/filelock/_api.py#L113
100
+ # When running pyopencl in an application with multiple ranks
101
+ # that share a cache_dir, higher timeouts can lead to
102
+ # application stalls even with low numbers of ranks.
103
+ # cf. https://github.com/inducer/pyopencl/pull/504
104
+ wait_time_seconds = 0.05
105
+
106
+ # Warn every 10 seconds if not able to acquire lock
107
+ warn_attempts = int(10/wait_time_seconds)
108
+
109
+ # Exit after 60 seconds if not able to acquire lock
110
+ exit_attempts = int(60/wait_time_seconds)
111
+
112
+ from time import sleep
113
+ sleep(wait_time_seconds)
114
+
115
+ attempts += 1
116
+
117
+ if attempts % warn_attempts == 0:
118
+ from warnings import warn
119
+ warn(
120
+ f"Could not obtain cache lock--delete '{self.lock_file}' "
121
+ "if necessary", stacklevel=2)
122
+
123
+ if attempts > exit_attempts:
124
+ raise RuntimeError("waited more than one minute "
125
+ "on the lock file '%s'"
126
+ "--something is wrong" % self.lock_file)
127
+
128
+ cleanup_m.register(self)
129
+
130
+ def clean_up(self):
131
+ os.close(self.fd)
132
+ os.unlink(self.lock_file)
133
+
134
+ def error_clean_up(self):
135
+ pass
136
+
137
+
138
+ class ModuleCacheDirManager(CleanupBase):
139
+ def __init__(self, cleanup_m, path):
140
+ from os import mkdir
141
+
142
+ self.path = path
143
+ try:
144
+ mkdir(self.path)
145
+ cleanup_m.register(self)
146
+ self.existed = False
147
+ except OSError:
148
+ self.existed = True
149
+
150
+ def sub(self, n):
151
+ from os.path import join
152
+ return join(self.path, n)
153
+
154
+ def reset(self):
155
+ _erase_dir(self.path)
156
+ os.mkdir(self.path)
157
+
158
+ def clean_up(self):
159
+ pass
160
+
161
+ def error_clean_up(self):
162
+ _erase_dir(self.path)
163
+
164
+ # }}}
165
+
166
+
167
+ # {{{ #include dependency handling
168
+
169
+ C_INCLUDE_RE = re.compile(r'^\s*\#\s*include\s+[<"](.+)[">]\s*$',
170
+ re.MULTILINE)
171
+
172
+
173
+ def get_dependencies(src, include_path):
174
+ result = {}
175
+
176
+ from os.path import join, realpath
177
+
178
+ def _inner(src):
179
+ for match in C_INCLUDE_RE.finditer(src):
180
+ included = match.group(1)
181
+
182
+ found = False
183
+ for ipath in include_path:
184
+ included_file_name = realpath(join(ipath, included))
185
+
186
+ if included_file_name not in result:
187
+ try:
188
+ src_file = open(included_file_name)
189
+ except OSError:
190
+ continue
191
+
192
+ try:
193
+ included_src = src_file.read()
194
+ finally:
195
+ src_file.close()
196
+
197
+ # prevent infinite recursion if some header file appears to
198
+ # include itself
199
+ result[included_file_name] = None
200
+
201
+ checksum = new_hash()
202
+ update_checksum(checksum, included_src)
203
+ _inner(included_src)
204
+
205
+ result[included_file_name] = (
206
+ os.stat(included_file_name).st_mtime,
207
+ checksum.hexdigest(),
208
+ )
209
+
210
+ found = True
211
+ break # stop searching the include path
212
+
213
+ if not found:
214
+ pass
215
+
216
+ _inner(src)
217
+
218
+ result = [(name, *vals) for name, vals in result.items()]
219
+ result.sort()
220
+
221
+ return result
222
+
223
+
224
+ def get_file_md5sum(fname):
225
+ checksum = new_hash()
226
+ inf = open(fname)
227
+ try:
228
+ contents = inf.read()
229
+ finally:
230
+ inf.close()
231
+ update_checksum(checksum, contents)
232
+ return checksum.hexdigest()
233
+
234
+
235
+ def check_dependencies(deps):
236
+ for name, date, md5sum in deps:
237
+ try:
238
+ possibly_updated = os.stat(name).st_mtime != date
239
+ except OSError:
240
+ return False
241
+ else:
242
+ if possibly_updated and md5sum != get_file_md5sum(name):
243
+ return False
244
+
245
+ return True
246
+
247
+ # }}}
248
+
249
+
250
+ # {{{ key generation
251
+
252
+ def get_device_cache_id(device):
253
+ from pyopencl.version import VERSION
254
+ platform = device.platform
255
+ return (VERSION,
256
+ platform.vendor, platform.name, platform.version,
257
+ device.vendor, device.name, device.version, device.driver_version)
258
+
259
+
260
+ def get_cache_key(device, options_bytes, src):
261
+ checksum = new_hash()
262
+ update_checksum(checksum, src)
263
+ update_checksum(checksum, options_bytes)
264
+ update_checksum(checksum, str(get_device_cache_id(device)))
265
+ return checksum.hexdigest()
266
+
267
+ # }}}
268
+
269
+
270
+ def retrieve_from_cache(cache_dir, cache_key):
271
+ class _InvalidInfoFileError(RuntimeError):
272
+ pass
273
+
274
+ from os.path import isdir, join
275
+ module_cache_dir = join(cache_dir, cache_key)
276
+ if not isdir(module_cache_dir):
277
+ return None
278
+
279
+ cleanup_m = CleanupManager()
280
+ try:
281
+ try:
282
+ CacheLockManager(cleanup_m, cache_dir)
283
+
284
+ mod_cache_dir_m = ModuleCacheDirManager(cleanup_m, module_cache_dir)
285
+ info_path = mod_cache_dir_m.sub("info")
286
+ binary_path = mod_cache_dir_m.sub("binary")
287
+
288
+ # {{{ load info file
289
+
290
+ try:
291
+ from pickle import load
292
+
293
+ try:
294
+ info_file = open(info_path, "rb")
295
+ except OSError as err:
296
+ raise _InvalidInfoFileError() from err
297
+
298
+ try:
299
+ try:
300
+ info = load(info_file)
301
+ except EOFError as err:
302
+ raise _InvalidInfoFileError() from err
303
+ finally:
304
+ info_file.close()
305
+
306
+ except _InvalidInfoFileError:
307
+ mod_cache_dir_m.reset()
308
+ from warnings import warn
309
+ warn(
310
+ "PyOpenCL encountered an invalid info file for "
311
+ f"cache key '{cache_key}'", stacklevel=2)
312
+ return None
313
+
314
+ # }}}
315
+
316
+ # {{{ load binary
317
+
318
+ binary_file = open(binary_path, "rb")
319
+ try:
320
+ binary = binary_file.read()
321
+ finally:
322
+ binary_file.close()
323
+
324
+ # }}}
325
+
326
+ if check_dependencies(info.dependencies):
327
+ return binary, info.log
328
+ else:
329
+ mod_cache_dir_m.reset()
330
+
331
+ except Exception:
332
+ cleanup_m.error_clean_up()
333
+ raise
334
+ finally:
335
+ cleanup_m.clean_up()
336
+
337
+
338
+ # {{{ top-level driver
339
+
340
+ @dataclass(frozen=True)
341
+ class _SourceInfo:
342
+ dependencies: list[tuple[str, ...]]
343
+ log: str | None
344
+
345
+
346
+ def _create_built_program_from_source_cached(ctx, src, options_bytes,
347
+ devices, cache_dir, include_path):
348
+ from os.path import join
349
+
350
+ if cache_dir is None:
351
+ import platformdirs
352
+
353
+ # Determine the cache directory in the same way as pytools.PersistentDict,
354
+ # which PyOpenCL uses for invoker caches.
355
+ if sys.platform == "darwin" and os.getenv("XDG_CACHE_HOME") is not None:
356
+ # platformdirs does not handle XDG_CACHE_HOME on macOS
357
+ # https://github.com/platformdirs/platformdirs/issues/269
358
+ cache_dir = join(os.getenv("XDG_CACHE_HOME"), "pyopencl")
359
+ else:
360
+ cache_dir = platformdirs.user_cache_dir("pyopencl", "pyopencl")
361
+
362
+ cache_dir = join(cache_dir,
363
+ "pyopencl-compiler-cache-v2-py{}".format(
364
+ ".".join(str(i) for i in sys.version_info)))
365
+
366
+ os.makedirs(cache_dir, exist_ok=True)
367
+
368
+ if devices is None:
369
+ devices = ctx.devices
370
+
371
+ cache_keys = [get_cache_key(device, options_bytes, src) for device in devices]
372
+
373
+ binaries = []
374
+ to_be_built_indices = []
375
+ logs = []
376
+ for i, (_device, cache_key) in enumerate(zip(devices, cache_keys, strict=True)):
377
+ cache_result = retrieve_from_cache(cache_dir, cache_key)
378
+
379
+ if cache_result is None:
380
+ logger.debug("build program: binary cache miss (key: %s)", cache_key)
381
+
382
+ to_be_built_indices.append(i)
383
+ binaries.append(None)
384
+ logs.append(None)
385
+ else:
386
+ logger.debug("build program: binary cache hit (key: %s)", cache_key)
387
+
388
+ binary, log = cache_result
389
+ binaries.append(binary)
390
+ logs.append(log)
391
+
392
+ message = (75*"="+"\n").join(
393
+ f"Build on {dev} succeeded, but said:\n\n{log}"
394
+ for dev, log in zip(devices, logs, strict=True)
395
+ if log is not None and log.strip())
396
+
397
+ if message:
398
+ from pyopencl import compiler_output
399
+ compiler_output(
400
+ "Built kernel retrieved from cache. Original from-source "
401
+ "build had warnings:\n"+message)
402
+
403
+ # {{{ build on the build-needing devices, in one go
404
+
405
+ result = None
406
+ already_built = False
407
+ was_cached = not to_be_built_indices
408
+
409
+ if to_be_built_indices:
410
+ # defeat implementation caches:
411
+ from uuid import uuid4
412
+ src = src + "\n\n__constant int pyopencl_defeat_cache_%s = 0;" % (
413
+ uuid4().hex)
414
+
415
+ logger.debug(
416
+ "build program: start building program from source on %s",
417
+ ", ".join(str(devices[i]) for i in to_be_built_indices))
418
+
419
+ prg = _cl._Program(ctx, src)
420
+ prg.build(options_bytes, [devices[i] for i in to_be_built_indices])
421
+
422
+ logger.debug("build program: from-source build complete")
423
+
424
+ prg_devs = prg.get_info(_cl.program_info.DEVICES)
425
+ prg_bins = prg.get_info(_cl.program_info.BINARIES)
426
+ prg_logs = prg._get_build_logs()
427
+
428
+ for dest_index in to_be_built_indices:
429
+ dev = devices[dest_index]
430
+ src_index = prg_devs.index(dev)
431
+ binaries[dest_index] = prg_bins[src_index]
432
+ _, logs[dest_index] = prg_logs[src_index]
433
+
434
+ if len(to_be_built_indices) == len(devices):
435
+ # Important special case: if code for all devices was built,
436
+ # then we may simply use the program that we just built as the
437
+ # final result.
438
+
439
+ result = prg
440
+ already_built = True
441
+
442
+ if result is None:
443
+ result = _cl._Program(ctx, devices, binaries)
444
+
445
+ # }}}
446
+
447
+ # {{{ save binaries to cache
448
+
449
+ if to_be_built_indices:
450
+ cleanup_m = CleanupManager()
451
+ try:
452
+ try:
453
+ CacheLockManager(cleanup_m, cache_dir)
454
+
455
+ for i in to_be_built_indices:
456
+ cache_key = cache_keys[i]
457
+ binary = binaries[i]
458
+
459
+ mod_cache_dir_m = ModuleCacheDirManager(cleanup_m,
460
+ join(cache_dir, cache_key))
461
+ info_path = mod_cache_dir_m.sub("info")
462
+ binary_path = mod_cache_dir_m.sub("binary")
463
+ source_path = mod_cache_dir_m.sub("source.cl")
464
+
465
+ with open(source_path, "w") as outf:
466
+ outf.write(src)
467
+
468
+ with open(binary_path, "wb") as outf:
469
+ outf.write(binary)
470
+
471
+ from pickle import dump
472
+ info_file = open(info_path, "wb")
473
+ dump(_SourceInfo(
474
+ dependencies=get_dependencies(src, include_path),
475
+ log=logs[i]), info_file)
476
+ info_file.close()
477
+
478
+ except Exception:
479
+ cleanup_m.error_clean_up()
480
+ raise
481
+ finally:
482
+ cleanup_m.clean_up()
483
+
484
+ # }}}
485
+
486
+ return result, already_built, was_cached
487
+
488
+
489
+ def create_built_program_from_source_cached(ctx, src, options_bytes, devices=None,
490
+ cache_dir=None, include_path=None):
491
+ try:
492
+ was_cached = False
493
+ already_built = False
494
+ if cache_dir is not False:
495
+ prg, already_built, was_cached = \
496
+ _create_built_program_from_source_cached(
497
+ ctx, src, options_bytes, devices, cache_dir,
498
+ include_path=include_path)
499
+ if was_cached and not already_built:
500
+ prg.build(options_bytes, devices)
501
+ already_built = True
502
+ else:
503
+ prg = _cl._Program(ctx, src)
504
+
505
+ except Exception as e:
506
+ from pyopencl import Error
507
+ build_program_failure = (isinstance(e, Error)
508
+ and e.code == _cl.status_code.BUILD_PROGRAM_FAILURE)
509
+
510
+ # Mac error on intel CPU driver: can't build from cached version.
511
+ # If we get a build_program_failure from the cached version then
512
+ # build from source instead, otherwise report the failure.
513
+ if build_program_failure and not was_cached:
514
+ raise
515
+
516
+ if not build_program_failure:
517
+ from traceback import format_exc
518
+ from warnings import warn
519
+ warn(
520
+ "PyOpenCL compiler caching failed with an exception:\n"
521
+ f"[begin exception]\n{format_exc()}[end exception]",
522
+ stacklevel=2)
523
+
524
+ prg = _cl._Program(ctx, src)
525
+ was_cached = False
526
+ already_built = False
527
+
528
+ if not already_built:
529
+ prg.build(options_bytes, devices)
530
+
531
+ return prg, was_cached
532
+
533
+ # }}}
534
+
535
+ # vim: foldmethod=marker