Skip to content

Add spice -> kernel pipeline (spice_kernel) for the --shape fast constants - #31

Open
WillMatthews wants to merge 6 commits into
masterfrom
spice-to-kernel-pipeline
Open

Add spice -> kernel pipeline (spice_kernel) for the --shape fast constants#31
WillMatthews wants to merge 6 commits into
masterfrom
spice-to-kernel-pipeline

Conversation

@WillMatthews

@WillMatthews WillMatthews commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Second of two stacked PRs. Stacked on #30 (archive) — review/merge #30 first; the base will retarget to master (via fast-output-shaping) as the stack lands.

This PR (pipeline): src/spice_kernel.cpp — a C++ tool that closes the loop from the circuit to the shaper. It:

  1. runs spice/jseries_fastout.cir via ngspice and extracts the single-photon fast-output pulse;
  2. derives the two --shape fast time constants — tauLoad = R_Lfast·N·C_f (read from the deck's .param lines, so it can't drift from the circuit) and tauRecovery (fitted on the fast tail);
  3. validates the analytic kernel shape_fast() implements against the SPICE pulse;
  4. writes a ready-to-run sipm_fast.json (via the existing save_params_json) whose tauLoad is circuit-calibrated.

Built via make spice_kernel (excluded from the default all link, like server.cpp):

$ make spice_kernel && ./build/apps/spice_kernel
=== spice -> --shape fast kernel ===
  circuit: N=14410, C_f=3.47 fF, R_Lfast=50 ohm, OV=2.5 V
  tauLoad     = R_Lfast*N*C_f =   2.500 ns   (-> params 'tauLoad')
  tauRecovery = loaded fit    =  24.539 ns   (fast-tail recovery)
  kappa       = C_f/(C_d+C_q) = 5.4219e-02
  FWHM: spice 1.93 ns vs analytic kernel 1.51 ns
  peak-normalised shape RMS error (kernel vs spice, <40 ns): 0.088
  [PASS] shape RMS 0.088 < 0.10
  wrote sipm_fast.json

Then simspad -p sipm_fast.json -i light.npy -o resp.npy --shape fast gives a fast-output trace calibrated to the circuit rather than to a hand-picked constant (verified to round-trip through simspad). Needs ngspice on PATH; the generated params file is gitignored.

C++ only — no Python added to the package.

Closes #29.

…tants

src/spice_kernel.cpp runs the equivalent circuit (spice/jseries_fastout.cir),
extracts the single-photon fast-output pulse, and derives the two constants
the fast/bench shapers consume:

  tauLoad     = R_Lfast * N * C_f   (fast-rail RC, read from the deck's .param
                                     values so it cannot drift from the circuit)
  tauRecovery = loaded fit on the fast tail

It validates the analytic kernel shape_fast() implements against the SPICE
pulse (peak-normalised shape RMS 0.088 < 0.10) and writes a ready-to-run
sipm_fast.json (via save_params_json) whose tauLoad is circuit-calibrated, so

  simspad -p sipm_fast.json -i light.npy -o resp.npy --shape fast

produces a circuit-calibrated fast-output trace instead of one with a
hand-picked time constant.

Built via `make spice_kernel` (excluded from the default `all` link, like
server.cpp). Needs ngspice on PATH; the generated params file is gitignored.

Closes #29.
@WillMatthews
WillMatthews force-pushed the spice-to-kernel-pipeline branch from 92702d8 to 45878b3 Compare June 12, 2026 00:16
@WillMatthews WillMatthews changed the title Add spice -> kernel pipeline for the --shape fast time constants Add spice -> kernel pipeline (spice_kernel) for the --shape fast constants Jun 12, 2026
Base automatically changed from spice-fast-output-circuit to master June 15, 2026 20:25
WillMatthews and others added 5 commits June 15, 2026 21:52
Datasheet-grounded fast-output equivalent circuits for the J-Series 35 um
parts (MicroFJ-30035/40035/60035), with all inputs traced to MICROJ-SERIES/D
Tables 1-3 (gain, cell count, recharge tau, fast/anode capacitance). The
datasheet is internally consistent with the model: Cmicro = G*e/OV gives
N*Cmicro within 96-100% of the datasheet anode capacitances.

Also:
- template_fastout.cir / template_stdout.cir: parameterised templates that
  derive all internal component values from datasheet headline numbers, so no
  component is hand-picked. The stdout template covers devices with no fast pin.
- pulse_to_kernel.py: coupling-aware net-charge diagnostic (bipolar/AC fast
  output ~0 vs unipolar/DC standard output ~1), so the slow-output path is
  legible. The anode v(a) in every fast deck builds a slow kernel directly.
- spice/README.md: device library, datasheet provenance, and an honest fidelity
  note -- the lumped N*Cf fast rail over-broadens the fast output (deck FWHM
  1.8/2.7/5.4 ns vs datasheet 1.5/1.7/3.0 ns, worse for large arrays); the
  standard output (set by recharge tau) is well-matched. For an exact fast
  output, digitise the datasheet's measured pulse (Figs. 5/6) into a kernel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lFile was lost)

A non-numeric (string) value only advanced the scan past the colon, so the
next '"' search landed on the value's opening quote: the value became a key
and stole the following key's number. Concretely, "kernelDt" after
"kernelFile" was dropped, kernelDt fell back to the simulation dt, and
resample_kernel became a no-op -- a 10 ps SPICE kernel played back on a 50 ps
grid came out 5x too slow (FWHM 9.7 ns instead of 1.93 ns). Skip past the
closing quote instead.

Caught by the sipm-eq-paper cross-validation smoke test (--shape kernel at
dt != kernelDt).
…rnels

The 120 ns window truncated the ~24.5 ns loaded fast tail, leaving a
+1.3%-of-lobe net-charge residual in any kernel tabulated from the run
(the physical AC-coupled terminal has exactly zero). Downstream, a
pure-integrator conservation observer integrates that DC into 4-6 V of
spurious drift over a ~6 us payload -- enough to rail the state estimate.
At 350 ns the residual is ~1e-6 of the lobe (sub-mV drift).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant