velxio/frontend/src/__tests__/spice-ac.test.ts

80 lines
2.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, it, expect } from 'vitest';
import { runNetlist, type ComplexNumber } from './helpers/testSolver';
function magDB(c: ComplexNumber): number {
return 20 * Math.log10(Math.sqrt(c.real * c.real + c.img * c.img));
}
function phaseDeg(c: ComplexNumber): number {
return (Math.atan2(c.img, c.real) * 180) / Math.PI;
}
function re(v: number | ComplexNumber): number {
return typeof v === 'number' ? v : v.real;
}
describe('ngspice — AC (small-signal) analysis', () => {
it('RC low-pass: −3 dB at f_c = 1/(2πRC)', { timeout: 30_000 }, async () => {
const netlist = `AC sweep RC low-pass
V1 in 0 AC 1
R1 in out 1k
C1 out 0 159.155n
.ac dec 20 10 1Meg
.end`;
const { vec } = await runNetlist(netlist);
const freq = vec('frequency');
const vout = vec('v(out)') as ComplexNumber[];
const mag = vout.map(magDB);
let f3db: number | null = null;
for (let i = 0; i < freq.length - 1; i++) {
if (mag[i] > -3 && mag[i + 1] <= -3) {
const a = mag[i];
const b = mag[i + 1];
const fa = re(freq[i]);
const fb = re(freq[i + 1]);
f3db = fa + ((-3 - a) / (b - a)) * (fb - fa);
break;
}
}
expect(f3db).not.toBeNull();
expect(f3db!).toBeGreaterThan(900);
expect(f3db!).toBeLessThan(1100);
expect(mag[0]).toBeGreaterThan(-0.5);
// Phase at cutoff ≈ −45°
let fcIdx = 0;
for (let i = 0; i < freq.length; i++) {
if (re(freq[i]) >= 1000) {
fcIdx = i;
break;
}
}
const ph = phaseDeg(vout[fcIdx]);
expect(ph).toBeLessThan(-40);
expect(ph).toBeGreaterThan(-50);
});
it('Parallel-tank RLC bandpass: peak at f₀ = 1/(2π√LC)', { timeout: 30_000 }, async () => {
const netlist = `RLC bandpass
V1 in 0 AC 1
R1 in out 1k
L1 out 0 1m
C1 out 0 1u
.ac dec 30 10 1Meg
.end`;
const { vec } = await runNetlist(netlist);
const freq = vec('frequency');
const vout = vec('v(out)') as ComplexNumber[];
const mag = vout.map(magDB);
let peakIdx = 0;
for (let i = 1; i < mag.length; i++) if (mag[i] > mag[peakIdx]) peakIdx = i;
const fpeak = re(freq[peakIdx]);
const expectedF0 = 1 / (2 * Math.PI * Math.sqrt(1e-3 * 1e-6));
expect(fpeak).toBeGreaterThan(expectedF0 * 0.8);
expect(fpeak).toBeLessThan(expectedF0 * 1.2);
expect(mag[peakIdx]).toBeGreaterThan(-1);
expect(mag[peakIdx]).toBeLessThan(0.01);
});
});