Skip to content

Fourier Smoothing in Aeon: Symmetry, Cutoffs, and Better Tests

Published:
14 min read

Aeon’s DiscreteFourierApproximation is a series transformer for smoothing real-valued time series in the frequency domain. When sort = false, r defines a contiguous low-index frequency cutoff over the representation’s coordinates. When sort = true, the transformer ranks the coefficients by amplitude instead, so the selected set need not be a contiguous low-frequency band. Both modes transform a sequence, zero selected spectral coordinates, and reconstruct the samples.

Real-valued input adds structure that a selection rule cannot ignore. Its positive- and negative-frequency coefficients occur as conjugate pairs, apart from the self-conjugate DC and even-length Nyquist coordinates. A useful evaluation therefore looks beyond a plausible waveform: it measures passband gain, stopband rejection, and whether reconstruction respects that conjugate symmetry. Otherwise, a retained coordinate can appear to survive selection while returning only half of the cosine it represents.

These basis-function, gain, stopband, and round-trip checks expose concrete failure modes; they do not prove the transformer universally correct across every input and option. A unit cosine reveals passband gain, a neighboring rejected cosine reveals the cutoff for the frozen sort = false path, and a full-retention mixture checks the complete transform-mask-inverse path. The numerical observations below freeze those checks to a direct, deterministic model: N=64, sort = false, unit cosines at bins 1 through 32, r ∈ {0.25, 0.50, 0.75, 1.0}, and absolute tolerance 1e-10. They are not measurements of classifier quality, throughput, or a production workload. They show exactly which component is being kept, reconstructed, or removed.

The assertion that stayed green

The old implementation took an ordinary two-sided FFT, chose a count with max(floor(r * N), 1), retained the first indices when sort = false, and took the real part of the inverse transform. Its existing value test had a useful property: it checked that the one-channel and multichannel paths agreed. But the same masking rule ran in both paths. If the mask discarded the negative-frequency conjugate in each case, both outputs could be equally wrong and still compare equal.

A consistency assertion answers, “Do these paths produce the same values?” It does not answer, “Is the value of a selected basis component correct?” The second question needs an external expectation. Here, the expectation is especially cheap: inject a unit cosine at a frequency known to lie in the passband and assert that the recovered amplitude is one, within a numerical tolerance.

The issue was not an arbitrary scaling constant. For real input, a frequency-domain coefficient has a companion. The missing partner explains the one-half result, identifies the edge cases where it does not apply, and suggests a representation where keeping a positive-frequency term also preserves the information needed to reconstruct a real signal.

A short reproduction

The following is intentionally short enough to run in a Python REPL. It contrasts the old two-sided mask with the merged real-spectrum mask on the smallest frozen witness: a unit cosine at bin 3, with N = 64 and r = 0.25. The index arithmetic shows where the factor comes from. floor(0.25 × 64) is 16, so the old path leaves indices 0-15 untouched: coefficient 3 remains, while coefficient 61 is zeroed. floor(0.25 × 33) is 8, so the real path leaves bins 0-7 untouched. Bin 3 remains in both cases, but only the real representation carries the information needed to complete its negative-frequency partner during inversion. No post-inverse scale factor creates the difference; the surviving coefficient set does.

import numpy as np

N = 64
r = 0.25
k = 3
x = np.cos(2 * np.pi * k * np.arange(N) / N)

old = np.fft.fft(x)
old[np.arange(N) >= max(int(np.floor(r * N)), 1)] = 0
old_y = np.fft.ifft(old).real

