65 lines
2.5 KiB
TypeScript
65 lines
2.5 KiB
TypeScript
|
|
/**
|
|||
|
|
* circuitVerifier — the "cannot emulate" path.
|
|||
|
|
*
|
|||
|
|
* When ngspice cannot find a stable operating point it returns NaN/Infinity
|
|||
|
|
* for the offending branch current (the classic case: an LED with no series
|
|||
|
|
* resistor — a near-short across the supply). The verifier must surface that
|
|||
|
|
* as a BLOCKING `unstable-solve` fault, NOT silently treat it as 0 A and wave
|
|||
|
|
* the circuit through (the production bug behind the 9V→LED report).
|
|||
|
|
*
|
|||
|
|
* The real Node ngspice build always converges this circuit, so we mock the
|
|||
|
|
* solver to deterministically return non-finite currents.
|
|||
|
|
*/
|
|||
|
|
import { describe, it, expect, vi } from 'vitest';
|
|||
|
|
|
|||
|
|
vi.mock('../simulation/spice/runNetlist', () => ({
|
|||
|
|
runNetlist: vi.fn(async () => ({
|
|||
|
|
variableNames: ['v(n0)', 'v(n1)', 'i(v_bat)', 'i(v_led1_sense)'],
|
|||
|
|
dcValue: (name: string) => {
|
|||
|
|
switch (name) {
|
|||
|
|
case 'v(n0)':
|
|||
|
|
return 1.05;
|
|||
|
|
case 'v(n1)':
|
|||
|
|
return -1.05;
|
|||
|
|
case 'i(v_bat)':
|
|||
|
|
return NaN; // source current — no stable solution
|
|||
|
|
case 'i(v_led1_sense)':
|
|||
|
|
return Infinity; // LED forward current — no stable solution
|
|||
|
|
default:
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
vec: () => [],
|
|||
|
|
vAtLast: () => 0,
|
|||
|
|
findVar: () => -1,
|
|||
|
|
})),
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
import { verifyCircuit } from '../simulation/verify/circuitVerifier';
|
|||
|
|
import type { BuildNetlistInput } from '../simulation/spice/types';
|
|||
|
|
|
|||
|
|
describe('verifyCircuit — non-finite solve → unstable-solve fault', () => {
|
|||
|
|
it('blocks a 9V battery wired straight to an LED (no resistor)', async () => {
|
|||
|
|
const input: BuildNetlistInput = {
|
|||
|
|
components: [
|
|||
|
|
{ id: 'bat', metadataId: 'battery-9v', properties: {} },
|
|||
|
|
{ id: 'led1', metadataId: 'led', properties: { color: 'red' } },
|
|||
|
|
],
|
|||
|
|
wires: [
|
|||
|
|
{ id: 'w1', start: { componentId: 'bat', pinName: '+' }, end: { componentId: 'led1', pinName: 'A' } },
|
|||
|
|
{ id: 'w2', start: { componentId: 'led1', pinName: 'C' }, end: { componentId: 'bat', pinName: '−' } },
|
|||
|
|
],
|
|||
|
|
boards: [],
|
|||
|
|
analysis: { kind: 'op' },
|
|||
|
|
};
|
|||
|
|
const result = await verifyCircuit(input);
|
|||
|
|
const codes = result.errors.map((e) => e.code);
|
|||
|
|
// Both the source and the LED report the unstable solve — either is enough
|
|||
|
|
// to block, and both must be `unstable-solve` (not silently dropped to 0 A).
|
|||
|
|
expect(codes, JSON.stringify(result.errors)).toContain('unstable-solve');
|
|||
|
|
expect(result.errors.length).toBeGreaterThan(0);
|
|||
|
|
const ledFault = result.errors.find((e) => e.componentId === 'led1');
|
|||
|
|
expect(ledFault?.code).toBe('unstable-solve');
|
|||
|
|
});
|
|||
|
|
});
|