Engine
Rust Engine
The quantum simulator core is written in Rust and compiled to WebAssembly. 442 lines. 12 physics unit tests. All 15 JS-engine bugs corrected.
Why Rust?
Zero GC
No garbage collector — no pauses during tight numerical loops. Critical for VQE which calls the gate loop millions of times.
LLVM SIMD
LLVM auto-vectorises the complex multiply loop. AVX2 processes 4 pairs per cycle.
Safe Memory
Ownership system prevents use-after-free and data races at compile time. No segfaults.
WASM + Native
Same lib.rs compiles to WASM (browser) and native (cargo test). Zero code duplication.
Build Instructions
Prerequisites
# 1. Install Rust toolchain
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
# 2. Add WebAssembly target
rustup target add wasm32-unknown-unknown
# 3. Install wasm-pack
cargo install wasm-pack
# Verify
rustc --version # rustc 1.78.0+
wasm-pack --version # wasm-pack 0.13.0+
WASM Build (for browser/Node.js)
# From the repository root — one command does everything
bash build.sh
# OR manually:
cd quantum_engine
wasm-pack build --target bundler --out-dir ../wasm_pkg --release -- --features wasm
# Output files in wasm_pkg/:
# quantum_engine.js — JavaScript glue code
# quantum_engine_bg.wasm — compiled Rust (~178 KB)
# quantum_engine.d.ts — TypeScript declarations
Run Unit Tests (native, fast)
cd quantum_engine
cargo test
# Expected output:
# running 12 tests
# test tests::s_on_one ... ok
# test tests::bell ... ok
# test tests::cross_shard_cnot ... ok
# test tests::grover_4q ... ok
# test tests::qft_roundtrip ... ok
# ... (12 total)
# test result: ok. 12 passed; 0 failed
Architecture
Complex64 — Zero-overhead arithmetic
#[derive(Clone, Copy)]
pub struct C64 { pub re: f64, pub im: f64 }
impl C64 {
// e^(iθ) = cos θ + i sin θ
pub fn phase(t: f64) -> Self {
Self::new(t.cos(), t.sin())
}
pub fn norm_sq(self) -> f64 {
self.re * self.re + self.im * self.im
}
}
// Rust operator overloading — zero cost, LLVM-optimised
impl std::ops::Mul for C64 {
type Output = Self;
fn mul(self, b: Self) -> Self {
Self::new(
self.re*b.re - self.im*b.im,
self.re*b.im + self.im*b.re
)
}
}
Amps — Sparse/Dense Adaptive Storage
// Automatically switches between sparse and dense
// Sparse: HashMap — O(nonzero) gates
// Dense: Vec (interleaved re/im) — cache-friendly
// Threshold: switch to dense at 12% fill
enum Storage {
Sparse(HashMap<u64, C64>),
Dense(Vec<f64>), // [re0,im0, re1,im1, ...]
}
// Hot path — only non-zero amplitudes visited
pub fn each<F: FnMut(u64, C64)>(&self, mut f: F) {
match &self.s {
Stor::Sp(map) => map.iter().for_each(|(&i,&a)| f(i,a)),
Stor::De(arr) => { /* iterate non-zero entries */ }
}
}
Shard — 10-Qubit Subsystem
Large registers are split into 10-qubit shards. Gates within a shard are local. Cross-shard gates trigger a merge operation.
// BUG FIX #1: HashSet anchors cover both |0⟩ and |1⟩ cases
pub fn apply_1q(&mut self, lq: usize, gate: G2) {
let stride = 1u64 << lq;
let mut anchors: HashSet<u64> = HashSet::new();
self.a.each(|i, _| { anchors.insert(i & !stride); });
// Now covers both i0 (bit=0) and i1 (bit=1) states
for &i0 in &anchors {
let i1 = i0 | stride;
let (a0, a1) = (self.a.get(i0), self.a.get(i1));
self.a.set(i0, gate[0][0]*a0 + gate[0][1]*a1);
self.a.set(i1, gate[1][0]*a0 + gate[1][1]*a1);
}
}
All 15 Bug Fixes
| # | Bug | Symptom | Fix |
|---|---|---|---|
| 1 | apply_1q missed |1⟩ | Gates silent on pure |1⟩ states | HashSet of idx & !stride — covers both bit values |
| 2 | CNOT missed ctrl=1,tgt=1 | Swap skipped when both bits set | Anchor from all ctrl=1 indices |
| 3 | SWAP only one orientation | SWAP|11⟩ wrong | Canonical anchor from both orientations |
| 4 | Toffoli anchor incomplete | Toffoli wrong for nQ>3 | Set-based anchors for all ctrl1=1,ctrl2=1 |
| 5 | mergeShards bit order | Cross-shard gates applied to wrong qubits | ia|(ib<<na) — shard A in LOW bits |
| 6 | statevector returned √prob | Phase information lost | Tensor-product actual {re,im} from Amps |
| 7 | Bit string MSB-first | Qubit 0 mapped to wrong bit | Build string[i] from bit i of idx |
| 8 | iSWAP decomposition | SWAP+S+S gave −|11⟩ | H,CNOT(a→b),CNOT(b→a),H,S,S |
| 9 | state_fidelity no conjugate | F(ψ,ψ) ≠ 1 for complex states | Conjugate bra: re+=a1.re*b.re+a1.im*b.im |
| 10 | QFT phase off by 2 | QFT+IQFT ≠ identity | 2π/2^(k-j+1) not π/2^(k-j) |
| 11 | Grover oracle 2 controls only | Grover fails for nQ>3 | n_controlled_z_all() merges all shards |
| 12 | Grover diffusion CZ(0,1) only | Diffusion wrong for nQ>2 | Same n-controlled-Z fix |
| 13 | RXX/RYY/MS missing | NameError in stdlib | Added via decomposition |
| 14 | pauli_expectation was null | VQE energy calculations broken | Full implementation via clone+rotate |
| 15 | QFT arg order | Inverse QFT received nQ instead of flag | Fixed function signature |
Unit Tests (12)
#[test] fn s_on_one() {
// S|1⟩ = i|1⟩ — was broken in JS engine
let mut q = QReg::new("q",1); q.X(0); q.S(0);
let sv = q.statevector();
assert!((sv[0].re).abs() < 1e-10); // re=0
assert!((sv[0].im - 1.0).abs() < 1e-10); // im=1
}
#[test] fn bell() { /* P(01)=0, P(10)=0 */ }
#[test] fn cross_shard_cnot() { /* q[0]=q[10] always */ }
#[test] fn grover_4q() { /* target found >90% */ }
#[test] fn qft_roundtrip() { /* QFT+IQFT = identity */ }
#[test] fn fidelity_tests() { /* F(ψ,ψ)=1, F(+i,-i)=0 */ }
// ... 6 more tests