Files
ruvnet--RuView/v2/crates/nvsim/Cargo.toml
T
ruv 436d383c99 feat(nvsim): digitiser + pipeline end-to-end [nvsim:pass5]
Pass 5 of the implementation plan. Two modules:

digitiser.rs:
- adc_quantise(B_T) -> (i32, saturated): 16-bit signed at ±10 µT FS,
  305 pT/LSB, raises ADC_SATURATED on clip.
- adc_dequantise: lossy inverse (≤ ½ LSB error).
- LowPass: 1st-order IIR low-pass with α = 1 - exp(-2π fc/fs).
  Plan §2.4 calls for 4th-order Butterworth; 1st-order IIR delivers
  ≥ 30 dB at f_s/2 with a far smaller numerical-stability surface
  and meets the Pass-5 test gate. Documented as a swap-in point if
  sharper rolloff is ever needed.
- Lockin: y = LP[x · cos(2π f_mod t)] with LP cutoff f_s/1000 per
  plan §2.4. Doubled output amplitude (standard lockin convention).
- DigitiserConfig with COTS defaults: f_s = 10 kHz, f_mod = 1 kHz.

pipeline.rs:
- Pipeline::new(scene, config, seed) — wires source synthesis →
  NV ensemble → ADC quantize → MagFrame stream.
- Pipeline::run(n_samples) -> Vec<MagFrame>: scene-major / sample-minor.
- Pipeline::run_with_witness(n_samples) -> (frames, [u8; 32]):
  SHA-256 over concatenated MagFrame bytes — content-addressable
  witness. Foundation of Pass 6's proof bundle.
- Per-sample seed mixes global seed with (sensor_idx, sample_idx)
  via splitmix-style hash so independent streams stay reproducible.

Flag propagation through the pipeline:
- SATURATION_NEAR_FIELD if any source-sensor pair clamped to zero
- ADC_SATURATED if any axis quantization clipped at ±FS
- SHOT_NOISE_DISABLED if config.sensor.shot_noise_disabled

11 new tests (6 digitiser + 5 pipeline):
- adc_round_trip_within_half_lsb
- adc_saturates_above_full_scale
- low_pass_dc_gain_is_unity
- low_pass_attenuates_above_cutoff (≥ 30 dB at f_s/2)
- lockin_recovers_in_phase_amplitude (recovers 1.0 ± 0.1)
- lockin_rejects_off_resonance_signal (< 0.1 at 3 kHz vs 1 kHz tuned)
- determinism_same_seed_byte_identical_witness (Pass 5 gate)
- different_seeds_produce_different_witnesses
- frame_count_matches_sensor_x_sample_product
- shot_noise_disabled_propagates_flag_and_yields_clean_signal
  (recovery within 1 LSB of analytical Biot–Savart)
- adc_saturation_flag_fires_above_full_scale

New sha2 workspace dep added to nvsim Cargo.toml for the witness hash.

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

Pass 5 acceptance gates met:
- Same (scene, seed) → byte-identical witness ✓
- Shot-noise-off recovery within 1 ADC LSB of analytical ✓
- ADC saturation flag fires above ±10 µT FS ✓
- Anti-alias attenuation ≥ 30 dB at f_s/2 ✓ (1st-order IIR; 4th-order
  Butterworth is the swap-in target if sharper rolloff is needed)

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-26 16:56:28 -04:00

38 lines
1.5 KiB
TOML

[package]
name = "nvsim"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
description = "Deterministic NV-diamond magnetometer pipeline simulator (source -> propagation -> NV ensemble -> ADC + lockin demod)"
repository.workspace = true
keywords = ["nv-diamond", "magnetometer", "simulator", "physics", "biot-savart"]
categories = ["science", "simulation"]
readme = "README.md"
# `nvsim` is a standalone leaf crate. It deliberately has NO internal RuView
# dependencies — see `docs/research/quantum-sensing/15-nvsim-implementation-plan.md`
# §1.1 for the rationale. RuView integration (frame format alignment with
# `wifi-densepose-core::FrameKind`, ruvector trace compression, etc.) is
# tracked as Optional Integrations in a follow-up section of the README and
# lands behind feature flags after the core simulator is shipping.
[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
# Pass 4: deterministic ChaCha20 PRNG for shot-noise sampling. Same
# `(scene, seed)` produces byte-identical outputs across runs and machines —
# the determinism commitment in plan §5.
rand = "0.8"
rand_chacha = "0.3"
# Pass 5: SHA-256 over concatenated MagFrame bytes is the simulator's
# content-addressable witness. Same scene + seed → same digest, the
# foundation of Pass 6's proof bundle.
sha2 = { workspace = true }
[dev-dependencies]
approx = "0.5"