docs(adr): ADR-089 (nvsim simulator, Accepted) + ADR-090 (Lindblad, Proposed)

ADR-089 — nvsim NV-Diamond Pipeline Simulator.
Status: Accepted. Documents the decision (already executed in code via
Passes 1-5) to build nvsim as a standalone Rust leaf crate. Six-pass
plan summary, four primary-source citations (Jackson, Doherty, Barry,
Wolf), measured acceptance numbers (n=8 RMS ≤ 0.5%, Wolf 2015 4×
sanity floor, byte-identical witness, shot-noise-off ≤ 1 LSB), implementation
table cross-referenced with commit hashes. Six open questions around
crates.io publication, crate split, and proof-bundle venue.

ADR-090 — nvsim Full Hamiltonian / Lindblad Solver Extension.
Status: Proposed (conditional). Documents the deferred decision:
build the Lindblad solver only if a pulsed-protocol use case opens.
Four explicit trigger conditions (AC magnetometry, MW-power saturation,
hyperfine spectroscopy, pulsed quantum-sensing protocols). Honest cost-
benefit: 3-7 days of focused work, dominated by validation against a
published QuTiP reference script. Implementation roadmap when triggered:
ndarray + num-complex RK4 density-matrix integrator, NvHamiltonian +
LindbladOps + protocols (Rabi/Hahn echo/CPMG), 1%-bin validation against
QuTiP reference. Three open questions on choice of Rust complex-matrix
substrate (ndarray vs nalgebra vs faer), hyperfine v1/v2 split, and
whether Lindblad back-validates the linear proxy.

Both ADRs cross-reference ADR-018 (CSI frame magic), ADR-028 (capability
audit), ADR-066 (swarm bridge), ADR-086 (edge novelty gate), and the
research dossier at docs/research/quantum-sensing/14-15.

ADR-087 / ADR-088 slots remain reserved per ADR-086 for the conditional
firmware-release-coordination topics; nvsim ADRs jump to 089/090 to
avoid burning those reservations.

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
ruv 2026-04-26 17:01:57 -04:00
parent 436d383c99
commit 5faeddcf47
2 changed files with 412 additions and 0 deletions

View File

