Commit Graph

5 Commits

Author SHA1 Message Date
ruv 2dddd458e7 docs(nvsim): plain-language README — intro, capabilities, comparison, value-prop, usage
Rewrites README from minimal stub to a real crate-front-page. Audience:
sensor researcher / DSP engineer / ML auditor / educator picking nvsim
out of a list of magnetometer simulators and asking "should I use this?"

Structure (per request):
- one-paragraph intro that explains what NV-diamond magnetometers are,
  why simulating them matters, and what nvsim is *not* (no hardware
  control, no fT claims, no Hamiltonian solver)
- four-row "if you are a..." why-might-you-use-it table
- capabilities table for what's shipping today (Passes 1-4) and a
  "not yet shipped" section for Passes 5-6
- comparison table vs Magpylib, QuTiP-NV-scripts, vendor closed sims
- three-point value proposition (forward end-to-end pipeline, strong
  determinism, honest physics)
- usage guide: install, scene-field-at, NvSensor::sample, attenuate,
  MagFrame round-trip
- acceptance commitments (the four plan §5 numbers)
- six primary-source citations (Jackson, Doherty, Barry, Wolf, Cullity,
  Ortner & Bandeira)
- limitations / out-of-scope

Honest framing throughout — keeps the user-corrected wording
"deterministic Rust simulator with explicit physics approximations
and no hidden mocks", marks digitiser/pipeline/proof as 🚧 Pass 5/6
in the comparison table, and explicitly flags the conjectural
defaults in propagation that the implementation already documents
in code.

License unchanged (MIT OR Apache-2.0, workspace default).

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-26 16:52:01 -04:00
ruv 177624174e feat(nvsim): sensor.rs NV ensemble [nvsim:pass4]
Pass 4 of the implementation plan — the load-bearing physics module.
Linear-readout proxy for ODMR ensemble magnetometry per Barry et al.
*Rev. Mod. Phys.* 92, 015004 (2020) §III.A. Full Hamiltonian + Lindblad
dynamics is *out of scope* (plan §6); the leading-order formulae below
are validated as adequate for ensemble magnetometers in the linear
regime.

Public API (re-exported from lib.rs):

- NvSensorConfig — gamma_fwhm_hz / t1_s / t2_s / t2_star_s / contrast /
  n_spins / shot_noise_disabled. Defaults match Barry 2020 Table III
  for COTS bulk diamond.
- NvSensor::cots_defaults() / new(config)
- NvSensor::lorentzian(δν) — normalised Lorentzian, 1.0 on resonance,
  0.5 at half-width
- NvSensor::t2_envelope(t) — exp(-t/T2)
- NvSensor::shot_noise_floor_t_sqrt_hz(t) — δB ∝ 1/(γ_e·C·√(N·t·T2*))
- NvSensor::sample(B_in, dt, seed) -> NvReading — projects B onto 4 NV
  axes, adds shot noise, recovers via LSQ inversion, returns:
    b_recovered, sigma_per_axis, noise_floor_t_sqrt_hz, odmr_nu_plus_hz
- nv_axes() — 4 〈111〉 crystallographic axes (Doherty 2013 §3)

LSQ inversion uses the closed-form (AᵀA) = (4/3) I for the regular
tetrahedron — verified by `nv_axes_form_orthogonal_set_in_aggregate`.

Determinism (plan §5): shot noise is sampled from a ChaCha20 PRNG
seeded explicitly per `sample` call. Same (B_in, dt, seed) ⇒
byte-identical NvReading. New rand + rand_chacha deps, both
crates.io-pinned.

8 new tests:
- lorentzian_fwhm_within_5_percent (FWHM = 1.0 ± 0.05 MHz)
- shot_noise_scales_as_one_over_sqrt_t_over_5_decades
  (Barry 2020 Eq. 35; 5 decades from 1 µs to 100 ms)
- t2_envelope_is_exp_minus_t_over_t2
- lsq_recovery_residual_below_one_percent_with_noise_off
  (4-axis LSQ inversion exactness)
- zero_input_with_noise_yields_approximately_zero_mean
  (n=1024 sample mean ≤ σ_mean of zero per axis)
