Back to Ruview

NV-Diamond Sensor Simulator — Implementation Plan

docs/research/quantum-sensing/15-nvsim-implementation-plan.md

1.99.0-pip15.7 KB
Original Source

NV-Diamond Sensor Simulator — Implementation Plan

Quantum Sensing Series (15/—) — Executable Build Spec

Date: 2026-04-25 Status: Plan only — no source code yet Branch: feat/nvsim-pipeline-simulator (untracked artefact) Companion: 14-nv-diamond-sensor-simulator.md (SOTA + verdict + scope caveats) Drives: /loop — six independently shippable passes, one module per iteration

Working document. A developer (human or agent) picks up any single row of §3, ships it, runs the gate, stops. Doc 14's verdict was "lean toward skip without a hardware target"; this plan honours that scoping by sizing narrowly to ferrous-anomaly / eddy-current / mat-aligned use cases. Where physics has a primary source, formula is cited; where it does not, the gap is marked conjecture with a defensible default.


Section 1 — Crate scaffold

1.1 Crate name — locked: nvsim

Standalone, not prefixed with wifi-densepose-: the simulator is generally useful outside RuView's WiFi-CSI context (magnetic-anomaly modeling, NV-physics teaching, COTS-sensor noise-floor sanity checks), so it lives in the workspace as a peer leaf. Public API: use nvsim::scene::DipoleSource;. Placement: v2/crates/nvsim/, pure leaf crate (no internal RuView deps).

1.2 Cargo.toml

toml
[package]
name = "nvsim"
version.workspace = true
edition.workspace = true
license.workspace = true
description = "Deterministic NV-diamond magnetometer pipeline simulator (source -> propagation -> NV -> ADC)"

[dependencies]
ndarray = { workspace = true }                 # 3-vector field math, time-series buffers
rustfft = { workspace = true }                 # spectral analysis + lockin demod cross-check
num-complex = { workspace = true }             # phasor algebra in lockin
num-traits = { workspace = true }
rand = "0.8"                                   # Monte-Carlo shot noise (NOT in workspace yet -> add)
rand_chacha = "0.3"                            # deterministic seed -> ChaCha20 PRNG
sha2 = "0.10"                                  # witness hashing (already used in -core)
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
wifi-densepose-core = { path = "../wifi-densepose-core" }   # FrameKind extension only

[dev-dependencies]
criterion = "0.5"
approx = "0.5"

[features]
default = []
ruvector = ["dep:ruvector-core"]               # optional witness/sketch reuse — Section 4
[dependencies.ruvector-core]
path = "../../../vendor/ruvector/crates/ruvector-core"
optional = true

[[bench]]
name = "pipeline_throughput"
harness = false

1.3 Module layout (one file each, < 500 lines per CLAUDE.md)

FileLoC budgetPurpose
src/lib.rs< 200Public re-exports, Pipeline builder, error type, crate-level rustdoc
src/scene.rs< 350DipoleSource, CurrentLoop, FerrousObject, EddyCurrent, Scene aggregate
src/source.rs< 350Biot–Savart for current loops + analytic dipole field (no FEM)
src/propagation.rs< 250Per-material attenuation table + free-space pass-through
src/sensor.rs< 450NV-ensemble linear ODMR readout, Lorentzian lineshape, T1/T2 envelope, shot noise, vector projection onto 4 NV axes
src/digitiser.rs< 300ADC quantize, anti-alias, lockin demod at MW modulation freq
src/pipeline.rs< 250Wires the four layers; emits MagFrame stream
src/frame.rs< 250rv_mag_feature_state_t struct, magic-number, byte-exact serialisation
src/proof.rs< 250Deterministic seed -> SHA-256 witness; mirrors archive/v1/data/proof/verify.py

Total: ~2,650 LoC Rust + ~400 LoC tests + 1 bench. 3-week sprint per doc 14 §5.

1.4 Frame magic number

ADR-018 reserves 0xC51F... for CSI. Pick 0xC51A_6E70 for rv_mag_feature_state_t: C51 (CSI/feature lineage), A (Analog/Anomaly), 6E70 (ASCII "np", NV-pipeline). u32 little-endian, first 4 bytes of every frame. Consumers reading 0xC51F... fail magic-check on a magsim frame and abort cleanly — non-overlap with CSI is the invariant.