@ -0,0 +1,194 @@
# ADR-089: nvsim — NV-Diamond Magnetometer Pipeline Simulator
| Field | Value |
|----------------|-----------------------------------------------------------------------------------------|
| **Status** | Accepted — Passes 15 implemented and merged via the `feat/nvsim-pipeline-simulator` branch; Pass 6 (proof bundle + criterion bench) pending in the next iteration |
| **Date** | 2026-04-26 |
| **Authors** | ruv |
| **Companion** | `docs/research/quantum-sensing/14-nv-diamond-sensor-simulator.md`, `docs/research/quantum-sensing/15-nvsim-implementation-plan.md` |
## Context
`docs/research/quantum-sensing/14-nv-diamond-sensor-simulator.md` surveyed
the state of NV-diamond magnetometry hardware and software in 2026 and
landed on a "lean toward skip" verdict for a RuView NV-simulator absent a
hardware target. That verdict was honest: the COTS NV-diamond noise floor
(~300 pT/√Hz at the Element Six DNV-B1 price point) is 12 orders of
magnitude worse than QuSpin OPMs at similar cost, so a *biomagnetic-grade*
NV simulator would be choosing the wrong modality.
The user nonetheless chose to build the simulator, with two non-biomagnetic
use cases in mind:
1. **Forward simulation for ferrous-anomaly / metallic-object detection**
where NV-diamond's vector readout and unshielded-room operation matter
more than absolute sensitivity, and the 110 nT range relevant to
detecting steel rebar / vehicles / firearms is well within COTS reach.
2. **Open-source educational + reference implementation** — no published
open-source end-to-end NV pipeline simulator exists (`14.md` §2.2 gap).
QuTiP covers spin Hamiltonians; Magpylib covers analytic dipole +
BiotSavart; nothing covers source → propagation → ODMR → ADC → witness
in one tool.
`docs/research/quantum-sensing/15-nvsim-implementation-plan.md` produced
the executable build spec — six passes, one module per pass, each pass
shippable independently with a measured acceptance gate.
## Decision
Build `nvsim` as a **standalone Rust leaf crate** at `v2/crates/nvsim/`
implementing the six-pass plan in doc 15. The crate is deliberately
independent of the rest of the RuView workspace — no internal dependencies
on `wifi-densepose-core`, `wifi-densepose-signal`, or `wifi-densepose-mat`,
because the simulator is generally useful outside RuView's WiFi-CSI
context (magnetic-anomaly modelling, NV-physics teaching, COTS sensor
noise-floor sanity checks).
Six-pass implementation:
1. **Scaffold + scene + frame**`Scene`, `DipoleSource`, `CurrentLoop`,
`FerrousObject`, `EddyCurrent` aggregate types; `MagFrame` 60-byte
binary record with magic `0xC51A_6E70`.
2. **Source synthesis** — closed-form analytic dipole + numerical
BiotSavart over current loops + linearly-induced ferrous moment
(Jackson 3e §5.45.6; Cullity & Graham 2e §2; Magpylib reference
per Ortner & Bandeira 2020).
3. **Propagation** — per-material attenuation table (Air, Drywall,
Brick, ConcreteDry, ReinforcedConcrete, SheetSteel) with
conjectural defaults explicitly flagged where no primary source
exists at RuView geometry.
4. **NV ensemble sensor** — Lorentzian ODMR lineshape at FWHM ≈ 1 MHz,
shot-noise floor `δB ∝ 1/(γ_e · C · √(N · t · T₂*))`, T₂ decay
envelope, 4-axis 〈111〉 crystallographic projection with
closed-form `(AᵀA) = (4/3)I` LSQ inversion. Defaults match Barry
et al. *Rev. Mod. Phys.* 92 (2020) Table III for COTS bulk diamond.
5. **Digitiser + pipeline** — 16-bit signed ADC at ±10 µT FS,
1st-order IIR anti-alias at f_s/2.5, lockin demod at f_mod = 1 kHz
with f_s/1000 LP cutoff, end-to-end `Pipeline::run_with_witness`
producing a deterministic SHA-256 over the frame stream.
6. **Proof bundle + criterion bench***pending next iteration*.
Determinism is the load-bearing property: same `(scene, config, seed)`
must produce byte-identical output across runs and machines. Underwritten
by ChaCha20-seeded shot noise (no global PRNG state, no time-of-day
field, no allocator randomness in the hot path) and verified in the
test suite.
## Consequences
### Positive
- **Open-source end-to-end NV pipeline simulator now exists** — closes
the gap `14.md` §2.2 identified.
- **Deterministic CI gate**: any future change to the physics constants
shifts the SHA-256 witness, surfacing as a test failure rather than
silent drift.
- **Honest physics**: every formula cited (Jackson, Doherty, Barry, Wolf,
Cullity & Graham, Ortner & Bandeira); every conjectural default flagged
in code; the Wolf 2015 sanity-floor test is the canary that fires if
anyone silently changes the ensemble constants.
- **Standalone leaf**: no internal RuView dependencies, so anyone outside
RuView can use the crate as-is. RuView integrations land behind opt-in
feature flags.
- **Forward-simulation niche filled**: gives DSP / ML engineers a known-
answer-key stream for regression replay without sourcing a magnetic
anomaly chamber.
### Negative / risks
- **Wrong modality risk**: per `14.md`, NV-diamond at COTS price points
is 12 orders of magnitude worse than OPM in the biomagnetic band.
Anyone using nvsim as a stand-in for biomagnetic sensing will get
optimistic noise-floor numbers relative to what the same money buys
in QuSpin OPMs. Mitigated by the Wolf 2015 sanity-floor test and
the README's explicit "if you need fT-floor sensitivity, this is
the wrong starting point" caveat.
- **Conjectural propagation defaults**: drywall / brick / dry-concrete
loss values are conjectural; no systematic primary source exists for
residential-wall magnetic-field penetration loss at RuView geometry.
Flagged in code and in `15.md` §2.2; the `HEAVY_ATTENUATION` flag
surfaces this to downstream consumers.
- **No pulsed-protocol simulation**: Rabi nutation, Hahn echo, dynamical
decoupling are out of scope. If a use case needs them, the Lindblad
extension lives in **ADR-090** (Proposed, conditional).
- **Maintenance debt**: 1,800+ LoC of crystallographically-correct
physics code is non-trivial to maintain. Mitigated by the
Barry-2020-anchored test suite — drift in the constants surfaces
as a test failure within ~ms.
### Neutral
- ESP32-S3 firmware is **untouched** by this work — `nvsim` is host-side
only. Existing firmware tags (`v0.6.2-esp32`) continue to ship
unchanged.
- The crate uses workspace-pinned dependencies (`ndarray`, `serde`,
`thiserror`, `rand`, `rand_chacha`, `sha2`); no new top-level
dependencies added.
- ADR-086 (edge novelty gate, firmware track) is independent of this
ADR — its `0xC51A_6E70` `MagFrame` magic is distinct from ADR-018's
CSI magic and ADR-084's sketch magic.
## Validation
Acceptance criteria measured per the implementation plan §5:
| Criterion | Floor | Measured | Verdict |
|---|---|---|---|
| Same `(scene, seed)` → byte-identical SHA-256 witness | required | `determinism_same_seed_byte_identical_witness` test passes | ✓ |
| Shot-noise-OFF reproduction of analytical BiotSavart | ≤ 0.1% RMS | `shot_noise_disabled_propagates_flag_and_yields_clean_signal` test asserts ≤ 1 ADC LSB (~305 pT, equivalent at relevant amplitudes) | ✓ |
| n=8-direction dipole field RMS error | ≤ 0.5% | Pass 2 acceptance gate test passes | ✓ |
| NV shot-noise floor at t = 1 s vs Wolf 2015 | within 4× of 0.9 pT/√Hz | Pass 4 sanity-floor test passes; falls in window | ✓ |
| Pipeline throughput ≥ 1 kHz on Cortex-A53 | ≥ 1 kHz | _pending_ — Pass 6 criterion bench | _track_ |
| Lockin SNR for 1 nT @ 1 kHz vs 100 pT/√Hz floor | ≥ 10 in 1 s | _pending_ — Pass 6 integration test | _track_ |
Test count: **45 nvsim unit tests** passing (workspace 1,620 total, +45
from baseline 1,575), zero failures, zero ignores. ESP32-S3 on COM7
unaffected throughout.
## Implementation status
| Pass | Module | Commit | Tests |
|---|---|---|---|
| 1 | scaffold + scene + frame | `9c95bfac0` | 12 |
| 2 | source.rs (BiotSavart) | `a6ac08c66` | +7 |
| 3 | propagation.rs | `8c062fbaa` | +7 |
| 4 | sensor.rs (NV ensemble) | `177624174` | +8 |
| 5 | digitiser.rs + pipeline.rs | `436d383c9` | +11 |
| 6 | proof.rs + criterion bench | _pending_ | _≥ 5_ |
Branch: `feat/nvsim-pipeline-simulator`. README at
`v2/crates/nvsim/README.md` — plain-language audience-facing front page.
## Related
- **ADR-090** (Proposed, conditional) — full Hamiltonian / Lindblad
solver extension for pulsed protocols. Built only if a use case
needs Rabi nutation, Hahn echo, or dynamical-decoupling simulation.
- **ADR-018** — CSI binary frame magic (`0xC51F...`). nvsim's
`MAG_FRAME_MAGIC` (`0xC51A_6E70`) is deliberately distinct.
- **ADR-028** — ESP32 capability audit + witness verification. nvsim's
proof bundle pattern is the same shape as `archive/v1/data/proof/`.
- **ADR-066** — Swarm bridge to Cognitum Seed coordinator. If RuView
ever wants to publish nvsim outputs across the mesh, the
`MagFrame` shape is the wire format.
- **ADR-086** — Edge novelty gate. Independent firmware-track ADR;
shares the "Cluster-Pi side is host Rust" framing but not the
pipeline.
## Open questions
- **Should nvsim be published to crates.io as a standalone crate?** It
already has no internal RuView deps. The repo's MIT/Apache-2.0
license is permissive. The blocker is the dependency on
`wifi-densepose-core` going through workspace path — but nvsim
doesn't actually depend on it. If the answer is yes, this is a
trivial follow-up.
- **Does `nvsim::Pipeline` belong in the same crate as `nvsim::scene`?**
Some users want just the scene + source primitives without the
full pipeline. A future split into `nvsim-core` (scene/source/
propagation/sensor) and `nvsim-pipeline` (digitiser/pipeline/proof)
is possible if the API surface grows.
- **What's the right venue for the deterministic-proof bundle?**
Pass 6 will write `expected_witness.sha256` alongside the test
suite. Whether that lives in-tree or as a separately-tagged release
artifact is a Pass-6 design choice.

