An interactive Bloch-sphere visualiser for the pulse sequences used in NV-centre magnetometry. Eleven sequences, a live ensemble of spins, the pulse timeline, and the signal plot — all driven by one simulator, so the animated marker on the plot and the curve it sits on always come from the same evolution.
Open index.html in a browser. No build step, no dependencies, no server.
| CW-ODMR | continuous drive and laser, frequency sweep |
| Pulsed ODMR | π pulse then read, narrower lines |
| Rabi | nutation vs pulse length |
| T₁ relaxometry | population decay to the mixed state |
| Ramsey | free-induction decay, T₂* |
| Hahn echo | one π pulse refocuses static dephasing, T₂ |
| AC magnetometry | echo synchronised to an AC field so the phase adds |
| DEER | π pulse on a neighbouring P1 spin; coupling stops refocusing |
| CPMG-N | N identical π pulses |
| XY4 | alternating axes |
| XY8-k | eight-pulse block, repeated |
Honest about its own limits, because a visualiser that quietly idealises things is worse than no visualiser.
- The Bloch sphere is the effective two-level subspace {|0⟩, |−1⟩}: north pole |0⟩ is bright, south pole |−1⟩ is dark. This gets shaky at large θ, where the states mix.
- Resonance frequencies come from exact diagonalisation of
D·Sz² + E(Sx²−Sy²) + γB·S. No high-field or small-angle approximation. - Pulses are rotations about the full effective field
(Ω cos φ, Ω sin φ, Δ), so finite pulse length and off-resonant tilt are included rather than idealised away. A 10 MHz drive costs Ramsey contrast, and you can see it. - The ensemble is ~33 weighted spins with Gaussian-distributed static detunings (99 with ¹⁴N hyperfine on, 66 for DEER, which splits over the two P1 orientations). Dephasing is the fan spreading; an echo is the fan re-converging.
- Decoherence is phenomenological: σ gives the refocusable T₂* part, and an
exp[−(t/T₂)^1.5]envelope withT₂ → T₂·N^(2/3)stands in for the fluctuating bath. It is not a bath simulation. - Optical initialisation is taken as perfect.
- Time is warped for display: rotation angles use real durations, but each segment gets its own wall-clock budget so a 50 ns pulse stays visible next to a 100 µs delay. The timeline says so.
- Off-axis readout contrast is leading-order only. It is ≈1 across the whole exposed range (|B| ≤ 10 mT, any θ) because γB ≪ D, so it is kept in the pipeline but not surfaced as a readout.
Units throughout: MHz, µs, mT.
Drag the sphere to look around. Drag on the signal plot to scrub the swept parameter. Sweep & collect walks it automatically and drops data points onto the theory curve as each readout fires.
space play/pause · ← → step the sweep · r restart
index.html built, self-contained — this is the thing you open
build.py inlines src/ into index.html
src/core.js physics and the sequence registry
src/ui.js renderers, controls, animation loop
src/shell.html markup, CSS, and the two inline points
test/test.js 57 assertions against closed-form results
python3 build.py # src/ -> index.html
node test/test.js # -> ALL PASStest/test.js checks the physics against closed-form answers rather than against
previous output: exact spin-1 diagonalisation in the axial, zero-field and fully
transverse limits; exact π, π/2 and 2π rotations; Ramsey against −cos(2πΔτ);
refocusing to −1 for Hahn, CPMG, XY4 and XY8; 1/e points at τ = T₂ and
τ = T₂·N^(2/3); the AC echo phase against (2/π)γB_ACτ; DEER against
−cos(πaτ) with the coupling inert when the bath pulse is off; and the substepped
animation against the exact single-shot run for every sequence, so what you watch
is what the curve claims.
One note on the XY vs CPMG comparison, since the folk version is wrong: with the state along the CPMG pulse axis, CPMG-8 is the most forgiving of rotation-angle error, because a state parallel to the rotation axis doesn't care about it. Perpendicular, CPMG collapses while XY8 holds. The in-app notes say this rather than repeating "XY always beats CPMG".
GitHub Pages serves this directly — index.html is at the repo root, so enabling
Pages on the default branch is all it takes.