deploy(nvsim): include nvsim-pkg WASM artifacts (force past wasm-pack .gitignore)
This commit is contained in:
parent
fdac4bfcda
commit
87bc2fe505
|
|
@ -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).
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* In-browser pipeline. Wraps [`Pipeline`] with JS-friendly construction
|
||||
* (JSON for `Scene` and `PipelineConfig`) and `Vec<u8>` 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<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
|
||||
*
|
||||
* @returns {Promise<InitOutput>}
|
||||
*/
|
||||
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
|
||||
|
|
@ -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<u8>` 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 };
|
||||
Binary file not shown.
|
|
@ -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;
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "nvsim",
|
||||
"type": "module",
|
||||
"collaborators": [
|
||||
"rUv <ruv@ruv.net>",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue