pyrebasis 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. pyrebasis-0.1.0/Cargo.lock +345 -0
  2. pyrebasis-0.1.0/Cargo.toml +23 -0
  3. pyrebasis-0.1.0/LICENSE +21 -0
  4. pyrebasis-0.1.0/PKG-INFO +167 -0
  5. pyrebasis-0.1.0/README.md +135 -0
  6. pyrebasis-0.1.0/pyproject.toml +77 -0
  7. pyrebasis-0.1.0/python/rebasis/__init__.py +74 -0
  8. pyrebasis-0.1.0/python/rebasis/_ops.py +271 -0
  9. pyrebasis-0.1.0/python/rebasis/_regrid.py +125 -0
  10. pyrebasis-0.1.0/python/rebasis/_space.py +359 -0
  11. pyrebasis-0.1.0/python/rebasis/py.typed +0 -0
  12. pyrebasis-0.1.0/python/rebasis/xarray.py +108 -0
  13. pyrebasis-0.1.0/rebasis/Cargo.toml +19 -0
  14. pyrebasis-0.1.0/rebasis/README.md +26 -0
  15. pyrebasis-0.1.0/rebasis/src/banded.rs +137 -0
  16. pyrebasis-0.1.0/rebasis/src/coordinate.rs +111 -0
  17. pyrebasis-0.1.0/rebasis/src/element.rs +853 -0
  18. pyrebasis-0.1.0/rebasis/src/error.rs +15 -0
  19. pyrebasis-0.1.0/rebasis/src/integrate.rs +430 -0
  20. pyrebasis-0.1.0/rebasis/src/lib.rs +38 -0
  21. pyrebasis-0.1.0/rebasis/src/ops.rs +554 -0
  22. pyrebasis-0.1.0/rebasis/src/profile.rs +677 -0
  23. pyrebasis-0.1.0/rebasis/src/quadrature.rs +85 -0
  24. pyrebasis-0.1.0/rebasis/src/space.rs +428 -0
  25. pyrebasis-0.1.0/rebasis/src/sparse.rs +248 -0
  26. pyrebasis-0.1.0/rebasis/src/special.rs +257 -0
  27. pyrebasis-0.1.0/rebasis/tests/coordinates.rs +204 -0
  28. pyrebasis-0.1.0/rebasis/tests/derivatives.rs +373 -0
  29. pyrebasis-0.1.0/rebasis/tests/lineshapes_and_regrid.rs +287 -0
  30. pyrebasis-0.1.0/rebasis/tests/properties.rs +168 -0
  31. pyrebasis-0.1.0/rebasis-py/Cargo.toml +25 -0
  32. pyrebasis-0.1.0/rebasis-py/build.rs +6 -0
  33. pyrebasis-0.1.0/rebasis-py/src/lib.rs +654 -0
