Idea
spectraldiff currently manages the FFT's implicit periodicity with two boolean hyperparameters, even_extension and pad_to_zero_dxdt, the second of which pads 100 samples on each end and smooths them in. Section 7.5.1 of the taxonomy paper already notes the weakness: an even extension removes the value discontinuity across the wrap but "can still result in a corner if the signal edges are not flat," and a corner in the signal is a jump in its derivative.
A more principled treatment removes both problems in three steps, each with a reason:
- Detrend — subtract the straight line joining the endpoints, so
x[0] == x[N-1] and the periodic wrap closes.
- Odd-extend through the last endpoint —
x[N+k] = 2*x[N-1] - x[N-2-k], which continues value and slope, so no corner. Reflecting x[0] as well would repeat it across the wrap, a flat spot rather than a continuation, so that one sample is left out and the extension is 2N-2.
- Differentiate, then add the slope back — exact, since the derivative of a line is a constant.
This drops both hyperparameters and the padding block, and the extension is 2N-2 rather than the 2N of even extension.
Results
On noiseless analytic signals at a fixed cutoff_freq=0.2, it is much better (max relative error):
| signal |
even + pad |
detrend + odd ext |
| sin |
1.52e+00 |
5.66e-15 |
| ramp + wiggle |
1.47e+00 |
2.00e-02 |
| exp |
1.41e+00 |
6.80e-03 |
| quadratic |
1.43e+00 |
3.38e-03 |
But that is not the case that matters, and the honest result is worse. Optimizing both variants with pynumdiff.optimize over six sims x two step sizes x two noise levels, the new version is a slight net loss: geometric mean of new/old RMSE 1.028, better in 11 of 24, median ratio 1.029. It wins clearly on lin_auto (0.495, 0.593) and on coarse dt generally, and loses worst on cruise at high noise (2.063) and pop_dyn (1.396, 1.576).
The reconciliation: on noisy data with the cutoff optimized, the low-pass filter dominates and edge treatment is second order, while the two booleans give the optimizer freedom to pick whichever treatment suits each signal. Removing two degrees of freedom costs about what the better extension gains.
Open questions
- Keep the odd extension but retain a flag, so the optimizer can still choose? Recovers the freedom, keeps some complexity.
- Is detrending alone the win, independent of the extension change? It has the cleanest justification and may be carrying the
lin_auto improvement by itself.
- Six error-bound cells move in each direction, so
spectraldiff's test bounds need regenerating either way.
A branch, spectraldiff-odd-extension, has a working implementation with the search space trimmed and bounds regenerated; 210 tests pass. Not merged, given the above.
Investigated by Claude Opus 5 (Claude Code) on behalf of @pavelkomarov, PyNumDiff maintainer.
Idea
spectraldiffcurrently manages the FFT's implicit periodicity with two boolean hyperparameters,even_extensionandpad_to_zero_dxdt, the second of which pads 100 samples on each end and smooths them in. Section 7.5.1 of the taxonomy paper already notes the weakness: an even extension removes the value discontinuity across the wrap but "can still result in a corner if the signal edges are not flat," and a corner in the signal is a jump in its derivative.A more principled treatment removes both problems in three steps, each with a reason:
x[0] == x[N-1]and the periodic wrap closes.x[N+k] = 2*x[N-1] - x[N-2-k], which continues value and slope, so no corner. Reflectingx[0]as well would repeat it across the wrap, a flat spot rather than a continuation, so that one sample is left out and the extension is2N-2.This drops both hyperparameters and the padding block, and the extension is
2N-2rather than the2Nof even extension.Results
On noiseless analytic signals at a fixed
cutoff_freq=0.2, it is much better (max relative error):But that is not the case that matters, and the honest result is worse. Optimizing both variants with
pynumdiff.optimizeover six sims x two step sizes x two noise levels, the new version is a slight net loss: geometric mean of new/old RMSE 1.028, better in 11 of 24, median ratio 1.029. It wins clearly onlin_auto(0.495, 0.593) and on coarsedtgenerally, and loses worst oncruiseat high noise (2.063) andpop_dyn(1.396, 1.576).The reconciliation: on noisy data with the cutoff optimized, the low-pass filter dominates and edge treatment is second order, while the two booleans give the optimizer freedom to pick whichever treatment suits each signal. Removing two degrees of freedom costs about what the better extension gains.
Open questions
lin_autoimprovement by itself.spectraldiff's test bounds need regenerating either way.A branch,
spectraldiff-odd-extension, has a working implementation with the search space trimmed and bounds regenerated; 210 tests pass. Not merged, given the above.Investigated by Claude Opus 5 (Claude Code) on behalf of @pavelkomarov, PyNumDiff maintainer.