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.
- cupmb-1.1.0/LICENSE +21 -0
- cupmb-1.1.0/MANIFEST.in +9 -0
- cupmb-1.1.0/PKG-INFO +226 -0
- cupmb-1.1.0/README.md +170 -0
- cupmb-1.1.0/cupmb/__init__.py +46 -0
- cupmb-1.1.0/cupmb/backend/__init__.py +87 -0
- cupmb-1.1.0/cupmb/backend/cupy_backend.py +549 -0
- cupmb-1.1.0/cupmb/backend/pycuda_backend.py +578 -0
- cupmb-1.1.0/cupmb/bbox.py +75 -0
- cupmb-1.1.0/cupmb/core.py +2389 -0
- cupmb-1.1.0/cupmb/enums.py +44 -0
- cupmb-1.1.0/cupmb/kernels/pmb.cuh +348 -0
- cupmb-1.1.0/cupmb/kernels/pmb1D.cuh +262 -0
- cupmb-1.1.0/cupmb/kernels/pmb2D.cuh +371 -0
- cupmb-1.1.0/cupmb/kernels/pmb3D.cuh +476 -0
- cupmb-1.1.0/cupmb/kernels/trove/aos.h +280 -0
- cupmb-1.1.0/cupmb/kernels/trove/array.h +287 -0
- cupmb-1.1.0/cupmb/kernels/trove/block.h +67 -0
- cupmb-1.1.0/cupmb/kernels/trove/detail/dismember.h +127 -0
- cupmb-1.1.0/cupmb/kernels/trove/detail/fallback.h +133 -0
- cupmb-1.1.0/cupmb/kernels/trove/memory.h +170 -0
- cupmb-1.1.0/cupmb/kernels/trove/print_array.h +48 -0
- cupmb-1.1.0/cupmb/kernels/trove/ptr.h +80 -0
- cupmb-1.1.0/cupmb/kernels/trove/rotate.h +117 -0
- cupmb-1.1.0/cupmb/kernels/trove/shfl.h +106 -0
- cupmb-1.1.0/cupmb/kernels/trove/static_gcd.h +77 -0
- cupmb-1.1.0/cupmb/kernels/trove/static_mod_inverse.h +54 -0
- cupmb-1.1.0/cupmb/kernels/trove/transpose.h +686 -0
- cupmb-1.1.0/cupmb/kernels/trove/utility.h +162 -0
- cupmb-1.1.0/cupmb/kernels/trove/warp.h +52 -0
- cupmb-1.1.0/cupmb/parameters.py +31 -0
- cupmb-1.1.0/cupmb/properties.py +136 -0
- cupmb-1.1.0/cupmb/utils/IO.py +25 -0
- cupmb-1.1.0/cupmb/utils/__init__.py +4 -0
- cupmb-1.1.0/cupmb/utils/kernel_cache.py +129 -0
- cupmb-1.1.0/cupmb/utils/logger.py +64 -0
- cupmb-1.1.0/cupmb/utils/nvrtc_build.py +274 -0
- cupmb-1.1.0/cupmb/utils/types.py +51 -0
- cupmb-1.1.0/cupmb/utils/units.py +91 -0
- cupmb-1.1.0/cupmb.egg-info/PKG-INFO +226 -0
- cupmb-1.1.0/cupmb.egg-info/SOURCES.txt +68 -0
- cupmb-1.1.0/cupmb.egg-info/dependency_links.txt +1 -0
- cupmb-1.1.0/cupmb.egg-info/requires.txt +41 -0
- cupmb-1.1.0/cupmb.egg-info/top_level.txt +1 -0
- cupmb-1.1.0/examples/0_simple.py +46 -0
- cupmb-1.1.0/examples/1_animation.py +101 -0
- cupmb-1.1.0/examples/2_normals.py +220 -0
- cupmb-1.1.0/examples/3_glinterop_cupy.py +215 -0
- cupmb-1.1.0/examples/3_glinterop_pycuda.py +214 -0
- cupmb-1.1.0/examples/4_isolines.py +112 -0
- cupmb-1.1.0/examples/5_moving_sphere.py +111 -0
- cupmb-1.1.0/examples/6_moving_bbox.py +137 -0
- cupmb-1.1.0/examples/7_predicate.py +204 -0
- cupmb-1.1.0/examples/8_levelset.py +1131 -0
- cupmb-1.1.0/examples/9_gamma.py +167 -0
- cupmb-1.1.0/ext/trove/LICENSE +24 -0
- cupmb-1.1.0/pyproject.toml +110 -0
- cupmb-1.1.0/requirements.txt +7 -0
- cupmb-1.1.0/requirements_dev.txt +10 -0
- cupmb-1.1.0/requirements_examples.txt +12 -0
- cupmb-1.1.0/setup.cfg +4 -0
- cupmb-1.1.0/setup.py +21 -0
- cupmb-1.1.0/tests/conftest.py +79 -0
- cupmb-1.1.0/tests/pmb_testing.py +301 -0
- cupmb-1.1.0/tests/test_api.py +62 -0
- cupmb-1.1.0/tests/test_device_driven.py +195 -0
- cupmb-1.1.0/tests/test_kernel_cache.py +158 -0
- cupmb-1.1.0/tests/test_marching.py +387 -0
- cupmb-1.1.0/tests/test_predicate.py +215 -0
- 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.
|
cupmb-1.1.0/MANIFEST.in
ADDED
|
@@ -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]
|