merged = np.fft.rfft(x)
merged[np.arange(N // 2 + 1) >= max(int(np.floor(r * (N // 2 + 1))), 1)] = 0
merged_y = np.fft.irfft(merged, n=N)

print(old_y.max(), merged_y.max())
assert np.isclose(old_y.max(), 0.5, atol=1e-10)
assert np.isclose(merged_y.max(), 1.0, atol=1e-10)

In the fixed-loop reproduction, the printed old peak is approximately 0.5000000000000002; the merged peak is approximately 0.9999999999999997. Those are observed floating results, not exact decimal identities. The test predicate is the tolerance: each expected gain is checked within 1e-10.

The max(..., 1) detail is intentional. At r = 0, both models keep the single DC coordinate rather than producing an empty spectrum. At full retention, the even-length Nyquist coordinate is included. Neither edge case is an example of a discarded conjugate pair, so neither licenses a blanket “all retained terms halve” statement.

One coefficient is not a real signal

For a real-valued sequence, the DFT is Hermitian:

X[N-k] = conjugate(X[k]).

For a bin-aligned unit cosine, the positive and negative coefficients make equal real contributions to the inverse. Writing the relevant pair and ignoring the other frequencies gives:

x[n] = cos(2πkn/N)
     = (X[k] exp(2πikn/N) + X[N-k] exp(-2πikn/N)) / N.

If a two-sided mask retains X[k] but sets X[N-k] to zero, the real part of the inverse has only one of those equal contributions:

real{X[k] exp(2πikn/N) / N} = 1/2 cos(2πkn/N).

That is the conditional half-gain identity. It applies to a non-DC component when exactly one member of its conjugate pair survives the old mask. It does not apply to DC, which is self-conjugate. For even N, Nyquist is also self-conjugate. It also does not apply when both pair members survive. At r = 0.75, for example, the old model has enough retained coordinates that higher bins can regain full amplitude once both sides of their pair fall below the old keep boundary.

A diagram for the frozen N=64, k=3, r=0.25, sort=false fixture: the old two-sided mask keeps X at k but the conjugate X at N minus k is discarded, so the real inverse has half gain; the merged real-spectrum path keeps the nonnegative coefficient and completes the pair structurally, recovering full gain. DC and Nyquist are marked as self-conjugate edge cases.

Figure 1. A non-DC cosine needs its conjugate pair for full amplitude: discarding one partner yields half gain in the shown old-mask fixture, while real-spectrum reconstruction makes the completion structural.

The NumPy real-transform documentation makes the representation boundary explicit: for real input, the negative-frequency terms are redundant conjugates. rfft returns the nonnegative part, including DC and, for an even length, Nyquist. irfft receives the original length and supplies the conjugate completion on inverse reconstruction. Passing that length matters because the nonredundant spectrum alone does not distinguish every odd/even output-length choice.

That does not mean any spectrum edit is safe merely because it uses rfft. It means the representation encodes the symmetry the old first-index mask could violate. A transform that changes individual complex coefficients still needs to respect the real-output contract. The merged implementation uses rfft(x), masks its nonnegative coordinates, and calls irfft(masked, n=N); the exact source is pinned in the squash-merge implementation.

The plot: gain and the changed cutoff

The gain plot is the central check because it makes two facts visible at once. The solid old line shows the one-half plateau created by keeping just one partner. The dashed merged line returns gain one for each retained non-DC cosine and zero for the rejected range. It also shows that the horizontal boundary moves: the denominator behind r changed.

Three vertically stacked recovered-gain plots for N=64 compare the old two-sided mask with the merged real-spectrum mask at r = 0.25, r = 0.50, and r = 0.75. The old series has a half-gain plateau wherever one conjugate partner remains; the merged series has full gain in its passband and zero in its stopband. Each panel labels the old and merged coordinate counts and their distinct cutoff transitions.

Figure 2. Recovered gain is the missing invariant: the merged real-spectrum path restores gain one in its passband, but the same r now denotes a different cutoff because it is a fraction of a different coordinate set.

At r = 0.25, the old model keeps 16 of 64 coordinates. Bins 1-15 recover gain one-half and bins 16-32 are rejected. The merged model keeps 8 of 33 coordinates; bins 1-7 recover gain one and bins 8-32 are rejected. At r = 0.50, the old model keeps 32 of 64 coordinates, yielding half gain for bins 1-31 and rejecting bin 32. The merged model keeps 16 of 33, yielding full gain for bins 1-15 and rejecting bins 16-32.

The r = 0.75 row is the useful counterexample to a simplified story. The old model keeps 48 of 64 coordinates. Bins 1-16 have one-half gain, while bins 17-32 have both pair members and therefore gain one. The merged model keeps 24 of 33, so bins 1-23 have gain one and bins 24-32 are rejected. At r = 1.0, both models retain the full frozen range and bins 1-32 have gain one. Across the computed rows, the greatest disagreement from these hand-derived gain transitions is approximately 2.220446049250313e-15, well within 1e-10.

This is the compatibility tradeoff. At N=64 and r=0.5, the old two-sided mask keeps 32 of 64 coordinates while the merged real-spectrum mask keeps 16 of 33; the same r therefore selects a different cutoff. The repair preserves amplitude for the selected non-DC bins. It does not promise that an existing numeric r selects the same frequency range. A caller for whom the cutoff is the contract should choose r from the intended boundary, inspect the resulting retained bins, and rerun signal-level tests after the representation change.

A cutoff migration test can make that distinction executable. Select the highest bin intended to pass and the first bin intended to fail, form the mask using the representation’s actual width, and assert gain one on the first witness and zero on the second. The pair catches a shifted boundary without conflating it with the half-amplitude defect. A passband assertion alone proves the gain of one selected bin; it does not prove that the selected bin set still matches a caller’s intended band.

The frozen model uses sort = false because it is a precise index-mask fixture. It is not a claim that sort=True is immune. Ranking coefficients by amplitude can also split a conjugate pair if the selection rule is not constructed around real-signal symmetry.

A bounded Aeon case study

The abstract invariant became concrete in Aeon issue #3710 and the maintainer-reviewed, merged PR #3711. The contribution gives this article a practical case study in how real-signal symmetry can shape a small repair and a much better test. The submitted PR revision was a10dcceff403a86f01805655bd86f37c15d6ab8a; its distinct squash merge was 490f9fa61ce0f38d7899ab61170eff994dfafa7d.

The focused pre-change regression capture had 14 outcomes: 3 failed and 11 passed. Those failures exposed the amplitude error that the cross-channel assertion could not see. The surrounding series-suite capture is narrower evidence than a repository-wide claim. In the matched unmodified capture, 209 passed and one skipped; after the submitted change, 217 passed and one skipped. With the same one-skipped state in both captures, the exact pass-count delta is eight. The count says that revision exercised more cases in that matched suite. It does not establish inclusion in a published release or consequences for systems that consume the transformer.

The resulting repair is small in code and specific in scope: it uses the nonredundant real spectrum and reconstructs with the original length. The transferable diagnostic is to name the signal invariant, construct a basis-function witness, and distinguish a gain correction from a changed cutoff.

The merged repair changes the representation

The merged diff replaces the ordinary fft with rfft and the real part of an ordinary inverse with irfft(..., n=original_length). It also documents r over the N/2 + 1 real-spectrum coordinates rather than over the old two-sided width. The complete source and tests are fixed to squash merge 490f9fa.

The explicit output length in irfft is part of the same care. An even length has a Nyquist coordinate, while an odd length has a different nonnegative spectrum shape. Supplying n=N tells the inverse which sequence length and endpoint convention to reconstruct. The frozen full-retention mixture has a maximum absolute reconstruction error of approximately 1.815214645262131e-14 in the old direct model and 1.4127587988355117e-14 in the merged direct model. Both are numerical round trips inside 1e-10, not claims of bitwise equality.

The length argument and mask width therefore belong in the same regression setup. The mask decides which nonnegative coordinates survive; n=N tells the inverse how those coordinates map back to samples. Testing only one choice leaves the other free to change the reconstructed endpoint or the selected band.

Test the invariant, not just agreement

The most useful outcome of the failure is a test design that distinguishes agreement from correctness. A passband oracle starts with a unit cosine whose bin is known to be retained, then asks for recovered gain one. In the frozen merged bin-3 fixture, the expected gain is exactly 1 and the observed gain is approximately 0.9999999999999998. A stopband oracle chooses a known rejected bin instead: for merged bin 8 at r = 0.25, the exact target is 0 and the observed result is approximately 2.033782021272921e-31. The tolerance for both is 1e-10.

Three test-oracle cards for the frozen N=64 merged model show passband gain at bin 3 and r=0.25, stopband rejection at bin 8 and r=0.25, and full-retention reconstruction at r=1. Each card states an exact target, an approximate observed value, PASS status, and tolerance 1e-10; the full-retention card also compares the old and merged numerical round-trip errors.

Figure 3. The three oracles separate amplitude, rejection, and reconstruction: each has an exact target, a numerical tolerance, and an observable failure that cross-channel agreement alone cannot supply.

The third oracle closes the representation loop. A deterministic mixture of DC, cosine, sine, and Nyquist components at r = 1 should reconstruct the input within the tolerance. It guards the complete transform rather than a single bin. Because no component is intentionally rejected, a failure implicates the complete transform-mask-inverse pipeline and can arise in selection or reconstruction. The mixture also exercises the self-conjugate endpoints that the bin-3 witness does not.

The merged test file adds amplitude cases, odd/even full-retention checks, and a high-frequency rejection check alongside the existing consistency coverage.

Limits

Operational checklist

  1. Write down the signal invariant before selecting a Fourier representation: gain, phase, energy, cutoff, or another quantity with a measurable oracle.
  2. For real input, inspect whether every retained non-DC coefficient has the conjugate structure needed by the inverse; prefer a real-spectrum API when that is the contract.
  3. Add a passband unit-cosine test with an explicit expected gain and tolerance, then add a stopband test for a bin that should be removed.
  4. Test full retention with DC, cosine, sine, and Nyquist content; pass the original length to real inverse transforms so odd/even reconstruction is unambiguous.
  5. For each caller-facing r, verify the actual frequency cutoff after the representation change.
  6. Keep cross-path consistency tests, but pair them with an independent oracle that would fail if every path shared the same wrong scale.

Primary sources

Newsletter subscriptions are not currently available.