Skip to content

Commit c4639dc

Browse files
Staacksclaude
andcommitted
Specify the Fourier modules dft, ifft, idft and normalization (file format 1.21)
fft only guarantees identical results on both platforms for a power-of-two number of samples, has no inverse and no normalization. Three modules and one attribute, specified ahead of the implementations: dft and idft are the exact discrete Fourier transform and its inverse for ANY length (N samples in, N values out on every platform, any exact algorithm), ifft is the inverse of fft under fft's power-of-two guarantee, and normalization on all four names which direction carries the 1/N factor - backward (default: unscaled forward, 1/N inverse, the NumPy/SciPy/MATLAB/FFTW convention, so fft is unchanged and ifft after fft returns the input), forward, ortho, none. Same slots and kernel as fft, stated once under "Fourier transforms" on the advanced-math page and in rule fourier-transform-conventions with the per-platform state; the attribute is a YAML anchor on dft aliased on the other three, agreement: planned. Found on the way and recorded open: fft-single-sample-input (Android writes nothing for one sample, iOS writes it back; one is a power of two). The permanent fft-non-power-of-two-input entry now covers ifft and points at dft/idft instead of a planned module. Corpus: golden vectors for dft, ifft and idft (odd lengths, length one, empty input, every normalization value, round trips of the dft spectra) and four normalization cases for fft, computed with the extended reference implementation; generated/analysis-fourier exercises the four modules with every attribute value, invalid/fourier-unknown-normalization pins the enum rule; manifest regenerated (which also drops bluetooth@nameRegex from the nameRegex fixture's additions, as the shipped collection now uses it). The vectors declare 1.21 and fail on both apps until the modules exist. The implementation items go to the Android, iOS and editor handoff files. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 1cccb2c commit c4639dc

67 files changed

