mirror of
https://git.code.sf.net/p/ngspice/ngspice
synced 2026-09-04 03:03:32 +02:00
Enhancement-241 fixed an amplitude normalization that divided by the ZERO-PADDED transform size instead of the number of input samples -- in the `fft` COMMAND (frontend/com_fft.c). The identical mistake survived in maths/cmaths/cmath4.c, the vector-expression function reached by `let F = fft(v)`: a separate implementation of the same computation. Found by continuing the oracle campaign that produced E-241, and located with E-241's own discriminator, the DC bin. One signal, 4001 samples padded to 4096, DC offset 2.0: fft s ; the COMMAND -> mag(s)[0] = 2.000000 correct let F = fft(s) ; the FUNCTION -> mag(F)[0] = 1.953613 = 2.0 * 4001/4096 X[0] is the sum of the samples, D*length for a DC offset D, so dividing by the padded N reads back D*length/N. As E-241 put it: a DC value cannot depend on how many samples were taken. This is a contradiction, not a matter of convention. cx_fft holds TWO complete implementations -- one for complex input, one for real -- and each has an FFTW branch and a Green's radix-2 branch. In BOTH, the FFTW branch already used the input length while Green's used the padded size: real branch FFTW: scale = ((double)length)/2.0 Green: ((double)N)/2 <- wrong complex branch FFTW: scale = (double) fpts Green: (double) N <- wrong the correct version sitting a few lines from the wrong one inside the same function -- the same shape as `avg` disagreeing with `integ` in E-302. HAVE_LIBFFTW3 is undefined in this build, so Green's is the live path and the defect was reachable. oracle before after closed form real-input, DC bin 1.953613 2.000000 2.0 real-input, ifft(fft(x)) 2.3e-02 1.1e-16 0 complex-input, bin 0 0.9766780 0.9998683 0.9998683 The round trip is an INDEPENDENT confirmation: nothing here touches ifft, so its going from 2.3% error to machine precision is evidence from a direction the fix did not aim at -- the pair only inverts when the forward normalization is right. cx_ifft was audited and deliberately left alone for that reason. Every caller of the Green's radix-2 kernel was audited, not only the one that failed: com_fft.c's fft (x2) and spec/PSD (x2) are correct from E-241; cx_ifft is correct; trannoise/1-f-code.c cannot pad at all, since n_pts is grown to 2^n_exp by construction; and fft/ifft are the only transform functions in the expression table, so there is no spec twin to miss. Verification: examples/fftexpr_examples/verify_fftexpr.py -- 6 checks under both solvers, all against closed form (DC bin on both paths, round trip padded and unpadded, complex-input bin 0 against the analytic mean of an RC response). It scores 3/6 on the pre-fix binary, so it is a real regression guard. E-241's own suite (fftnorm_examples) and ifftreal_examples pass unchanged. Full sweep 241/241 OK. Co-Authored-By: Claude Opus 4.8 <[email protected]>