cupmb 1.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.
Files changed (70) hide show
  1. cupmb-1.1.0/LICENSE +21 -0
  2. cupmb-1.1.0/MANIFEST.in +9 -0
  3. cupmb-1.1.0/PKG-INFO +226 -0
  4. cupmb-1.1.0/README.md +170 -0
  5. cupmb-1.1.0/cupmb/__init__.py +46 -0
  6. cupmb-1.1.0/cupmb/backend/__init__.py +87 -0
  7. cupmb-1.1.0/cupmb/backend/cupy_backend.py +549 -0
  8. cupmb-1.1.0/cupmb/backend/pycuda_backend.py +578 -0
  9. cupmb-1.1.0/cupmb/bbox.py +75 -0
  10. cupmb-1.1.0/cupmb/core.py +2389 -0
  11. cupmb-1.1.0/cupmb/enums.py +44 -0
  12. cupmb-1.1.0/cupmb/kernels/pmb.cuh +348 -0
  13. cupmb-1.1.0/cupmb/kernels/pmb1D.cuh +262 -0
  14. cupmb-1.1.0/cupmb/kernels/pmb2D.cuh +371 -0
  15. cupmb-1.1.0/cupmb/kernels/pmb3D.cuh +476 -0
  16. cupmb-1.1.0/cupmb/kernels/trove/aos.h +280 -0
  17. cupmb-1.1.0/cupmb/kernels/trove/array.h +287 -0
  18. cupmb-1.1.0/cupmb/kernels/trove/block.h +67 -0
  19. cupmb-1.1.0/cupmb/kernels/trove/detail/dismember.h +127 -0
  20. cupmb-1.1.0/cupmb/kernels/trove/detail/fallback.h +133 -0
  21. cupmb-1.1.0/cupmb/kernels/trove/memory.h +170 -0
  22. cupmb-1.1.0/cupmb/kernels/trove/print_array.h +48 -0
  23. cupmb-1.1.0/cupmb/kernels/trove/ptr.h +80 -0
  24. cupmb-1.1.0/cupmb/kernels/trove/rotate.h +117 -0
  25. cupmb-1.1.0/cupmb/kernels/trove/shfl.h +106 -0
  26. cupmb-1.1.0/cupmb/kernels/trove/static_gcd.h +77 -0
  27. cupmb-1.1.0/cupmb/kernels/trove/static_mod_inverse.h +54 -0
  28. cupmb-1.1.0/cupmb/kernels/trove/transpose.h +686 -0
  29. cupmb-1.1.0/cupmb/kernels/trove/utility.h +162 -0
  30. cupmb-1.1.0/cupmb/kernels/trove/warp.h +52 -0
  31. cupmb-1.1.0/cupmb/parameters.py +31 -0
  32. cupmb-1.1.0/cupmb/properties.py +136 -0
  33. cupmb-1.1.0/cupmb/utils/IO.py +25 -0
  34. cupmb-1.1.0/cupmb/utils/__init__.py +4 -0
  35. cupmb-1.1.0/cupmb/utils/kernel_cache.py +129 -0
  36. cupmb-1.1.0/cupmb/utils/logger.py +64 -0
  37. cupmb-1.1.0/cupmb/utils/nvrtc_build.py +274 -0
  38. cupmb-1.1.0/cupmb/utils/types.py +51 -0
  39. cupmb-1.1.0/cupmb/utils/units.py +91 -0
  40. cupmb-1.1.0/cupmb.egg-info/PKG-INFO +226 -0
  41. cupmb-1.1.0/cupmb.egg-info/SOURCES.txt +68 -0
  42. cupmb-1.1.0/cupmb.egg-info/dependency_links.txt +1 -0
  43. cupmb-1.1.0/cupmb.egg-info/requires.txt +41 -0
  44. cupmb-1.1.0/cupmb.egg-info/top_level.txt +1 -0
  45. cupmb-1.1.0/examples/0_simple.py +46 -0
  46. cupmb-1.1.0/examples/1_animation.py +101 -0
  47. cupmb-1.1.0/examples/2_normals.py +220 -0
  48. cupmb-1.1.0/examples/3_glinterop_cupy.py +215 -0
  49. cupmb-1.1.0/examples/3_glinterop_pycuda.py +214 -0
  50. cupmb-1.1.0/examples/4_isolines.py +112 -0
  51. cupmb-1.1.0/examples/5_moving_sphere.py +111 -0
  52. cupmb-1.1.0/examples/6_moving_bbox.py +137 -0
  53. cupmb-1.1.0/examples/7_predicate.py +204 -0
  54. cupmb-1.1.0/examples/8_levelset.py +1131 -0
  55. cupmb-1.1.0/examples/9_gamma.py +167 -0
  56. cupmb-1.1.0/ext/trove/LICENSE +24 -0
  57. cupmb-1.1.0/pyproject.toml +110 -0
  58. cupmb-1.1.0/requirements.txt +7 -0
  59. cupmb-1.1.0/requirements_dev.txt +10 -0
  60. cupmb-1.1.0/requirements_examples.txt +12 -0
  61. cupmb-1.1.0/setup.cfg +4 -0
  62. cupmb-1.1.0/setup.py +21 -0
  63. cupmb-1.1.0/tests/conftest.py +79 -0
  64. cupmb-1.1.0/tests/pmb_testing.py +301 -0
  65. cupmb-1.1.0/tests/test_api.py +62 -0
  66. cupmb-1.1.0/tests/test_device_driven.py +195 -0
  67. cupmb-1.1.0/tests/test_kernel_cache.py +158 -0
  68. cupmb-1.1.0/tests/test_marching.py +387 -0
  69. cupmb-1.1.0/tests/test_predicate.py +215 -0
  70. cupmb-1.1.0/tests/test_regressions.py +360 -0