- shot_noise_floor_within_4x_of_wolf_2015_reference
  (Pass-4 acceptance gate per plan §3 / §7-2)
- determinism_same_seed_produces_byte_identical_reading
- nv_axes_form_orthogonal_set_in_aggregate
  ((AᵀA) = (4/3)I tetrahedron property)

Pass 4 acceptance gate: shot-noise floor at t=1s lands within 4× of
Wolf 2015's 0.9 pT/√Hz bulk-diamond reference. Gate PASSED — no
abort under §7-2.

Validated:
- cargo test -p nvsim → 34 passed (was 26; +8).
- cargo test --workspace --no-default-features → 1,609 passed,
  0 failed, 8 ignored (was 1,601; +8).
- ESP32-S3 on COM7 unaffected.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-26 16:40:49 -04:00
ruv 8c062fbaa4 feat(nvsim): propagation.rs material attenuation [nvsim:pass3]
Pass 3 of the implementation plan. Adds per-material attenuation along
sensor–source line-of-sight segments. Free-space 1/r³ falloff stays in
source.rs (it's part of the dipole formula); this layer applies the
*additional* attenuation when LoS crosses material slabs.

Public API:

- Material enum: Air, Drywall, Brick, ConcreteDry,
  ReinforcedConcrete, SheetSteel
- LosSegment { material, path_m }
- material_loss_db_per_m(Material) -> f64 — table lookup
- material_is_heavy(Material) -> bool — gates HEAVY_ATTENUATION flag
- attenuate(B, segments) -> (Vec3, heavy_flag) — top-level transform
- Propagator struct as a stateless wrapper with room for future
  per-frequency parameters

Per-material loss values (DC–10 kHz) per plan §2.2:
- Air / Drywall / Brick: 0 dB/m (drywall + brick conjectural; no
  systematic primary source for residential-wall magnetic-field
  penetration loss at RuView geometry — gap flagged in plan §6.3)
- ConcreteDry: 0.5 dB/m (Ulrich NDT&E Int. 35, 2002 proxy — also
  conjectural)
- ReinforcedConcrete: 20 dB/m + heavy_flag
- SheetSteel: 100 dB/m representative DC bulk loss + heavy_flag

NaN-safety per Pass-3 acceptance gate: segments with non-finite or
non-positive `path_m` are silently skipped — no NaN/Inf propagates
to the digitiser. Asserted in
test_nan_or_negative_path_is_skipped_without_nan_in_output.

7 new tests:
- free_space_is_identity_transform
- drywall_is_approximately_zero_db
- dry_concrete_attenuates_at_half_db_per_meter
  (1 dB total = 10^(-1/20) ≈ 0.8913 linear)
- reinforced_concrete_attenuates_and_raises_heavy_flag
  (4 dB total = 10^(-0.2) ≈ 0.6310 linear)
- nan_or_negative_path_is_skipped_without_nan_in_output
  — Pass-3 NaN guard
- empty_los_returns_input_unchanged
- propagator_struct_dispatches_to_free_function

Validated:
- cargo test -p nvsim → 26 passed (was 19; +7).
- cargo test --workspace --no-default-features → 1,601 passed,
  0 failed, 8 ignored (was 1,594; +7).
- ESP32-S3 on COM7 streaming live CSI (cb #200, recent reboot).

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-26 16:24:58 -04:00
ruv a6ac08c662 feat(nvsim): source.rs Biot–Savart synthesis [nvsim:pass2]
Pass 2 of the implementation plan. Adds magnetic-field synthesis at
arbitrary sensor locations, all in f64 for near-field stability per
plan §7-1.

Public API (re-exported from lib.rs):

- dipole_field(&DipoleSource, sensor_pos) -> ([f64; 3], near_field_flag)
  Closed-form analytic dipole: B = (μ₀ / 4π r³)[3(m·r̂)r̂ − m]
  (Jackson 3e §5.6).
- current_loop_field(&CurrentLoop, sensor_pos) -> (Vec3, flag)
  Numerical Biot–Savart over n_segments straight chords (default 64);
  flag fires if any chord midpoint < R_MIN_M (1 mm) of sensor.
- ferrous_field(&FerrousObject, ambient_b, sensor_pos) -> (Vec3, flag)
  Linear induced moment m = χ·V·H_ambient (Cullity & Graham 2e §2),
  re-radiates as a dipole.
- scene_field_at(&Scene, sensor_pos) -> (Vec3, flag) — aggregate.
- scene_field_at_sensors(&Scene) -> Vec<(Vec3, flag)> — for every sensor.
- R_MIN_M = 1 mm — near-field clamp constant.

Pass 2 acceptance per plan §3 — n=8 RMS gate ≤ 0.5%. Test
`dipole_n8_directions_within_half_percent_rms` independently
recomputes the formula in-test rather than calling the implementation
twice, so the gate guards against an implementation that
accidentally agrees with a buggy reference.

7 new tests:
- on-axis dipole matches B_z = μ₀ m / (2π z³)
- equatorial dipole matches B_z = -μ₀ m / (4π r³)
- n=8 directions RMS ≤ 0.5% — Pass 2 acceptance gate
- on-axis current loop matches μ₀ I a² / [2(a²+z²)^(3/2)]
- near-field r < 1 mm clamps to (0, flag=true)
- zero-ambient ferrous object emits zero field
- two opposite dipoles aggregate to zero at colocated sensor

Validated:
- cargo test -p nvsim → 19 passed (was 12; +7).
- cargo test --workspace --no-default-features → 1,594 passed,
  0 failed, 8 ignored (was 1,587; +7).
- ESP32-S3 on COM7 streaming live CSI (cb #8900).

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-26 16:11:49 -04:00
ruv 9c95bfac0c feat(nvsim): scaffold + scene + frame [nvsim:pass1]
Pass 1 of the NV-diamond magnetometer pipeline simulator per
docs/research/quantum-sensing/15-nvsim-implementation-plan.md.

Standalone leaf crate at v2/crates/nvsim — deliberately NO internal
RuView dependencies. RuView ecosystem integrations
(wifi-densepose-core frame alignment, ruvector trace compression)
are tracked as Optional Integrations in README and land behind
feature flags after the core simulator ships.

Surfaces shipped:

- scene::Scene — aggregate ground-truth scene (dipoles, current loops,
  ferrous objects, eddy-current discs, sensor positions, ambient field)
- scene::DipoleSource — point magnetic dipole, SI units
- scene::CurrentLoop — planar current loop with 64-segment default
  Biot–Savart discretisation
- scene::FerrousObject — linearly-induced moment from ambient field
  (χ_steel ≈ 5000 default per Cullity & Graham 2e §2)
- scene::EddyCurrent — Faraday + Ohm eddy-current disc primitive
- frame::MagFrame — 60-byte fixed-layout binary record, magic
  0xC51A_6E70 (distinct from ADR-018 CSI 0xC51F... and ADR-084 sketch
  0xC511_0084)
- frame::flag::* — bit-set constants (saturation, ADC clip, heavy
  attenuation, shot-noise-disabled). Raw u16 to avoid pulling
  bitflags as a workspace dep.
- NvsimError — typed errors for parse / serialisation failures
- MU_0, GAMMA_E, D_GS — shared physics constants

12 unit tests covering:
- scene JSON round-trip preserves all primitive types
- magic locked to documented value (0xC51A_6E70)
- frame size fixed at 60 bytes
- frame round-trip is byte-exact
- frame deserialiser rejects short / bad-magic / bad-version inputs
- byte-order determinism across repeated serialisations
- flag set/check helpers

Acceptance per plan §3 Pass 1:
- cargo check -p nvsim --no-default-features → clean
- cargo test -p nvsim --no-default-features → 12 passed (target ≥6)
- Workspace test count 1,575 → 1,587 (+12)
- ESP32-S3 on COM7 unaffected (cb #625100, alive)

Two research documents committed alongside:
- 14-nv-diamond-sensor-simulator.md (469 lines, SOTA + verdict)
- 15-nvsim-implementation-plan.md (268 lines, 6-pass build spec)

Status: Pass 1 only. Passes 2-6 (source, propagation, sensor,
digitiser+pipeline, proof+bench) ship in subsequent commits per the
implementation plan.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-26 15:57:58 -04:00