Lines changed: 2494 additions & 34 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎corpus/analysis/README.md‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,12 @@ are left out:
7272
- The static-buffer reset on the user's clear-data action (ruled with
7373
the write-once lifecycle) — the runner has no user-clear step; the
7474
write-once skip itself is pinned by execution/static-write-once.
75-
- `fft` with non-power-of-two input (`fft-non-power-of-two-input`,
76-
permanent) — power-of-two lengths only. The fft and crosscorrelation
77-
tolerances are widened to cover Android's float32 native path, and
78-
gausssmooth's for iOS's single-precision vImage path.
75+
- `fft` and `ifft` with non-power-of-two input (`fft-non-power-of-two-input`,
76+
permanent) — power-of-two lengths of at least two only (a single sample
77+
is `fft-single-sample-input`, open). `dft` and `idft` have no such
78+
restriction and are pinned at odd lengths too. The Fourier and
79+
crosscorrelation tolerances are widened to cover Android's float32
80+
native path, and gausssmooth's for iOS's single-precision vImage path.
7981
- The `info` module — every output is live device state; no vector
8082
exists for it.
8183
- `periodicity` without an explicit min/max search range (the adaptive

‎corpus/analysis/cases/dft.yml‎

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# Golden vectors: dft (file format 1.21). The exact forward DFT for ANY
2+
# length: N samples in, N bins out, kernel e^(-2 pi i k n / N), unscaled
3+
# under the default normalization. Odd lengths are the point of the module
4+
# and are pinned here; the power-of-two case must equal fft. A single-
5+
# precision implementation is admitted by the tolerance. See
6+
# spec/rules.yml fourier-transform-conventions.
7+
module: dft
8+
cases:
9+
- name: length-one
10+
description: >-
11+
N=1: the transform of a single sample is the identity.
12+
expected_source: reference
13+
buffers:
14+
re: {size: 1, init: [3.5]}
15+
im: {size: 1, init: [-1.25]}
16+
outre: {size: 1}
17+
outim: {size: 1}
18+
inputs:
19+
- {as: re, buffer: re}
20+
- {as: im, buffer: im}
21+
outputs:
22+
- {as: re, buffer: outre}
23+
- {as: im, buffer: outim}
24+
tolerance: {rel: 0.0001, abs: 1e-05}
25+
expect:
26+
outre: [3.5]
27+
outim: [-1.25]
28+
- name: length-three
29+
description: >-
30+
N=3 complex input - the smallest length fft does not guarantee.
31+
expected_source: reference
32+
buffers:
33+
re: {size: 3, init: [1.0, -2.0, 0.5]}
34+
im: {size: 3, init: [0.0, 1.0, -1.0]}
35+
outre: {size: 3}
36+
outim: {size: 3}
37+
inputs:
38+
- {as: re, buffer: re}
39+
- {as: im, buffer: im}
40+
outputs:
41+
- {as: re, buffer: outre}
42+
- {as: im, buffer: outim}
43+
tolerance: {rel: 0.0001, abs: 1e-05}
44+
expect:
45+
outre: [-0.5, 3.4820508075688767, 0.017949192431123695]
46+
outim: [0.0, 2.1650635094610973, -2.1650635094610977]
47+
- name: length-five
48+
description: >-
49+
N=5 complex input.
50+
expected_source: reference
51+
buffers:
52+
re: {size: 5, init: [2.0, 0.0, -1.0, 3.0, 1.0]}
53+
im: {size: 5, init: [0.0, 0.5, 0.0, -0.5, 1.0]}
54+
outre: {size: 5}
55+
outim: {size: 5}
56+
inputs:
57+
- {as: re, buffer: re}
58+
- {as: im, buffer: im}
59+
outputs:
60+
- {as: re, buffer: outre}
61+
- {as: im, buffer: outim}
62+
tolerance: {rel: 0.0001, abs: 1e-05}
63+
expect:
64+
outre: [5.0, 0.5093473736237117, 1.0395961100811357, 2.57843787866876, 0.872618637626394]
65+
outim: [1.0, 4.170231514214941, -4.584474801638036, 1.8484068241382463, -2.4341635367151513]
66+
- name: length-seven-real-ramp
67+
description: >-
68+
N=7 real ramp 0..6, im absent: bin 0 is the sum 21, the other bins
69+
are the mirrored pairs of a real signal.
70+
expected_source: reference
71+
buffers:
72+
re: {size: 7, init: [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0]}
73+
outre: {size: 7}
74+
outim: {size: 7}
75+
inputs:
76+
- {as: re, buffer: re}
77+
outputs:
78+
- {as: re, buffer: outre}
79+
- {as: im, buffer: outim}
80+
tolerance: {rel: 0.0001, abs: 1e-05}
81+
expect:
82+
outre: [21.0, -3.5000000000000018, -3.500000000000001, -3.499999999999993, -3.4999999999999996, -3.4999999999999707, -3.499999999999972]
83+
outim: [0.0, 7.267824888003179, 2.7911568610884125, 0.7988521603655276, -0.7988521603655272, -2.7911568610884188, -7.267824888003183]
84+
- name: power-of-two-equals-fft
85+
description: >-
86+
N=8 cosine at bin 2: identical to fft/real-cosine.
87+
expected_source: reference
88+
buffers:
89+
re: {size: 8, init: [1.0, 0.0, -1.0, 0.0, 1.0, 0.0, -1.0, 0.0]}
90+
outre: {size: 8}
91+
outim: {size: 8}
92+
inputs:
93+
- {as: re, buffer: re}
94+
outputs:
95+
- {as: re, buffer: outre}
96+
- {as: im, buffer: outim}
97+
tolerance: {rel: 0.0001, abs: 1e-05}
98+
expect:
99+
outre: [0.0, 7.267471740958731e-17, 4.0, -3.2904645469127765e-16, 0.0, 2.3617750313732183e-15, 4.0, 9.345669101455923e-16]
100+
outim: [0.0, -1.1102230246251565e-16, 7.347880794884119e-16, -4.440892098500626e-16, -4.898587196589412e-16, -6.661338147750939e-16, 2.204364238465236e-15, -8.881784197001252e-16]
101+
- name: normalization-forward
102+
description: >-
103+
N=5 complex input with normalization=forward: every bin divided
104+
by 5.
105+
expected_source: reference
106+
attributes: {normalization: forward}
107+
buffers:
108+
re: {size: 5, init: [2.0, 0.0, -1.0, 3.0, 1.0]}
109+
im: {size: 5, init: [0.0, 0.5, 0.0, -0.5, 1.0]}
110+
outre: {size: 5}
111+
outim: {size: 5}
112+
inputs:
113+
- {as: re, buffer: re}
114+
- {as: im, buffer: im}
115+
outputs:
116+
- {as: re, buffer: outre}
117+
- {as: im, buffer: outim}
118+
tolerance: {rel: 0.0001, abs: 1e-05}
119+
expect:
120+
outre: [1.0, 0.10186947472474235, 0.20791922201622715, 0.515687575733752, 0.1745237275252788]
121+
outim: [0.2, 0.8340463028429883, -0.9168949603276073, 0.3696813648276493, -0.4868327073430303]
122+
- name: normalization-ortho
123+
description: >-
124+
N=3 complex input with normalization=ortho: every bin divided by
125+
sqrt(3).
126+
expected_source: reference
127+
attributes: {normalization: ortho}
128+
buffers:
129+
re: {size: 3, init: [1.0, -2.0, 0.5]}
130+
im: {size: 3, init: [0.0, 1.0, -1.0]}
131+
outre: {size: 3}
132+
outim: {size: 3}
133+
inputs:
134+
- {as: re, buffer: re}
135+
- {as: im, buffer: im}
136+
outputs:
137+
- {as: re, buffer: outre}
138+
- {as: im, buffer: outim}
139+
tolerance: {rel: 0.0001, abs: 1e-05}
140+
expect:
141+
outre: [-0.2886751345948129, 2.010362971081845, 0.01036297108184566]
142+
outim: [0.0, 1.2500000000000004, -1.2500000000000009]
143+
- name: normalization-none
144+
description: >-
145+
N=3 complex input with normalization=none: identical to backward
146+
for a forward module.
147+
expected_source: reference
148+
attributes: {normalization: none}
149+
buffers:
150+
re: {size: 3, init: [1.0, -2.0, 0.5]}
151+
im: {size: 3, init: [0.0, 1.0, -1.0]}
152+
outre: {size: 3}
153+
outim: {size: 3}
154+
inputs:
155+
- {as: re, buffer: re}
156+
- {as: im, buffer: im}
157+
outputs:
158+
- {as: re, buffer: outre}
159+
- {as: im, buffer: outim}
160+
tolerance: {rel: 0.0001, abs: 1e-05}
161+
expect:
162+
outre: [-0.5, 3.4820508075688767, 0.017949192431123695]
163+
outim: [0.0, 2.1650635094610973, -2.1650635094610977]
164+
- name: empty-input
165+
description: >-
166+
An empty input gives empty outputs.
167+
expected_source: reference
168+
buffers:
169+
re: {size: 1}
170+
outre: {size: 1}
171+
outim: {size: 1}
172+
inputs:
173+
- {as: re, buffer: re}
174+
outputs:
175+
- {as: re, buffer: outre}
176+
- {as: im, buffer: outim}
177+
tolerance: {rel: 0.0001, abs: 1e-05}
178+
expect:
179+
outre: []
180+
outim: []

‎corpus/analysis/cases/fft.yml‎

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
# Golden vectors: fft. Unnormalized forward DFT, full complex
2-
# spectrum. Power-of-two lengths ONLY - other lengths are platform-
3-
# defined (fft-non-power-of-two-input). Android's native path is
4-
# float32, hence the tolerance.
1+
# Golden vectors: fft. Forward DFT, kernel e^(-2 pi i k n / N), full
2+
# complex spectrum; the normalization attribute (file format 1.21) sets the
3+
# factor, backward (unscaled) by default. Power-of-two lengths of at least
4+
# two ONLY - other lengths are platform-defined (fft-non-power-of-two-
5+
# input), a single sample is fft-single-sample-input. Android's native
6+
# path is float32, hence the tolerance. See spec/rules.yml
7+
# fourier-transform-conventions.
58
module: fft
69
cases:
710
- name: real-cosine
@@ -40,3 +43,85 @@ cases:
4043
expect:
4144
outre: [10.0, -2.000000000000001, -2.0, -1.9999999999999982]
4245
outim: [0.0, 1.9999999999999996, 1.9999999999999991, -2.000000000000001]
46+
- name: normalization-forward
47+
description: >-
48+
N=8 cosine with normalization=forward: the two mirrored bins hold
49+
half the amplitude each, 0.5.
50+
expected_source: reference
51+
attributes: {normalization: forward}
52+
buffers:
53+
re: {size: 8, init: [1.0, 0.0, -1.0, 0.0, 1.0, 0.0, -1.0, 0.0]}
54+
outre: {size: 8}
55+
outim: {size: 8}
56+
inputs:
57+
- {as: re, buffer: re}
58+
outputs:
59+
- {as: re, buffer: outre}
60+
- {as: im, buffer: outim}
61+
tolerance: {rel: 0.0001, abs: 1e-05}
62+
expect:
63+
outre: [0.0, 9.084339676198414e-18, 0.5, -4.1130806836409706e-17, 0.0, 2.952218789216523e-16, 0.5, 1.1682086376819903e-16]
64+
outim: [0.0, -1.3877787807814457e-17, 9.184850993605148e-17, -5.551115123125783e-17, -6.123233995736765e-17, -8.326672684688674e-17, 2.755455298081545e-16, -1.1102230246251565e-16]
65+
- name: normalization-ortho
66+
description: >-
67+
N=4 complex input with normalization=ortho: the backward result
68+
divided by sqrt(4) = 2.
69+
expected_source: reference
70+
attributes: {normalization: ortho}
71+
buffers:
72+
re: {size: 4, init: [1.0, 2.0, 3.0, 4.0]}
73+
im: {size: 4, init: [0.5, -0.5, 0.5, -0.5]}
74+
outre: {size: 4}
75+
outim: {size: 4}
76+
inputs:
77+
- {as: re, buffer: re}
78+
- {as: im, buffer: im}
79+
outputs:
80+
- {as: re, buffer: outre}
81+
- {as: im, buffer: outim}
82+
tolerance: {rel: 0.0001, abs: 1e-05}
83+
expect:
84+
outre: [5.0, -1.0000000000000004, -1.0, -0.9999999999999991]
85+
outim: [0.0, 0.9999999999999998, 0.9999999999999996, -1.0000000000000004]
86+
- name: normalization-none
87+
description: >-
88+
N=4 complex input with normalization=none: for the forward
89+
transform identical to backward.
90+
expected_source: reference
91+
attributes: {normalization: none}
92+
buffers:
93+
re: {size: 4, init: [1.0, 2.0, 3.0, 4.0]}
94+
im: {size: 4, init: [0.5, -0.5, 0.5, -0.5]}
95+
outre: {size: 4}
96+
outim: {size: 4}
97+
inputs:
98+
- {as: re, buffer: re}
99+
- {as: im, buffer: im}
100+
outputs:
101+
- {as: re, buffer: outre}
102+
- {as: im, buffer: outim}
103+
tolerance: {rel: 0.0001, abs: 1e-05}
104+
expect:
105+
outre: [10.0, -2.000000000000001, -2.0, -1.9999999999999982]
106+
outim: [0.0, 1.9999999999999996, 1.9999999999999991, -2.000000000000001]
107+
- name: normalization-backward-explicit
108+
description: >-
109+
N=4 complex input with normalization=backward written out:
110+
identical to the default (and to the pre-1.21 module).
111+
expected_source: reference
112+
attributes: {normalization: backward}
113+
buffers:
114+
re: {size: 4, init: [1.0, 2.0, 3.0, 4.0]}
115+
im: {size: 4, init: [0.5, -0.5, 0.5, -0.5]}
116+
outre: {size: 4}
117+
outim: {size: 4}
118+
inputs:
119+
- {as: re, buffer: re}
120+
- {as: im, buffer: im}
121+
outputs:
122+
- {as: re, buffer: outre}
123+
- {as: im, buffer: outim}
124+
tolerance: {rel: 0.0001, abs: 1e-05}
125+
expect:
126+
outre: [10.0, -2.000000000000001, -2.0, -1.9999999999999982]
127+
outim: [0.0, 1.9999999999999996, 1.9999999999999991, -2.000000000000001]

0 commit comments

Comments
 (0)