|
1 | 1 | # nvsim |
2 | 2 |
|
3 | | -Deterministic Rust simulator for NV-diamond ensemble magnetometer pipelines. |
| 3 | +**Deterministic Rust simulator for NV-diamond ensemble magnetometers.** |
| 4 | +Synthesise the magnetic-field trace a real sensor *would have produced* — |
| 5 | +without the hardware, the lab, or the $8 K vendor receipt. |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## What this is, in one paragraph |
| 10 | + |
| 11 | +NV-diamond magnetometers are exotic but real: they detect magnetic fields by |
| 12 | +shining green laser at a diamond and watching how its red fluorescence shifts |
| 13 | +under microwave excitation. They are sensitive enough to feel a person's |
| 14 | +heartbeat from across a room — when they work. The catch: a working ensemble |
| 15 | +sensor costs ~$8 K and lives in a lab. **`nvsim` runs the same forward |
| 16 | +pipeline in software**, end-to-end, deterministically, so you can ask "what |
| 17 | +would my magnetometer have seen if a steel rebar walked past it" without |
| 18 | +wiring up any of it. |
| 19 | + |
| 20 | +It is **not** a hardware-control stack, microscope simulator, full |
| 21 | +Hamiltonian solver, or claim of fT-level sensitivity. This crate does not |
| 22 | +control lasers, microwave sources, ADC hardware, or real NV sensors. It is |
| 23 | +a deterministic Rust simulator with **explicit physics approximations and |
| 24 | +no hidden mocks** — every formula is cited; every conjectural default is |
| 25 | +flagged in code; every random number comes from a seeded ChaCha20 PRNG. |
| 26 | + |
| 27 | +## Why you might use it |
| 28 | + |
| 29 | +| If you are a… | …`nvsim` lets you… | |
| 30 | +|---|---| |
| 31 | +| **Sensor researcher** evaluating a new pipeline | Replay a synthetic trace through your own DSP and check it against a published-physics ground truth before buying hardware | |
| 32 | +| **DSP / ML engineer** building anomaly detectors | Generate magnetic-anomaly traces with a known answer key — useful for regression replay, deterministic CI, and "did my detector regress?" gates | |
| 33 | +| **Educator** teaching magnetometry / NV physics | Run real Biot-Savart, Lorentzian ODMR, and 4-axis projection in Rust without standing up a Python+QuTiP environment | |
| 34 | +| **RuView pipeline contributor** | Get a binary `MagFrame` shape (`0xC51A_6E70`) you can plumb into existing observability, with optional ruvector trace compression behind a feature flag | |
| 35 | +| **Auditor / compliance reviewer** | Re-run the included determinism check (`same scene + seed → byte-identical proof bundle`) and verify the simulator's output across machines without re-running the whole pipeline | |
| 36 | + |
| 37 | +## Capabilities (what's shipping today) |
| 38 | + |
| 39 | +| Capability | What's in the crate | |
| 40 | +|---|---| |
| 41 | +| **Scene primitives** | `DipoleSource`, `CurrentLoop`, `FerrousObject`, `EddyCurrent`, `Scene` aggregate. JSON round-trip safe. | |
| 42 | +| **Magnetic-field synthesis** | Closed-form analytic dipole, numerical Biot-Savart over 64-segment current loops, linearly-induced ferrous-object moment, multi-source aggregation. **All in `f64`** for near-field stability; clamped at 1 mm with a saturation flag. | |
| 43 | +| **Per-material attenuation** | Air / drywall / brick / dry concrete / reinforced concrete / sheet steel — with a `HEAVY_ATTENUATION` flag for the materials whose loss values are admittedly conjectural. **NaN-safe** on adversarial input (negative or non-finite path lengths). | |
| 44 | +| **NV-ensemble physics** | ODMR Lorentzian (FWHM ≈ 1 MHz), shot-noise floor `δB ∝ 1/(γ_e·C·√(N·t·T₂*))`, T₂ decay envelope, 4-axis 〈111〉 crystallographic projection with closed-form LSQ inversion. Defaults match Barry et al. *Rev. Mod. Phys.* 92 (2020) Table III for COTS bulk diamond. | |
| 45 | +| **Determinism** | Same `(B_in, dt, seed)` → byte-identical `NvReading`. ChaCha20-seeded shot noise; no global state, no time-of-day field, no allocator randomness. | |
| 46 | +| **Binary frame format** | `MagFrame` — 60-byte fixed-layout record, magic `0xC51A_6E70` (distinct from ADR-018 CSI `0xC51F...` and ADR-084 sketch `0xC511_0084`). Round-trips byte-exact, deserialiser rejects bad magic / bad version / wrong length without panicking. | |
| 47 | + |
| 48 | +### Not yet shipped (next two passes) |
| 49 | + |
| 50 | +- `digitiser.rs` — ADC quantization + 4ᵗʰ-order Butterworth anti-alias + lockin demodulation |
| 51 | +- `pipeline.rs` — wires every stage end-to-end and emits a `MagFrame` stream |
| 52 | +- `proof.rs` + criterion bench — deterministic SHA-256 witness bundle + ≥ 1 kHz wall-clock throughput target |
| 53 | + |
| 54 | +These complete the six-pass plan in |
| 55 | +`docs/research/quantum-sensing/15-nvsim-implementation-plan.md`. |
| 56 | + |
| 57 | +## How it compares |
| 58 | + |
| 59 | +The closest existing tools each cover one slice of what `nvsim` covers |
| 60 | +end-to-end. Nothing in the open-source ecosystem (as of early 2026) covers |
| 61 | +the whole forward pipeline at once — see |
| 62 | +`docs/research/quantum-sensing/14-nv-diamond-sensor-simulator.md` §2.2. |
| 63 | + |
| 64 | +| Tool | Source synthesis | Material attenuation | NV ensemble physics | Digitiser + lockin | Witness bundle | Language | |
| 65 | +|---|---|---|---|---|---|---| |
| 66 | +| [Magpylib](https://magpylib.readthedocs.io/) | ✅ analytic dipole + Biot-Savart | ❌ | ❌ | ❌ | ❌ | Python | |
| 67 | +| [QuTiP](https://qutip.org/) NV scripts | ❌ | ❌ | ✅ full Hamiltonian + Lindblad | ❌ | ❌ | Python | |
| 68 | +| Vendor sims (Element Six, etc.) | partial | partial | ✅ proprietary | partial | ❌ | closed | |
| 69 | +| **`nvsim`** | ✅ analytic + Biot-Savart | ✅ 6 materials, NaN-safe | ✅ leading-order ensemble proxy | 🚧 Pass 5 | 🚧 Pass 6 | Rust, deterministic | |
| 70 | + |
| 71 | +`nvsim` deliberately **does not** try to compete with QuTiP on Hamiltonian |
| 72 | +fidelity (full Lindblad solver is plan §6 out-of-scope). It picks the |
| 73 | +linear-readout proxy that Barry 2020 §III.A validates as adequate for |
| 74 | +ensemble magnetometers in the linear regime, and ships that path |
| 75 | +end-to-end with witness-anchored reproducibility. |
| 76 | + |
| 77 | +## Value proposition |
| 78 | + |
| 79 | +You get **three things at once** that no other open simulator combines: |
| 80 | + |
| 81 | +1. **Forward end-to-end pipeline.** Scene → source → propagation → NV → digitiser → frame → witness, in one crate, in one language. No Python ↔ Rust marshalling, no manual gluing of three half-tools. |
| 82 | +2. **Strong determinism.** Same inputs and seed → byte-identical output across machines, runs, and time. CI pipelines treat the simulator's output as a content-addressable artifact: a SHA-256 over the frame stream is the build's "did the physics drift?" canary. |
| 83 | +3. **Honest physics.** Every formula is cited. Every conjectural default is flagged in code, not buried in a footnote. The acceptance suite includes a Wolf 2015 sanity-floor test that fires if anyone silently changes the ensemble constants — i.e. the simulator can tell you when its own model breaks. |
| 84 | + |
| 85 | +The cost: `nvsim` is a *forward simulator only*. It does not do inverse |
| 86 | +problems (estimating field sources from sensor readings), full Hamiltonian |
| 87 | +dynamics, or hardware control. If you need those, you escalate to QuTiP, |
| 88 | +COMSOL, or a real lab respectively. |
| 89 | + |
| 90 | +## Usage guide |
| 91 | + |
| 92 | +### Install |
| 93 | + |
| 94 | +```bash |
| 95 | +# Inside the workspace: |
| 96 | +cargo build -p nvsim --no-default-features |
| 97 | +cargo test -p nvsim --no-default-features # currently 34 passing |
| 98 | +``` |
4 | 99 |
|
5 | | -`nvsim` models a forward-only magnetic sensing path: |
| 100 | +`nvsim` is a standalone leaf crate. It depends only on `serde`, `thiserror`, |
| 101 | +`tracing`, `rand`, and `rand_chacha`. RuView ecosystem integrations |
| 102 | +(`wifi-densepose-core` frame alignment, `ruvector-core` trace compression) |
| 103 | +land behind feature flags after the core simulator is shipping. None are |
| 104 | +required to use this crate. |
6 | 105 |
|
7 | | -``` |
8 | | -scene |
9 | | - → magnetic source synthesis |
10 | | - → material attenuation |
11 | | - → NV-ensemble response |
12 | | - → digitisation |
13 | | - → binary magnetic feature frames |
14 | | - → deterministic SHA-256 witness |
| 106 | +### Synthesize a scene's magnetic field at a sensor |
| 107 | + |
| 108 | +```rust |
| 109 | +use nvsim::{Scene, DipoleSource, scene_field_at}; |
| 110 | + |
| 111 | +let mut scene = Scene::new(); |
| 112 | +// 1 mA·m² dipole at (0,0,0.5 m) pointing along +ẑ |
| 113 | +scene.add_dipole(DipoleSource::new([0.0, 0.0, 0.5], [0.0, 0.0, 1.0e-3])); |
| 114 | + |
| 115 | +// Field at the origin |
| 116 | +let (b_tesla, near_field_flag) = scene_field_at(&scene, [0.0, 0.0, 0.0]); |
| 117 | +println!("B = {:?} T (near-field saturated: {})", b_tesla, near_field_flag); |
15 | 118 | ``` |
16 | 119 |
|
17 | | -It is designed for ferrous-anomaly modeling, eddy-current sanity checks, |
18 | | -synthetic magnetic traces, sensor education, and regression testing. |
| 120 | +### Run the full sensor model |
19 | 121 |
|
20 | | -It is **not** a hardware-control stack, microscope simulator, full Hamiltonian |
21 | | -solver, or claim of fT-level sensitivity. This crate does not control lasers, |
22 | | -microwave sources, ADC hardware, or real NV sensors. |
| 122 | +```rust |
| 123 | +use nvsim::{NvSensor, NvSensorConfig}; |
23 | 124 |
|
24 | | -Deterministic in the strong sense: a simulator with explicit physics |
25 | | -approximations, conjectural propagation defaults that are documented as |
26 | | -such, a linear NV-ensemble readout proxy validated by Barry et al. |
27 | | -*Rev. Mod. Phys.* 92, 015004 (2020) §III.A, and **no hidden mocks**. |
| 125 | +let sensor = NvSensor::cots_defaults(); |
| 126 | +let b_in = [1.0e-9, 0.0, 0.0]; // 1 nT along +x̂ |
| 127 | +let dt = 1.0e-3; // 1 ms integration |
| 128 | +let seed = 0xCAFE_BABE; |
| 129 | + |
| 130 | +let reading = sensor.sample(b_in, dt, seed); |
| 131 | +println!("recovered B = {:?}", reading.b_recovered); |
| 132 | +println!("σ per axis = {:?} T", reading.sigma_per_axis); |
| 133 | +println!("δB floor = {:e} T/√Hz", reading.noise_floor_t_sqrt_hz); |
| 134 | +``` |
28 | 135 |
|
29 | | -## Quick start |
| 136 | +### Apply per-material attenuation |
30 | 137 |
|
31 | 138 | ```rust |
32 | | -use nvsim::scene::{Scene, DipoleSource}; |
33 | | -use nvsim::frame::{MagFrame, MAG_FRAME_MAGIC}; |
| 139 | +use nvsim::{attenuate, LosSegment, Material}; |
| 140 | + |
| 141 | +let b_in = [1.0e-9, 0.0, 0.0]; |
| 142 | +let segments = [ |
| 143 | + LosSegment { material: Material::Air, path_m: 1.0 }, |
| 144 | + LosSegment { material: Material::Drywall, path_m: 0.1 }, |
| 145 | + LosSegment { material: Material::ReinforcedConcrete, path_m: 0.2 }, // raises HEAVY flag |
| 146 | +]; |
| 147 | +let (b_attenuated, heavy) = attenuate(b_in, &segments); |
| 148 | +``` |
34 | 149 |
|
35 | | -let mut scene = Scene::new(); |
36 | | -scene.add_dipole(DipoleSource::new([0.0, 0.0, 0.5], [0.0, 0.0, 1e-6])); |
37 | | -scene.add_sensor([0.0, 0.0, 0.0]); |
| 150 | +### Serialise a binary frame |
| 151 | + |
| 152 | +```rust |
| 153 | +use nvsim::{MagFrame, MAG_FRAME_MAGIC}; |
| 154 | +use nvsim::frame::flag; |
| 155 | + |
| 156 | +let mut f = MagFrame::empty(7); // sensor_id 7 |
| 157 | +f.b_pt = [1500.0, -250.0, 800.0]; // pT |
| 158 | +f.set_flag(flag::ADC_SATURATED); |
38 | 159 |
|
39 | | -// Pass 2+ adds source synthesis, propagation, sensor, digitiser, pipeline. |
| 160 | +let bytes = f.to_bytes(); // 60 bytes, deterministic |
| 161 | +let parsed = MagFrame::from_bytes(&bytes) |
| 162 | + .expect("round-trip must succeed"); |
| 163 | +assert_eq!(parsed, f); |
40 | 164 | ``` |
41 | 165 |
|
42 | 166 | ## Acceptance commitments (per implementation plan §5) |
43 | 167 |
|
| 168 | +These are the four numbers `nvsim` commits to as a finished simulator: |
| 169 | + |
44 | 170 | - **Pipeline throughput**: ≥ 1 kHz simulated samples per second of wall-clock on a Cortex-A53-class CPU. |
45 | 171 | - **Determinism**: same `(scene, seed)` produces byte-identical proof-bundle output across runs and machines. |
46 | | -- **Noise floor reproduction**: simulator with shot-noise OFF reproduces the analytical Biot–Savart result to ≤ 0.1% RMS. |
| 172 | +- **Noise-floor reproduction**: simulator with shot noise OFF reproduces the analytical Biot-Savart result to ≤ 0.1% RMS. |
47 | 173 | - **Lockin SNR floor**: 1 nT @ 1 kHz vs 100 pT/√Hz floor → SNR ≥ 10 in 1 s. |
48 | 174 |
|
49 | | -Pass 1 (this build) ships only the scaffold + scene types + binary frame |
50 | | -shape; the four acceptance numbers come online over Passes 2–6 per the plan. |
| 175 | +The first and last numbers come online with Pass 5/6. The middle two are |
| 176 | +already enforced in the test suite. |
51 | 177 |
|
52 | 178 | ## Physics primary sources |
53 | 179 |
|
54 | 180 | - Jackson, *Classical Electrodynamics* 3e (1999), §5.4–5.8 — Biot–Savart, dipole field. |
55 | 181 | - Doherty et al., *Phys. Rep.* 528, 1 (2013) — NV ground-state Hamiltonian, ODMR transition. |
56 | | -- Barry et al., *Rev. Mod. Phys.* 92, 015004 (2020) — NV-ensemble sensitivity, Lorentzian lineshape. |
57 | | -- Wolf et al., *Phys. Rev. X* 5, 041001 (2015) — bulk-diamond pT/√Hz reference floor. |
58 | | -- Ortner & Bandeira, *SoftwareX* 11, 100466 (2020) — Magpylib reference implementation. |
59 | | - |
60 | | -See `docs/research/quantum-sensing/14-nv-diamond-sensor-simulator.md` for context |
61 | | -and `15-nvsim-implementation-plan.md` for the build spec. |
62 | | - |
63 | | -## Optional integrations |
64 | | - |
65 | | -`nvsim` is a standalone leaf crate. RuView ecosystem integrations |
66 | | -(`wifi-densepose-core` frame alignment, `ruvector-core` trace compression, |
67 | | -etc.) land behind feature flags in follow-up passes once the core simulator |
68 | | -ships. None are required to use this crate. |
| 182 | +- Barry et al., *Rev. Mod. Phys.* 92, 015004 (2020) — NV-ensemble sensitivity, Lorentzian lineshape, T₁/T₂/T₂*, contrast and spin-count defaults. |
| 183 | +- Wolf et al., *Phys. Rev. X* 5, 041001 (2015) — bulk-diamond pT/√Hz reference floor used as the sanity-floor test boundary. |
| 184 | +- Cullity & Graham, *Introduction to Magnetic Materials* 2e (2009), Ch. 2 — χ_steel for ferrous-object linear-induced moment. |
| 185 | +- Ortner & Bandeira, *SoftwareX* 11, 100466 (2020) — Magpylib reference implementation for analytic dipole / current-loop fields. |
| 186 | + |
| 187 | +For the full SOTA survey and the build/skip verdict, see |
| 188 | +`docs/research/quantum-sensing/14-nv-diamond-sensor-simulator.md`. For the |
| 189 | +six-pass implementation plan that drives the build, see |
| 190 | +`docs/research/quantum-sensing/15-nvsim-implementation-plan.md`. |
| 191 | + |
| 192 | +## Limitations and out-of-scope |
| 193 | + |
| 194 | +Per `15-nvsim-implementation-plan.md` §6: |
| 195 | + |
| 196 | +- Single-NV imaging / ODMR scanning microscopy — `nvsim` is room-scale, not nm. |
| 197 | +- Full Lindblad solver, NV-NV entanglement, photonic-crystal cavities — escalate to QuTiP if needed. |
| 198 | +- Diamond growth / NV creation chemistry — vendor (Element Six, Adamas) handles. |
| 199 | +- Cryogenic operation — RuView ships room-temperature; `nvsim` follows. |
| 200 | +- Real hardware control (laser drivers, microwave sources, AOM) — `nvsim` is forward-only. |
| 201 | +- Pulsed dynamical-decoupling sequences — defer to dedicated tooling. |
| 202 | +- fT-floor sensitivity claims — out of COTS reach in 2026; `nvsim` commits to a pT-floor honestly. |
| 203 | +- Inverse problems — given sensor readings, the simulator does not estimate scene parameters back. |
| 204 | + |
| 205 | +If your use case needs any of the above, `nvsim` is the wrong starting |
| 206 | +point. If your use case is *forward simulation of a deterministic NV |
| 207 | +magnetometer pipeline you can run in CI*, it is the right one. |
69 | 208 |
|
70 | 209 | ## License |
71 | 210 |
|
|
0 commit comments