@@ -0,0 +1,345 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "autocfg"
7
+ version = "1.5.1"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
10
+
11
+ [[package]]
12
+ name = "crossbeam-deque"
13
+ version = "0.8.8"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a"
16
+ dependencies = [
17
+ "crossbeam-epoch",
18
+ "crossbeam-utils",
19
+ ]
20
+
21
+ [[package]]
22
+ name = "crossbeam-epoch"
23
+ version = "0.9.21"
24
+ source = "registry+https://github.com/rust-lang/crates.io-index"
25
+ checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d"
26
+ dependencies = [
27
+ "crossbeam-utils",
28
+ ]
29
+
30
+ [[package]]
31
+ name = "crossbeam-utils"
32
+ version = "0.8.23"
33
+ source = "registry+https://github.com/rust-lang/crates.io-index"
34
+ checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
35
+
36
+ [[package]]
37
+ name = "either"
38
+ version = "1.19.0"
39
+ source = "registry+https://github.com/rust-lang/crates.io-index"
40
+ checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be"
41
+
42
+ [[package]]
43
+ name = "heck"
44
+ version = "0.5.0"
45
+ source = "registry+https://github.com/rust-lang/crates.io-index"
46
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
47
+
48
+ [[package]]
49
+ name = "libc"
50
+ version = "0.2.190"
51
+ source = "registry+https://github.com/rust-lang/crates.io-index"
52
+ checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
53
+
54
+ [[package]]
55
+ name = "libm"
56
+ version = "0.2.16"
57
+ source = "registry+https://github.com/rust-lang/crates.io-index"
58
+ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
59
+
60
+ [[package]]
61
+ name = "matrixmultiply"
62
+ version = "0.3.11"
63
+ source = "registry+https://github.com/rust-lang/crates.io-index"
64
+ checksum = "3f607c237553f086e7043417a51df26b2eb899d3caff94e6a67592ff992fedc7"
65
+ dependencies = [
66
+ "autocfg",
67
+ "rawpointer",
68
+ ]
69
+
70
+ [[package]]
71
+ name = "ndarray"
72
+ version = "0.16.1"
73
+ source = "registry+https://github.com/rust-lang/crates.io-index"
74
+ checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841"
75
+ dependencies = [
76
+ "matrixmultiply",
77
+ "num-complex",
78
+ "num-integer",
79
+ "num-traits",
80
+ "portable-atomic",
81
+ "portable-atomic-util",
82
+ "rawpointer",
83
+ "rayon",
84
+ ]
85
+
86
+ [[package]]
87
+ name = "ndarray"
88
+ version = "0.17.2"
89
+ source = "registry+https://github.com/rust-lang/crates.io-index"
90
+ checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d"
91
+ dependencies = [
92
+ "matrixmultiply",
93
+ "num-complex",
94
+ "num-integer",
95
+ "num-traits",
96
+ "portable-atomic",
97
+ "portable-atomic-util",
98
+ "rawpointer",
99
+ ]
100
+
101
+ [[package]]
102
+ name = "num-complex"
103
+ version = "0.4.6"
104
+ source = "registry+https://github.com/rust-lang/crates.io-index"
105
+ checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
106
+ dependencies = [
107
+ "num-traits",
108
+ ]
109
+
110
+ [[package]]
111
+ name = "num-integer"
112
+ version = "0.1.47"
113
+ source = "registry+https://github.com/rust-lang/crates.io-index"
114
+ checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b"
115
+ dependencies = [
116
+ "num-traits",
117
+ ]
118
+
119
+ [[package]]
120
+ name = "num-traits"
121
+ version = "0.2.19"
122
+ source = "registry+https://github.com/rust-lang/crates.io-index"
123
+ checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
124
+ dependencies = [
125
+ "autocfg",
126
+ ]
127
+
128
+ [[package]]
129
+ name = "numpy"
130
+ version = "0.29.0"
131
+ source = "registry+https://github.com/rust-lang/crates.io-index"
132
+ checksum = "6a5b15d63a5ff39e378daed0e1340d3a5964703ea9712eb09a0dc66fade996f4"
133
+ dependencies = [
134
+ "libc",
135
+ "ndarray 0.17.2",
136
+ "num-complex",
137
+ "num-integer",
138
+ "num-traits",
139
+ "pyo3",
140
+ "pyo3-build-config",
141
+ "rustc-hash",
142
+ ]
143
+
144
+ [[package]]
145
+ name = "once_cell"
146
+ version = "1.21.4"
147
+ source = "registry+https://github.com/rust-lang/crates.io-index"
148
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
149
+
150
+ [[package]]
151
+ name = "portable-atomic"
152
+ version = "1.15.0"
153
+ source = "registry+https://github.com/rust-lang/crates.io-index"
154
+ checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
155
+
156
+ [[package]]
157
+ name = "portable-atomic-util"
158
+ version = "0.2.8"
159
+ source = "registry+https://github.com/rust-lang/crates.io-index"
160
+ checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715"
161
+ dependencies = [
162
+ "portable-atomic",
163
+ ]
164
+
165
+ [[package]]
166
+ name = "proc-macro2"
167
+ version = "1.0.107"
168
+ source = "registry+https://github.com/rust-lang/crates.io-index"
169
+ checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
170
+ dependencies = [
171
+ "unicode-ident",
172
+ ]
173
+
174
+ [[package]]
175
+ name = "pyo3"
176
+ version = "0.29.3"
177
+ source = "registry+https://github.com/rust-lang/crates.io-index"
178
+ checksum = "700d18fa267b73b9b521fd7e13580e2f446916f176cacee1ab63fcc8191f1655"
179
+ dependencies = [
180
+ "libc",
181
+ "once_cell",
182
+ "portable-atomic",
183
+ "pyo3-build-config",
184
+ "pyo3-ffi",
185
+ "pyo3-macros",
186
+ ]
187
+
188
+ [[package]]
189
+ name = "pyo3-build-config"
190
+ version = "0.29.3"
191
+ source = "registry+https://github.com/rust-lang/crates.io-index"
192
+ checksum = "7b3fc0c4d08f6bb10e71fe39dfb9e2f59c6eb6854e22ec8092f50c69a4499adb"
193
+ dependencies = [
194
+ "target-lexicon",
195
+ ]
196
+
197
+ [[package]]
198
+ name = "pyo3-ffi"
199
+ version = "0.29.3"
200
+ source = "registry+https://github.com/rust-lang/crates.io-index"
201
+ checksum = "dfc0b8e19df29aad7086cf977bb0c2a2f143e30567eb113e9cf72b62ca698330"
202
+ dependencies = [
203
+ "libc",
204
+ "pyo3-build-config",
205
+ ]
206
+
207
+ [[package]]
208
+ name = "pyo3-macros"
209
+ version = "0.29.3"
210
+ source = "registry+https://github.com/rust-lang/crates.io-index"
211
+ checksum = "6100e8a4b5eba53afaa5ed078364851a0b2499c553a44c31026b929049b49dc6"
212
+ dependencies = [
213
+ "proc-macro2",
214
+ "pyo3-macros-backend",
215
+ "quote",
216
+ "syn 2.0.119",
217
+ ]
218
+
219
+ [[package]]
220
+ name = "pyo3-macros-backend"
221
+ version = "0.29.3"
222
+ source = "registry+https://github.com/rust-lang/crates.io-index"
223
+ checksum = "6143877a16e82b5a727b7127ff4cd86858a24a28d745d72f43e6f227c7b1bdb3"
224
+ dependencies = [
225
+ "heck",
226
+ "proc-macro2",
227
+ "quote",
228
+ "syn 2.0.119",
229
+ ]
230
+
231
+ [[package]]
232
+ name = "quote"
233
+ version = "1.0.47"
234
+ source = "registry+https://github.com/rust-lang/crates.io-index"
235
+ checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
236
+ dependencies = [
237
+ "proc-macro2",
238
+ ]
239
+
240
+ [[package]]
241
+ name = "rawpointer"
242
+ version = "0.2.1"
243
+ source = "registry+https://github.com/rust-lang/crates.io-index"
244
+ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
245
+
246
+ [[package]]
247
+ name = "rayon"
248
+ version = "1.12.0"
249
+ source = "registry+https://github.com/rust-lang/crates.io-index"
250
+ checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
251
+ dependencies = [
252
+ "either",
253
+ "rayon-core",
254
+ ]
255
+
256
+ [[package]]
257
+ name = "rayon-core"
258
+ version = "1.13.0"
259
+ source = "registry+https://github.com/rust-lang/crates.io-index"
260
+ checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
261
+ dependencies = [
262
+ "crossbeam-deque",
263
+ "crossbeam-utils",
264
+ ]
265
+
266
+ [[package]]
267
+ name = "rebasis"
268
+ version = "0.1.0"
269
+ dependencies = [
270
+ "libm",
271
+ "ndarray 0.16.1",
272
+ "rayon",
273
+ "thiserror",
274
+ ]
275
+
276
+ [[package]]
277
+ name = "rebasis-py"
278
+ version = "0.1.0"
279
+ dependencies = [
280
+ "ndarray 0.16.1",
281
+ "numpy",
282
+ "pyo3",
283
+ "pyo3-build-config",
284
+ "rebasis",
285
+ ]
286
+
287
+ [[package]]
288
+ name = "rustc-hash"
289
+ version = "2.1.3"
290
+ source = "registry+https://github.com/rust-lang/crates.io-index"
291
+ checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
292
+
293
+ [[package]]
294
+ name = "syn"
295
+ version = "2.0.119"
296
+ source = "registry+https://github.com/rust-lang/crates.io-index"
297
+ checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
298
+ dependencies = [
299
+ "proc-macro2",
300
+ "quote",
301
+ "unicode-ident",
302
+ ]
303
+
304
+ [[package]]
305
+ name = "syn"
306
+ version = "3.0.9"
307
+ source = "registry+https://github.com/rust-lang/crates.io-index"
308
+ checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
309
+ dependencies = [
310
+ "proc-macro2",
311
+ "quote",
312
+ "unicode-ident",
313
+ ]
314
+
315
+ [[package]]
316
+ name = "target-lexicon"
317
+ version = "0.13.5"
318
+ source = "registry+https://github.com/rust-lang/crates.io-index"
319
+ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
320
+
321
+ [[package]]
322
+ name = "thiserror"
323
+ version = "2.0.21"
324
+ source = "registry+https://github.com/rust-lang/crates.io-index"
325
+ checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
326
+ dependencies = [
327
+ "thiserror-impl",
328
+ ]
329
+
330
+ [[package]]
331
+ name = "thiserror-impl"
332
+ version = "2.0.21"
333
+ source = "registry+https://github.com/rust-lang/crates.io-index"
334
+ checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
335
+ dependencies = [
336
+ "proc-macro2",
337
+ "quote",
338
+ "syn 3.0.9",
339
+ ]
340
+
341
+ [[package]]
342
+ name = "unicode-ident"
343
+ version = "1.0.27"
344
+ source = "registry+https://github.com/rust-lang/crates.io-index"
345
+ checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
@@ -0,0 +1,23 @@
1
+ [workspace]
2
+ members = ["rebasis", "rebasis-py"]
3
+ resolver = "3"
4
+
5
+ [workspace.package]
6
+ version = "0.1.0"
7
+ edition = "2024"
8
+ rust-version = "1.85"
9
+ license = "MIT"
10
+ repository = "https://github.com/usask-arg/rebasis"
11
+
12
+ [workspace.dependencies]
13
+ # 0.16 matches sasktran2, which will depend on the core crate.
14
+ ndarray = { version = "0.16", features = ["rayon"] }
15
+ rayon = "1.10"
16
+ thiserror = "2"
17
+
18
+ [profile.release]
19
+ lto = "fat"
20
+ codegen-units = 1
21
+
22
+ [profile.test]
23
+ opt-level = 2
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rebasis contributors
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,167 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyrebasis
3
+ Version: 0.1.0
4
+ Classifier: Development Status :: 2 - Pre-Alpha
5
+ Classifier: Intended Audience :: Science/Research
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Programming Language :: Rust
8
+ Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
9
+ Requires-Dist: numpy>=1.26
10
+ Requires-Dist: scipy>=1.11
11
+ Requires-Dist: sphinx>=8 ; extra == 'docs'
12
+ Requires-Dist: furo>=2024.8 ; extra == 'docs'
13
+ Requires-Dist: myst-nb>=1.1 ; extra == 'docs'
14
+ Requires-Dist: numpydoc>=1.8 ; extra == 'docs'
15
+ Requires-Dist: ipykernel>=6.29 ; extra == 'docs'
16
+ Requires-Dist: matplotlib>=3.8 ; extra == 'docs'
17
+ Requires-Dist: xarray>=2024.7 ; extra == 'docs'
18
+ Requires-Dist: pytest>=8 ; extra == 'test'
19
+ Requires-Dist: xarray>=2024.7 ; extra == 'test'
20
+ Requires-Dist: xarray>=2024.7 ; extra == 'xarray'
21
+ Provides-Extra: docs
22
+ Provides-Extra: test
23
+ Provides-Extra: xarray
24
+ License-File: LICENSE
25
+ Summary: Linear maps between function bases: instrument line shapes, regridding, projection
26
+ License-Expression: MIT
27
+ Requires-Python: >=3.11
28
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
29
+ Project-URL: Documentation, https://usask-arg.github.io/rebasis/
30
+ Project-URL: Repository, https://github.com/usask-arg/rebasis
31
+
32
+ # rebasis
33
+
34
+ [![CI](https://github.com/usask-arg/rebasis/actions/workflows/ci.yml/badge.svg)](https://github.com/usask-arg/rebasis/actions/workflows/ci.yml)
35
+ [![Docs](https://img.shields.io/badge/docs-usask--arg.github.io%2Frebasis-blue)](https://usask-arg.github.io/rebasis/)
36
+
37
+ Linear maps between function bases, with a Rust core and a Python interface.
38
+
39
+ ```bash
40
+ pip install pyrebasis # import rebasis
41
+ cargo add rebasis # the Rust crate
42
+ ```
43
+
44
+ The PyPI distribution is `pyrebasis` because `rebasis` there is an unrelated project; the import
45
+ name is `rebasis`. Documentation: <https://usask-arg.github.io/rebasis/>.
46
+
47
+ A quantity is represented as f(x) = Σ_j f_j B_j(x). rebasis computes the sparse matrices that
48
+ turn the coefficients f_j into:
49
+
50
+ | operation | what it computes | typical use |
51
+ |---|---|---|
52
+ | `measure(kernels, basis)` | g_i = ∫ K_i f / ∫ K_i | instrument line shape applied to a high-resolution spectrum |
53
+ | `evaluate(basis, x)` | g_i = f(x_i) | interpolation, plotting a reconstruction |
54
+ | `project(src, dst)` | best L2 fit Σ g_i G_i ≈ f | conservative regridding, up- or down-sampling |
55
+ | `Fit(kernels, model)` | model coefficients whose measurements match data | smooth conservative upsampling, smoothing splines, inverting `measure` |
56
+
57
+ Inner products are exact for piecewise polynomials (boxes, hats, B-splines, tabulated shapes) and
58
+ accurate to rounding for the analytic line shapes; there are no tolerance or resolution
59
+ parameters.
60
+
61
+ ```python
62
+ import numpy as np
63
+ import rebasis as rb
64
+
65
+ wavel = np.linspace(300, 310, 100_001) # high-resolution model grid
66
+ hires = rb.Space.hats(wavel) # radiance = linear interpolant of the samples
67
+ ils = rb.Space.voigts(np.arange(301, 309, 0.1), fwhm_gaussian=0.4, fwhm_lorentzian=0.05)
68
+
69
+ W = rb.measure(ils, hires) # scipy.sparse.csr_array, (80, 100001)
70
+ measured = W @ radiance # or rb.apply_along(W, radiance, axis=...)
71
+
72
+ coarse = rb.Space.boxes(np.arange(300, 310.01, 0.5))
73
+ binned = rb.regrid(radiance, hires, coarse) # conservative; bounded=True to avoid overshoot
74
+ ```
75
+
76
+ ## Spaces
77
+
78
+ Elements are shapes, not densities: everything peaks at 1. Normalization is a property of the
79
+ operation (`measure(..., normalization="integral" | "rowsum" | "none")`), not of the element, so
80
+ the same space can serve as a basis in one call and as kernels in another. Per-element widths
81
+ can be scalars or arrays.
82
+
83
+ **Bases**
84
+
85
+ - `Space.hats(nodes)`: piecewise linear; coefficients are values at the nodes. The basis for a
86
+ function known by samples.
87
+ - `Space.boxes(edges)`, `Space.boxes_from_centers(centers)`: piecewise constant; coefficients
88
+ are bin averages.
89
+ - `Space.bsplines_clamped(breakpoints, degree)`, `Space.bsplines(knots, degree)`: B-splines of
90
+ degree 0 to 3.
91
+ - `Space.deltas(x)`: point evaluation as kernels, point masses as a basis.
92
+
93
+ **Line shapes** (`cutoff` is the half width of the truncated support, in the unit given)
94
+
95
+ | constructor | shape | cutoff unit |
96
+ |---|---|---|
97
+ | `gaussians(c, sigma= \| fwhm=)` | Gaussian | σ (default 6) |
98
+ | `lorentzians(c, hwhm= \| fwhm=)` | Lorentzian | HWHM (100) |
99
+ | `voigts(c, sigma= \| fwhm_gaussian=, gamma= \| fwhm_lorentzian=)` | Voigt (Faddeeva, ~3e-15) | Voigt HWHM (100) |
100
+ | `pseudo_voigts(c, fwhm=, eta=)` | η·L + (1−η)·G | HWHM (100) |
101
+ | `super_gaussians(c, fwhm=, power=, asymmetry=0)` | exp(−ln2·\|2t/w\|^p), optionally asymmetric | HWHM per side (6) |
102
+ | `smoothed_boxes(c, width=, sigma= \| fwhm=)` | slit ⊗ Gaussian | σ past the box edge (6) |
103
+ | `fts(c, max_opd=, apodization=)` | FTS line shape: `none` (sinc), `triangle` (sinc²), `hann`, `hamming`, `blackman`, `blackman_harris`, `norton_beer_weak/medium/strong`, or custom cosine-sum / Norton–Beer coefficients | 1/(2·max_opd) (100) |
104
+ | `rectangles(c, width=)`, `triangles(c, fwhm=)`, `trapezoids(c, top_width=, bottom_width=)` | exact piecewise linear | |
105
+ | `shifted(c, shape_x, shape_y, stretch=1, interp="linear" \| "pchip")` | one tabulated shape, stored once | |
106
+ | `tabulated(c, x, y, interp=)` | a tabulated shape per channel | |
107
+
108
+ `examples/lineshapes.py` plots all of them.
109
+
110
+ ## Regridding
111
+
112
+ - `rb.regrid(data, src, dst, axis, method=...)`: `"project"` (conservative, default),
113
+ `"measure"` (`dst` as averaging kernels), `"interpolate"`, or `"fit"` (smooth conservative
114
+ upsampling of bin averages onto a spline `dst`).
115
+ - `bounded=True` gives the best L2 approximation that conserves the integral and creates no new
116
+ extrema: each coefficient stays within the range of the inputs it overlaps. It solves a
117
+ small banded quadratic program exactly, so corrections stay local.
118
+ `bounded="positive"` and `bounded=(lo, hi)` apply global bounds instead.
119
+ - `skipna=True` handles missing data.
120
+ - `weight="cos_lat_deg"` gives area weighting in latitude.
121
+ - `rb.regrid_nd(data, {axis: (src, dst[, weight]), ...})`: tensor-product grids, one axis at a
122
+ time (exact for separable grids).
123
+ - `rebasis.xarray.regrid(ds, {"lat": new_lat, "lon": new_lon}, weight={"lat": "cos_lat_deg"})`
124
+ regrids DataArrays and Datasets by dimension name.
125
+ - `rb.Fit(kernels, model, smoothing=..., penalty="derivative", order=2)` is the general
126
+ penalized least-squares fit. The penalty is ∫(f⁽ʳ⁾)² (a smoothing spline) or coefficient
127
+ differences (P-splines).
128
+
129
+ ## Derivatives with respect to line-shape parameters
130
+
131
+ `rb.measure_derivative(kernels, basis, parameter)` returns `(W, dW)`, where `dW` is ∂W/∂p with
132
+ row i differentiated with respect to kernel i's parameter. For a spectrum `f`, `dW @ f` is the
133
+ Jacobian of the measurements with respect to per-channel parameters (diagonal), or with respect
134
+ to one parameter shared by all channels. The derivatives are exact: they include the moving
135
+ truncation edges and the normalization, and they agree with finite differences to the accuracy
136
+ of the finite differences.
137
+
138
+ | parameter | kernels |
139
+ |---|---|
140
+ | `center` | all (spectral shift) |
141
+ | `scale` | all but deltas: width · ∂/∂width, every width scaled together (squeeze or stretch) |
142
+ | `sigma` | Gaussian, Voigt, smoothed box |
143
+ | `gamma` | Lorentzian, Voigt |
144
+ | `eta` | pseudo-Voigt |
145
+ | `power`, `asymmetry` | super-Gaussian |
146
+ | `half_width` | smoothed box |
147
+
148
+ ## Wavelength and wavenumber
149
+
150
+ `space.to_wavenumber(jacobian=...)` (and `to_wavelength`, `reciprocal`, `affine`) uses each
151
+ element exactly in another coordinate, e.g. an instrument response in nm against a model grid in
152
+ cm⁻¹: `rb.measure(ils_nm.to_wavenumber(jacobian=True), hires_cminv)`. The required `jacobian` is
153
+ `True` for kernels and spectral densities, `False` for plain functions.
154
+
155
+ ## Development
156
+
157
+ ```bash
158
+ uv sync # builds the extension (release profile)
159
+ uv run pytest
160
+ cargo test -p rebasis
161
+ uv run --extra docs sphinx-build -W -b html docs docs/_build/html
162
+ ```
163
+
164
+ The Rust crate `rebasis` (in `rebasis/`) has no Python dependency and is intended to be used
165
+ directly by sasktran2; `rebasis-py/` holds the pyo3 bindings. `examples/` has a benchmark and
166
+ figure scripts.
167
+
@@ -0,0 +1,135 @@
1
+ # rebasis
2
+
3
+ [![CI](https://github.com/usask-arg/rebasis/actions/workflows/ci.yml/badge.svg)](https://github.com/usask-arg/rebasis/actions/workflows/ci.yml)
4
+ [![Docs](https://img.shields.io/badge/docs-usask--arg.github.io%2Frebasis-blue)](https://usask-arg.github.io/rebasis/)
5
+
6
+ Linear maps between function bases, with a Rust core and a Python interface.
7
+
8
+ ```bash
9
+ pip install pyrebasis # import rebasis
10
+ cargo add rebasis # the Rust crate
11
+ ```
12
+
13
+ The PyPI distribution is `pyrebasis` because `rebasis` there is an unrelated project; the import
14
+ name is `rebasis`. Documentation: <https://usask-arg.github.io/rebasis/>.
15
+
16
+ A quantity is represented as f(x) = Σ_j f_j B_j(x). rebasis computes the sparse matrices that
17
+ turn the coefficients f_j into:
18
+
19
+ | operation | what it computes | typical use |
20
+ |---|---|---|
21
+ | `measure(kernels, basis)` | g_i = ∫ K_i f / ∫ K_i | instrument line shape applied to a high-resolution spectrum |
22
+ | `evaluate(basis, x)` | g_i = f(x_i) | interpolation, plotting a reconstruction |
23
+ | `project(src, dst)` | best L2 fit Σ g_i G_i ≈ f | conservative regridding, up- or down-sampling |
24
+ | `Fit(kernels, model)` | model coefficients whose measurements match data | smooth conservative upsampling, smoothing splines, inverting `measure` |
25
+
26
+ Inner products are exact for piecewise polynomials (boxes, hats, B-splines, tabulated shapes) and
27
+ accurate to rounding for the analytic line shapes; there are no tolerance or resolution
28
+ parameters.
29
+
30
+ ```python
31
+ import numpy as np
32
+ import rebasis as rb
33
+
34
+ wavel = np.linspace(300, 310, 100_001) # high-resolution model grid
35
+ hires = rb.Space.hats(wavel) # radiance = linear interpolant of the samples
36
+ ils = rb.Space.voigts(np.arange(301, 309, 0.1), fwhm_gaussian=0.4, fwhm_lorentzian=0.05)
37
+
38
+ W = rb.measure(ils, hires) # scipy.sparse.csr_array, (80, 100001)
39
+ measured = W @ radiance # or rb.apply_along(W, radiance, axis=...)
40
+
41
+ coarse = rb.Space.boxes(np.arange(300, 310.01, 0.5))
42
+ binned = rb.regrid(radiance, hires, coarse) # conservative; bounded=True to avoid overshoot
43
+ ```
44
+
45
+ ## Spaces
46
+
47
+ Elements are shapes, not densities: everything peaks at 1. Normalization is a property of the
48
+ operation (`measure(..., normalization="integral" | "rowsum" | "none")`), not of the element, so
49
+ the same space can serve as a basis in one call and as kernels in another. Per-element widths
50
+ can be scalars or arrays.
51
+
52
+ **Bases**
53
+
54
+ - `Space.hats(nodes)`: piecewise linear; coefficients are values at the nodes. The basis for a
55
+ function known by samples.
56
+ - `Space.boxes(edges)`, `Space.boxes_from_centers(centers)`: piecewise constant; coefficients
57
+ are bin averages.
58
+ - `Space.bsplines_clamped(breakpoints, degree)`, `Space.bsplines(knots, degree)`: B-splines of
59
+ degree 0 to 3.
60
+ - `Space.deltas(x)`: point evaluation as kernels, point masses as a basis.
61
+
62
+ **Line shapes** (`cutoff` is the half width of the truncated support, in the unit given)
63
+
64
+ | constructor | shape | cutoff unit |
65
+ |---|---|---|
66
+ | `gaussians(c, sigma= \| fwhm=)` | Gaussian | σ (default 6) |
67
+ | `lorentzians(c, hwhm= \| fwhm=)` | Lorentzian | HWHM (100) |
68
+ | `voigts(c, sigma= \| fwhm_gaussian=, gamma= \| fwhm_lorentzian=)` | Voigt (Faddeeva, ~3e-15) | Voigt HWHM (100) |
69
+ | `pseudo_voigts(c, fwhm=, eta=)` | η·L + (1−η)·G | HWHM (100) |
70
+ | `super_gaussians(c, fwhm=, power=, asymmetry=0)` | exp(−ln2·\|2t/w\|^p), optionally asymmetric | HWHM per side (6) |
71
+ | `smoothed_boxes(c, width=, sigma= \| fwhm=)` | slit ⊗ Gaussian | σ past the box edge (6) |
72
+ | `fts(c, max_opd=, apodization=)` | FTS line shape: `none` (sinc), `triangle` (sinc²), `hann`, `hamming`, `blackman`, `blackman_harris`, `norton_beer_weak/medium/strong`, or custom cosine-sum / Norton–Beer coefficients | 1/(2·max_opd) (100) |
73
+ | `rectangles(c, width=)`, `triangles(c, fwhm=)`, `trapezoids(c, top_width=, bottom_width=)` | exact piecewise linear | |
74
+ | `shifted(c, shape_x, shape_y, stretch=1, interp="linear" \| "pchip")` | one tabulated shape, stored once | |
75
+ | `tabulated(c, x, y, interp=)` | a tabulated shape per channel | |
76
+
77
+ `examples/lineshapes.py` plots all of them.
78
+
79
+ ## Regridding
80
+
81
+ - `rb.regrid(data, src, dst, axis, method=...)`: `"project"` (conservative, default),
82
+ `"measure"` (`dst` as averaging kernels), `"interpolate"`, or `"fit"` (smooth conservative
83
+ upsampling of bin averages onto a spline `dst`).
84
+ - `bounded=True` gives the best L2 approximation that conserves the integral and creates no new
85
+ extrema: each coefficient stays within the range of the inputs it overlaps. It solves a
86
+ small banded quadratic program exactly, so corrections stay local.
87
+ `bounded="positive"` and `bounded=(lo, hi)` apply global bounds instead.
88
+ - `skipna=True` handles missing data.
89
+ - `weight="cos_lat_deg"` gives area weighting in latitude.
90
+ - `rb.regrid_nd(data, {axis: (src, dst[, weight]), ...})`: tensor-product grids, one axis at a
91
+ time (exact for separable grids).
92
+ - `rebasis.xarray.regrid(ds, {"lat": new_lat, "lon": new_lon}, weight={"lat": "cos_lat_deg"})`
93
+ regrids DataArrays and Datasets by dimension name.
94
+ - `rb.Fit(kernels, model, smoothing=..., penalty="derivative", order=2)` is the general
95
+ penalized least-squares fit. The penalty is ∫(f⁽ʳ⁾)² (a smoothing spline) or coefficient
96
+ differences (P-splines).
97
+
98
+ ## Derivatives with respect to line-shape parameters
99
+
100
+ `rb.measure_derivative(kernels, basis, parameter)` returns `(W, dW)`, where `dW` is ∂W/∂p with
101
+ row i differentiated with respect to kernel i's parameter. For a spectrum `f`, `dW @ f` is the
102
+ Jacobian of the measurements with respect to per-channel parameters (diagonal), or with respect
103
+ to one parameter shared by all channels. The derivatives are exact: they include the moving
104
+ truncation edges and the normalization, and they agree with finite differences to the accuracy
105
+ of the finite differences.
106
+
107
+ | parameter | kernels |
108
+ |---|---|
109
+ | `center` | all (spectral shift) |
110
+ | `scale` | all but deltas: width · ∂/∂width, every width scaled together (squeeze or stretch) |
111
+ | `sigma` | Gaussian, Voigt, smoothed box |
112
+ | `gamma` | Lorentzian, Voigt |
113
+ | `eta` | pseudo-Voigt |
114
+ | `power`, `asymmetry` | super-Gaussian |
115
+ | `half_width` | smoothed box |
116
+
117
+ ## Wavelength and wavenumber
118
+
119
+ `space.to_wavenumber(jacobian=...)` (and `to_wavelength`, `reciprocal`, `affine`) uses each
120
+ element exactly in another coordinate, e.g. an instrument response in nm against a model grid in
121
+ cm⁻¹: `rb.measure(ils_nm.to_wavenumber(jacobian=True), hires_cminv)`. The required `jacobian` is
122
+ `True` for kernels and spectral densities, `False` for plain functions.
123
+
124
+ ## Development
125
+
126
+ ```bash
127
+ uv sync # builds the extension (release profile)
128
+ uv run pytest
129
+ cargo test -p rebasis
130
+ uv run --extra docs sphinx-build -W -b html docs docs/_build/html
131
+ ```
132
+
133
+ The Rust crate `rebasis` (in `rebasis/`) has no Python dependency and is intended to be used
134
+ directly by sasktran2; `rebasis-py/` holds the pyo3 bindings. `examples/` has a benchmark and
135
+ figure scripts.