docs/adr/ADR-090-nvsim-lindblad-extension.md
| Field | Value |
|---|---|
| Status | Proposed — conditional. Only built if a pulsed-protocol use case emerges. Default-off, opt-in feature gate. |
| Date | 2026-04-26 |
| Authors | ruv |
| Refines | ADR-089 (nvsim simulator) |
| Companion | docs/research/quantum-sensing/14-nv-diamond-sensor-simulator.md §3.1, docs/research/quantum-sensing/15-nvsim-implementation-plan.md §6 |
ADR-089's nvsim::sensor module
implements a leading-order linear-readout proxy for NV-ensemble
magnetometry per Barry et al. Rev. Mod. Phys. 92, 015004 (2020) §III.A.
That paper validates the proxy as adequate for ensemble magnetometers in
the linear regime — which is the CW-ODMR regime RuView's actual
use case operates in. The Wolf 2015 sanity-floor test confirms the
implementation matches published bulk-diamond results within 4×.
What the proxy does not model:
For RuView's CW-ODMR ensemble use case (ferrous-anomaly detection, metallic-object screening), none of these matter — Barry 2020 §III.A is explicit that the linear-readout proxy is adequate. For future use cases that involve pulsed protocols (e.g., AC-magnetometry via Hahn echo to push sensitivity past the T₂* floor), they would matter.
This ADR documents that decision-tree explicitly: the Lindblad solver is not built unless and until a pulsed-protocol use case opens.
Defer the full Hamiltonian + Lindblad solver to a conditional, opt-in
feature gate named lindblad on the nvsim crate. Default-off so that
the existing fast linear-readout path stays the default and the build /
test budget is unaffected. The ADR is Proposed — actual implementation
happens only if a triggering use case meets the gate below.
This ADR transitions from Proposed → Accepted when any one of the following is true:
If none of those triggers, the linear proxy is sufficient and this ADR remains Proposed indefinitely.
sensor.rs matches Wolf 2015 within 4×.
We're not under-modelling — we're correctly-modelling.QuTiP is the obvious off-the-shelf option and is what 15.md §6 originally
proposed deferring to. Two reasons we'd prefer an in-tree Rust
implementation if we ever build it:
cargo test --workspace --no-default-features already runs in seconds. Adding QuTiP would
pull a Python dependency into CI and slow the gate.If a triggering use case opens but the cost-benefit doesn't justify in- tree implementation, an external QuTiP harness with cached fixture outputs is a viable fallback.
nvsim::sensor module
docstrings already say what's not modelled. ADR-090 is the
formal accountability for that boundary.If this ADR transitions to Accepted, the implementation is:
lindblad feature to nvsim/Cargo.toml — opt-in, default-off.
Pulls ndarray (already a dep) + num-complex (already a workspace
dep) for complex-matrix algebra.src/lindblad.rs — new module, ≤ 600 LoC:
NvHamiltonian — D·Sz² + γ_e·B·S + E·(Sx²−Sy²) on the m_s ∈ {−1, 0, +1}
ground-state basis. Optional ¹⁴N or ¹⁵N hyperfine extension.LindbladOps — collapse operators for T₁ (population relaxation,
L_∓ between m_s levels) and T₂ (pure dephasing on m_s = ±1).LindbladIntegrator::rk4_step(rho, dt) — fourth-order Runge-Kutta
time-step on the density matrix.Pulse enum — supports CW, square, Gaussian-shaped MW pulses.src/lindblad_protocols.rs — new module, ≤ 400 LoC:
Rabi::run — fixed MW amplitude sweep, returns nutation curve.HahnEcho::run — π/2 — τ — π — τ — π/2 detection sequence.Cpmg::run — repeated π pulses for dynamical decoupling.Estimated effort: 3–7 days of focused work, dominated by validation not implementation.
This ADR is Proposed until any of the four trigger conditions in §" Trigger conditions" fires. When that happens:
ndarray + num-complex (already workspace deps; lowest
surface area but unergonomic for matrix algebra); (b) nalgebra with
ComplexField trait (richer matrix algebra, +1 workspace dep);
(c) faer (more recent, focused on numerics performance, +1 workspace
dep). Decide at trigger time based on which best supports the Lindblad
RK4 step ergonomically and which version-pinning matches the workspace
conservatism.MagFrame (0xC51A_6E70) shape; pulsed-protocol
results add to the per-frame metadata, not a new frame format.