this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

WIP: plan

+176
+176
PLAN.md
··· 1 + # Plan: Shunt Lumped Elements (caps branch) 2 + 3 + ## Goal 4 + 5 + Extend the simulator to support arbitrary sequences of transmission line segments and 6 + shunt lumped elements (R, L, C, short, open). The topology is a flat ordered list of 7 + blocks — not a strict alternating pattern. Any arrangement is valid: 8 + 9 + ``` 10 + [TL, TL, TL, R, C, TL, R, TL, C, TL, TL] 11 + ``` 12 + 13 + Multiple T-lines in a row share a pure impedance-mismatch boundary (no shunt element). 14 + Multiple shunt elements in a row share the same electrical node (their admittances add). 15 + The sequence ends with a terminal condition (R / C / L / open / short) replacing the 16 + current single `RL` parameter. 17 + 18 + Example circuit this covers (KiCad netlist pattern): 19 + 20 + ```spice 21 + Rs1 S A 40 22 + OT1 A GND B GND __T1 23 + C1 B GND 2p 24 + OT2 B GND C GND __T2 25 + ``` 26 + 27 + Rendered as a block sequence: `[TL(T1), C(2p), TL(T2)]` with `Rg=40Ω`, terminal=open. 28 + 29 + ## Physics approach 30 + 31 + Use the **Method of Characteristics with delay lines**. Each lossless T-line segment 32 + propagates V⁺ and V⁻ waves exactly (no numerical error in propagation). Reactive 33 + junction nodes are integrated one step at a time using the trapezoidal rule. 34 + 35 + This is a strict extension of the current model: with only resistive elements, the 36 + simulation degenerates exactly to the existing bounce-series result. 37 + 38 + ### Time step 39 + 40 + Choose Δt = min segment delay / `oversample` (default oversample = 4, user-adjustable). 41 + Each T-line segment must be an integer number of time steps long (round len/c to nearest 42 + integer ≥ 1 step, then adjust effective len to match). 43 + 44 + ### Propagation (per time step, per segment) 45 + 46 + V⁺ and V⁻ are stored as ring buffers (one entry per time step of delay). 47 + Advancing one time step = rotating the ring buffer index. No arithmetic needed. 48 + 49 + ### Junction boundary conditions 50 + 51 + At each internal node connecting segment i (left) and segment i+1 (right), with a shunt 52 + element to ground: 53 + 54 + Let V = node voltage, Z_L = left segment Z0, Z_R = right segment Z0. 55 + Thevenin from left: V_th = V⁺_L_arriving * 2, Z_th = Z_L ∥ Z_R. 56 + 57 + **Shunt R:** algebraic. V = (V⁺_L·2 + V⁺_R·2 - 0) · (Z_L∥Z_R∥R_sh) / ... simplifies 58 + to the standard multi-port resistive formula. Identical to current Γ formula 59 + when no shunt R is present (R_sh = ∞). 60 + 61 + **Shunt C:** ODE dV/dt = (V_drive - V) / τ, τ = C · Z_eq, Z_eq = Z_L ∥ Z_R. 62 + Trapezoidal step: 63 + V[n] = (V[n-1] + Δt/τ · V_drive[n]) / (1 + Δt/τ) 64 + (V_drive is the open-circuit node voltage from the incoming waves.) 65 + 66 + **Shunt L:** dual of C. Current i_L is the state variable. 67 + i_L[n] = (i_L[n-1] + Δt/Z_eq · V_drive[n]) / (1 + Δt·Z_eq/L) -- TBD, 68 + derive carefully at implementation time. 69 + 70 + **Short / Open:** degenerate cases of R=0 / R=∞. Handle as-is (Γ = -1 / +1). 71 + 72 + After solving V at the node, the outgoing waves are: 73 + ``` 74 + V⁻_L_outgoing = V - V⁺_L_arriving (leftward, into segment i) 75 + V⁺_R_outgoing = V - V⁺_R_arriving (rightward, into segment i+1) 76 + ``` 77 + 78 + ### Source and load terminals 79 + 80 + Source: same Thevenin model as before (Vg, Rg → V1, gS). Handled as a left terminal 81 + with no right segment (RL is the "shunt" to the right). 82 + 83 + Load: same RL model as before. For reactive loads (C, L to ground at the far end), 84 + use the same ODE formulas as internal junction — just no right segment to feed. 85 + 86 + ## Model data structure changes 87 + 88 + ```js 89 + // Current 90 + model.segments = [{ Z0 }] // N entries 91 + model.Rg, model.RL // scalars 92 + 93 + // New 94 + model.blocks = [ 95 + { type: 'tl', Z0, len }, // transmission line segment 96 + { type: 'R', value }, // shunt resistor to ground (ohms) 97 + { type: 'C', value }, // shunt capacitor to ground (farads) 98 + { type: 'L', value }, // shunt inductor to ground (henries) 99 + { type: 'short' }, // shunt short to ground (Γ = -1) 100 + // 'open' not needed mid-circuit; it's a no-op (infinite impedance = nothing) 101 + ] 102 + model.terminal = { type, value } // replaces model.RL; type: 'R'|'C'|'L'|'open'|'short' 103 + model.Rg // unchanged (series source resistance) 104 + model.Vg // unchanged 105 + ``` 106 + 107 + The solver flattens `model.blocks` into an alternating node/segment graph: 108 + 109 + - Consecutive shunt blocks (`R`, `C`, `L`, `short`) with no `tl` between them collapse 110 + onto the same electrical node; their admittances are summed before solving the 111 + boundary condition. 112 + - Consecutive `tl` blocks separated by no shunt block share a pure impedance-mismatch 113 + boundary (implicitly no shunt element). 114 + 115 + ## New physics.js API 116 + 117 + Replace `buildBounceSeries` with: 118 + 119 + ```js 120 + // Returns { tGrid, vPlus, vMinus, vNode } 121 + // tGrid: Float64Array of length nSteps (time values in τ_d units) 122 + // vPlus: [nSegs][nSteps × nStepsPerSeg] — V⁺ at each spatial sample, each time 123 + // vMinus: same for V⁻ 124 + // vNode: [nNodes][nSteps] — voltage at each junction node over time 125 + // 126 + // nNodes = nSegs + 1 (source node, internal nodes, load node) 127 + function simulateTimeDomain(model, opts) 128 + // opts: { tEnd, oversample, nSpatialSamples } 129 + ``` 130 + 131 + Keep existing helper functions (`riseShape`, `waveVoltageAt`, etc.) — they're still 132 + used for the source waveform shape. 133 + 134 + Add a query function replacing `totalVoltageAt`: 135 + 136 + ```js 137 + // Returns V(z, tIdx) from the simulation grid (linear interpolation in z). 138 + function voltageAt(z, tIdx, simResult) 139 + ``` 140 + 141 + `computeDynamicState` becomes a thin wrapper that samples the precomputed arrays 142 + at the current animation time. 143 + 144 + ## render.js changes 145 + 146 + **Circuit diagram (`drawCircuit`):** 147 + - Draw N abutting T-line boxes (already works for multi-segment) 148 + - At each internal junction node, draw the shunt element symbol (C, L, R, short) below 149 + the node point, going to the GND rail 150 + 151 + **Plot (`drawPlot`):** 152 + - V(z,t) field: same as now, backed by `voltageAt(z, tIdx, sim)` 153 + - V⁺ / V⁻ component view: toggle to show forward/backward waves per segment (new) 154 + - Node voltage traces: show V_node(t) for each internal junction as time-domain overlays 155 + (similar to current VS/VL traces) 156 + 157 + ## app.js / UI changes 158 + 159 + - Junction controls: for each internal junction, a type selector (none / R / C / L / 160 + short) and a value input (hidden when type is none or short) 161 + - Load control: extend existing RL input to support C / L / open / short types 162 + - `oversample` slider (hidden/advanced): controls simulation time resolution 163 + - On model change: re-run `simulateTimeDomain`, cache result, redraw 164 + 165 + ## Work order 166 + 167 + 1. **Physics core** — `simulateTimeDomain` with resistive-only junctions, validate 168 + against existing `buildBounceSeries` output (existing SPICE tests still pass) 169 + 2. **Resistive junction** — confirm multi-segment R-junction matches SPICE 170 + 3. **Shunt C** — implement ODE step, add C-junction SPICE netlist for validation 171 + 4. **Shunt L** — same 172 + 5. **Model / UI** — add junction controls to app.js 173 + 6. **Render** — update circuit diagram to show shunt elements; add V⁺/V⁻ view 174 + 7. **Node voltage traces** — add per-junction V(t) overlay to plot 175 + 176 + Steps 1–4 can be done and validated in `physics.test.js` before touching the UI.