rbfgpu 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,336 @@
1
+ Metadata-Version: 2.4
2
+ Name: rbfgpu
3
+ Version: 1.0.0
4
+ Summary: GPU/CPU agnostic Radial Basis Function interpolation (thin plate splines and friends) on top of XuPy.
5
+ Author-email: Pietro Ferraiuolo <pietro.ferraiuolo@inaf.it>
6
+ Maintainer-email: Pietro Ferraiuolo <pietro.ferraiuolo@inaf.it>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/pietroferraiuolo/rbfgpu
9
+ Project-URL: Repository, https://github.com/pietroferraiuolo/rbfgpu
10
+ Project-URL: Issues, https://github.com/pietroferraiuolo/rbfgpu/issues
11
+ Project-URL: Changelog, https://github.com/pietroferraiuolo/rbfgpu/blob/main/CHANGELOG.md
12
+ Keywords: rbf,radial basis function,thin plate spline,interpolation,scattered data,gpu,cupy,numpy,scipy
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: numpy>=2.0
27
+ Requires-Dist: xupy>=2.0
28
+ Provides-Extra: cuda12
29
+ Requires-Dist: cupy-cuda12x>=14; extra == "cuda12"
30
+ Provides-Extra: cuda13
31
+ Requires-Dist: cupy-cuda13x>=14; extra == "cuda13"
32
+ Provides-Extra: test
33
+ Requires-Dist: pytest; extra == "test"
34
+ Requires-Dist: pytest-cov; extra == "test"
35
+ Requires-Dist: scipy; extra == "test"
36
+ Dynamic: license-file
37
+
38
+ # rbfGPU
39
+
40
+ [![PyPI version](https://img.shields.io/pypi/v/rbfgpu.svg)](https://pypi.org/project/rbfgpu/)
41
+ [![Python versions](https://img.shields.io/pypi/pyversions/rbfgpu.svg)](https://pypi.org/project/rbfgpu/)
42
+ [![License: MIT](https://img.shields.io/pypi/l/rbfgpu.svg)](LICENSE)
43
+ [![Tests](https://github.com/pietroferraiuolo/rbfgpu/actions/workflows/tests.yml/badge.svg)](https://github.com/pietroferraiuolo/rbfgpu/actions/workflows/tests.yml)
44
+
45
+ Radial Basis Function interpolation of scattered N-dimensional data, running on
46
+ the GPU through [XuPy](https://pypi.org/project/xupy/) (CuPy) and falling back to
47
+ NumPy on the CPU — **same code, same API, same results**.
48
+
49
+ `rbfgpu` exists for the regime where `scipy.interpolate.RBFInterpolator` becomes
50
+ the bottleneck: from a few thousand constraint points upwards, the O(n²) Gram
51
+ matrix and the O(n³) solve dominate, and a GPU turns minutes into seconds. Below
52
+ that threshold it is still a perfectly good CPU interpolator.
53
+
54
+ ```python
55
+ import numpy as np
56
+ from rbfgpu import RBFInterpolator
57
+
58
+ y = np.random.default_rng(0).random((20000, 2)) # constraint points
59
+ d = np.sin(4 * y[:, 0]) * np.cos(4 * y[:, 1]) # data at those points
60
+
61
+ rbf = RBFInterpolator(y, d) # thin plate spline, degree=1
62
+ x = np.random.default_rng(1).random((500000, 2)) # evaluation grid
63
+ values = rbf(x) # shape (500000,)
64
+ ```
65
+
66
+ ## Features
67
+
68
+ - **Thin plate splines and friends**: `linear`, `thin_plate_spline`, `cubic`,
69
+ `quintic`, `multiquadric`, `inverse_multiquadric`, `inverse_quadratic`,
70
+ `gaussian`, `matern1_2`, `matern3_2`, `matern5_2`, or your own callable.
71
+ - **SciPy compatible semantics**: kernel names, definitions and signs, and the
72
+ `degree` defaulting rule follow `scipy.interpolate.RBFInterpolator`, and a
73
+ differential test suite (`test/test_scipy_parity.py`) pins the agreement. Where
74
+ rbfgpu is more permissive (a data-driven default `epsilon`, `degree=-1`, custom
75
+ callables) the difference is documented and tested.
76
+ - **GPU or CPU, one code path**: every array operation goes through the XuPy
77
+ namespace, which resolves to CuPy when a usable NVIDIA GPU is present and to
78
+ NumPy otherwise. There is no `if on_gpu:` branch to maintain, and CI tests the
79
+ same code on both backends.
80
+ - **Bounded memory**: evaluation is chunked automatically from a byte budget
81
+ (`max_chunk_bytes`), or explicitly via `rbf(x, chunk_size=...)`.
82
+ - **Polynomial tail of any degree** (`-1` disables it), which is what makes the
83
+ interpolant well posed for conditionally positive definite kernels and lets it
84
+ extrapolate.
85
+ - **Smoothing** (Tikhonov), scalar or per point, for noisy data.
86
+ - **Vector valued data**: `d` may be `(n,)`, `(n, k)` or `(n, k, l, ...)`; every
87
+ output is solved for in a single factorisation and the trailing shape is
88
+ preserved.
89
+ - **Exportable**: `to_cpu()` returns the solved coefficients as NumPy arrays.
90
+ - Typed (`py.typed`), no dependencies beyond `numpy` and `xupy`.
91
+
92
+ ## Installation
93
+
94
+ ```bash
95
+ pip install rbfgpu # CPU (NumPy)
96
+ pip install "rbfgpu[cuda12]" # GPU, CuPy for CUDA 12.x
97
+ pip install "rbfgpu[cuda13]" # GPU, CuPy for CUDA 13.x
98
+ ```
99
+
100
+ Pick the extra matching the "CUDA Version" reported by `nvidia-smi`. Without a
101
+ CuPy extra the package still works, on the CPU: XuPy detects the situation and
102
+ resolves its namespace to NumPy.
103
+
104
+ ```python
105
+ >>> import rbfgpu
106
+ >>> rbfgpu.backend_info() # without a usable CuPy installation
107
+ {'on_gpu': False, 'backend': 'numpy', 'device': 'cpu', 'numpy_version': '2.4.6',
108
+ 'xupy_version': '2.0.0', 'cupy_version': None}
109
+ ```
110
+
111
+ On a machine with a usable CuPy, `on_gpu` is `True`, `backend` is `'cupy'` and
112
+ `device` names the GPU. `rbfgpu.require_gpu()` raises a `RuntimeError` when the
113
+ GPU is expected but missing, so a script cannot silently fall back to the CPU.
114
+
115
+ ## API
116
+
117
+ ### `RBFInterpolator(y, d, ...)`
118
+
119
+ | argument | default | meaning |
120
+ | --- | --- | --- |
121
+ | `y` | — | constraint point coordinates, `(npoints, ndims)` (a 1-D array means `ndims=1`) |
122
+ | `d` | — | data values at `y`, `(npoints, ...)` |
123
+ | `kernel` | `'thin_plate_spline'` | kernel name (see `available_kernels()`) or a callable `r -> phi(r)` |
124
+ | `epsilon` | `None` | shape parameter: distances are scaled by it. `1` for the scale invariant kernels, otherwise the RMS nearest neighbour distance |
125
+ | `smoothing` | `0.0` | Tikhonov regularization, scalar or `(npoints,)` |
126
+ | `degree` | `None` | polynomial degree (`-1` = none). Defaults to the kernel's minimum, or 0 |
127
+ | `memory_efficient` | `True` | chunk the evaluation to stay under the memory budget |
128
+ | `max_chunk_bytes` | 1 GiB | memory budget for the temporaries of one chunk |
129
+ | `min_chunk_size` | 4096 | floor for the automatically estimated chunk size |
130
+
131
+ Calling the object evaluates the interpolant: `rbf(x)` returns an array of shape
132
+ `(npts,) + d.shape[1:]` on the same backend as the interpolant; `rbf(x,
133
+ chunk_size=1000)` overrides the automatic chunking.
134
+
135
+ Useful attributes: `kernel`, `epsilon`, `degree`, `n_poly`, `npoints`, `ndim`,
136
+ `output_dim`, `d_shape`, `smoothing`, `coeffs_rbf`, `coeffs_poly`, `on_gpu`.
137
+
138
+ ### Helpers
139
+
140
+ ```python
141
+ from rbfgpu import (
142
+ available_kernels, # ('cubic', 'gaussian', ..., 'thin_plate_spline')
143
+ resolve_kernel, # name/alias/callable -> Kernel(function, min_degree, ...)
144
+ euclidean_distance, # (n, d) x (m, d) -> (n, m), epsilon scaled
145
+ default_epsilon, # RMS nearest neighbour distance of a point cloud
146
+ monomial_exponents, # every exponent tuple with |p| <= degree
147
+ polynomial_matrix, # monomial basis evaluated at some points
148
+ backend_info, is_on_gpu, require_gpu, as_numpy,
149
+ )
150
+ ```
151
+
152
+ ## Kernels
153
+
154
+ | name | φ(r) | scale invariant | default `degree` |
155
+ | --- | --- | --- | --- |
156
+ | `linear` | `-r` | yes | 0 |
157
+ | `thin_plate_spline` (aliases `tps`, `thin_plate`) | `r² log r` | yes | 1 |
158
+ | `cubic` | `r³` | yes | 1 |
159
+ | `quintic` | `-r⁵` | yes | 2 |
160
+ | `multiquadric` | `-sqrt(1 + r²)` | no | 0 |
161
+ | `inverse_multiquadric` (alias `imq`) | `1/sqrt(1 + r²)` | no | 0 |
162
+ | `inverse_quadratic` (alias `inverse_quartic`) | `1/(1 + r²)` | no | 0 |
163
+ | `gaussian` | `exp(-r²)` | no | 0 |
164
+ | `matern1_2` | `exp(-r)` | no | 0 |
165
+ | `matern3_2` | `(1 + √3 r) exp(-√3 r)` | no | 0 |
166
+ | `matern5_2` | `(1 + √5 r + 5/3 r²) exp(-√5 r)` | no | 0 |
167
+
168
+ Any callable `phi(r) -> array` works too; it then requires `epsilon` and `degree`
169
+ to be passed explicitly:
170
+
171
+ ```python
172
+ import xupy as xp
173
+ from rbfgpu import RBFInterpolator
174
+
175
+ rbf = RBFInterpolator(y, d, kernel=lambda r: xp.exp(-r), epsilon=1.0, degree=-1)
176
+ ```
177
+
178
+ ### How it works
179
+
180
+ The interpolant is a linear combination of kernels centred on the data points,
181
+ plus a polynomial tail:
182
+
183
+ ```
184
+ f(x) = Σᵢ aᵢ φ(ε‖x − yᵢ‖) + Σⱼ bⱼ pⱼ(x)
185
+ ```
186
+
187
+ The coefficients solve the symmetric block system
188
+
189
+ ```
190
+ [K + λI P] [a] = [d]
191
+ [Pᵀ 0] [b] [0]
192
+ ```
193
+
194
+ with `K` the kernel Gram matrix, `P` the monomial matrix, `λ` the smoothing and
195
+ `Pᵀa = 0` the side conditions that make the problem well posed for conditionally
196
+ positive definite kernels (and allow extrapolation). The Gram matrix is written
197
+ straight into the block matrix, so the `(n, n)` kernel matrix and the
198
+ `(n + n_poly)²` system are never resident in device memory at the same time.
199
+
200
+ ### Choosing `epsilon` and `degree`
201
+
202
+ - The scale invariant kernels (`linear`, `thin_plate_spline`, `cubic`, `quintic`)
203
+ ignore `epsilon`: it only rescales the data, which is equivalent to rescaling
204
+ `smoothing`.
205
+ - The other kernels need a sensible `epsilon`. rbfgpu defaults to
206
+ `sqrt(mean(nearest neighbour distance))` — a reasonable starting point but
207
+ **not** a guarantee: too small an `epsilon` gives very wide kernels and an
208
+ ill-conditioned system (the data is then interpolated poorly), too large gives
209
+ a nearly diagonal one. Tune it, ideally by cross validation. (SciPy refuses to
210
+ guess and raises instead.)
211
+ - `degree` below the kernel's minimum is allowed but warns: the interpolant may
212
+ then be ill posed. `degree=-1` removes the polynomial tail entirely.
213
+ - `smoothing > 0` trades exactness for stability, and is the first thing to try
214
+ when the build fails with `RuntimeError: ... singular matrix ...`.
215
+
216
+ ## Memory and chunking
217
+
218
+ Evaluation allocates `O(n_eval × npoints)` floats. By default the evaluation grid
219
+ is split into chunks sized from a byte budget, which keeps the peak device memory
220
+ bounded without any intervention:
221
+
222
+ ```python
223
+ rbf = RBFInterpolator(y, d, max_chunk_bytes=256 * 1024**2) # 256 MiB budget
224
+ values = rbf(huge_grid) # chunked automatically
225
+ values = rbf(huge_grid, chunk_size=4096) # or fixed explicitly
226
+ ```
227
+
228
+ `memory_efficient=False` evaluates in one batch (fastest, most memory hungry), and
229
+ `min_chunk_size` keeps very small budgets from degenerating into one point at a
230
+ time.
231
+
232
+ ## Backends
233
+
234
+ The backend follows XuPy, so it can be inspected and switched without touching
235
+ rbfgpu:
236
+
237
+ ```python
238
+ import xupy as xp
239
+ from rbfgpu import RBFInterpolator
240
+
241
+ rbf_gpu = RBFInterpolator(y, d) # CuPy arrays, if a GPU is available
242
+ rbf_gpu.on_gpu # True
243
+
244
+ with xp.backend("cpu"): # scoped, thread local
245
+ rbf_cpu = RBFInterpolator(y, d) # NumPy arrays, whatever the default is
246
+ ```
247
+
248
+ Arrays never move implicitly: an interpolant keeps the backend it was built on.
249
+ `rbfgpu.as_numpy(array)` brings any backend array to the host, and
250
+ `RBFInterpolator.to_cpu()` exports the whole solved interpolant as NumPy arrays
251
+ (coefficients, centres, monomial exponents, kernel name, `epsilon`), so that it
252
+ can be stored or evaluated elsewhere.
253
+
254
+ ## Relationship with SciPy
255
+
256
+ `rbfgpu.RBFInterpolator` is deliberately close to `scipy.interpolate.RBFInterpolator`:
257
+
258
+ | | rbfgpu | SciPy |
259
+ | --- | --- | --- |
260
+ | backends | CuPy (GPU) or NumPy (CPU) | NumPy, or an Array API backend when `SCIPY_ARRAY_API=1` |
261
+ | kernels | the same 11 names, plus callables | 8 names (Matérn kernels are newer), plus `LowLevelCallable` |
262
+ | `degree` default | `max(min_degree, 0)`, same rule | same |
263
+ | `epsilon` default | 1 for the scale invariant kernels, otherwise `default_epsilon(y)` | 1 for the scale invariant kernels, otherwise **required** (raises) |
264
+ | `smoothing`, vector valued `d`, trailing dimensions | yes | yes |
265
+ | `neighbors` (local interpolation with a KD-tree) | **no** | yes |
266
+ | chunked evaluation with an explicit byte budget | yes | internal memory budget |
267
+
268
+ Not implemented, by design: `neighbors`. Local interpolation needs a KD-tree
269
+ query per evaluation point, which is a different (and host side) algorithm. If you
270
+ need it, use SciPy.
271
+
272
+ ### Working with the results
273
+
274
+ On the GPU backend the interpolant returns **CuPy** arrays, and CuPy refuses to
275
+ mix with NumPy operands. Bring results to the host before combining them with
276
+ NumPy data:
277
+
278
+ ```python
279
+ values = rbf(x) # cupy.ndarray on a GPU, numpy.ndarray on a CPU
280
+ residual = rbfgpu.as_numpy(values) - d_numpy # ok
281
+ # residual = values - d_numpy # TypeError on the GPU backend
282
+ ```
283
+
284
+ ## Benchmark
285
+
286
+ `benchmarks/bench_vs_scipy.py` times the build and the evaluation of both
287
+ implementations and reports the largest difference between the two interpolants
288
+ (they solve the same system, so it must stay at round-off level):
289
+
290
+ ```bash
291
+ python benchmarks/bench_vs_scipy.py --sizes 1000 2000 4000 8000 --eval 200000
292
+ ```
293
+
294
+ The speed-up depends entirely on the GPU, on the number of constraint points and
295
+ on the number of evaluation points: measure it on your own hardware rather than
296
+ trusting a table in a README.
297
+
298
+ ## Development
299
+
300
+ ```bash
301
+ git clone https://github.com/pietroferraiuolo/rbfgpu.git
302
+ cd rbfgpu
303
+ pip install -e ".[test]" # add ,cuda12 to test the GPU backend as well
304
+ python -m pytest # the whole suite, on every available backend
305
+ python -m pytest -m "not slow" # skip the large-problem tests
306
+ python benchmarks/bench_vs_scipy.py --sizes 500 1000
307
+ ```
308
+
309
+ The `backend` fixture in `test/conftest.py` parametrizes every test over `"cpu"`
310
+ and `"gpu"`; the GPU case is skipped, never failed, when CuPy is unusable.
311
+ `test/test_scipy_parity.py` compares against SciPy and is skipped when SciPy is
312
+ not installed. Reference values in `test/_util.py` are re-implemented with plain
313
+ NumPy on purpose: they must not come from the code under test.
314
+
315
+ ## Releasing
316
+
317
+ 1. Bump `rbfgpu/__version__.py` and add an entry to `CHANGELOG.md`.
318
+ 2. Commit, tag `vX.Y.Z` and push the tag.
319
+ 3. Create the GitHub Release: `.github/workflows/publish.yml` builds the sdist and
320
+ the wheel, runs the test suite against the built wheel, and uploads to PyPI.
321
+
322
+ The workflow authenticates with the `PYPI_API_TOKEN` repository secret (an API
323
+ token scoped to the `rbfgpu` project). To use PyPI *trusted publishing* instead,
324
+ register the repository on the project's publishing page and follow the comment
325
+ at the bottom of `publish.yml`.
326
+
327
+ ## Provenance
328
+
329
+ Extracted from the `opticalib` adaptive optics framework
330
+ (`opticalib/simulator/_API/_rbf_gpu.py`), where it accelerates the interpolation
331
+ of deformable mirror influence functions, and generalised into a standalone
332
+ package.
333
+
334
+ ## License
335
+
336
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,13 @@
1
+ rbfgpu/__init__.py,sha256=C-NWYov8jywaGvZw2IMhDixuBa6wgcmG2KnomPpnmd8,2326
2
+ rbfgpu/__version__.py,sha256=ccDOIcRP5NnC1YDip79oUcsUuqLlVWXMI-t_tk-YPxk,758
3
+ rbfgpu/_backend.py,sha256=AUlx_MVq90GMYd58vef5nqws57lAflPNFVgE2hSiKto,3657
4
+ rbfgpu/_distance.py,sha256=0CHUmgbVcAa2qoenM53xCJ111deaKg6rPS2NHv3U2EE,4913
5
+ rbfgpu/_kernels.py,sha256=-WlKY9_8vFoiG0QfQVRD48arsm16HDq0nQbP6Cmnw9o,5974
6
+ rbfgpu/_polynomial.py,sha256=CkXUB1ztd4bcaHoGJLN2_GX27yxDrr_u3jbU7UwoaHc,3156
7
+ rbfgpu/interpolator.py,sha256=fhWOhZFSZ49d5x0YycCuvd1Bt9aylDg_iLEBZI7P5Lk,20233
8
+ rbfgpu/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ rbfgpu-1.0.0.dist-info/licenses/LICENSE,sha256=eWC0Dzf87edb-3AcBCS5Y4AUYLzoNHMP-pOBWrJBSDA,1102
10
+ rbfgpu-1.0.0.dist-info/METADATA,sha256=KMh0bUHEoHwgVS2qk_F7fvsEGFENK-85P8JQ2TDU2_o,14828
11
+ rbfgpu-1.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
12
+ rbfgpu-1.0.0.dist-info/top_level.txt,sha256=N_Q_LPv6tQyDVcea9wpsvhSxr2ye_gsd9vUKWrgPeoQ,7
13
+ rbfgpu-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pietro Ferraiuolo (pietro.ferraiuolo@inaf.it)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ rbfgpu