Repository navigation
Releases
v0.2.2
Full Changelog: v0.2.1...v0.2.2
v0.2.1
[0.2.1] - 2026-09-20
Added
nufftcf.default_N1(n_points, span, lags): given the same
(n_points, span, lags)acompute_*_nufftcall will see, returns the
exactN1(NUFFT frequency-grid size) that call will use by default.
Since v0.2.0, that default depends onlags(througheff_span, see
CHANGELOG) rather than being the fixed
32 * n_pointsit used to be, and there was previously no way to know
its value ahead of a call other than reimplementing the formula
yourself. Useful to log/report alongside results, or as a starting point
before passing a largerN1explicitly for extra precision.nufftcf.effective_spanis now also exported at the top level (it was
previously only reachable vianufftcf.utils.effective_span), alongside
the newdefault_N1.
Changed
- Internal consolidation: the
N1 = 32 * n_points * eff_span / span
formula was independently duplicated at four call sites across
nufft_ccf.pyandnufft_acf.py(a leftover from how the v0.2.0 fixes
were added incrementally). All four now call the single
utils.default_N1helper described above -- no behavior change, just
removes the risk of the copies silently drifting apart in a future edit.
No numerical behavior changes in this release: outputs are bit-for-bit
identical to v0.2.0 for the same inputs.
Full Changelog: v0.2.0...v0.2.1
v0.2.0
[0.2.0] - 2026-09-19
Fixed — periodic wrap-around in *_nufft estimators (CCF and ACF)
compute_ccf_rectangle_nufft, compute_ccf_gaussian_nufft,
compute_acf_rectangle_nufft, and compute_acf_gaussian_nufft mapped the
data's time axis onto the full NUFFT periodic domain [0, 2*pi), of period
exactly equal to the observed data span (t.max() - t.min()). Requested
lags live inside that same period, not in a padding margin beyond it
(unlike compute_ccf_*_fft, which zero-pads to n1+n2-1 samples before
its FFT round-trip specifically to avoid this).
As a result, a requested lag approaching the data span aliased with real
data from the other end of the record: the estimate could leave the
Pearson-valid range [-1, 1] entirely, and diverge from the _fft/
_realspace reference by an order of magnitude or more as |lag|
approached the span. The bug was invisible in the regime validated by the
existing notebooks (lag_max a few % of the span), which is why it went
unnoticed until now.
Fix: the NUFFT periodic domain is now padded to
eff_span = span + 2*max(|lags|) (mirroring the n1+n2-1 margin _fft
already used), and the real data is mapped onto a centered sub-arc of the
circle instead of the whole circle. No fictitious zero-valued sample is
added — a NUFFT type-1 transform already treats "no point placed here" as
a zero contribution, exactly like a genuine zero-padding sample would.
New helpers: utils.effective_span, utils.padded_angular_map.
Fixed — array-index vs. physical-lag smoothing order (CCF and ACF)
A second, independent, pre-existing bug was found while writing regression
tests for the fix above (confirmed present before it too, so it is not a
regression introduced by the padding fix). All four *_nufft estimators
build lags_eval = [0.0] + lags before smoothing. gaussian_filter1d /
uniform_filter1d smooth along array index, not physical lag value —
so whenever lags[0] was not itself close to 0 (e.g. lags starting at
a large negative value, or any lag array where 0 falls near an edge rather
than being embedded in it), the huge lag=0 spectrum value leaked into
whatever lag happened to be array-adjacent to it, contaminating an
estimate that could otherwise have very few real sample pairs behind it.
Fix: lags_eval is now sorted by physical value before smoothing, and
the result is un-sorted back to the caller's order afterwards, so
array-adjacency always matches physical-adjacency regardless of the order
lags was supplied in.
Changed — N1 (NUFFT frequency-grid size) default
Padding the periodic domain to eff_span (see above) compresses the real
data into a narrower arc of that domain, which — at a fixed N1 — reduces
the NUFFT resolution available per unit of physical time, even at lags
far from the domain edge. The default N1 (previously 32 * n,
unconditionally) is now scaled by eff_span / span to compensate.
This does not fully restore the estimator's baseline precision (see
"Known follow-up" below) — it specifically offsets the additional
resolution loss introduced by the padding margin itself. This also
consolidates two previously-duplicated N1-default computations (the main
NUFFT round-trip and the CCF's _acf_scale_at_lag0 helper independently
computed the same default; they were coincidentally always identical
before this change and are now computed once and shared).
⚠️ Numerical behavior change
Because of the three changes above, values returned by compute_ccf_*_nufft
and compute_acf_*_nufft can shift by O(10⁻³ – 10⁻²) relative to
v0.1.x, even for lags well within the previously-documented "safe"
range (lag_max a few % of the span). This is expected: the old values
included a small, unpredictable-in-sign bias from the wrap-around bug that
partially masked a separate, pre-existing N1-resolution limitation; the
new values no longer have that bias.
Validated on notebook/nufftcf_ccf_demo.ipynb's rho sweep (5 values ×
2 kernels): the maximum deviation from the exact realspace reference
across the sweep went from +0.016 (v0.1.x) to ±0.007 (this release)
— i.e. the new values are, on average, closer to ground truth than
before, not just differently biased. pip install -e ".[benchmark]" +
benchmark/ results confirm no performance regression at realistic
dataset sizes (see "Performance" below).
If you have saved reference outputs from compute_ccf_*_nufft /
compute_acf_*_nufft (e.g. regression tests in downstream projects, or
published results), expect to regenerate them against this release.
compute_ccf_*_fft, compute_acf_*_fft, and the *_realspace estimators
are entirely unaffected (unchanged code paths, unchanged outputs).
Added
tests/test_nufft_padding.py: 11 new regression tests covering both
fixes above, on a regular-grid case (compared against the exact
compute_ccf_rectangle_fft/compute_acf_rectangle_fftreference over
the full valid lag range,|lag|up ton-1) and an irregular-grid
case (compared againstcompute_ccf_*_realspace/compute_acf_*_realspace
up tolag/span ≈ 0.9).
Performance
N1 scaling by eff_span/span only matters when lag_max is a
significant fraction of the span. Benchmarked (benchmark/, plus an
additional direct main-vs-this-release timing comparison):
- Negligible impact (
~0.8×–1.1×, within measurement noise) for
lag_max ≪ span— the regime the package targets and that the existing
benchmarks exercise. - Up to
~2–3×slower in the worst case tested (lag_maxcomparable to
span, on a very short series), but the absolute added cost stays in
the low milliseconds. - The
O(n log n)NUFFT advantage over Pastas'O(n²)(the package's
core value proposition) is unaffected: still ~60–280× faster than Pastas
onbenchmark/benchmark_acf.pyacross the tested range.
Known follow-up (not fixed in this release)
A separate, pre-existing NUFFT+gaussian-kernel approximation residual
(~10–20% relative, on one tested strongly-gappy irregular Ornstein-
Uhlenbeck configuration) remains near the true CCF/ACF peak on irregular
sampling. It is present identically before and after both fixes above, and
is unrelated to either — tracked separately, not addressed here.
Full Changelog: v0.1.4...v0.2.0
v0.1.4
v0.1.3
v0.1.2
nufftcf.__version__was a string hardcoded insrc/nufftcf/__init__.py,
independent from theversionfield inpyproject.toml(the one actually
used to build the PyPI release). It had drifted after the 0.1.1 release
(still reported0.1.0).__version__is now read dynamically from the
installed package's metadata (importlib.metadata.version("nufftcf")),
so it can no longer go out of sync withpyproject.toml.
v0.1.1
Added
compute_ccf_rectangle_fftandcompute_ccf_gaussian_fftin a new
fft_ccf.pymodule: FFT-based cross-correlation (CCF) estimators for two
REGULARLY-sampled series sharing the same sampling step and a common
integer sampling lattice. These are the CCF counterparts of the existing
compute_acf_rectangle_fft/compute_acf_gaussian_fft(fft_acf.py),
completing the FFT estimator family (which previously covered ACF only).
Samegaussian/rectanglekernel definitions, and same lag-0
normalisation approach, ascompute_ccf_*_nufft/compute_ccf_*_realspace;
on a shared regular grid, results matchcompute_ccf_*_realspacealmost
exactly (no NUFFT approximation error involved), and are computed via
scipy.signal.correlate+scipy.ndimagefiltering instead of NUFFT or a
numba two-pointer scan.tests/test_fft_ccf.py: 28 new tests covering shape/sanity checks, grid-
compatibility error handling (mismatcheddt, non-lattice offset,
irregular grid), agreement withcompute_ccf_*_realspace, agreement with
compute_acf_*_fftwhen both signals are identical, lag-shift detection,
boundedness for independent signals, and a multi-signal smoke test.
Fixed
compute_b_gaussian_cross,compute_b_rectangle_cross,
compute_c_gaussian_cross, andcompute_c_rectangle_cross(in
kernels.py) were already listed innufftcf.__all__but never actually
imported into the package namespace insrc/nufftcf/__init__.py, so
from nufftcf import compute_b_gaussian_cross(and the other three)
raised anImportError. They are now imported and exported correctly.
v0.1.0 — nufftcf
First public release of nufftcf (renamed from nufftacf), with ACF and CCF estimation via NUFFT. Available on PyPI: pip install nufftcf