velxio/test/test_circuit/test/transient_rc.test.js

65 lines
2.2 KiB
JavaScript
Raw Normal View History

feat: electrical simulation via ngspice-WASM (eecircuit-engine) Adds full SPICE-accurate electrical simulation to Velxio, behind a lazy- loaded ⚡ toolbar toggle. Arduino / ESP32 / RP2040 sketches now co-simulate with real analog behaviour: correct voltages on wires, real I–V curves on LEDs, working potentiometers, NTC thermistors read by analogRead(), PWM driving RC filters, transistors, op-amps, diodes, MOSFETs, etc. Engine: eecircuit-engine (ngspice compiled to WebAssembly). Main bundle stays at 2.4 MB; the 20 MB SPICE chunk only loads when the user activates electrical mode. Disabled at build time via VITE_ELECTRICAL_SIM=false. Frontend additions: - simulation/spice/: SpiceEngine wrapper + lazy entry, NetlistBuilder with UnionFind over wires, componentToSpice mapping (24 metadataIds incl. real part numbers: 2N2222, 2N3055, BC547, IRF540, 2N7000, 1N4148, 1N4007, 1N4733, LEDs, NTC, op-amp ideal), CircuitScheduler with debounced coalescing, AVRSpiceBridge for quasi-static co-simulation. - store/useElectricalStore: Zustand slice, feature-flag aware. - components/analog-ui/: ⚡ toolbar toggle + SVG voltage overlay. - components/components-instruments/: Voltmeter, Ammeter probes. - 62 tests (spice-*, netlist-builder, component-to-spice, instruments). Sandbox (test/test_circuit/): 47-test validation sandbox that proved the approach (hand-rolled MNA baseline + ngspice pipeline) before porting to the app. Kept as reference. Docs: docs/wiki/circuit-emulation-*.md (13 engineering pages covering architecture, solvers, components, AVR bridge, gotchas, performance, integration plan, API reference, appendix) + electrical-simulation- user-guide.md (end-user facing). Reference plan: test/test_circuit/plan/phase_8_velxio_implementation.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 19:11:54 +07:00
import { describe, it, expect } from 'vitest';
import { Circuit, Resistor, VoltageSource, Capacitor } from '../src/index.js';
describe('RC transient — charging curve', () => {
it('V(τ) ≈ 63.2% of V_source (R=10k, C=100µF, V=5V)', () => {
const R = 10000;
const C = 100e-6;
const V = 5;
const tau = R * C; // 1s
const circuit = new Circuit();
circuit.addComponent(new VoltageSource('V1', 'vcc', 'gnd', V));
circuit.addComponent(new Resistor('R1', 'vcc', 'out', R));
circuit.addComponent(new Capacitor('C1', 'out', 'gnd', C, 0));
const dt = 0.005; // 5 ms
const samples = circuit.runTransient(tau * 3, dt, 10);
// Find sample nearest t = τ
const target = samples.find(s => Math.abs(s.t - tau) < dt);
expect(target).toBeDefined();
const Vt = target.nodeVoltages.out;
const expected = V * (1 - 1 / Math.E);
expect(Vt).toBeGreaterThan(expected * 0.95);
expect(Vt).toBeLessThan(expected * 1.05);
// Final value ≈ V
const last = samples[samples.length - 1];
expect(last.nodeVoltages.out).toBeGreaterThan(V * 0.90);
});
it('5τ reaches > 99% of V_source', () => {
const R = 1000;
const C = 1e-6;
const V = 3.3;
const tau = R * C; // 1 ms
const circuit = new Circuit();
circuit.addComponent(new VoltageSource('V1', 'vcc', 'gnd', V));
circuit.addComponent(new Resistor('R1', 'vcc', 'out', R));
circuit.addComponent(new Capacitor('C1', 'out', 'gnd', C, 0));
circuit.runTransient(5 * tau, tau / 200);
expect(circuit.nodeVoltage('out')).toBeGreaterThan(V * 0.99);
});
});
describe('RC transient — discharge', () => {
it('capacitor pre-charged to 5V discharges through R', () => {
const R = 10000;
const C = 10e-6;
const tau = R * C; // 0.1 s
const circuit = new Circuit();
circuit.addComponent(new Resistor('R1', 'out', 'gnd', R));
circuit.addComponent(new Capacitor('C1', 'out', 'gnd', C, 5)); // pre-charged
const samples = circuit.runTransient(3 * tau, tau / 100, 10);
// V(τ) ≈ 5 · 1/e = 1.84 V
const atTau = samples.find(s => Math.abs(s.t - tau) < 0.002);
expect(atTau.nodeVoltages.out).toBeGreaterThan(1.5);
expect(atTau.nodeVoltages.out).toBeLessThan(2.2);
});
});