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 +21 -0
- nufftcf-0.1.0/PKG-INFO +400 -0
- nufftcf-0.1.0/README.md +357 -0
- nufftcf-0.1.0/pyproject.toml +57 -0
- nufftcf-0.1.0/setup.cfg +4 -0
- nufftcf-0.1.0/src/nufftcf/__init__.py +89 -0
- nufftcf-0.1.0/src/nufftcf/fft_acf.py +221 -0
- nufftcf-0.1.0/src/nufftcf/kernels.py +265 -0
- nufftcf-0.1.0/src/nufftcf/nufft_acf.py +118 -0
- nufftcf-0.1.0/src/nufftcf/nufft_ccf.py +199 -0
- nufftcf-0.1.0/src/nufftcf/realspace_acf.py +53 -0
- nufftcf-0.1.0/src/nufftcf/realspace_ccf.py +116 -0
- nufftcf-0.1.0/src/nufftcf/utils.py +17 -0
- nufftcf-0.1.0/src/nufftcf.egg-info/PKG-INFO +400 -0
- nufftcf-0.1.0/src/nufftcf.egg-info/SOURCES.txt +19 -0
- nufftcf-0.1.0/src/nufftcf.egg-info/dependency_links.txt +1 -0
- nufftcf-0.1.0/src/nufftcf.egg-info/requires.txt +22 -0
- nufftcf-0.1.0/src/nufftcf.egg-info/top_level.txt +1 -0
- nufftcf-0.1.0/tests/test_ccf.py +223 -0
- nufftcf-0.1.0/tests/test_fft_acf.py +137 -0
- nufftcf-0.1.0/tests/test_nufft_acf.py +106 -0
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
|
+
[](https://github.com/jecampagne/nufftcf/actions/workflows/tests.yml)
|
|
47
|
+
[](https://github.com/jecampagne/nufftcf/actions/workflows/lint.yml)
|
|
48
|
+
[](LICENSE)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
[](https://github.com/jecampagne/nufftcf/actions/workflows/tests.yml)
|
|
52
|
+
[](https://github.com/jecampagne/nufftcf/actions/workflows/tests.yml)
|
|
53
|
+
[%20%7C%20intel-orange?logo=apple)](https://github.com/jecampagne/nufftcf/actions/workflows/tests.yml)
|
|
54
|
+
[](https://github.com/jecampagne/nufftcf/actions/workflows/tests.yml)
|
|
55
|
+
[](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.
|