nufftcf 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.
nufftcf-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 J.-E. Campagne
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
7
+ deal in the Software without restriction, including without limitation the
8
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9
+ sell 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
13
+ all 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
20
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21
+ DEALINGS IN THE SOFTWARE.
nufftcf-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,400 @@
1
+ Metadata-Version: 2.4
2
+ Name: nufftcf
3
+ Version: 0.1.0
4
+ Summary: Fast autocorrelation and cross-correlation functions estimation for irregularly- and regularly-sampled time series, via NUFFT + Numba-optimized real-space kernels (gaussian and rectangle).
5
+ Author-email: Jean-Eric Campagne <jeaneric.campagne@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/jecampagne/nufftcf
8
+ Project-URL: Documentation, https://jecampagne.github.io/nufftcf/
9
+ Project-URL: Repository, https://github.com/jecampagne/nufftcf
10
+ Project-URL: Issues, https://github.com/jecampagne/nufftcf/issues
11
+ Keywords: nufft,finufft,autocorrelation,acf,cross-correlation,ccf,irregular-sampling,time-series,numba
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Topic :: Scientific/Engineering
21
+ Requires-Python: >=3.11
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: numpy>=1.22
25
+ Requires-Dist: pandas>=1.4
26
+ Requires-Dist: numba>=0.56
27
+ Requires-Dist: scipy>=1.8
28
+ Requires-Dist: finufft>=2.1
29
+ Provides-Extra: benchmark
30
+ Requires-Dist: pastas>=1.0; extra == "benchmark"
31
+ Requires-Dist: matplotlib>=3.5; extra == "benchmark"
32
+ Provides-Extra: test
33
+ Requires-Dist: pytest>=7.0; extra == "test"
34
+ Provides-Extra: dev
35
+ Requires-Dist: black>=24.0; extra == "dev"
36
+ Requires-Dist: pytest>=7.0; extra == "dev"
37
+ Provides-Extra: docs
38
+ Requires-Dist: mkdocs>=1.5; extra == "docs"
39
+ Requires-Dist: mkdocs-material>=9.5; extra == "docs"
40
+ Requires-Dist: mkdocstrings[python]>=0.24; extra == "docs"
41
+ Requires-Dist: pymdown-extensions>=10.0; extra == "docs"
42
+ Dynamic: license-file
43
+
44
+ # nufftcf
45
+
46
+ [![Tests](https://github.com/jecampagne/nufftcf/actions/workflows/tests.yml/badge.svg)](https://github.com/jecampagne/nufftcf/actions/workflows/tests.yml)
47
+ [![Lint](https://github.com/jecampagne/nufftcf/actions/workflows/lint.yml/badge.svg)](https://github.com/jecampagne/nufftcf/actions/workflows/lint.yml)
48
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
49
+
50
+
51
+ [![Python](https://img.shields.io/badge/python-3.11%20|%203.12%20|%203.13%20|%203.14-blue)](https://github.com/jecampagne/nufftcf/actions/workflows/tests.yml)
52
+ [![Linux](https://img.shields.io/badge/linux-ubuntu--latest-orange?logo=linux)](https://github.com/jecampagne/nufftcf/actions/workflows/tests.yml)
53
+ [![macOS](https://img.shields.io/badge/macOS-latest%20(arm64)%20%7C%20intel-orange?logo=apple)](https://github.com/jecampagne/nufftcf/actions/workflows/tests.yml)
54
+ [![macOS](https://img.shields.io/badge/macOS-arm64%20%7C%20x86__64-orange?logo=apple)](https://github.com/jecampagne/nufftcf/actions/workflows/tests.yml)
55
+ [![Windows](https://img.shields.io/badge/windows-latest-orange?logo=windows)](https://github.com/jecampagne/nufftcf/actions/workflows/tests.yml)
56
+
57
+
58
+
59
+ Fast **autocorrelation** (ACF) and **cross-correlation** (CCF) function estimation
60
+ for **irregularly- and regularly-sampled** time series, scaling as $O(n\log n)$
61
+ thanks notably to the **Nonuniform Fast Fourier Transform** library developped by
62
+ the Flatiron Institut ([FINUFFT](https://github.com/flatironinstitute/finufft)).
63
+
64
+ With **`nufftcf`** three estimator families are provided for the ACF:
65
+
66
+ | Function | Sampling | Method | Scaling | Notes |
67
+ |---|---|---|---|---|
68
+ | `compute_acf_gaussian_nufft` | irregular | NUFFT + Wiener-Khinchin | $\sim~O(n\log n)$ | fastest for long irregular series; ~1-3% residual amplitude bias on strongly periodic signals (see below) |
69
+ | `compute_acf_rectangle_nufft` | irregular | NUFFT + Wiener-Khinchin | $\sim~O(n\log n)$ | same caveat as above |
70
+ | `compute_acf_gaussian_realspace` | irregular or regular | direct real-space weighted sum | $O(n)$ per lag | artifact-free reference |
71
+ | `compute_acf_rectangle_realspace` | irregular or regular | direct real-space weighted sum | $O(n)$ per lag | artifact-free reference |
72
+ | `compute_acf_regular_fft` | **regular only** | classic FFT correlation, no kernel | $\sim~O(n)$ | matches Pastas `bin_method="regular"` to numerical precision |
73
+ | `compute_acf_rectangle_fft` | **regular only** | classic FFT correlation + box filter | $\sim~O(n\log n)$ | faster than `_nufft`/`_realspace` on regular data (no NUFFT/numba overhead) |
74
+ | `compute_acf_gaussian_fft` | **regular only** | classic FFT correlation + gaussian filter |$\sim~O(n\log n)$ | same |
75
+
76
+ All seven ACF functions share the same calling convention:
77
+ `fn(lags, t, x, bin_width=0.5)` (`compute_acf_regular_fft` has no
78
+ `bin_width`, since it applies no smoothing kernel), and return `(c, b)` --
79
+ the ACF estimate and the effective pair count, both shape `(len(lags),)`.
80
+
81
+ ### Cross-correlation functions (CCF)
82
+
83
+ The same NUFFT + Wiener-Khinchin and real-space machinery is also available
84
+ for the **cross-correlation function** between two **irregularly-sampled**
85
+ series `(t, x)` and `(s, y)`, which may have different lengths and different
86
+ sampling times:
87
+
88
+ | Function | Sampling | Method | Scaling | Notes |
89
+ |---|---|---|---|---|
90
+ | `compute_ccf_gaussian_nufft` | irregular | NUFFT + Wiener-Khinchin | $\sim~O(n\log n)$ | fastest for long irregular series; same residual-bias caveat as the ACF `_nufft` variants |
91
+ | `compute_ccf_rectangle_nufft` | irregular | NUFFT + Wiener-Khinchin | $\sim~O(n\log n)$ | same caveat as above |
92
+ | `compute_ccf_gaussian_realspace` | irregular or regular | direct real-space weighted sum | $O(n)$ per lag | artifact-free reference |
93
+ | `compute_ccf_rectangle_realspace` | irregular or regular | direct real-space weighted sum | $O(n)$ per lag | artifact-free reference |
94
+
95
+ All four share the calling convention `fn(lags, t, x, s, y, bin_width=0.5)`
96
+ and return `(c, b)` -- the CCF estimate (Pearson-normalised, `c ~ 1` at
97
+ perfect correlation) and the effective pair count, both shape `(len(lags),)`.
98
+ By convention, a positive lag means `y` lags behind `x` (i.e. the CCF peaks
99
+ at `lag = tau0` when `y(t) ~ x(t - tau0)`).
100
+
101
+ **Important:** `t` and `s` must be expressed on a *common* time origin (e.g.
102
+ elapsed days since the same reference date for both series). `t_numeric_of`
103
+ alone uses each series' own first sample as origin, which is **not** suitable
104
+ for two independently-sampled series -- using it separately on `x` and `y`
105
+ would silently misalign the lags. Build `t`/`s` from a shared reference date
106
+ instead (see the example below).
107
+
108
+ For a worked comparison against **pyZDCF**, including a case with a known
109
+ theoretical CCF, see
110
+ [`notebook/nufftcf_ccf_demo.ipynb`](notebook/nufftcf_ccf_demo.ipynb).
111
+
112
+ ## Documentation
113
+
114
+ https://jecampagne.github.io/nufftcf/
115
+
116
+ ### Build the documentation locally
117
+
118
+ The docs are built with [MkDocs](https://www.mkdocs.org/) + the
119
+ [Material](https://squidfunk.github.io/mkdocs-material/) theme, and
120
+ [mkdocstrings](https://mkdocstrings.github.io/) generates the API
121
+ Reference page directly from the package's numpy-style docstrings.
122
+
123
+ ```bash
124
+ pip install -e ".[docs]"
125
+
126
+ # live preview with auto-reload at http://127.0.0.1:8000
127
+ mkdocs serve
128
+
129
+ # or a static build into site/
130
+ mkdocs build
131
+ ```
132
+
133
+ Deploying to the `gh-pages` branch (maintainers only):
134
+
135
+ ```bash
136
+ mkdocs gh-deploy
137
+ ```
138
+
139
+ ## Installation
140
+
141
+ ```bash
142
+ # from a local clone
143
+ git clone https://github.com/jecampagne/nufftcf.git
144
+ cd nufftcf
145
+ python3 -m venv venv
146
+ source venv/bin/activate # Windows: venv\Scripts\activate
147
+ pip install --upgrade pip
148
+
149
+ # Force prebuilt wheels for the compiled dependencies (finufft, numba, llvmlite).
150
+ # This avoids source builds that can fail or produce mismatched OpenMP runtimes,
151
+ # particularly on macOS -- see docs/installation.md#troubleshooting-macos.
152
+ pip install --only-binary=:all: finufft numba llvmlite
153
+
154
+ pip install -e ".[dev,test,benchmark]"
155
+
156
+ # from GitHub without any local clone
157
+ pip install "nufftcf @ git+https://github.com/jecampagne/nufftcf.git"
158
+ ```
159
+
160
+ Requires Python >= 3.11. Core dependencies: numpy, pandas, numba, scipy, finufft.
161
+
162
+ If `pip install` fails to build `finufft` or `llvmlite` from source
163
+ ("Failed to build installable wheels..."), see
164
+ [Troubleshooting (macOS)](docs/installation.md#troubleshooting-macos) --
165
+ in most cases the `--only-binary=:all:` step above, run in a clean venv,
166
+ resolves it.
167
+
168
+ Check installation using `pytest>=7.0`
169
+ ```bash
170
+ cd nufftcf
171
+ pytest tests/ -v
172
+ ```
173
+ Let me know via the [repository issues](https://github.com/jecampagne/nufftcf/issues) if you encounter any troubles.
174
+
175
+ ## Quick start
176
+
177
+ ```python
178
+ import numpy as np
179
+ import pandas as pd
180
+ from nufftcf import compute_acf_gaussian_nufft, t_numeric_of
181
+
182
+ # An irregularly-sampled series (any DatetimeIndex works)
183
+ idx = pd.date_range("2000-01-01", periods=5000, freq="D")[np.random.rand(5000) > 0.2]
184
+ x = pd.Series(np.random.randn(len(idx)), index=idx)
185
+
186
+ lags = np.arange(1.0, 366.0) # 1 to 365 days
187
+ t = t_numeric_of(x) # elapsed days since first sample
188
+
189
+ c, b = compute_acf_gaussian_nufft(lags, t, x.to_numpy(), bin_width=0.5)
190
+ # c: ACF estimate per lag (c ~ 1 at lag -> 0)
191
+ # b: effective number of contributing pairs per lag (useful to flag
192
+ # under-sampled lags, e.g. mask out lags where b is too small)
193
+ ```
194
+
195
+ ### Cross-correlation example
196
+
197
+ ```python
198
+ import numpy as np
199
+ import pandas as pd
200
+ from nufftcf import compute_ccf_gaussian_nufft
201
+
202
+ # Two irregularly-sampled series on a COMMON time origin (elapsed days since
203
+ # the same reference date), with y lagging behind x by tau0 = 60 days
204
+ ref_date = pd.Timestamp("2000-01-01")
205
+ n_days, tau0, alpha = 3650, 60, 10.0
206
+ rng = np.random.default_rng(0)
207
+
208
+ # shared latent Ornstein-Uhlenbeck-like signal (ACF ~ exp(-|u|/alpha))
209
+ phi = np.exp(-1.0 / alpha)
210
+ noise = rng.standard_normal(n_days + tau0)
211
+ z = np.empty(n_days + tau0)
212
+ z[0] = noise[0]
213
+ for i in range(1, n_days + tau0):
214
+ z[i] = phi * z[i - 1] + noise[i]
215
+
216
+ mask_x = rng.random(n_days) > 0.6 # series 1: ~40% of days kept
217
+ mask_y = rng.random(n_days) > 0.4 # series 2: ~60% of days kept
218
+
219
+ t = np.arange(n_days)[mask_x].astype(float) # elapsed days, series 1
220
+ s = np.arange(n_days)[mask_y].astype(float) # elapsed days, series 2 (same origin as t)
221
+
222
+ x = z[tau0:][mask_x] # x(t) = z(t)
223
+ y = z[:n_days][mask_y] # y(t) = z(t - tau0) -> y lags x by tau0 days
224
+
225
+ lags = np.arange(1.0, 181.0) # 1 to 180 days
226
+ c, b = compute_ccf_gaussian_nufft(lags, t, x, s, y, bin_width=0.5)
227
+ # c: CCF estimate per lag, peaks at lag = tau0 = 60
228
+ # b: effective number of contributing pairs per lag
229
+ ```
230
+
231
+ ## Which estimator should I use?
232
+
233
+ - **Your data is regularly sampled** (a fixed time step, no gaps): use the
234
+ `_fft` variants. They're faster than both `_nufft` (no NUFFT overhead) and
235
+ `_realspace` (no numba two-pointer scan) on regular data, and
236
+ `compute_acf_regular_fft` additionally gives you Pastas' "regular"
237
+ bin_method (no smoothing kernel) at a fraction of its cost (see
238
+ `benchmark/`).
239
+ - **Long, irregularly-sampled series** (tens of thousands of points or more)
240
+ where Pastas' real-space approach becomes impractically slow: use the
241
+ `_nufft` variants.
242
+ - **Strongly periodic, irregularly-sampled signals** (e.g. seasonal/annual
243
+ cycles) where you need the most accurate possible ACF and series length is
244
+ manageable: use the `_realspace` variants, or the `_nufft` variants with an
245
+ increased `N1` (e.g. `N1>32*len(x)`), which reduces but does not fully
246
+ eliminate the residual bias (see below).
247
+ - **Everything else, irregular case**: either `_nufft` or `_realspace` works;
248
+ `_nufft` will generally be faster.
249
+
250
+ ### A note on the NUFFT residual bias
251
+
252
+ The NUFFT-based estimators compute the power spectrum of the irregularly-sampled signal and invert it at the requested lags via the Wiener-Khinchin theorem. This implicitly relies on a finite-domain Fourier representation, which is mathematically equivalent to convolving the true spectrum with the "spectral window" induced by the irregular/gappy sampling pattern. A narrow spectral peak (a strongly periodic signal) is distorted much more visibly by this convolution than a broad, featureless spectrum (e.g. an AR(1)-type exponential decay), even though the absolute size of the distortion is similar in both cases.
253
+
254
+ In practice, with the default `N1 = 32 * len(x)` (the number of Fourier modes used internally by FINUFFT), this residual bias is on the order of 1–3% of the ACF amplitude for strongly periodic signals with irregular or gappy sampling, and negligible for smoothly-decaying, broadband signals. Reducing N1 speeds up the computation slightly at the cost of a larger bias; increasing it beyond `32 * len(x)` gives diminishing returns for most practical series.
255
+
256
+ The `_realspace` estimators do not have this limitation (no implicit
257
+ periodicity assumption), at the cost of O(n) scaling per lag rather than
258
+ O(n log n) -- for most practical series lengths both are fast; benchmark
259
+ on your own data if it matters (see `benchmark/`).
260
+
261
+ ### Regularly-sampled data: the `_fft` estimators
262
+
263
+ When `t` is on a regular grid, `compute_acf_regular_fft` /
264
+ `compute_acf_rectangle_fft` / `compute_acf_gaussian_fft` (in `fft_acf.py`)
265
+ skip NUFFT entirely and use a plain `scipy.signal.correlate` (classic FFT
266
+ correlation) instead -- faster, and with no finufft/numba dependency in the
267
+ hot path. All three raise `ValueError` if `t` isn't regularly spaced (use
268
+ `_nufft`/`_realspace` for that).
269
+
270
+ - `compute_acf_regular_fft` reproduces Pastas' `bin_method="regular"`
271
+ (a windowed Pearson correlation, no smoothing kernel) to numerical
272
+ precision (`atol=1e-9` in `tests/test_fft_acf.py`), via an O(n) cumulative
273
+ -moments computation (`E[X^2] - E[X]^2`) instead of one `np.corrcoef` call
274
+ per lag.
275
+ - `compute_acf_rectangle_fft` / `compute_acf_gaussian_fft` match
276
+ `compute_acf_rectangle_realspace` / `compute_acf_gaussian_realspace`
277
+ almost exactly at the package's default `bin_width=0.5` (`atol=1e-9`).
278
+ For other `bin_width` values, expect a small residual at `lag=0`
279
+ specifically (a few %, decaying to <0.1% by lag~5) -- an inherent
280
+ discretization artifact of approximating a continuous symmetric kernel
281
+ window with a discrete digital filter, not a bug to chase further; see
282
+ `tests/test_fft_acf.py::test_rectangle_fft_matches_realspace_various_bin_widths`
283
+ for the exact numbers across `bin_width` values.
284
+
285
+ ## Notebooks
286
+
287
+ - [`pastas_vs_nufftcf.ipynb`](notebook/pastas_vs_nufftcf.ipynb)
288
+ compares **nufftcf** against **Pastas** on **irregularly**-sampled series
289
+ (sine and AR(1)-like, with random gaps), using the `_nufft` estimators.
290
+ - [`pastas_vs_nufftcf_regular.ipynb`](notebook/pastas_vs_nufftcf_regular.ipynb)
291
+ does the same on **regularly**-sampled series (sine, noisy sine,
292
+ noisy exponential decay, square wave), using the `_fft` estimators,
293
+ for all 3 of Pastas' bin methods (`regular`/`rectangle`/`gaussian`).
294
+ - [`zdcf_vs_nufftcf.ipynb`](notebook/zdcf_vs_nufftcf.ipynb) compares **nufftcf** against **pyzdcf** on the same **irregularly**-sampled series used in the `pastas_vs_nufftcf.ipynb`.
295
+ - [`nufftcf_ccf_demo.ipynb`](notebook/nufftcf_ccf_demo.ipynb) demonstrates the
296
+ **cross-correlation (CCF)** functions (`compute_ccf_gaussian_nufft`,
297
+ `compute_ccf_rectangle_nufft`) against **pyZDCF**, including a case with two
298
+ series built from coupled Ornstein-Uhlenbeck processes for which the
299
+ theoretical CCF is known analytically.
300
+
301
+ All are Colab-ready: the first cell installs **nufftcf** as well as **Pastas** or **pyzdcf** and third party libraries. Concerning **pyzdcf**, the repository was cloned and adapted to ensure compatibility with the pandas and other library versions used in this notebook, allowing it to run on Google Colab. These changes do not affect the quality of the computations.
302
+
303
+ ## Method
304
+
305
+ `nufftcf` is built on two ingredients:
306
+
307
+ 1. **[FINUFFT](https://github.com/flatironinstitute/finufft)** (Flatiron
308
+ Institute) to evaluate the power spectrum of the irregularly-sampled
309
+ signal via a type-1 non-uniform FFT, then invert it at the requested lags
310
+ via a type-2 NUFFT (Wiener-Khinchin theorem) -- this is what gives the
311
+ `_nufft` estimators their $\sim~O(n\ log\ n)$ scaling, instead of the $O(n^2)/O(n)$
312
+ per-lag direct sum.
313
+ 2. An analytical, kernel-specific correction for the number of
314
+ contributing sample pairs per lag (the `b` denominator in `kernels.py`),
315
+ for both the **Gaussian** and **rectangular/boxcar** smoothing kernels --
316
+ computed with an $O(n)$ two-pointer scan (since `t` is sorted), rather than
317
+ the naive $O(n^2)$ all-pairs count. This `b` is what turns the raw NUFFT
318
+ power spectrum into a properly normalized correlation.
319
+
320
+ On a **regular** grid, `fft_acf.py` gets the same `b` correction for free,
321
+ without the two-pointer scan: smoothing the deterministic "raw pair count"
322
+ ramp (`n - lag`) with the *same* discrete filter (gaussian or box) used for
323
+ the correlation numerator reproduces `b` exactly -- see the "Regularly
324
+ -sampled data" section above for the validation numbers and the two bugs
325
+ this caught in the original prototype.
326
+
327
+ See `nufft_acf.py`, `kernels.py` and `fft_acf.py` docstrings for the full
328
+ derivation, and `notebook/` / `benchmark/` for empirical validation.
329
+
330
+ ## Benchmark
331
+
332
+ - `benchmark/benchmark_acf.py` (+ `fit_benchmark_acf.py`): **Pastas** vs
333
+ `_nufft`, on **irregularly**-sampled series of varying length, both
334
+ kernels.
335
+ - `benchmark/benchmark_acf_regular.py` (+ `fit_benchmark_acf_regular.py`):
336
+ Pastas vs `_fft` *and* `_nufft`, on **regularly**-sampled series of
337
+ varying length, all 3 bin methods -- this is what lets you see, on
338
+ regular data, how much the dedicated `_fft` path buys over just reusing
339
+ the more general `_nufft` estimator.
340
+ - no real benchmarks are provided to compare **nufftcf** against **pyzdcf**, although in the plots obtained in `zdcf_vs_nufftcf.ipynb` one can appreciate that **nufftcf** is ~2 order of magnitude faster.
341
+
342
+ ```bash
343
+ pip install -e ".[benchmark]"
344
+ python benchmark/benchmark_acf.py
345
+ # -> benchmark_acf_results.csv
346
+ python benchmark/fit_benchmark_acf.py
347
+
348
+ python benchmark/benchmark_acf_regular.py
349
+ # -> benchmark_acf_regular_results.csv
350
+ python benchmark/fit_benchmark_acf_regular.py
351
+ ```
352
+
353
+ Adjust `durations_years` / `n_points_list` and the **Pastas** cutoffs
354
+ (`pastas_max_years`, `pastas_max_n_regular`, `pastas_max_n_kernel` -- **Pastas**'
355
+ "gaussian"/"rectangle" bin methods are $O(n^2)$ on regular data too, just like
356
+ on irregular data, while "regular" is empirically $\sim O(n)$ and stays usable
357
+ much longer; both were measured directly before picking these defaults, not
358
+ assumed) at the top of each script as needed. Each measurement uses several
359
+ repeats and keeps the minimum, to reduce noise from shared/cloud
360
+ environments (Colab, background browser activity, etc.).
361
+
362
+ `benchmark/*_macosx.{csv,png}` give the results on MacBook Pro (2020) 2 GHz Intel Core i5 quatre cœurs (osx Tahoe 26.5.1)-- re-run on your own machine for comaparison. You can share your results on the [Discussions](https://github.com/jecampagne/nufftcf/discussions) of the repository.
363
+
364
+ ## Citing
365
+
366
+ If you use `nufftcf`, please also cite FINUFFT, which it depends on:
367
+
368
+ > A. H. Barnett, J. F. Magland, and L. af Klinteberg (2019).
369
+ > *A parallel non-uniform fast Fourier transform library based on an
370
+ > "exponential of semicircle" kernel.* SIAM J. Sci. Comput. 41(5), C479-C504.
371
+ > https://github.com/flatironinstitute/finufft
372
+
373
+ > J.E Campagne (2026): *"Non Uniform FFT based Auto Correlation functions"*. https://github.com/jecampagne/nufftcf
374
+
375
+ ## License
376
+
377
+ [MIT](LICENSE)
378
+
379
+ ## Development
380
+
381
+ ```bash
382
+ pip install --only-binary=:all: finufft numba llvmlite
383
+ pip install -e ".[dev]"
384
+ black . # formatage
385
+ pytest tests/ # tests
386
+ ```
387
+
388
+ ## Tests
389
+
390
+ ```bash
391
+ pip install --only-binary=:all: finufft numba llvmlite
392
+ pip install -e ".[test]"
393
+ pytest tests/
394
+ ```
395
+
396
+ `tests/test_nufft_acf.py` (NUFFT vs realspace, irregular data),
397
+ `tests/test_ccf.py` (NUFFT vs realspace, cross-correlation), and
398
+ `tests/test_fft_acf.py` (fft vs realspace, and fft "regular" vs Pastas
399
+ itself, regular data) are correctness/sanity checks, not performance
400
+ benchmarks.