PyRXMesh 0.2.1rc1__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.
Files changed (66) hide show
  1. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/CMakeLists.txt +3 -0
  2. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/PKG-INFO +1 -1
  3. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/pyproject.toml +1 -1
  4. pyrxmesh-0.2.1rc2/pyrxmesh/__init__.py +168 -0
  5. pyrxmesh-0.2.1rc2/pyrxmesh/_numpy_interop.py +46 -0
  6. pyrxmesh-0.2.1rc2/pyrxmesh/_scipy_interop.py +41 -0
  7. pyrxmesh-0.2.1rc2/pyrxmesh/_torch_interop.py +206 -0
  8. pyrxmesh-0.2.1rc2/src/bindings/common.h +169 -0
  9. pyrxmesh-0.2.1rc2/src/bindings/dense_matrix_dlpack.h +228 -0
  10. pyrxmesh-0.2.1rc2/src/bindings/dispatch.h +318 -0
  11. pyrxmesh-0.2.1rc2/src/bindings/dlpack_utils.h +284 -0
  12. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/src/bindings/geometry.cu +6 -16
  13. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/src/bindings/mesh.cu +31 -90
  14. pyrxmesh-0.2.1rc2/src/bindings/plugin_launch.h +167 -0
  15. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/src/bindings/py_attribute.h +16 -39
  16. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/src/bindings/py_attributes.cu +13 -84
  17. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/src/bindings/py_dense_matrix.cu +0 -26
  18. pyrxmesh-0.2.1rc2/src/bindings/py_dense_matrix.h +585 -0
  19. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/src/bindings/py_solvers.cu +40 -34
  20. pyrxmesh-0.2.1rc2/src/bindings/py_solvers.h +571 -0
  21. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/src/bindings/py_sparse_matrix.cu +19 -90
  22. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/src/bindings/py_sparse_matrix.h +25 -65
  23. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/src/bindings/sparse_matrix_csr.h +6 -14
  24. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/src/bindings/sparse_matrix_dlpack.h +47 -178
  25. pyrxmesh-0.2.1rc2/tests/conftest.py +28 -0
  26. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/tests/test_attributes.py +9 -32
  27. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/tests/test_core_mesh.py +4 -24
  28. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/tests/test_dense_matrix.py +2 -18
  29. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/tests/test_geometry_io.py +2 -17
  30. pyrxmesh-0.2.1rc2/tests/test_solvers.py +182 -0
  31. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/tests/test_sparse_matrix.py +10 -33
  32. pyrxmesh-0.2.1rc1/pyrxmesh/__init__.py +0 -382
  33. pyrxmesh-0.2.1rc1/src/bindings/common.h +0 -462
  34. pyrxmesh-0.2.1rc1/src/bindings/dense_matrix_dlpack.h +0 -369
  35. pyrxmesh-0.2.1rc1/src/bindings/py_dense_matrix.h +0 -627
  36. pyrxmesh-0.2.1rc1/src/bindings/py_solvers.h +0 -607
  37. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/.clang-format +0 -0
  38. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/.gitignore +0 -0
  39. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/CUSTOM_CUDA_PLUGINS.md +0 -0
  40. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/DEVELOPING.md +0 -0
  41. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/LICENSE +0 -0
  42. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/README.md +0 -0
  43. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/cmake/build_config.json.in +0 -0
  44. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/cmake/pyrxmeshConfig.cmake.in +0 -0
  45. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/conftest.py +0 -0
  46. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/environment.yml +0 -0
  47. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/examples/custom_kernel_plugin/CMakeLists.txt +0 -0
  48. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/examples/custom_kernel_plugin/README.md +0 -0
  49. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/examples/custom_kernel_plugin/pyproject.toml +0 -0
  50. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/examples/custom_kernel_plugin/run_edge_lengths.py +0 -0
  51. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/examples/custom_kernel_plugin/src/rxmesh_edge_lengths/__init__.py +0 -0
  52. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/examples/custom_kernel_plugin/src/rxmesh_edge_lengths.cu +0 -0
  53. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/examples/load_and_show.py +0 -0
  54. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/include/pyrxmesh/plugin_api.h +0 -0
  55. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/meshes/bumpy.obj +0 -0
  56. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/meshes/sphere3.obj +0 -0
  57. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/pyrxmesh/cmake_dir.py +0 -0
  58. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/pyrxmesh/diff.py +0 -0
  59. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/pyrxmesh/geometry.py +0 -0
  60. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/pyrxmesh/plugin.py +0 -0
  61. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/src/bindings/core.cu +0 -0
  62. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/src/bindings/dlpack_minimal.h +0 -0
  63. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/src/bindings/handles.cu +0 -0
  64. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/src/bindings/py_hessian_sparse_matrix.h +0 -0
  65. {pyrxmesh-0.2.1rc1 → pyrxmesh-0.2.1rc2}/src/bindings/py_jacobian_sparse_matrix.h +0 -0
  66. {pyrxmesh-0.2.1rc1 → 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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyRXMesh
3
- Version: 0.2.1rc1
3
+ Version: 0.2.1rc2
4
4
  Summary: Python bindings for RXMesh, a CUDA/C++ library for mesh processing on the GPU.
5
5
  Author: Ahmed Mahmoud
6
6
  License-Expression: BSD-2-Clause
@@ -7,7 +7,7 @@ build-backend = "scikit_build_core.build"
7
7
 
8
8
  [project]
9
9
  name = "PyRXMesh"
10
- version = "0.2.1rc1"
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