to be defined in sndprint.c.
Also, strict_errorhandling needs to be set in the paranoia_parallel
cider examples to make ngspice exit for a non-cider build.
The fold is exact on paper, but floor() on a ratio of doubles can leave time a few ulp
PAST the last point: measured at 7.5e-23 s after about 1900 periods of a 1 ns clock.
The search below then matches no segment at all, drops out of the loop without assigning,
and the source silently keeps value's initialiser of 0 V for that one timepoint.
On a clock that is a dropout to zero in the middle of an edge -- a glitch a downstream
flip-flop reads as two extra edges, which is far worse than the tiny timing error it comes
from. Clamp so the last segment always matches.
precision deviations) than the vector contents, the graph will
not be plotted in this area.
Therefore extend the limits a bit (0.5%).
Gnuplot: Use snprintf instead of sprintf. Reduce limits extension to 0.005.
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 <noreply@anthropic.com>
Found while sweeping the file-parser code models (d_state/d_source/xfer/
file_source); confirmed with AddressSanitizer. Unlike the earlier code-model
finds (E-246/247/250, reads or bounded errors), these are out-of-bounds WRITES --
silent heap corruption.
1. xfer (analog/xfer/cfunc.mod, read_file): reads a transfer-function file (a
Touchstone-style `#` option line then data). It sscanf's up to 9 values per
line and a state machine stores every value (freq/real/imag triples), so a
line with more than one record stores more than 3. The allocation check
reserved only 3 (`if (i + 3 > size)`, ALLOC=1024), so a multi-record line
wrote past the buffer at the 1024-double boundary (ASan: heap-buffer-overflow
WRITE, cm_xfer). Fix: reserve the sscanf maximum of 9 (`if (i + 9 > size)`).
2. file_source (analog/file_source/cfunc.mod): stores one record per line -- a
timepoint plus `size` channel values = stepsize (size+1) doubles -- but
reserved only `size` (`vecallocated - size`), one short. At the reallocation
boundary the final channel wrote one double past the end (ASan:
heap-buffer-overflow WRITE, cm_filesource). Fix: reserve a full record
(`- stepsize`).
Both are heap OOB writes reachable from a valid-syntax netlist with a crafted
data file; the release build corrupts adjacent heap silently rather than always
crashing (UB either way). The sibling parsers were checked: d_source validates
its per-line token count against the declared width, and d_state's fixed line
buffer is fgets-bounded -- no analogous overrun.
Code-model-only change: analog.cm regenerated via cmpp and redeployed under
bin/*/codemodels/; the ngspice binary is unchanged. Verify
(examples/filefix_examples, 4 checks x2 solvers): valid transfer-function and
file_source files simulate; an xfer multi-record file and a boundary-crossing
file_source file run without overrunning. Both reproduced under ASan and shown
fixed. Full regression 208/208.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First numerical-correctness (wrong-number, not crash) find of a correctness-audit
campaign that checked ngspice analyses against analytic and numpy oracles.
com_fft.c has two paths: FFTW3 (exact length-point transform, scale=length/2,
correct) and a Green's radix-2 FFT (no FFTW3) that zero-pads the `length` input
samples up to the next power of two N. The non-FFTW path normalized the
single-sided amplitude by the PADDED size (scale=N/2) instead of the true sample
count (length/2), so every FFT whose sample count is not a power of two reported
amplitudes too small by length/N -- up to 2x. A .tran essentially never yields
exactly 2^k points, so this bit the common case silently. The DC bin is the clean
discriminator (always exactly bin 0, no scalloping): a 2.0 V DC offset read back
as 2.0*length/N, e.g. 1.0009 for a 1025-sample record padded to 2048. spec had
the same error in its power normalization (intres = N*N instead of length*length;
the FFTW path already used length*length).
Fix: scale = length/2 and intres = length*length in the non-FFTW path, matching
FFTW. The frequency axis was already padding-aware, so only the magnitude scale
needed changing; power-of-2 records (length == N) and FFTW-linked builds are
unchanged. Validated against numpy: after the fix ngspice's fft matches
numpy.fft.rfft of the same zero-padded record (normalized by length) bin-by-bin
to ~1e-9. fft/spec amplitudes now change for non-power-of-2 records (they become
correct). Regression 199/199.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
similar to 'gnuplot' (Meisam Bahadori and Claude 4.8).
Commit contains Enhancements 94, 95, 98, 99, 182, 183.
Add a plotting flag 'boxesplot', in addition to 'combplot'
This fixes a bug in gnuplot and pyplot,
now offering both distinct combplot and boxesplot. (H. Vogt).
matplotlib -- a Python counterpart to ngspice's 'gnuplot' command. Same
syntax (pyplot <file> <plot expressions...>): the first word is the output file name only when it is NOT a plot expression, the rest are ordinary plot expressions handled by plotit().
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add CKTtopologyReduce() (cktsetup.c) to remove degree-1 dangling
capacitors/resistors -- e.g. dead-end opamp compensation caps -- that
otherwise cause spurious 'Timestep too small' aborts. Node degree is
counted generically over all device types; the floating node is pinned
with a unit diagonal. Disable with 'set no_topo_reduce'.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>