1.5 Workspace wiring

Append crates/nvsim to v2/Cargo.toml members after wifi-densepose-vitals. No publishing-order changes (pure leaf, no internal deps). Update CLAUDE.md crate table in a separate PR after Pass 6 ships.


Section 2 — Physics-model commitments (no-mocks part)

Per layer: formula, units, primary source. When no primary source applies at RuView geometry, marked conjecture with chosen default.

2.1 source.rs — magnetic source synthesis

PrimitiveFormulaUnitsSource
Magnetic dipoleB(r) = (μ₀ / 4π r³) · [3(m·r̂)r̂ − m] with μ₀ = 4π×10⁻⁷ T·m/AT (output), m (position), A·m² (moment)Jackson, Classical Electrodynamics 3e, §5.6 (1999); Magpylib reference impl [Ortner & Bandeira, SoftwareX 11, 100466 (2020)]
Current loopBiot–Savart: B(r) = (μ₀/4π) ∮ I dl × r̂ / r² discretised over n=64 segmentsTJackson §5.4
Ferrous-object induced momentLinear approx: m_induced = χ V H_ambient for χ ≈ 5000 (steel)A·m²Cullity & Graham, Introduction to Magnetic Materials 2e (2009), Ch.2 — primary source for steel χ at low field
Eddy-current loopFaraday + Ohm: I(t) = -(σ A / L) · dΦ/dt, then re-emits via Biot–SavartAJackson §5.18; no primary source for arbitrary geometry — conjecture: assume thin-disc geometry, scalar L per object

Sign convention: right-hand rule on current; m parallel to coil normal. Units: SI; convert to pT at frame-emit time only. Singularity at r→0: clamp r_min = 1 mm; below that, return B = 0 and set flags |= SATURATION_NEAR_FIELD (conjectural — no published guidance for sub-mm dipole at RuView geometry — but deterministic).

2.2 propagation.rs — attenuation through air + materials

MaterialModel / coeff (DC–10 kHz)Source
Air / vacuumμ = μ₀, σ ≈ 0; 0 dB/mJackson §5.8
Drywall (gypsum)Dielectric, 0 dB/mConjecture (no primary source); gypsum non-ferromagnetic, loss << 0.1 dB/m
Brick (dry)Dielectric, 0 dB/mConjecture; same logic
Concrete (dry)0.5 dB/m defaultConjecture (Ulrich NDT&E Int. 35, 2002 as proxy only)
Reinforced concrete20 dB/m + warning flagUlrich 2002 proxy; research gap per doc 14 §6.3
Sheet steelSkin depth δ = √(2/μσω), freq-dependentJackson §8.1

Propagation is intentionally thin: free-space 1/r³ lives in source.rs. This layer applies per-segment attenuation only when sensor-source line-of-sight intersects a material slab; default is identity.

2.3 sensor.rs — NV-ensemble response

Full Hamiltonian is not solved (doc 14 §4.4 defers Lindblad dynamics to QuTiP). We implement the linear-readout proxy that Barry 2020 §III.A validates as adequate for ensemble magnetometers in the linear regime:

QuantityFormula / valueSource
ODMR transition`ν± = D ± γ_eB_∥
LineshapeLorentzian, Γ ≈ 1 MHz FWHMBarry RMP 92 (2020), Fig. 4
Shot-noise δB1 / (γ_e · C · √(N · t)) (leading order)Barry 2020 Eq. 35; Taylor Nat. Phys. 4 (2008)
C (ODMR contrast)0.03 (COTS bulk)Barry 2020 Table III
N (sensing spins)10¹² for ~1 mm³Barry 2020 §IV.A
T1 / T2 / T2*5 ms / 1 µs / 200 nsJarmola PRL 108 (2012); Barry 2020 Table III
Vector projection4 NV axes [111], [11̄1̄], [1̄11̄], [1̄1̄1]Doherty 2013 §3

Layer takes B_field: [f64; 3] from propagation, projects onto each of 4 axes, applies Lorentzian response at f_mod, scales by bandwidth-integrated noise δB · √(BW), then returns 3-vector via least-squares inversion of the 4-axis projection matrix.

Sanity floor derived from above (must hold in tests): δB(t=1s, BW=1Hz) ≈ 1.2 pT/√Hz, within 4× of Wolf 2015's 0.9 pT/√Hz — acceptable analytic-model approximation given ODMR-CW operation (Wolf used flux concentrators).

2.4 digitiser.rs — ADC + lockin demod

StepModel / defaultSource
Anti-alias4th-order Butterworth, f_c = f_s/2.5Oppenheim & Schafer 3e §7
Samplingf_s = 10 kHz, jitter 100 ns RMSConjecture — DNV-B1 1 kHz × 10 headroom
Quantisation16-bit signed, ±10 µT FS, LSB ≈ 305 pTDNV-B1 datasheet (proxy)
Lockin demody = LP[x·cos(2π f_mod t)], BW = f_s/1000, f_mod = 1 kHzSR830 app note + standard DSP
Output3-axis B in pT, per-axis σ estimate

Lockin is the final SNR-determining stage; Pass 5 pins it empirically.


Section 3 — Six-pass implementation plan

Each pass is one /loop iteration — independently shippable. Gate must pass before next pass begins; if not, abort and replan (§7).

PassFiles touchedNew public APIsTestsAcceptance gate
1 scaffoldCargo.toml, lib.rs, scene.rs, frame.rs, v2/Cargo.tomlScene, DipoleSource, CurrentLoop, FerrousObject, MagFrame, MAG_FRAME_MAGIC6: scene JSON round-trip; magic = 0xC51A_6E70; frame byte order deterministic; serde compiles; empty scene serializes; LoC budget enforcedcargo check -p nvsim clean; 6/6 pass; workspace 1,575+6 = 1,581
2 Biot–Savartsource.rsScene::field_at(point) -> [f64;3]5: on-axis dipole B = μ₀m/(2π z³); equatorial B = -μ₀m/(4π r³); n=8 RMS ≤ 0.5%; loop on-axis B_z = μ₀ I a²/[2(a²+z²)^{3/2}]; r→0 clamp = 0+flagn=8 ≤ 0.5%; else abort §7-1
3 propagationpropagation.rs, lib.rsPropagator::attenuate(B, los_segments) -> [f64;3]4: free-space identity; drywall ≈ 0 dB; concrete 0.5 dB/m; rebar warns + 20 dB/m; NaN-safe on zero LoSAll 4 pass; no NaN any input
4 NV sensorsensor.rsNvSensor::sample(B_in, dt) -> NvReading6: FWHM = 1.0 ± 0.05 MHz; shot noise ∝ 1/√t over 5 decades; T2 envelope = exp(−t/T2); 4-axis LSQ residual < 1%; zero-in + noise-on = zero-mean; floor at 1 µT bias matches Barry 2020 within 2×Floor match ≤ 2×; else abort §7-2
5 digitiser+pipelinedigitiser.rs, pipeline.rsPipeline::new(scene,config).run(n) -> Vec<MagFrame>; Lockin::demod5: (scene, seed=42) → SHA-256 witness; same seed = byte-identical; 1 nT @ 1 kHz vs 1 nT/√Hz floor → SNR ≥ 10 in 1 s; ADC saturates + flags above ±10 µT; anti-alias ≥ 40 dB at f_s/2+1 HzAll 5 pass; SNR floor met
6 proof+benchproof.rs, benches/pipeline_throughput.rs, lib.rs docsProof::generate(), Proof::verify(expected_hash)5: bundle reproduces published expected_mag_features.sha256; x86_64+aarch64 cross-platform OK; criterion ≥ 1 kHz dev; doc 14 xrefs resolve; workspace ≈ 1,606Bench ≥ 1 kHz dev AND ≥ 1 kHz Cortex-A53 (instr-count proxy); else abort §7-3

Cumulative test budget: 6+5+4+6+5+5 = 31 new tests, raising workspace from 1,575 to ~1,606. Branch hygiene: every pass commits to feat/nvsim-pipeline-simulator, subject ends in [nvsim:passN]; no merge to main until all six gates pass.


Section 4 — ruvector integration points

Doc 14 §4.6 did not mandate ruvector. Survey of legitimate uses with honest no-fit calls:

ruvector primitiveUse in nvsimDecision
sha2 (already in workspace)Hash time-series in proof.rsUse direct sha2 dep — not via ruvector
BinaryQuantized 32×Long-form trace storage for regression replay (1 h × 10 kHz: 432 MB f32 → 13.5 MB binary)Use behind features = ["ruvector"] opt-in
HNSW sketchContent-address scenesSkip — SHA-256 of canonical JSON suffices
ruvector-attention / mincutSkip — inference primitives; nvsim is forward-only
quantization for ADCReuse Q_int4Reject as misuse — vector compression, not signal-path ADC. Implement directly.

Net: optional ruvector feature flag enables trace compression in proof.rs only. Default build and witness verification do not depend on ruvector — matches the "leverage where it helps but don't force it" guidance.


Section 5 — Acceptance numbers the simulator commits to

Verbatim, measurable, non-aspirational.

  • Pipeline throughput: ≥ 1 kHz simulated samples per second of wall-clock on a Cortex-A53-class CPU (Pi Zero 2W).
  • Determinism: same (scene, seed) produces byte-identical proof-bundle output across runs and machines.
  • Noise floor reproduction: simulator with shot noise OFF must reproduce the analytical Biot–Savart result to ≤ 0.1% RMS error.
  • Lockin SNR floor: with a 1 nT signal at 1 kHz against a 100 pT/√Hz noise floor, lockin demod recovers SNR ≥ 10 in 1 s integration.

All four are Pass-6 acceptance tests or bench assertions. Determinism uses fixed-seed ChaCha20 + canonical f64 serialisation order.


Section 6 — Out of scope (committed to NOT building)

Explicit non-goals. Ruling them out is half the value of the plan.

ExcludedReason
Single-NV imaging / ODMR scanning microscopyRoom-scale, not nm; doc 14 §4.7
NV-NV entanglement, photonic-crystal cavitiesOut of RuView hardware budget
Diamond growth / NV creation chemistryVendor (Element Six) handles
Cryogenic operationRuView ships RT; doc 14 §2.2
Real hardware control (laser, MW, AOM)Simulator is forward-only
Full Hamiltonian + Lindblad solverDefer to QuTiP if ever needed; doc 14 §3.1
Pulsed dynamical-decoupling sequence designHardware-firmware concern; doc 14 §4.7
fT-floor sensitivityOut of COTS reach 2026; simulator commits to pT-floor
CSI+MAG paired training dataNo ground-truth pairs exist; doc 14 §5
Network transport / live ingestionDefer to wifi-densepose-api

Section 7 — Risk register and abort conditions

Three risks ordered by largest uncaught-downside payoff. Each has a concrete iteration-level abort. If abort fires, loop halts; replan required.

#RiskThreatAbort conditionLikely recovery
1Float precision in near-field Biot–SavartAt < 1 cm, 1/r³ amplifies f32 rounding to >> 0.5%; Pass 2's n=8 analytic test failsPass 2 cannot achieve ≤ 0.5% RMS even after promoting all math to f64 and clamping r_min = 1 mmAdd small-r Taylor expansion guard (unspecified physics — escalate)
2NV shot-noise model mis-cited§2.3 is leading-order; if 1 µT-bias floor differs from Barry 2020 Fig. 8 by > 2×, the simulator is making claims its model cannot backPass 4 noise-floor test fails 2× tolerance at 1 µT(a) include strain-broadening term, or (b) downgrade Section 5 lockin-SNR commitment — escalate
3Pipeline throughput < 1 kHz wall-clockPer-sample cost dominated by Pass 4 LSQ inversion + Pass 5 lockin convolution; on Cortex-A53 (4–6× slower) sub-1 kHz orphans deployabilityPass 6 criterion bench < 1 kHz on x86_64 dev hardware(a) cache pseudo-inverse, (b) IIR lockin, (c) drop f_s to 1 kHz and restate §5 — no auto-merge

Section 8 — How /loop consumes this plan

/loop reads §3, picks the next un-shipped row, ships exactly that pass: (1) read row; (2) verify previous gate PASS via git log --grep '\[nvsim:passN-1\]'; (3) implement only the row's "Files touched"; (4) run row tests + cargo test --workspace --no-default-features; (5) commit, subject ends [nvsim:passN]; (6) stop. Test failure: no commit. §7 abort fires: halt loop, surface to user.


Entry point for /loop on nvsim. Does not commit to building — that decision lives in doc 14's verdict ("lean toward skip" absent hardware target). If the verdict flips, this is the plan that ships.