cupmb-1.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021-2026 Jean-Baptiste Keck
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,9 @@
1
+ include LICENSE README.md pyproject.toml setup.py
2
+ include requirements.txt requirements_dev.txt requirements_examples.txt
3
+
4
+ # Device code, trove is a symlinked git submodule (followed by sdist).
5
+ graft cupmb/kernels
6
+ include ext/trove/LICENSE
7
+
8
+ recursive-include tests *.py
9
+ recursive-include examples *.py
cupmb-1.1.0/PKG-INFO ADDED
@@ -0,0 +1,226 @@
1
+ Metadata-Version: 2.4
2
+ Name: cupmb
3
+ Version: 1.1.0
4
+ Summary: Cuda Parallel Marching Blocks, supports both pycuda and cupy backends.
5
+ Author-email: Jean-Baptiste Keck <jbkeck@hotmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Repository, https://github.com/keckj/cupmb
8
+ Project-URL: Issues, https://github.com/keckj/cupmb/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3 :: Only
11
+ Classifier: Programming Language :: C++
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Environment :: GPU :: NVIDIA CUDA
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
16
+ Classifier: Topic :: Scientific/Engineering :: Visualization
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ License-File: ext/trove/LICENSE
21
+ Requires-Dist: numpy>=1.24
22
+ Requires-Dist: jinja2
23
+ Requires-Dist: termcolor
24
+ Requires-Dist: colorama
25
+ Requires-Dist: cusvna>=1.0.1
26
+ Provides-Extra: cupy12
27
+ Requires-Dist: cupy-cuda12x>=13; extra == "cupy12"
28
+ Provides-Extra: cupy13
29
+ Requires-Dist: cupy-cuda13x; extra == "cupy13"
30
+ Provides-Extra: pycuda
31
+ Requires-Dist: pycuda; extra == "pycuda"
32
+ Requires-Dist: cuda-python>=12.6; extra == "pycuda"
33
+ Provides-Extra: gl
34
+ Requires-Dist: cuda-python>=12.6; extra == "gl"
35
+ Requires-Dist: PyOpenGL; extra == "gl"
36
+ Provides-Extra: test
37
+ Requires-Dist: pytest>=7; extra == "test"
38
+ Requires-Dist: scipy; extra == "test"
39
+ Provides-Extra: examples
40
+ Requires-Dist: matplotlib>=3.8; extra == "examples"
41
+ Requires-Dist: trimesh; extra == "examples"
42
+ Requires-Dist: open3d>=0.19; extra == "examples"
43
+ Requires-Dist: pyglet<2; extra == "examples"
44
+ Requires-Dist: pyrr; extra == "examples"
45
+ Requires-Dist: glfw; extra == "examples"
46
+ Requires-Dist: PyOpenGL; extra == "examples"
47
+ Requires-Dist: PyOpenGL_accelerate; extra == "examples"
48
+ Provides-Extra: dev
49
+ Requires-Dist: cupmb[test]; extra == "dev"
50
+ Requires-Dist: build; extra == "dev"
51
+ Requires-Dist: pre-commit; extra == "dev"
52
+ Requires-Dist: black; extra == "dev"
53
+ Requires-Dist: ruff; extra == "dev"
54
+ Requires-Dist: scikit-image; extra == "dev"
55
+ Dynamic: license-file
56
+
57
+ <!--
58
+ Copyright (c) 2021-2026 Jean-Baptiste Keck <jbkeck@hotmail.com>
59
+ SPDX-License-Identifier: MIT
60
+ -->
61
+ <p align="center"><img src="https://raw.githubusercontent.com/keckj/cupmb/main/logo/logo.png" alt="Cuda Parallel Marching Blocks" width="800"></p>
62
+
63
+ <p align="center">
64
+ <img src="https://img.shields.io/badge/platform-linux-lightgrey.svg" alt="Platform: linux">
65
+ <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.10+-blue.svg" alt="Python 3.10+"></a>
66
+ <a href="https://developer.nvidia.com/cuda-toolkit-archive"><img src="https://img.shields.io/badge/cuda-12.x-green.svg" alt="CUDA 12.x"></a>
67
+ <a href="https://github.com/keckj/cupmb/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT"></a>
68
+ <a href="https://github.com/psf/black"><img src="https://img.shields.io/badge/code%20style-black-000000.svg" alt="Code style: black"></a>
69
+ </p>
70
+
71
+ # Cuda Parallel Marching Blocks
72
+
73
+ **cupmb** extracts the zero level set of a scalar field on the GPU from Python: root finding in 1D, marching squares in 2D and marching cubes in 3D. It implements the Parallel Marching Blocks (PMB) algorithm, see [Parallel Marching Blocks: A Practical Isosurfacing Algorithm for Large Data on Many-Core Architectures](https://pure.strath.ac.uk/ws/portalfiles/portal/92247319/Liu_etal_CGF22016_Parallel_marching_blocks_a_practical_isosurfacing_algorithm_large_data_many_core_architectures.pdf).
74
+
75
+ - **Indexed meshes**: vertices are shared between the simplices of a block, no triangle soup.
76
+ - **Your scalar field, compiled**: an analytic CUDA device function or a sampled array, compiled at runtime with NVRTC and cached on disk.
77
+ - **Skip empty space**: an optional block predicate (for instance with interval or affine arithmetic) discards the blocks that cannot contain the surface, the cost then scales with the surface instead of the volume.
78
+ - **Built for real time**: autotuned block sizes, a single device to host copy per call, no recompilation when the grid size or the extra parameters change, output buffers can be OpenGL buffers.
79
+ - **CuPy or PyCUDA** backends.
80
+
81
+ ## Performance
82
+ Median time of a call on a RTX 5090 (CUDA 12.9, CuPy 13.6, single precision, outputs kept on the device), as reported by the benchmarks of the examples:
83
+
84
+ | Example | Grid | Output | Without predicate | With predicate |
85
+ |---------|------|--------|------------------:|---------------:|
86
+ | [`7_predicate.py`](https://github.com/keckj/cupmb/blob/main/examples/7_predicate.py) | 2048³ | 27.4 M triangles | 44.5 ms | **4.1 ms** |
87
+ | [`8_levelset.py`](https://github.com/keckj/cupmb/blob/main/examples/8_levelset.py) (animated scene) | 2560 × 732 × 2284 | 15.1 to 17.7 M triangles | 30.9 ms | **21.5 ms** |
88
+ | [`9_gamma.py`](https://github.com/keckj/cupmb/blob/main/examples/9_gamma.py) (1D) | 2²⁶ | 117 roots | 6.0 ms | **1.7 ms** |
89
+
90
+ ## Requirements
91
+ - Linux, NVIDIA GPU and driver.
92
+ - Python >= 3.10.
93
+ - CUDA 12.x (tested with CUDA 12.9 on a sm_120 GPU). The CUDA 13 cupy wheel (`cupy-cuda13x`) can be selected with the `cupy13` extra.
94
+ - One of the two backends: [CuPy](https://cupy.dev/) (default) or [PyCUDA](https://documen.tician.de/pycuda/).
95
+ - Header only device libraries: [trove](https://github.com/bryancatanzaro/trove) (git submodule, shipped with the package) and [cusvna](https://github.com/keckj/cusvna) (`cusvna` package on PyPI, installed as a dependency).
96
+
97
+ ## Getting started
98
+ cupmb is available on [PyPI](https://pypi.org/project/cupmb/), pick a backend extra (`cupy12`, `cupy13` for CUDA 13 or `pycuda`):
99
+ ```bash
100
+ python3 -m pip install "cupmb[cupy12]"
101
+ ```
102
+
103
+ To run the examples, clone the repository with its submodules:
104
+ ```bash
105
+ git clone --recursive https://github.com/keckj/cupmb.git
106
+ cd cupmb
107
+ python3 -m venv .venv && source .venv/bin/activate
108
+ python3 -m pip install ".[cupy12,examples]" # cupmb, its CuPy backend and the dependencies of the examples
109
+ python3 examples/0_simple.py # meshes a sphere and displays it with trimesh
110
+ ```
111
+ If the repository was cloned without `--recursive`, run `git submodule update --init --recursive` before installing.
112
+
113
+ Available extras, combine them with commas:
114
+
115
+ | Extra | Content |
116
+ |------------|----------------------------------------------------------------------------|
117
+ | `cupy12` | CuPy backend for CUDA 12 (`cupy-cuda12x`) |
118
+ | `cupy13` | CuPy backend for CUDA 13 (`cupy-cuda13x`) |
119
+ | `pycuda` | PyCUDA backend (`pycuda`, `cuda-python` for NVRTC) |
120
+ | `gl` | Cuda/OpenGL interoperability (`cuda-python`, `PyOpenGL`) |
121
+ | `examples` | Dependencies of the examples (trimesh, open3d, matplotlib, glfw, ...) |
122
+ | `test` | Test suite (`pytest`, `scipy`) |
123
+ | `dev` | Test suite, `build`, `pre-commit`, `black`, `ruff`, `scikit-image` |
124
+
125
+ All the dependencies of the examples (both backends included) can also be installed with `python3 -m pip install -r requirements_examples.txt`.
126
+
127
+ ## Minimal usage
128
+ ```python
129
+ from cupmb import ParallelMarchingBlocks
130
+
131
+ fdef = r"""
132
+ namespace cupmb {
133
+ template <typename T, typename I, I dim>
134
+ __device__ __forceinline__ T scalar(
135
+ const trove::array<T,dim>& p,
136
+ const trove::array<I,dim>& i) {
137
+ const T& x = trove::get<0>(p);
138
+ const T& y = trove::get<1>(p);
139
+ const T& z = trove::get<2>(p);
140
+ return x*x + y*y + z*z - T(0.45)*T(0.45);
141
+ }
142
+ }
143
+ """
144
+ pmb = ParallelMarchingBlocks(3, fdef, N=64, x0=-0.5, L=1.0)
145
+ V, I = pmb(to_numpy=True) # vertices (nv, 3) float32, triangles (nt, 3) uint32
146
+ ```
147
+ - The scalar function receives the position `p` and the integer coordinates `i` of each grid point. The grid size is a kernel argument and not a template parameter (former `template <typename T, typename I, I dim, I... N>` signatures are still accepted, `N` is then empty).
148
+ - Without `to_numpy=True` the output buffers stay on the device. When the zero level set is empty, `V` and `I` are `None`.
149
+ - Verbosity is controlled by the `CUPMB_LOGLEVEL` environment variable (`DEBUG`, `INFO`, `WARNING`, ...).
150
+
151
+ ## Features
152
+ **Output**
153
+ - PMB outputs an indexed mesh: vertices are shared between the simplices of a block, no triangle soup as with other marching cubes algorithms. Vertices lying on the faces shared by two blocks are duplicated, weld them by position if you need a fully connected mesh.
154
+ - 1D (zero crossings, single vertex simplices), 2D (marching squares, segments) and 3D (marching cubes, triangles) support, single or double precision for vertices. In 1D, `V` and `I` have shape `(n, 1)` and simplex `k` references vertex `k`, vertices are never duplicated on block boundaries.
155
+ - `uint32` or `uint64` simplex indices.
156
+ - Consistently oriented simplices: normals point from negative to positive scalar values (along the gradient). Triangles are counterclockwise when seen from the positive side (closed surfaces have a positive signed volume) and segments are counterclockwise around negative regions (positive signed area). 1D simplices (single vertices) carry no orientation, the normal is the sign of the derivative and can be computed as a vertex property.
157
+ - Custom user defined vertex properties (normals, colors, ...) computed on the GPU alongside the vertices.
158
+ - Vertices and simplices are stored as array of structures, vector loads and stores are optimized with [trove](https://github.com/bryancatanzaro/trove).
159
+
160
+ **Input**
161
+ - The scalar field can be analytic (function computed at runtime on the GPU) or stored in an array/texture. The sampled scalar field can be exported (`export_scalar_field=True`), it is stored x-fastest and indexed as `S[iz,iy,ix]` (shape `N[::-1]+1`, `S[iy,ix]` in 2D as expected by `matplotlib.imshow` or `contour`).
162
+ - User provided extra kernel parameters (constants or Python callables evaluated at each call).
163
+ - Dynamic domains (axis-aligned bounding box) and dynamic kernel parameters.
164
+
165
+ **Performance**
166
+ - Runtime KD-tree-pyramid domain decomposition (binary tree in 1D, quadtree in 2D and octtree in 3D) from analytic block predicates (`pdef`). Predicates can be computed with range analysis by using [interval arithmetic](https://en.wikipedia.org/wiki/Interval_arithmetic) or [affine arithmetic](https://en.wikipedia.org/wiki/Affine_arithmetic), for instance with the [cusvna](https://github.com/keckj/cusvna) headers (`#include <cusvna/af1.cuh>`, requires `enable_cuda_std_lib=True`).
167
+ - Once the buffers are large enough, a call performs a single device to host copy: kernels read the active block, vertex and simplex counts from device counters and grids are sized from the buffer capacities. When a count exceeds a capacity, the buffers are reallocated and the kernels launched again step by step.
168
+ - Low memory consumption with user configurable allocation policies (and possibly user provided buffers).
169
+
170
+ **Integration**
171
+ - Compatible with both [CuPy](https://cupy.dev/) and [PyCUDA](https://documen.tician.de/pycuda/) backends.
172
+ - Cuda/OpenGL interoperability: vertices and simplices can be written directly into OpenGL buffers.
173
+
174
+ ## Kernel cache
175
+ Kernels are compiled at runtime with NVRTC for each configuration (dimension, block sizes, types, user code, ...), autotuning compiles one module per candidate block size. The grid size is a kernel argument: changing `N` does not trigger any compilation. Compiled modules are cached on disk so that subsequent runs skip the compilation (example 8 starts in about 1.5 s instead of 25 s):
176
+ - the cache directory is `$CUPMB_CACHE_DIR/kernels`, it defaults to `~/.cache/cupmb/kernels` (`$XDG_CACHE_HOME/cupmb/kernels`), see `cupmb.kernel_cache_dir()`;
177
+ - set `CUPMB_DISABLE_KERNEL_CACHE=1` to disable it and call `cupmb.clear_kernel_cache()` (or remove the directory) to clear it;
178
+ - entries are keyed by the cupmb version, the backend and its NVRTC version, the device compute capability, the kernel source code, the build options and the content of the cupmb kernel headers (including trove and cusvna). Headers included from other directories through `extra_build_options` are not part of the key: clear the cache when they change.
179
+
180
+ ## Examples
181
+ All the examples run with `python3 -m pip install ".[cupy12,examples]"`, some of them need an additional extra:
182
+
183
+ | Example | Dim | Topic | Extra |
184
+ |---------|:---:|-------|-------|
185
+ | [`0_simple.py`](https://github.com/keckj/cupmb/blob/main/examples/0_simple.py) | 3D | Analytic sphere, mesh displayed with trimesh | |
186
+ | [`1_animation.py`](https://github.com/keckj/cupmb/blob/main/examples/1_animation.py) | 3D | Extra kernel parameter, mesh recomputed at each frame | |
187
+ | [`2_normals.py`](https://github.com/keckj/cupmb/blob/main/examples/2_normals.py) | 3D | Normals as a vertex property | |
188
+ | [`3_glinterop_cupy.py`](https://github.com/keckj/cupmb/blob/main/examples/3_glinterop_cupy.py) | 3D | Cuda/OpenGL interoperability with CuPy | `gl` |
189
+ | [`3_glinterop_pycuda.py`](https://github.com/keckj/cupmb/blob/main/examples/3_glinterop_pycuda.py) | 3D | Cuda/OpenGL interoperability with PyCUDA | `pycuda` |
190
+ | [`4_isolines.py`](https://github.com/keckj/cupmb/blob/main/examples/4_isolines.py) | 2D | Marching squares compared to `matplotlib.contour` | |
191
+ | [`5_moving_sphere.py`](https://github.com/keckj/cupmb/blob/main/examples/5_moving_sphere.py) | 3D | Extra parameters given as Python callables | |
192
+ | [`6_moving_bbox.py`](https://github.com/keckj/cupmb/blob/main/examples/6_moving_bbox.py) | 3D | Dynamic bounding box | |
193
+ | [`7_predicate.py`](https://github.com/keckj/cupmb/blob/main/examples/7_predicate.py) | 3D | Block predicate benchmark (affine arithmetic) | |
194
+ | [`8_levelset.py`](https://github.com/keckj/cupmb/blob/main/examples/8_levelset.py) | 3D | Real time meshing of an animated scene, OpenGL viewer | `gl` |
195
+ | [`9_gamma.py`](https://github.com/keckj/cupmb/blob/main/examples/9_gamma.py) | 1D | Root finding on 2²⁶ cells | |
196
+
197
+ - `0_simple.py` This simple example will show you how to execute the marching cubes algorithm to find the zero levelset of a scalar field representing a sphere. The scalar field is defined as $\phi(x,y,z) = x^2 + y^2 + z^2 - r^2$ where $r=0.45$ is a constant. The function is analytic and implemented directly as a device function. The default bounding box is $[0,1]^3$ so we offset it by $x_0=-0.5$ to find the zeros for $(x,y,z) \in [-0.5,+0.5]^3$. The output mesh (vertices `V`, simplices `I`) is copied to the host (numpy arrays) and is then displayed by using the [trimesh](https://trimesh.org/) module. Parameter `N` controls the resolution of the marching cubes algorithm.
198
+ - `1_animation.py` Sphere with a radius given as an extra kernel parameter (`extra_params=[("r", "T")]`, passed as `pmb(r=...)`), the mesh is recomputed at each frame and displayed with [open3d](https://www.open3d.org/).
199
+ - `2_normals.py` User defined vertex properties (`VertexProperty`): normals are computed on the GPU alongside the vertices and used for shading in an open3d GUI.
200
+ - `3_glinterop_cupy.py`, `3_glinterop_pycuda.py` Cuda/OpenGL interoperability: vertices and simplices are written directly into OpenGL buffers (no host round trip) and rendered with glfw/PyOpenGL (`lock_buffer`/`replace_buffer`), with the CuPy and PyCUDA backends respectively. The PyCUDA version requires PyCUDA to be built with OpenGL support (`pycuda.gl`), which is not the case of a default `python3 -m pip install pycuda`.
201
+ - `4_isolines.py` Marching squares in 2D: isolines of an analytic scalar field, the sampled scalar field is exported (`export_scalar_field=True`) and displayed with matplotlib, the cupmb segments (red) are compared to `matplotlib.contour` (black).
202
+ - `5_moving_sphere.py` Extra kernel parameters (`ExtraParameter`) given as Python callables that are evaluated before each call, here the center of a moving sphere.
203
+ - `6_moving_bbox.py` Dynamic domain: the bounding box follows the moving sphere through a user provided `update_bbox` callback.
204
+ - `7_predicate.py` Benchmark of the block predicate based domain decomposition against the naive block extraction, the predicate is written with cusvna affine arithmetic. Run `python3 examples/7_predicate.py --help` for the options (`-N` grid size, `--rigor` autotuning rigor, `--runs`), large grids require a lot of device memory. The mesh is then displayed with open3d at a lower resolution (`--display-N`, default 512, `--no-display` to disable), shaded with normals computed on the GPU as a vertex property.
205
+ - `8_levelset.py` Real time meshing of the animated scene of the last cusvna example (gyroid ball surrounded by a ring of heart, Kummer, Barth, tetrahedral, twisted box and metaballs level sets): the shapes are written once as templates evaluated on floats for the scalar field and on cusvna affine forms for the block predicate, vertices, triangles, normals and colors are written directly into OpenGL buffers and rendered with glfw/PyOpenGL. The predicate scales with the surface instead of the volume, it is slower at small `N` and pays off at high resolution.
206
+ - Controls (shown in an overlay when [pyimgui](https://github.com/pyimgui/pyimgui) is installed, logged at startup otherwise): `SPACE` pause, `+`/`-` (or `UP`/`DOWN`) animation speed, `*`/`/` (or `PAGE UP`/`PAGE DOWN`) multiply or divide the resolution by two between 64 and 4096 cells along x, `W` wireframe, mouse to rotate and zoom, `Q`/`ESC` quit. The viewer uses a power of two number of cells on each axis, the domain is extended around the scene. When the resolution changes, the marching blocks are rebuilt with the `ESTIMATE` autotuning rigor, the grid size being a kernel argument the kernels are not recompiled.
207
+ - Options: `-N` grid size (default 2560), `--no-predicate`, `--rigor` autotuning rigor, `--frames` number of frames, `--no-display` headless benchmark with and without predicate (median meshing times, triangle counts are checked to be identical).
208
+ - `9_gamma.py` Marching blocks in 1D: root finding on a very fine grid ($N=2^{26}$ by default). The gamma function has no zero, the example finds instead the solutions of $\Gamma(x)=e^{k\pi}$, $k \in \mathbb{Z}$, i.e. the zeros of $\sin(\ln\Gamma(x))$ for $x>0$, with a cusvna affine arithmetic predicate (binary tree). The roots are checked against a host bisection and displayed with matplotlib (`--no-plot` to disable).
209
+
210
+ ## Testing
211
+ ```bash
212
+ python3 -m pip install ".[cupy12,test]"
213
+ python3 -m pytest # requires a CUDA device, GPU tests are skipped otherwise
214
+ python3 -m pytest --runslow # also runs the large grid and large index tests (several GB of device memory)
215
+ ```
216
+ If `scikit-image` is installed (`dev` extra), the 3D meshes are also cross-checked against `skimage.measure.marching_cubes`.
217
+
218
+ ## Development
219
+ ```bash
220
+ python3 -m pip install -e ".[cupy12,dev]"
221
+ pre-commit install
222
+ python3 -m build # sdist and wheel in dist/
223
+ ```
224
+
225
+ ## License
226
+ cupmb is released under the [MIT License](https://github.com/keckj/cupmb/blob/main/LICENSE). The device header libraries keep their own licenses: [trove](https://github.com/bryancatanzaro/trove) (BSD 3-clause, NVIDIA, shipped with the package) and [cusvna](https://github.com/keckj/cusvna) (MIT).
cupmb-1.1.0/README.md ADDED
@@ -0,0 +1,170 @@
1
+ <!--
2
+ Copyright (c) 2021-2026 Jean-Baptiste Keck <jbkeck@hotmail.com>
3
+ SPDX-License-Identifier: MIT
4
+ -->
5
+ <p align="center"><img src="https://raw.githubusercontent.com/keckj/cupmb/main/logo/logo.png" alt="Cuda Parallel Marching Blocks" width="800"></p>
6
+
7
+ <p align="center">
8
+ <img src="https://img.shields.io/badge/platform-linux-lightgrey.svg" alt="Platform: linux">
9
+ <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.10+-blue.svg" alt="Python 3.10+"></a>
10
+ <a href="https://developer.nvidia.com/cuda-toolkit-archive"><img src="https://img.shields.io/badge/cuda-12.x-green.svg" alt="CUDA 12.x"></a>
11
+ <a href="https://github.com/keckj/cupmb/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT"></a>
12
+ <a href="https://github.com/psf/black"><img src="https://img.shields.io/badge/code%20style-black-000000.svg" alt="Code style: black"></a>
13
+ </p>
14
+
15
+ # Cuda Parallel Marching Blocks
16
+
17
+ **cupmb** extracts the zero level set of a scalar field on the GPU from Python: root finding in 1D, marching squares in 2D and marching cubes in 3D. It implements the Parallel Marching Blocks (PMB) algorithm, see [Parallel Marching Blocks: A Practical Isosurfacing Algorithm for Large Data on Many-Core Architectures](https://pure.strath.ac.uk/ws/portalfiles/portal/92247319/Liu_etal_CGF22016_Parallel_marching_blocks_a_practical_isosurfacing_algorithm_large_data_many_core_architectures.pdf).
18
+
19
+ - **Indexed meshes**: vertices are shared between the simplices of a block, no triangle soup.
20
+ - **Your scalar field, compiled**: an analytic CUDA device function or a sampled array, compiled at runtime with NVRTC and cached on disk.
21
+ - **Skip empty space**: an optional block predicate (for instance with interval or affine arithmetic) discards the blocks that cannot contain the surface, the cost then scales with the surface instead of the volume.
22
+ - **Built for real time**: autotuned block sizes, a single device to host copy per call, no recompilation when the grid size or the extra parameters change, output buffers can be OpenGL buffers.
23
+ - **CuPy or PyCUDA** backends.
24
+
25
+ ## Performance
26
+ Median time of a call on a RTX 5090 (CUDA 12.9, CuPy 13.6, single precision, outputs kept on the device), as reported by the benchmarks of the examples:
27
+
28
+ | Example | Grid | Output | Without predicate | With predicate |
29
+ |---------|------|--------|------------------:|---------------:|
30
+ | [`7_predicate.py`](https://github.com/keckj/cupmb/blob/main/examples/7_predicate.py) | 2048³ | 27.4 M triangles | 44.5 ms | **4.1 ms** |
31
+ | [`8_levelset.py`](https://github.com/keckj/cupmb/blob/main/examples/8_levelset.py) (animated scene) | 2560 × 732 × 2284 | 15.1 to 17.7 M triangles | 30.9 ms | **21.5 ms** |
32
+ | [`9_gamma.py`](https://github.com/keckj/cupmb/blob/main/examples/9_gamma.py) (1D) | 2²⁶ | 117 roots | 6.0 ms | **1.7 ms** |
33
+
34
+ ## Requirements
35
+ - Linux, NVIDIA GPU and driver.
36
+ - Python >= 3.10.
37
+ - CUDA 12.x (tested with CUDA 12.9 on a sm_120 GPU). The CUDA 13 cupy wheel (`cupy-cuda13x`) can be selected with the `cupy13` extra.
38
+ - One of the two backends: [CuPy](https://cupy.dev/) (default) or [PyCUDA](https://documen.tician.de/pycuda/).
39
+ - Header only device libraries: [trove](https://github.com/bryancatanzaro/trove) (git submodule, shipped with the package) and [cusvna](https://github.com/keckj/cusvna) (`cusvna` package on PyPI, installed as a dependency).
40
+
41
+ ## Getting started
42
+ cupmb is available on [PyPI](https://pypi.org/project/cupmb/), pick a backend extra (`cupy12`, `cupy13` for CUDA 13 or `pycuda`):
43
+ ```bash
44
+ python3 -m pip install "cupmb[cupy12]"
45
+ ```
46
+
47
+ To run the examples, clone the repository with its submodules:
48
+ ```bash
49
+ git clone --recursive https://github.com/keckj/cupmb.git
50
+ cd cupmb
51
+ python3 -m venv .venv && source .venv/bin/activate
52
+ python3 -m pip install ".[cupy12,examples]" # cupmb, its CuPy backend and the dependencies of the examples
53
+ python3 examples/0_simple.py # meshes a sphere and displays it with trimesh
54
+ ```
55
+ If the repository was cloned without `--recursive`, run `git submodule update --init --recursive` before installing.
56
+
57
+ Available extras, combine them with commas:
58
+
59
+ | Extra | Content |
60
+ |------------|----------------------------------------------------------------------------|
61
+ | `cupy12` | CuPy backend for CUDA 12 (`cupy-cuda12x`) |
62
+ | `cupy13` | CuPy backend for CUDA 13 (`cupy-cuda13x`) |
63
+ | `pycuda` | PyCUDA backend (`pycuda`, `cuda-python` for NVRTC) |
64
+ | `gl` | Cuda/OpenGL interoperability (`cuda-python`, `PyOpenGL`) |
65
+ | `examples` | Dependencies of the examples (trimesh, open3d, matplotlib, glfw, ...) |
66
+ | `test` | Test suite (`pytest`, `scipy`) |
67
+ | `dev` | Test suite, `build`, `pre-commit`, `black`, `ruff`, `scikit-image` |
68
+
69
+ All the dependencies of the examples (both backends included) can also be installed with `python3 -m pip install -r requirements_examples.txt`.
70
+
71
+ ## Minimal usage
72
+ ```python
73
+ from cupmb import ParallelMarchingBlocks
74
+
75
+ fdef = r"""
76
+ namespace cupmb {
77
+ template <typename T, typename I, I dim>
78
+ __device__ __forceinline__ T scalar(
79
+ const trove::array<T,dim>& p,
80
+ const trove::array<I,dim>& i) {
81
+ const T& x = trove::get<0>(p);
82
+ const T& y = trove::get<1>(p);
83
+ const T& z = trove::get<2>(p);
84
+ return x*x + y*y + z*z - T(0.45)*T(0.45);
85
+ }
86
+ }
87
+ """
88
+ pmb = ParallelMarchingBlocks(3, fdef, N=64, x0=-0.5, L=1.0)
89
+ V, I = pmb(to_numpy=True) # vertices (nv, 3) float32, triangles (nt, 3) uint32
90
+ ```
91
+ - The scalar function receives the position `p` and the integer coordinates `i` of each grid point. The grid size is a kernel argument and not a template parameter (former `template <typename T, typename I, I dim, I... N>` signatures are still accepted, `N` is then empty).
92
+ - Without `to_numpy=True` the output buffers stay on the device. When the zero level set is empty, `V` and `I` are `None`.
93
+ - Verbosity is controlled by the `CUPMB_LOGLEVEL` environment variable (`DEBUG`, `INFO`, `WARNING`, ...).
94
+
95
+ ## Features
96
+ **Output**
97
+ - PMB outputs an indexed mesh: vertices are shared between the simplices of a block, no triangle soup as with other marching cubes algorithms. Vertices lying on the faces shared by two blocks are duplicated, weld them by position if you need a fully connected mesh.
98
+ - 1D (zero crossings, single vertex simplices), 2D (marching squares, segments) and 3D (marching cubes, triangles) support, single or double precision for vertices. In 1D, `V` and `I` have shape `(n, 1)` and simplex `k` references vertex `k`, vertices are never duplicated on block boundaries.
99
+ - `uint32` or `uint64` simplex indices.
100
+ - Consistently oriented simplices: normals point from negative to positive scalar values (along the gradient). Triangles are counterclockwise when seen from the positive side (closed surfaces have a positive signed volume) and segments are counterclockwise around negative regions (positive signed area). 1D simplices (single vertices) carry no orientation, the normal is the sign of the derivative and can be computed as a vertex property.
101
+ - Custom user defined vertex properties (normals, colors, ...) computed on the GPU alongside the vertices.
102
+ - Vertices and simplices are stored as array of structures, vector loads and stores are optimized with [trove](https://github.com/bryancatanzaro/trove).
103
+
104
+ **Input**
105
+ - The scalar field can be analytic (function computed at runtime on the GPU) or stored in an array/texture. The sampled scalar field can be exported (`export_scalar_field=True`), it is stored x-fastest and indexed as `S[iz,iy,ix]` (shape `N[::-1]+1`, `S[iy,ix]` in 2D as expected by `matplotlib.imshow` or `contour`).
106
+ - User provided extra kernel parameters (constants or Python callables evaluated at each call).
107
+ - Dynamic domains (axis-aligned bounding box) and dynamic kernel parameters.
108
+
109
+ **Performance**
110
+ - Runtime KD-tree-pyramid domain decomposition (binary tree in 1D, quadtree in 2D and octtree in 3D) from analytic block predicates (`pdef`). Predicates can be computed with range analysis by using [interval arithmetic](https://en.wikipedia.org/wiki/Interval_arithmetic) or [affine arithmetic](https://en.wikipedia.org/wiki/Affine_arithmetic), for instance with the [cusvna](https://github.com/keckj/cusvna) headers (`#include <cusvna/af1.cuh>`, requires `enable_cuda_std_lib=True`).
111
+ - Once the buffers are large enough, a call performs a single device to host copy: kernels read the active block, vertex and simplex counts from device counters and grids are sized from the buffer capacities. When a count exceeds a capacity, the buffers are reallocated and the kernels launched again step by step.
112
+ - Low memory consumption with user configurable allocation policies (and possibly user provided buffers).
113
+
114
+ **Integration**
115
+ - Compatible with both [CuPy](https://cupy.dev/) and [PyCUDA](https://documen.tician.de/pycuda/) backends.
116
+ - Cuda/OpenGL interoperability: vertices and simplices can be written directly into OpenGL buffers.
117
+
118
+ ## Kernel cache
119
+ Kernels are compiled at runtime with NVRTC for each configuration (dimension, block sizes, types, user code, ...), autotuning compiles one module per candidate block size. The grid size is a kernel argument: changing `N` does not trigger any compilation. Compiled modules are cached on disk so that subsequent runs skip the compilation (example 8 starts in about 1.5 s instead of 25 s):
120
+ - the cache directory is `$CUPMB_CACHE_DIR/kernels`, it defaults to `~/.cache/cupmb/kernels` (`$XDG_CACHE_HOME/cupmb/kernels`), see `cupmb.kernel_cache_dir()`;
121
+ - set `CUPMB_DISABLE_KERNEL_CACHE=1` to disable it and call `cupmb.clear_kernel_cache()` (or remove the directory) to clear it;
122
+ - entries are keyed by the cupmb version, the backend and its NVRTC version, the device compute capability, the kernel source code, the build options and the content of the cupmb kernel headers (including trove and cusvna). Headers included from other directories through `extra_build_options` are not part of the key: clear the cache when they change.
123
+
124
+ ## Examples
125
+ All the examples run with `python3 -m pip install ".[cupy12,examples]"`, some of them need an additional extra:
126
+
127
+ | Example | Dim | Topic | Extra |
128
+ |---------|:---:|-------|-------|
129
+ | [`0_simple.py`](https://github.com/keckj/cupmb/blob/main/examples/0_simple.py) | 3D | Analytic sphere, mesh displayed with trimesh | |
130
+ | [`1_animation.py`](https://github.com/keckj/cupmb/blob/main/examples/1_animation.py) | 3D | Extra kernel parameter, mesh recomputed at each frame | |
131
+ | [`2_normals.py`](https://github.com/keckj/cupmb/blob/main/examples/2_normals.py) | 3D | Normals as a vertex property | |
132
+ | [`3_glinterop_cupy.py`](https://github.com/keckj/cupmb/blob/main/examples/3_glinterop_cupy.py) | 3D | Cuda/OpenGL interoperability with CuPy | `gl` |
133
+ | [`3_glinterop_pycuda.py`](https://github.com/keckj/cupmb/blob/main/examples/3_glinterop_pycuda.py) | 3D | Cuda/OpenGL interoperability with PyCUDA | `pycuda` |
134
+ | [`4_isolines.py`](https://github.com/keckj/cupmb/blob/main/examples/4_isolines.py) | 2D | Marching squares compared to `matplotlib.contour` | |
135
+ | [`5_moving_sphere.py`](https://github.com/keckj/cupmb/blob/main/examples/5_moving_sphere.py) | 3D | Extra parameters given as Python callables | |
136
+ | [`6_moving_bbox.py`](https://github.com/keckj/cupmb/blob/main/examples/6_moving_bbox.py) | 3D | Dynamic bounding box | |
137
+ | [`7_predicate.py`](https://github.com/keckj/cupmb/blob/main/examples/7_predicate.py) | 3D | Block predicate benchmark (affine arithmetic) | |
138
+ | [`8_levelset.py`](https://github.com/keckj/cupmb/blob/main/examples/8_levelset.py) | 3D | Real time meshing of an animated scene, OpenGL viewer | `gl` |
139
+ | [`9_gamma.py`](https://github.com/keckj/cupmb/blob/main/examples/9_gamma.py) | 1D | Root finding on 2²⁶ cells | |
140
+
141
+ - `0_simple.py` This simple example will show you how to execute the marching cubes algorithm to find the zero levelset of a scalar field representing a sphere. The scalar field is defined as $\phi(x,y,z) = x^2 + y^2 + z^2 - r^2$ where $r=0.45$ is a constant. The function is analytic and implemented directly as a device function. The default bounding box is $[0,1]^3$ so we offset it by $x_0=-0.5$ to find the zeros for $(x,y,z) \in [-0.5,+0.5]^3$. The output mesh (vertices `V`, simplices `I`) is copied to the host (numpy arrays) and is then displayed by using the [trimesh](https://trimesh.org/) module. Parameter `N` controls the resolution of the marching cubes algorithm.
142
+ - `1_animation.py` Sphere with a radius given as an extra kernel parameter (`extra_params=[("r", "T")]`, passed as `pmb(r=...)`), the mesh is recomputed at each frame and displayed with [open3d](https://www.open3d.org/).
143
+ - `2_normals.py` User defined vertex properties (`VertexProperty`): normals are computed on the GPU alongside the vertices and used for shading in an open3d GUI.
144
+ - `3_glinterop_cupy.py`, `3_glinterop_pycuda.py` Cuda/OpenGL interoperability: vertices and simplices are written directly into OpenGL buffers (no host round trip) and rendered with glfw/PyOpenGL (`lock_buffer`/`replace_buffer`), with the CuPy and PyCUDA backends respectively. The PyCUDA version requires PyCUDA to be built with OpenGL support (`pycuda.gl`), which is not the case of a default `python3 -m pip install pycuda`.
145
+ - `4_isolines.py` Marching squares in 2D: isolines of an analytic scalar field, the sampled scalar field is exported (`export_scalar_field=True`) and displayed with matplotlib, the cupmb segments (red) are compared to `matplotlib.contour` (black).
146
+ - `5_moving_sphere.py` Extra kernel parameters (`ExtraParameter`) given as Python callables that are evaluated before each call, here the center of a moving sphere.
147
+ - `6_moving_bbox.py` Dynamic domain: the bounding box follows the moving sphere through a user provided `update_bbox` callback.
148
+ - `7_predicate.py` Benchmark of the block predicate based domain decomposition against the naive block extraction, the predicate is written with cusvna affine arithmetic. Run `python3 examples/7_predicate.py --help` for the options (`-N` grid size, `--rigor` autotuning rigor, `--runs`), large grids require a lot of device memory. The mesh is then displayed with open3d at a lower resolution (`--display-N`, default 512, `--no-display` to disable), shaded with normals computed on the GPU as a vertex property.
149
+ - `8_levelset.py` Real time meshing of the animated scene of the last cusvna example (gyroid ball surrounded by a ring of heart, Kummer, Barth, tetrahedral, twisted box and metaballs level sets): the shapes are written once as templates evaluated on floats for the scalar field and on cusvna affine forms for the block predicate, vertices, triangles, normals and colors are written directly into OpenGL buffers and rendered with glfw/PyOpenGL. The predicate scales with the surface instead of the volume, it is slower at small `N` and pays off at high resolution.
150
+ - Controls (shown in an overlay when [pyimgui](https://github.com/pyimgui/pyimgui) is installed, logged at startup otherwise): `SPACE` pause, `+`/`-` (or `UP`/`DOWN`) animation speed, `*`/`/` (or `PAGE UP`/`PAGE DOWN`) multiply or divide the resolution by two between 64 and 4096 cells along x, `W` wireframe, mouse to rotate and zoom, `Q`/`ESC` quit. The viewer uses a power of two number of cells on each axis, the domain is extended around the scene. When the resolution changes, the marching blocks are rebuilt with the `ESTIMATE` autotuning rigor, the grid size being a kernel argument the kernels are not recompiled.
151
+ - Options: `-N` grid size (default 2560), `--no-predicate`, `--rigor` autotuning rigor, `--frames` number of frames, `--no-display` headless benchmark with and without predicate (median meshing times, triangle counts are checked to be identical).
152
+ - `9_gamma.py` Marching blocks in 1D: root finding on a very fine grid ($N=2^{26}$ by default). The gamma function has no zero, the example finds instead the solutions of $\Gamma(x)=e^{k\pi}$, $k \in \mathbb{Z}$, i.e. the zeros of $\sin(\ln\Gamma(x))$ for $x>0$, with a cusvna affine arithmetic predicate (binary tree). The roots are checked against a host bisection and displayed with matplotlib (`--no-plot` to disable).
153
+
154
+ ## Testing
155
+ ```bash
156
+ python3 -m pip install ".[cupy12,test]"
157
+ python3 -m pytest # requires a CUDA device, GPU tests are skipped otherwise
158
+ python3 -m pytest --runslow # also runs the large grid and large index tests (several GB of device memory)
159
+ ```
160
+ If `scikit-image` is installed (`dev` extra), the 3D meshes are also cross-checked against `skimage.measure.marching_cubes`.
161
+
162
+ ## Development
163
+ ```bash
164
+ python3 -m pip install -e ".[cupy12,dev]"
165
+ pre-commit install
166
+ python3 -m build # sdist and wheel in dist/
167
+ ```
168
+
169
+ ## License
170
+ cupmb is released under the [MIT License](https://github.com/keckj/cupmb/blob/main/LICENSE). The device header libraries keep their own licenses: [trove](https://github.com/bryancatanzaro/trove) (BSD 3-clause, NVIDIA, shipped with the package) and [cusvna](https://github.com/keckj/cusvna) (MIT).
@@ -0,0 +1,46 @@
1
+ #
2
+ # Copyright (c) 2021-2026 Jean-Baptiste Keck <jbkeck@hotmail.com>
3
+ # SPDX-License-Identifier: MIT
4
+ #
5
+
6
+ __version__ = "1.1.0"
7
+ """:noindex: The version of this package as a string (single source of truth for packaging)."""
8
+
9
+ __version_info__ = tuple(__version__.split("."))
10
+ """:noindex: The version of this package as a tuple of strings."""
11
+
12
+ __author__ = "Jean-Baptiste Keck"
13
+ """:noindex: The author of this package."""
14
+
15
+ from cupmb.utils.logger import logger, log_once
16
+
17
+ log_once("info", f"Initializing {__name__} version {__version__}.", logger)
18
+
19
+ from cupmb.enums import (
20
+ Backend,
21
+ BufferType,
22
+ AllocationPolicy,
23
+ AutotuningRigor,
24
+ )
25
+
26
+ from cupmb.bbox import BoundingBox
27
+ from cupmb.parameters import ExtraParameter
28
+ from cupmb.properties import VertexProperty
29
+
30
+ from cupmb.core import (
31
+ ParallelMarchingBlocks,
32
+ )
33
+ from cupmb.utils.kernel_cache import kernel_cache_dir, clear_kernel_cache
34
+
35
+ __all__ = [
36
+ "Backend",
37
+ "BufferType",
38
+ "AllocationPolicy",
39
+ "AutotuningRigor",
40
+ "BoundingBox",
41
+ "ExtraParameter",
42
+ "VertexProperty",
43
+ "ParallelMarchingBlocks",
44
+ "kernel_cache_dir",
45
+ "clear_kernel_cache",
46
+ ]
@@ -0,0 +1,87 @@
1
+ #
2
+ # Copyright (c) 2021-2026 Jean-Baptiste Keck <jbkeck@hotmail.com>
3
+ # SPDX-License-Identifier: MIT
4
+ #
5
+ import os
6
+
7
+ from cupmb.enums import Backend
8
+
9
+
10
+ def find_cuda_include_dirs(logger, extra_candidates=()):
11
+ """
12
+ Determine the CUDA toolkit include directories (used to build kernels against the CUDA standard library).
13
+
14
+ The CUDA root is determined from the CUPMB_CUDA_ROOT environment variable if it is set. Otherwise, the
15
+ first existing directory among extra_candidates (backend specific), CUDA_PATH, CUDA_HOME and /usr/local/cuda
16
+ is used. For CUDA >= 13 toolkits, the include/cccl directory is returned as well when it exists.
17
+ """
18
+ if "CUPMB_CUDA_ROOT" in os.environ:
19
+ cuda_root = os.environ["CUPMB_CUDA_ROOT"].strip()
20
+ if not os.path.isdir(cuda_root):
21
+ msg = f"CUPMB_CUDA_ROOT has been set to '{cuda_root}' but this directory does not exist."
22
+ logger.error(msg)
23
+ raise RuntimeError(msg)
24
+ else:
25
+ candidates = tuple(extra_candidates) + (
26
+ ("CUDA_PATH", os.environ.get("CUDA_PATH")),
27
+ ("CUDA_HOME", os.environ.get("CUDA_HOME")),
28
+ ("default CUDA root", "/usr/local/cuda"),
29
+ )
30
+ cuda_root = None
31
+ for source, candidate in candidates:
32
+ if not candidate:
33
+ continue
34
+ candidate = candidate.strip()
35
+ if os.path.isdir(os.path.join(candidate, "include")):
36
+ logger.debug(f"Using CUDA root '{candidate}' from {source}.")
37
+ cuda_root = candidate
38
+ break
39
+ logger.debug(f"Ignoring CUDA root '{candidate}' from {source} (no include directory).")
40
+ if cuda_root is None:
41
+ msg = (
42
+ "Could not determine the CUDA toolkit root directory, please set the CUPMB_CUDA_ROOT "
43
+ "(or CUDA_PATH) environment variable to your CUDA toolkit root directory."
44
+ )
45
+ logger.error(msg)
46
+ raise RuntimeError(msg)
47
+
48
+ cuda_include_dir = os.path.join(cuda_root, "include")
49
+ if not os.path.isdir(cuda_include_dir):
50
+ msg = (
51
+ f"Could not find CUDA include directory '{cuda_include_dir}', please set the CUPMB_CUDA_ROOT "
52
+ "environment variable to your CUDA toolkit root directory."
53
+ )
54
+ logger.error(msg)
55
+ raise RuntimeError(msg)
56
+
57
+ include_dirs = [cuda_include_dir]
58
+ cccl_include_dir = os.path.join(cuda_include_dir, "cccl")
59
+ if os.path.isdir(cccl_include_dir):
60
+ # CUDA >= 13 toolkits ship libcu++/CUB/Thrust headers in include/cccl
61
+ include_dirs.append(cccl_include_dir)
62
+
63
+ return tuple(map(os.path.realpath, include_dirs))
64
+
65
+
66
+ class CudaBackend:
67
+ def __new__(cls, backend, initialize_context=True):
68
+ if backend is Backend.CUPY:
69
+ from cupmb.backend.cupy_backend import CupyBackend
70
+
71
+ return CupyBackend
72
+ elif backend is Backend.PYCUDA:
73
+ from cupmb.backend.pycuda_backend import PyCudaBackend
74
+
75
+ if initialize_context:
76
+ import pycuda.autoinit # noqa: F401
77
+ return PyCudaBackend
78
+ else:
79
+ raise NotImplementedError(backend)
80
+
81
+ @classmethod
82
+ def determine_cuda_include_dirs(cls, logger):
83
+ return find_cuda_include_dirs(logger)
84
+
85
+ @classmethod
86
+ def determine_cuda_include_dir(cls, logger):
87
+ return cls.determine_cuda_include_dirs(logger)[0]