diff --git a/nvsim/nvsim-pkg/README.md b/nvsim/nvsim-pkg/README.md new file mode 100644 index 00000000..83394ddf --- /dev/null +++ b/nvsim/nvsim-pkg/README.md @@ -0,0 +1,231 @@ +# nvsim + +**Deterministic Rust simulator for NV-diamond ensemble magnetometers.** +Synthesise the magnetic-field trace a real sensor *would have produced* — +without the hardware, the lab, or the $8 K vendor receipt. + +--- + +## What this is, in one paragraph + +NV-diamond magnetometers are exotic but real: they detect magnetic fields by +shining green laser at a diamond and watching how its red fluorescence shifts +under microwave excitation. They are sensitive enough to feel a person's +heartbeat from across a room — when they work. The catch: a working ensemble +sensor costs ~$8 K and lives in a lab. **`nvsim` runs the same forward +pipeline in software**, end-to-end, deterministically, so you can ask "what +would my magnetometer have seen if a steel rebar walked past it" without +wiring up any of it. + +It is **not** a hardware-control stack, microscope simulator, full +Hamiltonian solver, or claim of fT-level sensitivity. This crate does not +control lasers, microwave sources, ADC hardware, or real NV sensors. It is +a deterministic Rust simulator with **explicit physics approximations and +no hidden mocks** — every formula is cited; every conjectural default is +flagged in code; every random number comes from a seeded ChaCha20 PRNG. + +## Why you might use it + +| If you are a… | …`nvsim` lets you… | +|---|---| +| **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 | +| **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 | +| **Educator** teaching magnetometry / NV physics | Run real Biot-Savart, Lorentzian ODMR, and 4-axis projection in Rust without standing up a Python+QuTiP environment | +| **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 | +| **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 | + +## Capabilities (what's shipping today) + +| Capability | What's in the crate | +|---|---| +| **Scene primitives** | `DipoleSource`, `CurrentLoop`, `FerrousObject`, `EddyCurrent`, `Scene` aggregate. JSON round-trip safe. | +| **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. | +| **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). | +| **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. | +| **Determinism** | Same `(B_in, dt, seed)` → byte-identical `NvReading`. ChaCha20-seeded shot noise; no global state, no time-of-day field, no allocator randomness. | +| **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. | + +### Not yet shipped (next two passes) + +- `digitiser.rs` — ADC quantization + 4ᵗʰ-order Butterworth anti-alias + lockin demodulation +- `pipeline.rs` — wires every stage end-to-end and emits a `MagFrame` stream +- `proof.rs` + criterion bench — deterministic SHA-256 witness bundle + ≥ 1 kHz wall-clock throughput target + +These complete the six-pass plan in +`docs/research/quantum-sensing/15-nvsim-implementation-plan.md`. + +## How it compares + +The closest existing tools each cover one slice of what `nvsim` covers +end-to-end. Nothing in the open-source ecosystem (as of early 2026) covers +the whole forward pipeline at once — see +`docs/research/quantum-sensing/14-nv-diamond-sensor-simulator.md` §2.2. + +| Tool | Source synthesis | Material attenuation | NV ensemble physics | Digitiser + lockin | Witness bundle | Language | +|---|---|---|---|---|---|---| +| [Magpylib](https://magpylib.readthedocs.io/) | ✅ analytic dipole + Biot-Savart | ❌ | ❌ | ❌ | ❌ | Python | +| [QuTiP](https://qutip.org/) NV scripts | ❌ | ❌ | ✅ full Hamiltonian + Lindblad | ❌ | ❌ | Python | +| Vendor sims (Element Six, etc.) | partial | partial | ✅ proprietary | partial | ❌ | closed | +| **`nvsim`** | ✅ analytic + Biot-Savart | ✅ 6 materials, NaN-safe | ✅ leading-order ensemble proxy | 🚧 Pass 5 | 🚧 Pass 6 | Rust, deterministic | + +`nvsim` deliberately **does not** try to compete with QuTiP on Hamiltonian +fidelity (full Lindblad solver is plan §6 out-of-scope). It picks the +linear-readout proxy that Barry 2020 §III.A validates as adequate for +ensemble magnetometers in the linear regime, and ships that path +end-to-end with witness-anchored reproducibility. + +## Value proposition + +You get **three things at once** that no other open simulator combines: + +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. +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. +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. + +The cost: `nvsim` is a *forward simulator only*. It does not do inverse +problems (estimating field sources from sensor readings), full Hamiltonian +dynamics, or hardware control. If you need those, you escalate to QuTiP, +COMSOL, or a real lab respectively. + +## Usage guide + +### Install + +```bash +# Inside the workspace: +cargo build -p nvsim --no-default-features +cargo test -p nvsim --no-default-features # currently 34 passing +``` + +`nvsim` is a standalone leaf crate. It depends only on `serde`, `thiserror`, +`tracing`, `rand`, and `rand_chacha`. RuView ecosystem integrations +(`wifi-densepose-core` frame alignment, `ruvector-core` trace compression) +land behind feature flags after the core simulator is shipping. None are +required to use this crate. + +### Synthesize a scene's magnetic field at a sensor + +```rust +use nvsim::{Scene, DipoleSource, scene_field_at}; + +let mut scene = Scene::new(); +// 1 mA·m² dipole at (0,0,0.5 m) pointing along +ẑ +scene.add_dipole(DipoleSource::new([0.0, 0.0, 0.5], [0.0, 0.0, 1.0e-3])); + +// Field at the origin +let (b_tesla, near_field_flag) = scene_field_at(&scene, [0.0, 0.0, 0.0]); +println!("B = {:?} T (near-field saturated: {})", b_tesla, near_field_flag); +``` + +### Run the full sensor model + +```rust +use nvsim::{NvSensor, NvSensorConfig}; + +let sensor = NvSensor::cots_defaults(); +let b_in = [1.0e-9, 0.0, 0.0]; // 1 nT along +x̂ +let dt = 1.0e-3; // 1 ms integration +let seed = 0xCAFE_BABE; + +let reading = sensor.sample(b_in, dt, seed); +println!("recovered B = {:?}", reading.b_recovered); +println!("σ per axis = {:?} T", reading.sigma_per_axis); +println!("δB floor = {:e} T/√Hz", reading.noise_floor_t_sqrt_hz); +``` + +### Apply per-material attenuation + +```rust +use nvsim::{attenuate, LosSegment, Material}; + +let b_in = [1.0e-9, 0.0, 0.0]; +let segments = [ + LosSegment { material: Material::Air, path_m: 1.0 }, + LosSegment { material: Material::Drywall, path_m: 0.1 }, + LosSegment { material: Material::ReinforcedConcrete, path_m: 0.2 }, // raises HEAVY flag +]; +let (b_attenuated, heavy) = attenuate(b_in, &segments); +``` + +### Serialise a binary frame + +```rust +use nvsim::{MagFrame, MAG_FRAME_MAGIC}; +use nvsim::frame::flag; + +let mut f = MagFrame::empty(7); // sensor_id 7 +f.b_pt = [1500.0, -250.0, 800.0]; // pT +f.set_flag(flag::ADC_SATURATED); + +let bytes = f.to_bytes(); // 60 bytes, deterministic +let parsed = MagFrame::from_bytes(&bytes) + .expect("round-trip must succeed"); +assert_eq!(parsed, f); +``` + +## Acceptance commitments (per implementation plan §5) + +These are the four numbers `nvsim` commits to as a finished simulator: + +- **Pipeline throughput**: ≥ 1 kHz simulated samples per second of wall-clock on a Cortex-A53-class CPU. +- **Determinism**: same `(scene, seed)` produces byte-identical proof-bundle output across runs and machines. +- **Noise-floor reproduction**: simulator with shot noise OFF reproduces the analytical Biot-Savart result to ≤ 0.1% RMS. +- **Lockin SNR floor**: 1 nT @ 1 kHz vs 100 pT/√Hz floor → SNR ≥ 10 in 1 s. + +The first and last numbers come online with Pass 5/6. The middle two are +already enforced in the test suite. + +## Physics primary sources + +- Jackson, *Classical Electrodynamics* 3e (1999), §5.4–5.8 — Biot–Savart, dipole field. +- Doherty et al., *Phys. Rep.* 528, 1 (2013) — NV ground-state Hamiltonian, ODMR transition. +- Barry et al., *Rev. Mod. Phys.* 92, 015004 (2020) — NV-ensemble sensitivity, Lorentzian lineshape, T₁/T₂/T₂*, contrast and spin-count defaults. +- Wolf et al., *Phys. Rev. X* 5, 041001 (2015) — bulk-diamond pT/√Hz reference floor used as the sanity-floor test boundary. +- Cullity & Graham, *Introduction to Magnetic Materials* 2e (2009), Ch. 2 — χ_steel for ferrous-object linear-induced moment. +- Ortner & Bandeira, *SoftwareX* 11, 100466 (2020) — Magpylib reference implementation for analytic dipole / current-loop fields. + +For the full SOTA survey and the build/skip verdict, see +`docs/research/quantum-sensing/14-nv-diamond-sensor-simulator.md`. For the +six-pass implementation plan that drives the build, see +`docs/research/quantum-sensing/15-nvsim-implementation-plan.md`. + +## Limitations and out-of-scope + +Per `15-nvsim-implementation-plan.md` §6: + +- Single-NV imaging / ODMR scanning microscopy — `nvsim` is room-scale, not nm. +- Full Lindblad solver, NV-NV entanglement, photonic-crystal cavities — escalate to QuTiP if needed. +- Diamond growth / NV creation chemistry — vendor (Element Six, Adamas) handles. +- Cryogenic operation — RuView ships room-temperature; `nvsim` follows. +- Real hardware control (laser drivers, microwave sources, AOM) — `nvsim` is forward-only. +- Pulsed dynamical-decoupling sequences — defer to dedicated tooling. +- fT-floor sensitivity claims — out of COTS reach in 2026; `nvsim` commits to a pT-floor honestly. +- Inverse problems — given sensor readings, the simulator does not estimate scene parameters back. + +If your use case needs any of the above, `nvsim` is the wrong starting +point. If your use case is *forward simulation of a deterministic NV +magnetometer pipeline you can run in CI*, it is the right one. + +## WebAssembly + +`nvsim` is **WASM-ready by construction**. Zero `std::time` / `std::fs` / +`std::env` / `std::process` / `std::thread` / `Mutex` / `RwLock` calls in +the crate's source — every dependency in the tree (`serde`, `thiserror`, +`tracing`, `rand`, `rand_chacha`, `sha2`, `ndarray`) compiles cleanly to +`wasm32-unknown-unknown`. The shot-noise PRNG is seeded from a +caller-supplied `u64` so no OS-entropy bridge is needed. + +```bash +rustup target add wasm32-unknown-unknown # one-time, on the dev machine +cargo build -p nvsim --target wasm32-unknown-unknown --no-default-features +``` + +Why it matters: cluster-Pi inference, browser-side sensor demos, and +Cloudflare-Worker / Deno-deploy edge workloads can all run the +deterministic pipeline. A 28-byte `MagFrame` shape and a 32-byte SHA-256 +witness make it straightforward to ship simulator output across any +HTTP / WebSocket / IPC channel. + +## License + +MIT OR Apache-2.0 (matches workspace default). diff --git a/nvsim/nvsim-pkg/nvsim.d.ts b/nvsim/nvsim-pkg/nvsim.d.ts new file mode 100644 index 00000000..0b516990 --- /dev/null +++ b/nvsim/nvsim-pkg/nvsim.d.ts @@ -0,0 +1,116 @@ +/* tslint:disable */ +/* eslint-disable */ + +/** + * In-browser pipeline. Wraps [`Pipeline`] with JS-friendly construction + * (JSON for `Scene` and `PipelineConfig`) and `Vec` outputs (raw + * concatenated [`MagFrame`] bytes — 60 bytes/frame, magic `0xC51A_6E70`). + */ +export class WasmPipeline { + free(): void; + [Symbol.dispose](): void; + /** + * nvsim build version (semver from Cargo.toml). + */ + static buildVersion(): string; + /** + * Bytes-per-frame for v1 — `60` today; surfaced so the dashboard + * can advance its parse cursor without re-deriving the layout. + */ + static frameBytes(): number; + /** + * Magic constant for the `MagFrame` v1 binary record. The dashboard's + * hex-dump panel highlights these four bytes (`0xC51A_6E70` → `701A6EC5` + * little-endian) as a sanity check. + */ + static frameMagic(): number; + /** + * Construct from JSON strings + a `seed` (BigInt-friendly; passed in + * as `f64` since wasm-bindgen does not yet ergonomically pass `u64`, + * then bit-cast through `as u64`). The dashboard sends seeds as + * `Number(seed_hex)` from a 32-bit value to fit cleanly. + */ + constructor(scene_json: string, config_json: string, seed: number); + /** + * Run `n_samples` of the pipeline and return the concatenated raw + * `MagFrame` bytes (`n_samples * sensors * 60` bytes). The dashboard + * parses this into typed records on the main thread. + */ + run(n_samples: number): Uint8Array; + /** + * Run + SHA-256 witness in one call. Returns a JS object + * `{ frames: Uint8Array, witness: Uint8Array }`. Same + * `(scene, config, seed)` produces byte-identical `witness` across + * runs, machines, and transports — the regression dashboard pins. + */ + runWithWitness(n_samples: number): any; +} + +/** + * Expected reference witness for `Proof::REFERENCE_SCENE_JSON @ seed=42, + * N=256` — the bytes the dashboard's Verify panel compares against. + */ +export function expectedReferenceWitnessHex(): string; + +/** + * Hex-encode a 32-byte witness for display. + */ +export function hexWitness(witness: Uint8Array): string; + +/** + * Convenience: parse the bundled reference scene to JSON. Lets the + * dashboard's "load reference scene" flow round-trip through the Rust + * type system instead of duplicating the JSON literal in the JS code. + */ +export function referenceSceneJson(): string; + +/** + * Run the canonical reference pipeline (`Proof::generate`) end-to-end and + * return the SHA-256 witness as a 32-byte `Uint8Array`. This is the + * dashboard's source of truth for the Verify-witness panel. + */ +export function referenceWitness(): Uint8Array; + +export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; + +export interface InitOutput { + readonly memory: WebAssembly.Memory; + readonly __wbg_wasmpipeline_free: (a: number, b: number) => void; + readonly expectedReferenceWitnessHex: (a: number) => void; + readonly hexWitness: (a: number, b: number, c: number) => void; + readonly referenceSceneJson: (a: number) => void; + readonly referenceWitness: (a: number) => void; + readonly wasmpipeline_buildVersion: (a: number) => void; + readonly wasmpipeline_frameBytes: () => number; + readonly wasmpipeline_frameMagic: () => number; + readonly wasmpipeline_new: (a: number, b: number, c: number, d: number, e: number, f: number) => void; + readonly wasmpipeline_run: (a: number, b: number, c: number) => void; + readonly wasmpipeline_runWithWitness: (a: number, b: number, c: number) => void; + readonly __wbindgen_export: (a: number) => void; + readonly __wbindgen_add_to_stack_pointer: (a: number) => number; + readonly __wbindgen_export2: (a: number, b: number, c: number) => void; + readonly __wbindgen_export3: (a: number, b: number) => number; + readonly __wbindgen_export4: (a: number, b: number, c: number, d: number) => number; +} + +export type SyncInitInput = BufferSource | WebAssembly.Module; + +/** + * Instantiates the given `module`, which can either be bytes or + * a precompiled `WebAssembly.Module`. + * + * @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated. + * + * @returns {InitOutput} + */ +export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput; + +/** + * If `module_or_path` is {RequestInfo} or {URL}, makes a request and + * for everything else, calls `WebAssembly.instantiate` directly. + * + * @param {{ module_or_path: InitInput | Promise }} module_or_path - Passing `InitInput` directly is deprecated. + * + * @returns {Promise} + */ +export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise } | InitInput | Promise): Promise; diff --git a/nvsim/nvsim-pkg/nvsim.js b/nvsim/nvsim-pkg/nvsim.js new file mode 100644 index 00000000..29235e02 --- /dev/null +++ b/nvsim/nvsim-pkg/nvsim.js @@ -0,0 +1,507 @@ +/* @ts-self-types="./nvsim.d.ts" */ + +/** + * In-browser pipeline. Wraps [`Pipeline`] with JS-friendly construction + * (JSON for `Scene` and `PipelineConfig`) and `Vec` outputs (raw + * concatenated [`MagFrame`] bytes — 60 bytes/frame, magic `0xC51A_6E70`). + */ +export class WasmPipeline { + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + WasmPipelineFinalization.unregister(this); + return ptr; + } + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_wasmpipeline_free(ptr, 0); + } + /** + * nvsim build version (semver from Cargo.toml). + * @returns {string} + */ + static buildVersion() { + let deferred1_0; + let deferred1_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.wasmpipeline_buildVersion(retptr); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + deferred1_0 = r0; + deferred1_1 = r1; + return getStringFromWasm0(r0, r1); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_export2(deferred1_0, deferred1_1, 1); + } + } + /** + * Bytes-per-frame for v1 — `60` today; surfaced so the dashboard + * can advance its parse cursor without re-deriving the layout. + * @returns {number} + */ + static frameBytes() { + const ret = wasm.wasmpipeline_frameBytes(); + return ret >>> 0; + } + /** + * Magic constant for the `MagFrame` v1 binary record. The dashboard's + * hex-dump panel highlights these four bytes (`0xC51A_6E70` → `701A6EC5` + * little-endian) as a sanity check. + * @returns {number} + */ + static frameMagic() { + const ret = wasm.wasmpipeline_frameMagic(); + return ret >>> 0; + } + /** + * Construct from JSON strings + a `seed` (BigInt-friendly; passed in + * as `f64` since wasm-bindgen does not yet ergonomically pass `u64`, + * then bit-cast through `as u64`). The dashboard sends seeds as + * `Number(seed_hex)` from a 32-bit value to fit cleanly. + * @param {string} scene_json + * @param {string} config_json + * @param {number} seed + */ + constructor(scene_json, config_json, seed) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0(scene_json, wasm.__wbindgen_export3, wasm.__wbindgen_export4); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(config_json, wasm.__wbindgen_export3, wasm.__wbindgen_export4); + const len1 = WASM_VECTOR_LEN; + wasm.wasmpipeline_new(retptr, ptr0, len0, ptr1, len1, seed); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + if (r2) { + throw takeObject(r1); + } + this.__wbg_ptr = r0 >>> 0; + WasmPipelineFinalization.register(this, this.__wbg_ptr, this); + return this; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * Run `n_samples` of the pipeline and return the concatenated raw + * `MagFrame` bytes (`n_samples * sensors * 60` bytes). The dashboard + * parses this into typed records on the main thread. + * @param {number} n_samples + * @returns {Uint8Array} + */ + run(n_samples) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.wasmpipeline_run(retptr, this.__wbg_ptr, n_samples); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var v1 = getArrayU8FromWasm0(r0, r1).slice(); + wasm.__wbindgen_export2(r0, r1 * 1, 1); + return v1; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * Run + SHA-256 witness in one call. Returns a JS object + * `{ frames: Uint8Array, witness: Uint8Array }`. Same + * `(scene, config, seed)` produces byte-identical `witness` across + * runs, machines, and transports — the regression dashboard pins. + * @param {number} n_samples + * @returns {any} + */ + runWithWitness(n_samples) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.wasmpipeline_runWithWitness(retptr, this.__wbg_ptr, n_samples); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } +} +if (Symbol.dispose) WasmPipeline.prototype[Symbol.dispose] = WasmPipeline.prototype.free; + +/** + * Expected reference witness for `Proof::REFERENCE_SCENE_JSON @ seed=42, + * N=256` — the bytes the dashboard's Verify panel compares against. + * @returns {string} + */ +export function expectedReferenceWitnessHex() { + let deferred1_0; + let deferred1_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.expectedReferenceWitnessHex(retptr); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + deferred1_0 = r0; + deferred1_1 = r1; + return getStringFromWasm0(r0, r1); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_export2(deferred1_0, deferred1_1, 1); + } +} + +/** + * Hex-encode a 32-byte witness for display. + * @param {Uint8Array} witness + * @returns {string} + */ +export function hexWitness(witness) { + let deferred3_0; + let deferred3_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passArray8ToWasm0(witness, wasm.__wbindgen_export3); + const len0 = WASM_VECTOR_LEN; + wasm.hexWitness(retptr, ptr0, len0); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); + var ptr2 = r0; + var len2 = r1; + if (r3) { + ptr2 = 0; len2 = 0; + throw takeObject(r2); + } + deferred3_0 = ptr2; + deferred3_1 = len2; + return getStringFromWasm0(ptr2, len2); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_export2(deferred3_0, deferred3_1, 1); + } +} + +/** + * Convenience: parse the bundled reference scene to JSON. Lets the + * dashboard's "load reference scene" flow round-trip through the Rust + * type system instead of duplicating the JSON literal in the JS code. + * @returns {string} + */ +export function referenceSceneJson() { + let deferred1_0; + let deferred1_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.referenceSceneJson(retptr); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + deferred1_0 = r0; + deferred1_1 = r1; + return getStringFromWasm0(r0, r1); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_export2(deferred1_0, deferred1_1, 1); + } +} + +/** + * Run the canonical reference pipeline (`Proof::generate`) end-to-end and + * return the SHA-256 witness as a 32-byte `Uint8Array`. This is the + * dashboard's source of truth for the Verify-witness panel. + * @returns {Uint8Array} + */ +export function referenceWitness() { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.referenceWitness(retptr); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +function __wbg_get_imports() { + const import0 = { + __proto__: null, + __wbg___wbindgen_throw_6ddd609b62940d55: function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }, + __wbg_length_ea16607d7b61445b: function(arg0) { + const ret = getObject(arg0).length; + return ret; + }, + __wbg_new_ab79df5bd7c26067: function() { + const ret = new Object(); + return addHeapObject(ret); + }, + __wbg_new_with_length_825018a1616e9e55: function(arg0) { + const ret = new Uint8Array(arg0 >>> 0); + return addHeapObject(ret); + }, + __wbg_set_7eaa4f96924fd6b3: function() { return handleError(function (arg0, arg1, arg2) { + const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2)); + return ret; + }, arguments); }, + __wbg_set_8c0b3ffcf05d61c2: function(arg0, arg1, arg2) { + getObject(arg0).set(getArrayU8FromWasm0(arg1, arg2)); + }, + __wbindgen_cast_0000000000000001: function(arg0) { + // Cast intrinsic for `F64 -> Externref`. + const ret = arg0; + return addHeapObject(ret); + }, + __wbindgen_cast_0000000000000002: function(arg0, arg1) { + // Cast intrinsic for `Ref(String) -> Externref`. + const ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }, + __wbindgen_object_drop_ref: function(arg0) { + takeObject(arg0); + }, + }; + return { + __proto__: null, + "./nvsim_bg.js": import0, + }; +} + +const WasmPipelineFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_wasmpipeline_free(ptr >>> 0, 1)); + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + +function dropObject(idx) { + if (idx < 1028) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function getArrayU8FromWasm0(ptr, len) { + ptr = ptr >>> 0; + return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len); +} + +let cachedDataViewMemory0 = null; +function getDataViewMemory0() { + if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) { + cachedDataViewMemory0 = new DataView(wasm.memory.buffer); + } + return cachedDataViewMemory0; +} + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return decodeText(ptr, len); +} + +let cachedUint8ArrayMemory0 = null; +function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; +} + +function getObject(idx) { return heap[idx]; } + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + wasm.__wbindgen_export(addHeapObject(e)); + } +} + +let heap = new Array(1024).fill(undefined); +heap.push(undefined, null, true, false); + +let heap_next = heap.length; + +function passArray8ToWasm0(arg, malloc) { + const ptr = malloc(arg.length * 1, 1) >>> 0; + getUint8ArrayMemory0().set(arg, ptr / 1); + WASM_VECTOR_LEN = arg.length; + return ptr; +} + +function passStringToWasm0(arg, malloc, realloc) { + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8ArrayMemory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); + const ret = cachedTextEncoder.encodeInto(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + +let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); +cachedTextDecoder.decode(); +const MAX_SAFARI_DECODE_BYTES = 2146435072; +let numBytesDecoded = 0; +function decodeText(ptr, len) { + numBytesDecoded += len; + if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) { + cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + cachedTextDecoder.decode(); + numBytesDecoded = len; + } + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} + +const cachedTextEncoder = new TextEncoder(); + +if (!('encodeInto' in cachedTextEncoder)) { + cachedTextEncoder.encodeInto = function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; + }; +} + +let WASM_VECTOR_LEN = 0; + +let wasmModule, wasm; +function __wbg_finalize_init(instance, module) { + wasm = instance.exports; + wasmModule = module; + cachedDataViewMemory0 = null; + cachedUint8ArrayMemory0 = null; + return wasm; +} + +async function __wbg_load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); + } catch (e) { + const validResponse = module.ok && expectedResponseType(module.type); + + if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') { + console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); + + } else { throw e; } + } + } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + } else { + return instance; + } + } + + function expectedResponseType(type) { + switch (type) { + case 'basic': case 'cors': case 'default': return true; + } + return false; + } +} + +function initSync(module) { + if (wasm !== undefined) return wasm; + + + if (module !== undefined) { + if (Object.getPrototypeOf(module) === Object.prototype) { + ({module} = module) + } else { + console.warn('using deprecated parameters for `initSync()`; pass a single object instead') + } + } + + const imports = __wbg_get_imports(); + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module); + } + const instance = new WebAssembly.Instance(module, imports); + return __wbg_finalize_init(instance, module); +} + +async function __wbg_init(module_or_path) { + if (wasm !== undefined) return wasm; + + + if (module_or_path !== undefined) { + if (Object.getPrototypeOf(module_or_path) === Object.prototype) { + ({module_or_path} = module_or_path) + } else { + console.warn('using deprecated parameters for the initialization function; pass a single object instead') + } + } + + if (module_or_path === undefined) { + module_or_path = new URL('nvsim_bg.wasm', import.meta.url); + } + const imports = __wbg_get_imports(); + + if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) { + module_or_path = fetch(module_or_path); + } + + const { instance, module } = await __wbg_load(await module_or_path, imports); + + return __wbg_finalize_init(instance, module); +} + +export { initSync, __wbg_init as default }; diff --git a/nvsim/nvsim-pkg/nvsim_bg.wasm b/nvsim/nvsim-pkg/nvsim_bg.wasm new file mode 100644 index 00000000..56c52ac3 Binary files /dev/null and b/nvsim/nvsim-pkg/nvsim_bg.wasm differ diff --git a/nvsim/nvsim-pkg/nvsim_bg.wasm.d.ts b/nvsim/nvsim-pkg/nvsim_bg.wasm.d.ts new file mode 100644 index 00000000..e5ef696f --- /dev/null +++ b/nvsim/nvsim-pkg/nvsim_bg.wasm.d.ts @@ -0,0 +1,19 @@ +/* tslint:disable */ +/* eslint-disable */ +export const memory: WebAssembly.Memory; +export const __wbg_wasmpipeline_free: (a: number, b: number) => void; +export const expectedReferenceWitnessHex: (a: number) => void; +export const hexWitness: (a: number, b: number, c: number) => void; +export const referenceSceneJson: (a: number) => void; +export const referenceWitness: (a: number) => void; +export const wasmpipeline_buildVersion: (a: number) => void; +export const wasmpipeline_frameBytes: () => number; +export const wasmpipeline_frameMagic: () => number; +export const wasmpipeline_new: (a: number, b: number, c: number, d: number, e: number, f: number) => void; +export const wasmpipeline_run: (a: number, b: number, c: number) => void; +export const wasmpipeline_runWithWitness: (a: number, b: number, c: number) => void; +export const __wbindgen_export: (a: number) => void; +export const __wbindgen_add_to_stack_pointer: (a: number) => number; +export const __wbindgen_export2: (a: number, b: number, c: number) => void; +export const __wbindgen_export3: (a: number, b: number) => number; +export const __wbindgen_export4: (a: number, b: number, c: number, d: number) => number; diff --git a/nvsim/nvsim-pkg/package.json b/nvsim/nvsim-pkg/package.json new file mode 100644 index 00000000..ea4c4a02 --- /dev/null +++ b/nvsim/nvsim-pkg/package.json @@ -0,0 +1,32 @@ +{ + "name": "nvsim", + "type": "module", + "collaborators": [ + "rUv ", + "WiFi-DensePose Contributors" + ], + "description": "Deterministic NV-diamond magnetometer pipeline simulator (source -> propagation -> NV ensemble -> ADC + lockin demod)", + "version": "0.3.0", + "license": "MIT OR Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/ruvnet/wifi-densepose" + }, + "files": [ + "nvsim_bg.wasm", + "nvsim.js", + "nvsim.d.ts" + ], + "main": "nvsim.js", + "types": "nvsim.d.ts", + "sideEffects": [ + "./snippets/*" + ], + "keywords": [ + "nv-diamond", + "magnetometer", + "simulator", + "physics", + "biot-savart" + ] +} \ No newline at end of file