PyRXMesh 0.2.1rc0__tar.gz → 0.2.1rc2__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.
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/CMakeLists.txt +3 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/PKG-INFO +1 -1
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/pyproject.toml +1 -1
- pyrxmesh-0.2.1rc2/pyrxmesh/__init__.py +168 -0
- pyrxmesh-0.2.1rc2/pyrxmesh/_numpy_interop.py +46 -0
- pyrxmesh-0.2.1rc2/pyrxmesh/_scipy_interop.py +41 -0
- pyrxmesh-0.2.1rc2/pyrxmesh/_torch_interop.py +206 -0
- pyrxmesh-0.2.1rc2/src/bindings/common.h +169 -0
- pyrxmesh-0.2.1rc2/src/bindings/dense_matrix_dlpack.h +228 -0
- pyrxmesh-0.2.1rc2/src/bindings/dispatch.h +318 -0
- pyrxmesh-0.2.1rc2/src/bindings/dlpack_utils.h +284 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/src/bindings/geometry.cu +6 -16
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/src/bindings/mesh.cu +31 -90
- pyrxmesh-0.2.1rc2/src/bindings/plugin_launch.h +167 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/src/bindings/py_attribute.h +16 -39
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/src/bindings/py_attributes.cu +13 -84
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/src/bindings/py_dense_matrix.cu +0 -26
- pyrxmesh-0.2.1rc2/src/bindings/py_dense_matrix.h +585 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/src/bindings/py_solvers.cu +40 -34
- pyrxmesh-0.2.1rc2/src/bindings/py_solvers.h +571 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/src/bindings/py_sparse_matrix.cu +19 -90
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/src/bindings/py_sparse_matrix.h +25 -65
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/src/bindings/sparse_matrix_csr.h +6 -14
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/src/bindings/sparse_matrix_dlpack.h +47 -178
- pyrxmesh-0.2.1rc2/tests/conftest.py +28 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/tests/test_attributes.py +9 -32
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/tests/test_core_mesh.py +4 -24
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/tests/test_dense_matrix.py +2 -18
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/tests/test_geometry_io.py +2 -17
- pyrxmesh-0.2.1rc2/tests/test_solvers.py +182 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/tests/test_sparse_matrix.py +10 -33
- pyrxmesh-0.2.1rc0/pyrxmesh/__init__.py +0 -382
- pyrxmesh-0.2.1rc0/src/bindings/common.h +0 -462
- pyrxmesh-0.2.1rc0/src/bindings/dense_matrix_dlpack.h +0 -369
- pyrxmesh-0.2.1rc0/src/bindings/py_dense_matrix.h +0 -627
- pyrxmesh-0.2.1rc0/src/bindings/py_solvers.h +0 -607
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/.clang-format +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/.gitignore +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/CUSTOM_CUDA_PLUGINS.md +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/DEVELOPING.md +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/LICENSE +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/README.md +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/cmake/build_config.json.in +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/cmake/pyrxmeshConfig.cmake.in +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/conftest.py +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/environment.yml +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/examples/custom_kernel_plugin/CMakeLists.txt +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/examples/custom_kernel_plugin/README.md +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/examples/custom_kernel_plugin/pyproject.toml +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/examples/custom_kernel_plugin/run_edge_lengths.py +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/examples/custom_kernel_plugin/src/rxmesh_edge_lengths/__init__.py +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/examples/custom_kernel_plugin/src/rxmesh_edge_lengths.cu +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/examples/load_and_show.py +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/include/pyrxmesh/plugin_api.h +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/meshes/bumpy.obj +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/meshes/sphere3.obj +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/pyrxmesh/cmake_dir.py +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/pyrxmesh/diff.py +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/pyrxmesh/geometry.py +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/pyrxmesh/plugin.py +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/src/bindings/core.cu +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/src/bindings/dlpack_minimal.h +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/src/bindings/handles.cu +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/src/bindings/py_hessian_sparse_matrix.h +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/src/bindings/py_jacobian_sparse_matrix.h +0 -0
- {pyrxmesh-0.2.1rc0 → pyrxmesh-0.2.1rc2}/src/bindings.cu +0 -0
|
@@ -98,7 +98,10 @@ pybind11_add_module(_rxmesh MODULE
|
|
|
98
98
|
src/bindings/handles.cu
|
|
99
99
|
src/bindings/mesh.cu
|
|
100
100
|
src/bindings/common.h
|
|
101
|
+
src/bindings/dispatch.h
|
|
102
|
+
src/bindings/plugin_launch.h
|
|
101
103
|
src/bindings/dlpack_minimal.h
|
|
104
|
+
src/bindings/dlpack_utils.h
|
|
102
105
|
|
|
103
106
|
src/bindings/py_attributes.cu
|
|
104
107
|
src/bindings/py_attribute.h
|
|
@@ -7,7 +7,7 @@ build-backend = "scikit_build_core.build"
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "PyRXMesh"
|
|
10
|
-
version = "0.2.
|
|
10
|
+
version = "0.2.1rc2"
|
|
11
11
|
description = "Python bindings for RXMesh, a CUDA/C++ library for mesh processing on the GPU."
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
requires-python = ">=3.10"
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""PyRXMesh: Python bindings for the RXMesh GPU mesh-processing library.
|
|
2
|
+
|
|
3
|
+
This package exposes:
|
|
4
|
+
|
|
5
|
+
- The native classes (``RXMeshStatic``, ``DenseMatrix``, ``SparseMatrix``,
|
|
6
|
+
``Attribute``, solvers, etc.) from the compiled ``_rxmesh`` extension.
|
|
7
|
+
- Optional NumPy / SciPy / PyTorch interop methods attached to those classes
|
|
8
|
+
via the ``_numpy_interop``, ``_scipy_interop``, and ``_torch_interop``
|
|
9
|
+
submodules. The torch / scipy modules import their backing library lazily,
|
|
10
|
+
so attaching them is free if the user never calls them.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
_dll_handles = []
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _add_windows_dll_dir(path: Path) -> None:
|
|
23
|
+
if os.name != "nt" or not path.exists():
|
|
24
|
+
return
|
|
25
|
+
try:
|
|
26
|
+
handle = os.add_dll_directory(str(path))
|
|
27
|
+
except (FileNotFoundError, OSError):
|
|
28
|
+
return
|
|
29
|
+
_dll_handles.append(handle)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _prepare_dll_search_path() -> None:
|
|
33
|
+
if os.name != "nt":
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
package_dir = Path(__file__).resolve().parent
|
|
37
|
+
_add_windows_dll_dir(package_dir)
|
|
38
|
+
_add_windows_dll_dir(package_dir / "bin")
|
|
39
|
+
_add_windows_dll_dir(package_dir / "lib")
|
|
40
|
+
|
|
41
|
+
for env_name in ("CUDA_PATH", "CUDA_HOME"):
|
|
42
|
+
cuda_root = os.environ.get(env_name)
|
|
43
|
+
if cuda_root:
|
|
44
|
+
_add_windows_dll_dir(Path(cuda_root) / "bin")
|
|
45
|
+
|
|
46
|
+
for env_name, cuda_root in os.environ.items():
|
|
47
|
+
if env_name.startswith("CUDA_PATH_V") and cuda_root:
|
|
48
|
+
_add_windows_dll_dir(Path(cuda_root) / "bin")
|
|
49
|
+
|
|
50
|
+
default_cuda_root = Path("C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA")
|
|
51
|
+
if default_cuda_root.exists():
|
|
52
|
+
for cuda_bin in sorted(default_cuda_root.glob("v*/bin"), reverse=True):
|
|
53
|
+
_add_windows_dll_dir(cuda_bin)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
_prepare_dll_search_path()
|
|
57
|
+
|
|
58
|
+
_extension_import_error: ModuleNotFoundError | None = None
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
from ._rxmesh import (
|
|
62
|
+
Attribute,
|
|
63
|
+
CGSolver,
|
|
64
|
+
CholeskySolver,
|
|
65
|
+
DEdgeHandle,
|
|
66
|
+
DType,
|
|
67
|
+
DenseMatrix,
|
|
68
|
+
EdgeAttributeFloat32,
|
|
69
|
+
EdgeAttributeFloat64,
|
|
70
|
+
EdgeAttributeInt32,
|
|
71
|
+
EdgeAttributeInt8,
|
|
72
|
+
EdgeHandle,
|
|
73
|
+
ElementKind,
|
|
74
|
+
FaceAttributeFloat32,
|
|
75
|
+
FaceAttributeFloat64,
|
|
76
|
+
FaceAttributeInt32,
|
|
77
|
+
FaceAttributeInt8,
|
|
78
|
+
FaceHandle,
|
|
79
|
+
HessianSparseMatrix,
|
|
80
|
+
JacobianSparseMatrix,
|
|
81
|
+
Layout,
|
|
82
|
+
Location,
|
|
83
|
+
LogLevel,
|
|
84
|
+
Op,
|
|
85
|
+
PCGSolver,
|
|
86
|
+
QRSolver,
|
|
87
|
+
RXMeshStatic,
|
|
88
|
+
LUSolver,
|
|
89
|
+
SparseMatrix,
|
|
90
|
+
VertexAttributeFloat32,
|
|
91
|
+
VertexAttributeFloat64,
|
|
92
|
+
VertexAttributeInt32,
|
|
93
|
+
VertexAttributeInt8,
|
|
94
|
+
VertexHandle,
|
|
95
|
+
abi_version,
|
|
96
|
+
build_config_tag,
|
|
97
|
+
create_plane,
|
|
98
|
+
cuda_stream_synchronize,
|
|
99
|
+
cuDSSCholeskySolver,
|
|
100
|
+
has_cudss,
|
|
101
|
+
init,
|
|
102
|
+
show,
|
|
103
|
+
)
|
|
104
|
+
except ModuleNotFoundError as exc:
|
|
105
|
+
if exc.name != f"{__name__}._rxmesh":
|
|
106
|
+
raise
|
|
107
|
+
_extension_import_error = exc
|
|
108
|
+
__all__ = []
|
|
109
|
+
else:
|
|
110
|
+
__all__ = [
|
|
111
|
+
"Attribute",
|
|
112
|
+
"CGSolver",
|
|
113
|
+
"CholeskySolver",
|
|
114
|
+
"DEdgeHandle",
|
|
115
|
+
"DType",
|
|
116
|
+
"DenseMatrix",
|
|
117
|
+
"EdgeAttributeFloat32",
|
|
118
|
+
"EdgeAttributeFloat64",
|
|
119
|
+
"EdgeAttributeInt32",
|
|
120
|
+
"EdgeAttributeInt8",
|
|
121
|
+
"EdgeHandle",
|
|
122
|
+
"ElementKind",
|
|
123
|
+
"FaceAttributeFloat32",
|
|
124
|
+
"FaceAttributeFloat64",
|
|
125
|
+
"FaceAttributeInt32",
|
|
126
|
+
"FaceAttributeInt8",
|
|
127
|
+
"FaceHandle",
|
|
128
|
+
"HessianSparseMatrix",
|
|
129
|
+
"JacobianSparseMatrix",
|
|
130
|
+
"Layout",
|
|
131
|
+
"Location",
|
|
132
|
+
"LogLevel",
|
|
133
|
+
"Op",
|
|
134
|
+
"PCGSolver",
|
|
135
|
+
"QRSolver",
|
|
136
|
+
"RXMeshStatic",
|
|
137
|
+
"LUSolver",
|
|
138
|
+
"SparseMatrix",
|
|
139
|
+
"VertexAttributeFloat32",
|
|
140
|
+
"VertexAttributeFloat64",
|
|
141
|
+
"VertexAttributeInt32",
|
|
142
|
+
"VertexAttributeInt8",
|
|
143
|
+
"VertexHandle",
|
|
144
|
+
"abi_version",
|
|
145
|
+
"build_config_tag",
|
|
146
|
+
"create_plane",
|
|
147
|
+
"cuda_stream_synchronize",
|
|
148
|
+
"cuDSSCholeskySolver",
|
|
149
|
+
"has_cudss",
|
|
150
|
+
"init",
|
|
151
|
+
"show",
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
# Attach optional interop methods to the native classes. Each submodule
|
|
155
|
+
# only does the patching; backing libraries (torch, scipy) are imported
|
|
156
|
+
# lazily inside the patched methods, so loading these modules is cheap.
|
|
157
|
+
from . import _numpy_interop # noqa: F401 (side-effect: monkey-patches)
|
|
158
|
+
from . import _scipy_interop # noqa: F401 (side-effect: monkey-patches)
|
|
159
|
+
from . import _torch_interop # noqa: F401 (side-effect: monkey-patches)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def __getattr__(name: str):
|
|
163
|
+
if _extension_import_error is not None:
|
|
164
|
+
raise ModuleNotFoundError(
|
|
165
|
+
"pyrxmesh._rxmesh is not installed yet. Build/install PyRXMesh "
|
|
166
|
+
"before using runtime mesh bindings."
|
|
167
|
+
) from _extension_import_error
|
|
168
|
+
raise AttributeError(name)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""NumPy interop helpers attached to ``SparseMatrix``.
|
|
2
|
+
|
|
3
|
+
Overrides ``SparseMatrix.multiply_vector`` so it accepts a NumPy 1D/2D array
|
|
4
|
+
in addition to a native ``DenseMatrix``. NumPy is already a hard PyRXMesh
|
|
5
|
+
dependency, so this module is always importable.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from . import DenseMatrix, Location, SparseMatrix
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_native_multiply_vector = SparseMatrix.multiply_vector
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _sparse_matrix_multiply_vector(self, vector, stream=None):
|
|
19
|
+
if isinstance(vector, DenseMatrix):
|
|
20
|
+
return _native_multiply_vector(self, vector, stream)
|
|
21
|
+
|
|
22
|
+
values = np.asarray(vector)
|
|
23
|
+
if values.ndim == 1:
|
|
24
|
+
values = values.reshape(-1, 1)
|
|
25
|
+
if values.ndim != 2 or values.shape[1] != 1:
|
|
26
|
+
raise ValueError(
|
|
27
|
+
"SparseMatrix.multiply_vector() expects a 1D array or a "
|
|
28
|
+
"DenseMatrix/array with shape (cols, 1)."
|
|
29
|
+
)
|
|
30
|
+
if values.shape[0] != self.cols:
|
|
31
|
+
raise ValueError(
|
|
32
|
+
"SparseMatrix.multiply_vector() vector length must match "
|
|
33
|
+
"matrix.cols."
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
dense = DenseMatrix(
|
|
37
|
+
self.cols,
|
|
38
|
+
1,
|
|
39
|
+
dtype=self.dtype,
|
|
40
|
+
location=Location.ALL,
|
|
41
|
+
)
|
|
42
|
+
dense.from_numpy_copy(values, target=Location.ALL, stream=stream)
|
|
43
|
+
return _native_multiply_vector(self, dense, stream)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
SparseMatrix.multiply_vector = _sparse_matrix_multiply_vector
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""SciPy interop helpers attached to ``SparseMatrix``.
|
|
2
|
+
|
|
3
|
+
This module is imported automatically by ``pyrxmesh/__init__.py`` and patches
|
|
4
|
+
``to_scipy_csr`` / ``to_scipy_csr_copy`` onto ``SparseMatrix``. ``scipy`` is
|
|
5
|
+
imported lazily on first call, so importing this module is cheap.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from . import Location, SparseMatrix
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _require_scipy_sparse(api):
|
|
14
|
+
try:
|
|
15
|
+
import scipy.sparse as scipy_sparse
|
|
16
|
+
except ImportError as exc:
|
|
17
|
+
raise ImportError(
|
|
18
|
+
f"SciPy is required for SparseMatrix.{api}(). "
|
|
19
|
+
"Install scipy or use to_numpy()/to_numpy_copy()."
|
|
20
|
+
) from exc
|
|
21
|
+
return scipy_sparse
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _sparse_matrix_to_scipy_csr(self):
|
|
25
|
+
scipy_sparse = _require_scipy_sparse("to_scipy_csr")
|
|
26
|
+
row_ptr, col_idx, values = self.to_numpy(Location.HOST)
|
|
27
|
+
return scipy_sparse.csr_matrix(
|
|
28
|
+
(values, col_idx, row_ptr), shape=self.shape
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _sparse_matrix_to_scipy_csr_copy(self, source=Location.HOST, stream=None):
|
|
33
|
+
scipy_sparse = _require_scipy_sparse("to_scipy_csr_copy")
|
|
34
|
+
row_ptr, col_idx, values = self.to_numpy_copy(source=source, stream=stream)
|
|
35
|
+
return scipy_sparse.csr_matrix(
|
|
36
|
+
(values, col_idx, row_ptr), shape=self.shape
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
SparseMatrix.to_scipy_csr = _sparse_matrix_to_scipy_csr
|
|
41
|
+
SparseMatrix.to_scipy_csr_copy = _sparse_matrix_to_scipy_csr_copy
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""PyTorch interop helpers attached to native PyRXMesh classes.
|
|
2
|
+
|
|
3
|
+
This module is imported automatically by ``pyrxmesh/__init__.py`` and patches
|
|
4
|
+
``to_torch`` / ``from_torch_copy`` (and friends) onto the relevant native
|
|
5
|
+
classes. ``torch`` itself is imported lazily on first use, so importing this
|
|
6
|
+
module does not pull torch into the process.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from . import (
|
|
12
|
+
Attribute,
|
|
13
|
+
DenseMatrix,
|
|
14
|
+
EdgeAttributeFloat32,
|
|
15
|
+
EdgeAttributeFloat64,
|
|
16
|
+
EdgeAttributeInt32,
|
|
17
|
+
EdgeAttributeInt8,
|
|
18
|
+
FaceAttributeFloat32,
|
|
19
|
+
FaceAttributeFloat64,
|
|
20
|
+
FaceAttributeInt32,
|
|
21
|
+
FaceAttributeInt8,
|
|
22
|
+
Location,
|
|
23
|
+
SparseMatrix,
|
|
24
|
+
VertexAttributeFloat32,
|
|
25
|
+
VertexAttributeFloat64,
|
|
26
|
+
VertexAttributeInt32,
|
|
27
|
+
VertexAttributeInt8,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _require_torch():
|
|
32
|
+
try:
|
|
33
|
+
import torch
|
|
34
|
+
except ImportError as exc:
|
|
35
|
+
raise ImportError(
|
|
36
|
+
"PyTorch is required for PyRXMesh torch interop helpers. "
|
|
37
|
+
"Install torch or use the DLPack/NumPy APIs directly."
|
|
38
|
+
) from exc
|
|
39
|
+
return torch
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _torch_dtype_name(dtype):
|
|
43
|
+
torch = _require_torch()
|
|
44
|
+
if dtype == torch.float32:
|
|
45
|
+
return "float32"
|
|
46
|
+
if dtype == torch.float64:
|
|
47
|
+
return "float64"
|
|
48
|
+
if dtype == torch.int32:
|
|
49
|
+
return "int32"
|
|
50
|
+
raise TypeError(f"Unsupported torch dtype for PyRXMesh copy: {dtype}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class _DenseMatrixDlpackView:
|
|
54
|
+
"""Wrap a ``DenseMatrix`` so ``torch.utils.dlpack.from_dlpack`` can
|
|
55
|
+
forward its ``stream`` argument into ``DenseMatrix.to_dlpack``."""
|
|
56
|
+
|
|
57
|
+
def __init__(self, matrix, location):
|
|
58
|
+
self._matrix = matrix
|
|
59
|
+
self._location = location
|
|
60
|
+
self._is_host = location == Location.HOST
|
|
61
|
+
self._is_device = location == Location.DEVICE
|
|
62
|
+
|
|
63
|
+
def __dlpack__(self, stream=None):
|
|
64
|
+
return self._matrix.to_dlpack(self._location, stream=stream)
|
|
65
|
+
|
|
66
|
+
def __dlpack_device__(self):
|
|
67
|
+
if self._is_host:
|
|
68
|
+
return (1, 0)
|
|
69
|
+
if self._is_device:
|
|
70
|
+
torch = _require_torch()
|
|
71
|
+
return (2, torch.cuda.current_device())
|
|
72
|
+
raise ValueError(
|
|
73
|
+
"DenseMatrix.to_torch() location must be Location.HOST or "
|
|
74
|
+
"Location.DEVICE."
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class _SparseMatrixDlpackView:
|
|
79
|
+
"""Wrap a single ``SparseMatrix`` CSR component for
|
|
80
|
+
``torch.utils.dlpack.from_dlpack``."""
|
|
81
|
+
|
|
82
|
+
def __init__(self, matrix, location, component):
|
|
83
|
+
self._matrix = matrix
|
|
84
|
+
self._location = location
|
|
85
|
+
self._component = component
|
|
86
|
+
self._is_host = location == Location.HOST
|
|
87
|
+
self._is_device = location == Location.DEVICE
|
|
88
|
+
|
|
89
|
+
def __dlpack__(self, stream=None):
|
|
90
|
+
if self._component == "row_ptr":
|
|
91
|
+
return self._matrix._row_ptr_dlpack(self._location, stream=stream)
|
|
92
|
+
if self._component == "col_idx":
|
|
93
|
+
return self._matrix._col_indices_dlpack(self._location, stream=stream)
|
|
94
|
+
if self._component == "values":
|
|
95
|
+
return self._matrix._values_dlpack(self._location, stream=stream)
|
|
96
|
+
raise ValueError("Unsupported SparseMatrix DLPack component.")
|
|
97
|
+
|
|
98
|
+
def __dlpack_device__(self):
|
|
99
|
+
if self._is_host:
|
|
100
|
+
return (1, 0)
|
|
101
|
+
if self._is_device:
|
|
102
|
+
torch = _require_torch()
|
|
103
|
+
return (2, torch.cuda.current_device())
|
|
104
|
+
raise ValueError(
|
|
105
|
+
"SparseMatrix.to_torch() location must be Location.HOST or "
|
|
106
|
+
"Location.DEVICE."
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _dense_matrix_to_torch(self, location=Location.DEVICE):
|
|
111
|
+
torch = _require_torch()
|
|
112
|
+
return torch.utils.dlpack.from_dlpack(
|
|
113
|
+
_DenseMatrixDlpackView(self, location)
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _dense_matrix_from_torch_copy(source):
|
|
118
|
+
tensor = source.detach() if hasattr(source, "detach") else source
|
|
119
|
+
return DenseMatrix.from_dlpack_copy(tensor)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _sparse_matrix_to_torch(self, location=Location.DEVICE):
|
|
123
|
+
torch = _require_torch()
|
|
124
|
+
crow = torch.utils.dlpack.from_dlpack(
|
|
125
|
+
_SparseMatrixDlpackView(self, location, "row_ptr")
|
|
126
|
+
)
|
|
127
|
+
col = torch.utils.dlpack.from_dlpack(
|
|
128
|
+
_SparseMatrixDlpackView(self, location, "col_idx")
|
|
129
|
+
)
|
|
130
|
+
val = torch.utils.dlpack.from_dlpack(
|
|
131
|
+
_SparseMatrixDlpackView(self, location, "values")
|
|
132
|
+
)
|
|
133
|
+
return torch.sparse_csr_tensor(
|
|
134
|
+
crow,
|
|
135
|
+
col,
|
|
136
|
+
val,
|
|
137
|
+
size=self.shape,
|
|
138
|
+
device=val.device,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _sparse_matrix_from_torch_values_copy(
|
|
143
|
+
self,
|
|
144
|
+
values,
|
|
145
|
+
target=Location.ALL,
|
|
146
|
+
stream=None,
|
|
147
|
+
):
|
|
148
|
+
tensor = values.detach() if hasattr(values, "detach") else values
|
|
149
|
+
self.from_dlpack_values_copy(tensor, target=target, stream=stream)
|
|
150
|
+
return self
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _sparse_matrix_from_torch_copy(source, dtype=None, stream=None):
|
|
154
|
+
torch = _require_torch()
|
|
155
|
+
if source.layout != torch.sparse_csr:
|
|
156
|
+
raise TypeError(
|
|
157
|
+
"SparseMatrix.from_torch_copy() expects a torch sparse CSR "
|
|
158
|
+
"tensor."
|
|
159
|
+
)
|
|
160
|
+
value_dtype = dtype or _torch_dtype_name(source.values().dtype)
|
|
161
|
+
return SparseMatrix.from_dlpack_copy(
|
|
162
|
+
source.crow_indices().detach(),
|
|
163
|
+
source.col_indices().detach(),
|
|
164
|
+
source.values().detach(),
|
|
165
|
+
tuple(source.shape),
|
|
166
|
+
dtype=value_dtype,
|
|
167
|
+
stream=stream,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _attribute_to_torch(self, location=Location.DEVICE):
|
|
172
|
+
torch = _require_torch()
|
|
173
|
+
return torch.utils.dlpack.from_dlpack(self.to_dlpack(location))
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _attribute_from_torch_copy(self, values, target=Location.ALL):
|
|
177
|
+
tensor = values.detach() if hasattr(values, "detach") else values
|
|
178
|
+
self.from_dlpack_copy(tensor, target=target)
|
|
179
|
+
return self
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
DenseMatrix.to_torch = _dense_matrix_to_torch
|
|
183
|
+
DenseMatrix.from_torch_copy = staticmethod(_dense_matrix_from_torch_copy)
|
|
184
|
+
SparseMatrix.to_torch = _sparse_matrix_to_torch
|
|
185
|
+
SparseMatrix.from_torch_copy = staticmethod(_sparse_matrix_from_torch_copy)
|
|
186
|
+
SparseMatrix.from_torch_values_copy = _sparse_matrix_from_torch_values_copy
|
|
187
|
+
|
|
188
|
+
_attribute_types = (
|
|
189
|
+
Attribute,
|
|
190
|
+
VertexAttributeFloat32,
|
|
191
|
+
VertexAttributeFloat64,
|
|
192
|
+
VertexAttributeInt32,
|
|
193
|
+
VertexAttributeInt8,
|
|
194
|
+
EdgeAttributeFloat32,
|
|
195
|
+
EdgeAttributeFloat64,
|
|
196
|
+
EdgeAttributeInt32,
|
|
197
|
+
EdgeAttributeInt8,
|
|
198
|
+
FaceAttributeFloat32,
|
|
199
|
+
FaceAttributeFloat64,
|
|
200
|
+
FaceAttributeInt32,
|
|
201
|
+
FaceAttributeInt8,
|
|
202
|
+
)
|
|
203
|
+
for _attribute_type in _attribute_types:
|
|
204
|
+
_attribute_type.to_torch = _attribute_to_torch
|
|
205
|
+
_attribute_type.from_torch_copy = _attribute_from_torch_copy
|
|
206
|
+
del _attribute_type, _attribute_types
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <array>
|
|
5
|
+
#include <cmath>
|
|
6
|
+
#include <cstdint>
|
|
7
|
+
#include <functional>
|
|
8
|
+
#include <limits>
|
|
9
|
+
#include <memory>
|
|
10
|
+
#include <optional>
|
|
11
|
+
#include <stdexcept>
|
|
12
|
+
#include <string>
|
|
13
|
+
#include <type_traits>
|
|
14
|
+
#include <utility>
|
|
15
|
+
#include <variant>
|
|
16
|
+
#include <vector>
|
|
17
|
+
|
|
18
|
+
#include <pybind11/functional.h>
|
|
19
|
+
#include <pybind11/numpy.h>
|
|
20
|
+
#include <pybind11/pybind11.h>
|
|
21
|
+
#include <pybind11/stl.h>
|
|
22
|
+
|
|
23
|
+
#include <cuda_runtime_api.h>
|
|
24
|
+
|
|
25
|
+
#include "pyrxmesh/plugin_api.h"
|
|
26
|
+
#include "rxmesh/handle.h"
|
|
27
|
+
#include "rxmesh/matrix/dense_matrix.h"
|
|
28
|
+
#include "rxmesh/rxmesh_static.h"
|
|
29
|
+
#include "rxmesh/types.h"
|
|
30
|
+
#include "rxmesh/util/log.h"
|
|
31
|
+
#include "rxmesh/util/macros.h"
|
|
32
|
+
|
|
33
|
+
#include "bindings/dispatch.h"
|
|
34
|
+
|
|
35
|
+
#if USE_POLYSCOPE
|
|
36
|
+
#include "polyscope/polyscope.h"
|
|
37
|
+
#endif
|
|
38
|
+
|
|
39
|
+
namespace py = pybind11;
|
|
40
|
+
|
|
41
|
+
namespace pyrxmesh_py {
|
|
42
|
+
|
|
43
|
+
inline constexpr uint32_t plugin_abi_version = PYRXMESH_PLUGIN_ABI_VERSION;
|
|
44
|
+
inline constexpr const char* build_config_tag = PYRXMESH_BUILD_CONFIG;
|
|
45
|
+
|
|
46
|
+
inline rxmesh::locationT parse_location(int location)
|
|
47
|
+
{
|
|
48
|
+
return static_cast<rxmesh::locationT>(location);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
inline int64_t cuda_stream_arg_value(py::object stream)
|
|
52
|
+
{
|
|
53
|
+
if (stream.is_none()) {
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
if (!py::isinstance<py::int_>(stream)) {
|
|
57
|
+
throw py::type_error("CUDA stream must be an integer or None.");
|
|
58
|
+
}
|
|
59
|
+
return stream.cast<int64_t>();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
inline cudaStream_t parse_cuda_stream_arg(py::object stream)
|
|
63
|
+
{
|
|
64
|
+
const int64_t value = cuda_stream_arg_value(std::move(stream));
|
|
65
|
+
if (value == 0) {
|
|
66
|
+
throw std::invalid_argument(
|
|
67
|
+
"CUDA stream value 0 is ambiguous and not supported.");
|
|
68
|
+
}
|
|
69
|
+
if (value == 1) {
|
|
70
|
+
return nullptr;
|
|
71
|
+
}
|
|
72
|
+
if (value == 2) {
|
|
73
|
+
return cudaStreamPerThread;
|
|
74
|
+
}
|
|
75
|
+
if (value > 2) {
|
|
76
|
+
return reinterpret_cast<cudaStream_t>(static_cast<uintptr_t>(value));
|
|
77
|
+
}
|
|
78
|
+
throw std::invalid_argument(
|
|
79
|
+
"CUDA stream value must be None, 1, 2, or a "
|
|
80
|
+
"positive raw stream pointer.");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
inline void cuda_stream_synchronize_arg(py::object stream)
|
|
84
|
+
{
|
|
85
|
+
using namespace rxmesh;
|
|
86
|
+
CUDA_ERROR(cudaStreamSynchronize(parse_cuda_stream_arg(std::move(stream))));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
inline void make_stream_wait_for_legacy_default(cudaStream_t stream)
|
|
90
|
+
{
|
|
91
|
+
using namespace rxmesh;
|
|
92
|
+
if (stream == nullptr) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
cudaEvent_t event = nullptr;
|
|
96
|
+
CUDA_ERROR(cudaEventCreateWithFlags(&event, cudaEventDisableTiming));
|
|
97
|
+
CUDA_ERROR(cudaEventRecord(event, nullptr));
|
|
98
|
+
CUDA_ERROR(cudaStreamWaitEvent(stream, event, 0));
|
|
99
|
+
CUDA_ERROR(cudaEventDestroy(event));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
inline void synchronize_device_transfer(rxmesh::locationT source,
|
|
103
|
+
rxmesh::locationT target)
|
|
104
|
+
{
|
|
105
|
+
using namespace rxmesh;
|
|
106
|
+
if (((source | target) & rxmesh::DEVICE) == rxmesh::DEVICE) {
|
|
107
|
+
CUDA_ERROR(cudaStreamSynchronize(nullptr));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
inline rxmesh::layoutT parse_layout(int layout)
|
|
112
|
+
{
|
|
113
|
+
return static_cast<rxmesh::layoutT>(layout);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
inline void ensure_polyscope_available()
|
|
117
|
+
{
|
|
118
|
+
#if !USE_POLYSCOPE
|
|
119
|
+
throw std::runtime_error(
|
|
120
|
+
"RXMesh was built with RX_USE_POLYSCOPE=OFF; visualization is not "
|
|
121
|
+
"available.");
|
|
122
|
+
#endif
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
inline void show_polyscope()
|
|
126
|
+
{
|
|
127
|
+
ensure_polyscope_available();
|
|
128
|
+
#if USE_POLYSCOPE
|
|
129
|
+
polyscope::show();
|
|
130
|
+
#endif
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
inline glm::fvec3 sequence_to_fvec3(const py::sequence& values,
|
|
134
|
+
const char* name)
|
|
135
|
+
{
|
|
136
|
+
if (py::len(values) != 3) {
|
|
137
|
+
throw std::invalid_argument(std::string(name) +
|
|
138
|
+
" must contain exactly 3 values.");
|
|
139
|
+
}
|
|
140
|
+
return glm::fvec3(values[0].cast<float>(),
|
|
141
|
+
values[1].cast<float>(),
|
|
142
|
+
values[2].cast<float>());
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
inline py::array_t<float> vec3_to_numpy(const glm::vec3& value)
|
|
146
|
+
{
|
|
147
|
+
py::array_t<float> out({static_cast<py::ssize_t>(3)});
|
|
148
|
+
auto view = out.mutable_unchecked<1>();
|
|
149
|
+
view(0) = value[0];
|
|
150
|
+
view(1) = value[1];
|
|
151
|
+
view(2) = value[2];
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
void register_module_core(py::module_& m);
|
|
156
|
+
void register_handles(py::module_& m);
|
|
157
|
+
void register_attribute(py::module_& m);
|
|
158
|
+
void register_dense_matrix(py::module_& m);
|
|
159
|
+
void register_geometry(py::module_& m);
|
|
160
|
+
void register_sparse_matrix(py::module_& m);
|
|
161
|
+
void register_solvers(py::module_& m);
|
|
162
|
+
void register_mesh(py::module_& m);
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
py::object make_sparse_matrix_from_mesh(
|
|
166
|
+
std::shared_ptr<rxmesh::RXMeshStatic> mesh,
|
|
167
|
+
rxmesh::Op op,
|
|
168
|
+
std::string dtype);
|
|
169
|
+
} // namespace pyrxmesh_py
|