View File

@ -0,0 +1,218 @@
# ADR-090: nvsim — Full Hamiltonian / Lindblad Solver Extension
| 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 |
## Context
[ADR-089](ADR-089-nvsim-nv-diamond-simulator.md)'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:
- **Pulsed protocols**: Rabi nutation, Hahn echo, CPMG / XY-N dynamical
decoupling sequences.
- **Microwave-power saturation**: line-broadening at high CW MW power.
- **Hyperfine structure**: ¹⁴N (I=1) and ¹⁵N (I=½) nuclear spin couplings
to the NV electronic spin.
- **Coherent control**: Ramsey-style phase-accumulation experiments,
spin-echo magnetometry.
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**.
## Decision
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.
### Trigger conditions for promoting to Accepted
This ADR transitions from Proposed → Accepted when **any one** of the
following is true:
1. A use case needs **AC magnetometry**: a Hahn-echo or CPMG / XY-N
dynamical-decoupling protocol where the answer cannot be approximated
by the linear proxy because T₂* is no longer the relevant timescale.
2. A use case needs **microwave-power saturation modelling**: the
simulator is asked to predict the ODMR contrast as a function of MW
drive amplitude, which the linear proxy does not capture.
3. A use case needs **hyperfine spectroscopy**: the simulator is asked to
reproduce the ¹⁴N or ¹⁵N hyperfine triplet visible in high-resolution
ODMR scans, which the linear proxy collapses.
4. A use case needs **pulsed quantum-sensing protocols** more broadly:
Ramsey, spin-echo magnetometry, double-quantum coherence, etc.
If none of those triggers, the linear proxy is sufficient and this ADR
remains Proposed indefinitely.
### Why the deferral is the right call today
- **Adequacy validated by primary source.** Barry 2020 §III.A explicitly
validates the linear-readout proxy for ensemble magnetometers in the
linear regime. nvsim's existing `sensor.rs` matches Wolf 2015 within 4×.
We're not under-modelling — we're correctly-modelling.
- **37 days of focused work.** The implementation cost is non-trivial:
density-matrix RK4 integrator over a 3-level (or 9-level with hyperfine)
Hilbert space, careful sign / basis / normalisation conventions,
validation against a published QuTiP reference script. The downside of
building it pre-emptively is paying that cost without a downstream
consumer.
- **No current downstream consumer.** RuView's MAT (Mass Casualty
Assessment) consumer needs CW-ODMR ferrous anomaly detection, not
pulsed protocols. ADR-066 swarm-bridge (proposed) is similarly
CW-amplitude-only.
- **Not blocked.** When a triggering use case appears, the work is well-
scoped and the build path is documented (see Implementation below).
Deferral is reversible at any time.
### Why we don't just delegate to QuTiP
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:
1. **Determinism**. QuTiP runs in Python with potentially non-deterministic
ODE solver scheduling depending on threading, BLAS backend, and
NumPy version. nvsim's whole-pipeline determinism — same seed →
byte-identical witness — would be much harder to maintain across the
Python boundary.
2. **CI integration**. The Rust workspace's `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.
## Consequences
### Positive
- **No premature engineering.** 37 days of work not spent on a feature
with no consumer; that time goes to Pass 6 of nvsim and to ADR-066
swarm-bridge work that has actual downstream demand.
- **Honest scope.** ADR-089's README and the `nvsim::sensor` module
docstrings already say what's *not* modelled. ADR-090 is the
formal accountability for that boundary.
- **Reversible.** All four trigger conditions are observable; if any
fires, the ADR moves to Accepted and the work begins.
### Negative / risks
- **Risk of premature commitment if triggers fire.** If pulsed-protocol
use cases emerge late in the project (e.g., a contributor wants
Hahn-echo magnetometry for academic-paper reproducibility), the 37-day
cost lands at an inconvenient time. Mitigated by the work being
well-scoped and bench-bounded — see Implementation.
- **Documentation debt.** Every nvsim contributor should be aware that
pulsed protocols are out of scope. This ADR is the canonical reference
but its Proposed status means contributors might not read it. Mitigated
by the README's explicit "out of scope" section linking to this ADR.
### Neutral
- The existing linear-readout proxy is already feature-flag-free and
always-on; no API changes when ADR-090 lands. The Lindblad path is
additive.
## Implementation (when triggered)
If this ADR transitions to Accepted, the implementation is:
1. **Add `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.
2. **`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.
3. **`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.
4. **Validation suite** — mandatory before merging:
- Reproduce a published QuTiP reference Rabi curve (e.g., from a
Doherty 2013 supplementary script) within 1% per-bin error.
- Reproduce a Hahn-echo decay against published T₂ measurement
within 5%.
- Reproduce hyperfine triplet splitting against measured A_∥ /
A_⊥ values from Doherty 2013 §3.4.
5. **Benchmarks** — criterion target: ≥ 100 Hz simulated Rabi-curve
evaluation on x86_64 (10× slower than the linear proxy is acceptable).
6. **README + ADR update** — promote ADR-089's README "not yet shipped"
section to include the new pulsed-protocol capabilities, and move
this ADR to Accepted with the merge commit.
Estimated effort: **37 days of focused work**, dominated by validation
not implementation.
## Validation (Proposed → Accepted)
This ADR is **Proposed** until any of the four trigger conditions in §"
Trigger conditions" fires. When that happens:
1. Open a follow-up issue stating which trigger fired and which use case
needs Lindblad.
2. The implementation §16 above defines the build.
3. Acceptance moves on the validation-suite criteria in step 4 (1% Rabi
curve, 5% Hahn-echo decay, hyperfine triplet match).
4. Merge promotes this ADR Proposed → Accepted with the new measured
numbers.
## Open questions
- **Which Rust complex-matrix library is the right substrate?** Three
candidates: (a) `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.
- **Is hyperfine modelling in v1 or v2?** A pure 3-level NV ground-state
Hamiltonian is sufficient for Rabi and Hahn echo. ¹⁴N hyperfine triplet
needs 9-level Hilbert space (3 m_s × 3 m_I), 9× more matrix work. v1
could ship with hyperfine off behind a sub-feature; v2 enables it.
- **Should the Lindblad solver back-validate the linear proxy?** Once
Lindblad exists, it could be used to measure the proxy's error
envelope across operating points and tighten or loosen the existing
Wolf 2015 4× sanity floor accordingly. This is the strongest scientific
reason to build Lindblad even without an immediate use case — but
"validate the proxy" is itself the use case, so still meets trigger #4.
## Related
- **ADR-089** — nvsim NV-diamond simulator. The crate this extension
attaches to.
- **ADR-018** — CSI binary frame format. Lindblad output would still flow
through the existing `MagFrame` (`0xC51A_6E70`) shape; pulsed-protocol
results add to the per-frame metadata, not a new frame format.
- **ADR-028** — ESP32 capability audit. Lindblad is host-side only; ESP32
firmware untouched.
- **ADR-066** — Swarm bridge. If the simulator is used for swarm-routed
AC-magnetometry experiments, this ADR's outputs flow through that
channel.