§5 said the browser crate's SIMD validation was done by hand, "which CI does not check". Since "wai-web-wasm: the packages validate with relaxed SIMD and threads off", wai-web-wasm.yml validates both bundles and reach/record/wai_web_bg.wasm with relaxed SIMD and threads disabled. §5 now says so, and says what CI still does not check: that they hold no other SIMD instruction. The changelog gains the CI entry for this branch. The commands that reproduce CI's wasmtime legs ran a stock wasmtime: the WAI Quantum READMEs (English and Chinese), the bit_identity test's header, the WAI README's edge example, and the conformance READMEs (quantum, qasm3, formats, answers, intents). Each now passes -W nan-canonicalization=y -W relaxed-simd-deterministic=y, as CI does. The determinism-cost run scripts under wai/research keep their commands: they record runs already made. The quantum docs check could not see that drift. For a `norun=ci:` block it stripped every leading VAR=value before matching the command against the workflow, and its pattern, \S*, did not read a quoted value. It now reads a quoted or bare value, and when the workflow sets VAR (as an env: key or in a script) the documented value must be one the workflow gives it, so CARGO_TARGET_WASM32_WASIP2_RUNNER=wasmtime is refused against a workflow that sets it to the flagged runner. Co-Authored-By: Claude Opus 5.5 <[email protected]>
582 lines
23 KiB
Rust
582 lines
23 KiB
Rust
//! The crate promises identical results natively, in the browser and at the
|
||
//! edge. Before every transcendental moved in-crate, four of these layers gave
|
||
//! different bits natively and under `wasm32-wasip2`. These digests were taken
|
||
//! natively and confirmed under `wasm32-wasip2`; any platform whose math
|
||
//! differs fails here, not in a user's diff.
|
||
//!
|
||
//! `CARGO_TARGET_WASM32_WASIP2_RUNNER='wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y' cargo test --release --target wasm32-wasip2 --features full`
|
||
#![cfg(all(
|
||
feature = "quantum",
|
||
feature = "quantum_vml",
|
||
feature = "quantum_phasor",
|
||
feature = "quantum_qfhrr",
|
||
feature = "quantum_kernel",
|
||
feature = "quantum_mps",
|
||
feature = "quantum_pauli",
|
||
feature = "quantum_spd",
|
||
feature = "quantum_bptn",
|
||
feature = "quantum_resource",
|
||
feature = "quantum_frame",
|
||
feature = "quantum_tn",
|
||
feature = "quantum_gbs",
|
||
feature = "quantum_sv",
|
||
feature = "quantum_arch",
|
||
feature = "quantum_ldpc",
|
||
feature = "quantum_qio",
|
||
feature = "quantum_uf",
|
||
feature = "quantum_noisy",
|
||
feature = "quantum_dmrg",
|
||
feature = "quantum_tdvp",
|
||
feature = "quantum_chem",
|
||
feature = "quantum_nqs"
|
||
))]
|
||
|
||
use wai_quantum::*;
|
||
|
||
/// FNV-1a over every value's IEEE-754 bits.
|
||
fn digest(xs: &[f64]) -> u64 {
|
||
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
|
||
for x in xs {
|
||
for b in x.to_bits().to_le_bytes() {
|
||
h = (h ^ u64::from(b)).wrapping_mul(0x100_0000_01b3);
|
||
}
|
||
}
|
||
h
|
||
}
|
||
|
||
fn trained() -> quantum_vml::BornFit {
|
||
quantum_vml::train_born(&quantum_vml::parity_target(3), 3, 2, 40, 0.1, 7)
|
||
}
|
||
|
||
#[test]
|
||
fn born_machine_training() {
|
||
let fit = trained();
|
||
let mut v = fit.params.clone();
|
||
v.extend(&fit.loss_curve);
|
||
v.extend(&fit.final_probs);
|
||
v.push(fit.final_kl);
|
||
assert_eq!(digest(&v), 0xd8e5_bd02_2201_2e5d);
|
||
}
|
||
|
||
#[test]
|
||
fn phasor_bridge() {
|
||
let p = quantum_phasor::random_params(3, 5);
|
||
let (a, b, c, d) = quantum_phasor::compare(3, &p, 64);
|
||
let mut v = a;
|
||
v.extend(b);
|
||
v.extend(c);
|
||
v.push(d);
|
||
assert_eq!(digest(&v), 0x08be_30dd_7ce1_9561);
|
||
}
|
||
|
||
#[test]
|
||
fn qfhrr_born_machine() {
|
||
let fit = trained();
|
||
let mut v = quantum_qfhrr::born_probs(3, 2, &fit.params, 4);
|
||
v.push(quantum_qfhrr::tv_vs_exact(3, 2, &fit.params, 4));
|
||
assert_eq!(digest(&v), 0x5407_7c4d_1be0_d985);
|
||
}
|
||
|
||
#[test]
|
||
fn phasor_kernel() {
|
||
let (x, y) = quantum_kernel::circles(200, 3);
|
||
let rff = quantum_kernel::sample_rff(2, 64, 1.0, 9);
|
||
let w = quantum_kernel::fit_ridge(&rff, &x, &y, 1e-3);
|
||
let mut v: Vec<f64> = x.iter().flatten().copied().collect();
|
||
v.extend(&w);
|
||
assert_eq!(digest(&v), 0x58ba_1666_5ef6_fd9e);
|
||
}
|
||
|
||
fn brickwork(gs: quantum::Gateset) -> quantum::Circuit {
|
||
let mut c = quantum::Circuit::with_gateset(8, gs);
|
||
for l in 0..4u8 {
|
||
for q in 0..8 {
|
||
c.h(q);
|
||
c.t(q);
|
||
}
|
||
for q in (l % 2..7).step_by(2) {
|
||
c.cx(q, q + 1);
|
||
}
|
||
}
|
||
c
|
||
}
|
||
|
||
#[test]
|
||
fn mps_truncation() {
|
||
let c = brickwork(quantum::Gateset::V1);
|
||
let (mps, _) = quantum_mps::run(&c, 4).unwrap();
|
||
assert_eq!(digest(&[quantum_mps::fidelity(&mps, &c.simulate().unwrap())]), 0xa807_44ca_a97f_bff8);
|
||
}
|
||
|
||
/// The same truncation against the circuit2 reference statevector. The MPS
|
||
/// itself is an ideal-angle simulation and does not read the phase table, so
|
||
/// only the reference it is compared with changes.
|
||
#[test]
|
||
fn mps_truncation_v2() {
|
||
let c = brickwork(quantum::Gateset::V2);
|
||
let (mps, _) = quantum_mps::run(&c, 4).unwrap();
|
||
assert_eq!(digest(&[quantum_mps::fidelity(&mps, &c.simulate().unwrap())]), 0xe408_42e9_2c89_20ed);
|
||
}
|
||
|
||
#[test]
|
||
fn sparse_pauli_propagation() {
|
||
let (e, _) = quantum_pauli::expect_z(&brickwork(quantum::Gateset::V1), 3, 1e-6).unwrap();
|
||
assert_eq!(digest(&[e]), 0x8cc6_5829_1fb1_8909);
|
||
}
|
||
|
||
#[test]
|
||
fn sparse_pauli_dynamics() {
|
||
let chain: Vec<(u32, u32)> = (0..29).map(|q| (q, q + 1)).collect();
|
||
let circuit = quantum_spd::kicked_ising(30, &chain, 0.5, 6);
|
||
let cfg = quantum_spd::SpdConfig { threshold: 1e-6, max_weight: None };
|
||
let r = quantum_spd::expect_z(&circuit, 7, &cfg).unwrap();
|
||
assert_eq!(digest(&[r.value, r.truncation_bound]), 0xfc44_f4b6_0e70_68b2);
|
||
}
|
||
|
||
#[test]
|
||
fn tensor_network_evolution() {
|
||
// Every qubit's ⟨Z⟩ and the discarded weight after three steps on the
|
||
// 127-qubit lattice at χ = 8: QR, the converged SVD and the parallel
|
||
// layers all feed these bits.
|
||
let edges = quantum_spd::heavy_hex_127();
|
||
let mut t = quantum_bptn::Tns::new(127, &edges, 8).unwrap();
|
||
t.run(&quantum_spd::kicked_ising(127, &edges, 0.6, 3)).unwrap();
|
||
let mut v: Vec<f64> = (0..127).map(|q| t.expect1(q, quantum_spd::Pauli::Z).unwrap()).collect();
|
||
v.push(t.discarded());
|
||
assert_eq!(digest(&v), 0x0e51_33ff_7987_aa26);
|
||
}
|
||
|
||
/// Every figure of the factoring and chemistry estimates on all six reference
|
||
/// qubits: distances, factory rounds, qubit counts, times and error rates.
|
||
/// The rotation T-count goes through the in-crate logarithm.
|
||
#[test]
|
||
fn resource_estimates() {
|
||
use quantum_resource::*;
|
||
let programs = [
|
||
(LogicalCounts { qubits: 12_581, measurements: 1_080_000_000, rotations: 12, rotation_depth: 12, t_gates: 12, ccz: 3_730_000_000 }, 1.0 / 3.0),
|
||
(LogicalCounts { qubits: 1318, measurements: 1_370_000_000, rotations: 206_000_000, rotation_depth: 205_000_000, t_gates: 55_300_000, ccz: 135_000_000_000 }, 0.01),
|
||
];
|
||
let mut v = Vec::new();
|
||
for (c, eps) in programs {
|
||
for q in PhysicalQubit::presets() {
|
||
let e = estimate_best(&c, &q, &EstimateConfig::new(eps)).unwrap();
|
||
v.extend([e.distance as f64, e.steps as f64, e.runtime_ns, e.logical_error, e.t_target]);
|
||
v.extend([e.factories as f64, e.physical_qubits as f64, e.isa.t_per_rotation as f64]);
|
||
for r in &e.factory.unwrap().rounds {
|
||
v.extend([r.distance as f64, r.copies as f64, r.duration_ns, r.failure, r.output_error]);
|
||
}
|
||
}
|
||
}
|
||
assert_eq!(digest(&v), 0xce9d_43b2_d498_f379);
|
||
}
|
||
|
||
/// Detection counts of a sampled surface-code run, the failures its decoding
|
||
/// leaves, and the code model fitted from a small grid: the sampler's random
|
||
/// stream, its logarithm-driven skips, the matching and the fit's `exp`.
|
||
#[test]
|
||
fn frame_sampling_and_matching() {
|
||
use quantum_frame::{parse, sample, surface_code_memory};
|
||
use quantum_match::{fit_code_model, memory_experiment};
|
||
let c = parse(&surface_code_memory(3, 3, 0.01)).unwrap();
|
||
let s = sample(&c, 10_000, 5);
|
||
let mut v: Vec<f64> = (0..s.detectors).map(|d| s.count(d) as f64).collect();
|
||
let mut pts = Vec::new();
|
||
for d in [3, 5] {
|
||
for p in [0.004, 0.008] {
|
||
let t = memory_experiment(d, d, p, 4_000, 9).unwrap();
|
||
v.push(t.failures as f64);
|
||
v.push(t.per_round());
|
||
pts.push(t);
|
||
}
|
||
}
|
||
let f = fit_code_model(&pts).unwrap();
|
||
v.extend([f.prefactor, f.threshold]);
|
||
assert_eq!(digest(&v), 0xcbb3_99f9_6d5c_e8f0);
|
||
}
|
||
|
||
/// A contraction plan found by the seeded hyper-optimiser, and the amplitude
|
||
/// its sliced, threaded contraction gives: the search's random stream and
|
||
/// logarithms, and the fixed-order complex arithmetic.
|
||
#[test]
|
||
fn tensor_network_plan_and_amplitude() {
|
||
use quantum_tn::{complex, contract_plan, find_path, Families, Gate, Network, PathOptions};
|
||
// A 3 × 4 grid, ten layers of fixed two-qubit gates and phased rotations.
|
||
let (w, h) = (4u32, 3u32);
|
||
let mut gates = Vec::new();
|
||
let mut k = 0.0f64;
|
||
for layer in 0..10u32 {
|
||
for q in 0..w * h {
|
||
k += 0.37;
|
||
let (c, s) = (1.0 / (1.0 + k * k).sqrt(), k / (1.0 + k * k).sqrt());
|
||
gates.push(Gate { qubits: vec![q], matrix: vec![complex(c, 0.0), complex(0.0, -s), complex(0.0, -s), complex(c, 0.0)] });
|
||
}
|
||
let horizontal = layer % 2 == 0;
|
||
for y in 0..h {
|
||
for x in 0..w {
|
||
let (a, b) = if horizontal { (y * w + x, y * w + x + 1) } else { (y * w + x, (y + 1) * w + x) };
|
||
let ok = if horizontal { x + 1 < w && (x + layer / 2) % 2 == 0 } else { y + 1 < h && (y + layer / 2) % 2 == 0 };
|
||
if ok {
|
||
// An iSWAP-like gate with a controlled phase.
|
||
let z = complex(0.0, 0.0);
|
||
let one = complex(1.0, 0.0);
|
||
let mi = complex(0.0, -1.0);
|
||
let ph = complex(0.5, -0.866_025_403_784_438_6);
|
||
gates.push(Gate { qubits: vec![a, b], matrix: vec![one, z, z, z, z, z, mi, z, z, mi, z, z, z, z, z, ph] });
|
||
}
|
||
}
|
||
}
|
||
}
|
||
let n = w * h;
|
||
let bits: Vec<u8> = (0..n).map(|q| (q % 3 == 1) as u8).collect();
|
||
let mut net = Network::amplitude(n, &gates, &bits, &[]).unwrap();
|
||
net.simplify();
|
||
let shape = net.shape();
|
||
let free = find_path(&shape, &net.dims, &net.open, &PathOptions { trials: 12, seed: 5, max_size: None, reconfigure: 6, families: Families::Both });
|
||
let cap = free.per_slice.max_size / 8.0;
|
||
let plan = find_path(&shape, &net.dims, &net.open, &PathOptions { trials: 12, seed: 5, max_size: Some(cap), reconfigure: 6, families: Families::Both });
|
||
let amp = contract_plan(&net, &plan).unwrap().data[0];
|
||
let steps: Vec<f64> = plan.path.steps.iter().flat_map(|&(a, b)| [a as f64, b as f64]).collect();
|
||
let mut v = vec![amp.re, amp.im, plan.per_slice.flops, plan.per_slice.max_size, plan.slices];
|
||
v.extend(steps);
|
||
assert_eq!(digest(&v), 0xf39b_7370_3ae9_ea0c);
|
||
}
|
||
|
||
/// A 24 × 24 hafnian and a 14-click torontonian (both summed in fixed chunks,
|
||
/// threaded natively and sequential under WASI), lossy-state probabilities,
|
||
/// exact samples and marginals.
|
||
#[test]
|
||
fn gaussian_boson_sampling() {
|
||
use quantum_gbs::{hafnian, random_unitary, GaussianState, Mat, C};
|
||
let u = random_unitary(24, 5);
|
||
let mut m = Mat::zeros(24);
|
||
for i in 0..24 {
|
||
for j in 0..24 {
|
||
let (x, y) = (u.at(i, j), u.at(j, i));
|
||
m.a[i * 24 + j] = C { re: x.re + y.re, im: x.im + y.im };
|
||
}
|
||
}
|
||
let h = hafnian(&m);
|
||
let r: Vec<f64> = (0..16).map(|i| 0.3 + 0.04 * i as f64).collect();
|
||
let st = GaussianState::squeezed(&r, &random_unitary(16, 6), 0.7).unwrap();
|
||
let pattern: Vec<bool> = (0..16).map(|i| i % 8 != 3).collect();
|
||
let mut v = vec![h.re, h.im, st.click_probability(&pattern).unwrap()];
|
||
let small = GaussianState::squeezed(&[0.8, 0.5, 0.6, 0.4, 0.7], &random_unitary(5, 7), 0.6).unwrap();
|
||
v.push(small.photon_probability(&[2, 0, 1, 1, 0]).unwrap());
|
||
for s in small.sample_clicks(300, 9) {
|
||
v.push(s.iter().enumerate().map(|(i, &b)| if b { (1u32 << i) as f64 } else { 0.0 }).sum());
|
||
}
|
||
let (p1, p2) = small.click_marginals();
|
||
v.extend(p1);
|
||
v.extend(p2);
|
||
assert_eq!(digest(&v), 0x039d_e851_06c3_567d);
|
||
}
|
||
|
||
/// A 16-qubit random circuit at fusion widths 0, 2 and 3: every kernel path
|
||
/// (block-parallel, split by the top bit, split into quarters, general fused,
|
||
/// diagonal), threaded natively and sequential under WASI, and the chunked
|
||
/// reductions (norm, expectation, samples).
|
||
#[test]
|
||
fn dense_state_vector() {
|
||
use gates::Gate;
|
||
use quantum_sv::StateVector;
|
||
let n = 16u32;
|
||
let mut g = Vec::new();
|
||
let mut a = 0.1f64;
|
||
for layer in 0..6u32 {
|
||
for q in 0..n {
|
||
a += 0.731;
|
||
g.push(match (q + layer) % 5 {
|
||
0 => Gate::rx(q, a),
|
||
1 => Gate::ry(q, a),
|
||
2 => Gate::rz(q, a),
|
||
3 => Gate::sqrt_w(q),
|
||
_ => Gate::t(q),
|
||
});
|
||
}
|
||
let mut q = layer % 2;
|
||
while q + 1 < n {
|
||
g.push(if q % 3 == 0 { Gate::fsim(q, q + 1, a, 0.5 * a) } else { Gate::cz(q, q + 1) });
|
||
q += 2;
|
||
}
|
||
g.push(Gate::iswap(0, n - 1));
|
||
}
|
||
let mut v = Vec::new();
|
||
for width in [0usize, 2, 3] {
|
||
let mut sv = StateVector::zero(n).unwrap();
|
||
sv.run(&g, width).unwrap();
|
||
v.extend(sv.re.iter().step_by(97).copied());
|
||
v.extend(sv.im.iter().step_by(89).copied());
|
||
v.push(sv.norm2());
|
||
v.push(sv.expectation(&[(0.5, vec![(0, 'Z'), (7, 'X')]), (1.5, vec![(3, 'Y'), (15, 'Y')])]));
|
||
v.extend(sv.sample(64, width as u64).into_iter().map(|x| x as f64));
|
||
}
|
||
assert_eq!(digest(&v), 0xd0df_e437_5b28_903f);
|
||
}
|
||
|
||
|
||
/// Storage layouts under every fit set, cultivation interpolation (in-crate
|
||
/// `ln` and `exp`), the threshold-to-storage conversion, and the whole factoring
|
||
/// machine at three key sizes.
|
||
#[test]
|
||
fn architecture_estimates() {
|
||
use quantum_arch::*;
|
||
let mut v = Vec::new();
|
||
for model in [StorageModel::si1000(), StorageModel::uniform(), StorageModel::uniform_refit()] {
|
||
for target in [1e-9, 3.7e-13, 1e-15] {
|
||
for b in [
|
||
best_block(&model, Storage::Cold, Yoke::Rows, target, 250),
|
||
best_block(&model, Storage::Hot, Yoke::Rows, target, 250),
|
||
grid_block(&model, 16, target),
|
||
]
|
||
.into_iter()
|
||
.flatten()
|
||
{
|
||
v.push(f64::from(b.distance));
|
||
v.push(b.qubits_per_logical());
|
||
v.push(b.error_per_logical_round);
|
||
}
|
||
}
|
||
}
|
||
for (p, t) in [(5e-4, 3.3e-9), (1e-3, 1e-7), (1e-3, 4.4e-6), (2e-3, 2.2e-5)] {
|
||
let c = cultivate(p, t).unwrap();
|
||
v.push(c.volume);
|
||
v.push(c.error);
|
||
}
|
||
let f = StorageFit::flat_from_threshold(0.023, 0.0100, 1e-3);
|
||
v.push(f.suppression);
|
||
v.push(f.per_patch_round(17, 1, 1));
|
||
for bits in [1024, 2048, 4096] {
|
||
let e = factoring_estimate(&FactoringParams::published(bits).unwrap(), &Machine::published()).unwrap();
|
||
v.push(e.physical_qubits() as f64);
|
||
v.push(e.ccz_rounds);
|
||
v.push(e.hours_per_shot);
|
||
v.push(e.hours_per_shot_rounded);
|
||
v.push(e.shot_survival);
|
||
v.push(e.days);
|
||
v.push(e.toffolis_per_shot);
|
||
}
|
||
assert_eq!(digest(&v), 0xd166_b4c6_c731_7a52);
|
||
}
|
||
|
||
/// A bivariate-bicycle memory: the circuit's error model (probabilities from
|
||
/// the independent-channel conversion), BP over it with in-crate logarithms,
|
||
/// and OSD on the shots BP leaves unexplained.
|
||
#[test]
|
||
fn qldpc_memory_decoding() {
|
||
use quantum_frame::{error_model, parse, sample, Basis};
|
||
use quantum_ldpc::*;
|
||
let c = parse(&memory_circuit(&BbCode::bb72(), 3, 0.005, Basis::Z)).unwrap();
|
||
let model = error_model(&c).unwrap();
|
||
let dec = BpOsd::new(&model, BpOsdConfig { max_iter: 40, ..BpOsdConfig::default() });
|
||
let det = sample(&c, 192, 11);
|
||
let mut v: Vec<f64> = model.mechanisms.iter().step_by(37).map(|m| m.probability).collect();
|
||
let mut osd = 0;
|
||
for s in 0..192 {
|
||
let d = dec.decode(&det.fired(s));
|
||
osd += u32::from(!d.converged);
|
||
v.push(d.observables as f64);
|
||
v.push(f64::from(d.iterations));
|
||
v.push(det.flips(s) as f64);
|
||
}
|
||
assert!(osd > 0, "the sample must exercise OSD");
|
||
assert_eq!(digest(&v), 0x5479_ebe5_f648_b739);
|
||
}
|
||
|
||
/// Optimisation: every simulated-bifurcation variant (batched, with fields
|
||
/// riding on an extra spin), annealing with in-crate `exp`, single-flip
|
||
/// descent, and the exact referee.
|
||
#[test]
|
||
fn quantum_inspired_optimisation() {
|
||
use quantum_qio::*;
|
||
let p = Ising::sk(40, 3);
|
||
let mut v = Vec::new();
|
||
for var in [Variant::Ballistic, Variant::Discrete, Variant::HeatedBallistic(0.0), Variant::HeatedDiscrete(0.0)] {
|
||
for t in simulated_bifurcation(&p, &SbConfig::published(var, 300, 11, 9), 3).trials {
|
||
v.push(t.energy);
|
||
v.extend(t.spins.iter().map(|&s| f64::from(s)));
|
||
}
|
||
}
|
||
let q = Ising::new(9, &[(0, 4, 0.75), (4, 8, -1.5), (1, 2, 1.0), (2, 6, 0.25)], &[0.5, 0.0, -0.3, 0.0, 0.1, 0.0, 0.0, 0.0, -0.9]).unwrap();
|
||
for t in simulated_bifurcation(&q, &SbConfig::published(Variant::Discrete, 200, 5, 2), 2).trials {
|
||
v.push(t.energy);
|
||
}
|
||
for t in simulated_annealing(&p, &SaConfig { sweeps: 200, beta0: 0.1, beta1: 2.5, trials: 6, seed: 4 }, 3).trials {
|
||
v.push(t.energy);
|
||
v.push(descend(&p, &t.spins).energy);
|
||
}
|
||
let g = ground_state(&Ising::sk(18, 5), 4).unwrap();
|
||
v.push(g.energy);
|
||
v.extend(g.spins.iter().map(|&s| f64::from(s)));
|
||
assert_eq!(digest(&v), 0xc01a_7afa_39c0_c487);
|
||
}
|
||
|
||
/// Union-find decoding: event-driven growth on the radix heap, merges and
|
||
/// peeling, over a sampled surface-code memory, with a reused workspace.
|
||
#[test]
|
||
fn union_find_decoding() {
|
||
use quantum_frame::{error_model, parse, sample, surface_code_memory};
|
||
let c = parse(&surface_code_memory(7, 7, 0.006)).unwrap();
|
||
let g = quantum_match::MatchingGraph::from_model(&error_model(&c).unwrap()).unwrap();
|
||
let uf = quantum_uf::UnionFind::new(&g);
|
||
let mut ws = uf.workspace();
|
||
let det = sample(&c, 512, 21);
|
||
let v: Vec<f64> = (0..512).map(|s| uf.decode(&det.fired(s), &mut ws).unwrap() as f64).collect();
|
||
assert!(v.iter().any(|&x| x != 0.0), "the sample must include flips");
|
||
assert_eq!(digest(&v), 0xe43c_2ad4_9244_3118);
|
||
}
|
||
|
||
/// Noisy circuits: trajectories under every channel (Pauli branches, damping
|
||
/// jumps with renormalisation, readout flips), the exact density matrix and
|
||
/// both cross-entropy figures.
|
||
#[test]
|
||
fn noisy_circuits() {
|
||
use gates::Gate;
|
||
use quantum_noisy::*;
|
||
let n = 6u32;
|
||
let mut g = Vec::new();
|
||
let mut a = 0.3f64;
|
||
for layer in 0..5u32 {
|
||
for q in 0..n {
|
||
a += 0.917;
|
||
g.push(if (q + layer) % 2 == 0 { Gate::rx(q, a) } else { Gate::ry(q, a) });
|
||
}
|
||
let mut q = layer % 2;
|
||
while q + 1 < n {
|
||
g.push(Gate::cz(q, q + 1));
|
||
q += 2;
|
||
}
|
||
}
|
||
let noise = NoiseModel { depol1: 0.01, depol2: 0.03, damping: 0.02, dephasing: 0.01, readout: 0.02 };
|
||
let obs = vec![vec![(1.0, vec![(0, 'Z')])], vec![(1.0, vec![(1, 'X'), (4, 'Y')])]];
|
||
let run = trajectories(&g, n, &noise, &obs, 40, 6, 3).unwrap();
|
||
let mut v: Vec<f64> = run.expectations.iter().flat_map(|e| [e.mean, e.stderr]).collect();
|
||
v.extend(run.samples.iter().map(|&x| x as f64));
|
||
let rho = DensityMatrix::run(&g, n, &noise).unwrap();
|
||
v.extend(obs.iter().map(|o| rho.expectation(o)));
|
||
v.push(rho.trace());
|
||
let mut ideal = quantum_sv::StateVector::zero(n).unwrap();
|
||
ideal.run(&g, 0).unwrap();
|
||
v.push(exact_xeb(&ideal, &rho, noise.readout));
|
||
let x = linear_xeb(&ideal, &run.samples);
|
||
v.push(x.mean);
|
||
v.push(x.stderr);
|
||
v.push(digital_fidelity(&g, n, &noise));
|
||
assert_eq!(digest(&v), 0x78b2_ddfb_e7c3_6bea);
|
||
}
|
||
|
||
/// DMRG: the symmetric eigensolver, Lanczos with re-orthogonalisation,
|
||
/// density-matrix truncation and entropies (in-crate `ln`), on two chains;
|
||
/// the free-fermion Ising energy; and exact diagonalisation.
|
||
#[test]
|
||
fn dmrg_ground_states() {
|
||
use quantum_dmrg::*;
|
||
let mut v = Vec::new();
|
||
let cfg = DmrgConfig { max_bond: 16, sweeps: 2, ..DmrgConfig::default() };
|
||
for chain in [Chain::heisenberg(10, 1.0, 0.8, 0.1), Chain::ising(12, 1.0, 0.9)] {
|
||
let r = dmrg(&chain, &cfg);
|
||
v.extend(r.sweep_energies);
|
||
v.push(r.discarded);
|
||
v.extend(r.entropies);
|
||
v.push(exact_ground_energy(&chain).unwrap());
|
||
}
|
||
v.push(ising_exact_energy(40, 1.0, 0.7));
|
||
let a: Vec<f64> = (0..36).map(|k| ((k * 7 % 11) as f64 - 5.0) / (1.0 + (k % 6) as f64)).collect();
|
||
let sym: Vec<f64> = (0..36).map(|k| a[k] + a[(k % 6) * 6 + k / 6]).collect();
|
||
let (vals, vecs) = symmetric_eigen(&sym, 6);
|
||
v.extend(vals);
|
||
v.extend(vecs.iter().map(|x| x.abs()));
|
||
assert_eq!(digest(&v), 0xcdd9_d9fb_5ba6_d71c);
|
||
}
|
||
|
||
#[test]
|
||
fn tdvp_time_evolution() {
|
||
use quantum_dmrg::{Chain, Mps, Op, Scalar, C};
|
||
use quantum_tdvp::*;
|
||
let mut v = Vec::new();
|
||
// A truncated Heisenberg quench from the Néel state, and the Ising quench
|
||
// with its free-fermion referee.
|
||
let n = 10;
|
||
let neel = Mps::basis(&(0..n).map(|i| i % 2 == 1).collect::<Vec<_>>());
|
||
let mut ev = Evolution::new(&Chain::heisenberg(n, 1.0, 0.7, 0.1), &neel, TdvpConfig { dt: 0.1, max_bond: 6, threads: 1, ..TdvpConfig::default() });
|
||
for _ in 0..6 {
|
||
v.push(ev.step().discarded);
|
||
}
|
||
v.extend(ev.expect(Op::z()));
|
||
v.extend(ev.bond_expect(Op::sz(), Op::sz()));
|
||
v.extend(ev.entropies());
|
||
v.push(ev.energy());
|
||
let o = ev.overlap(&neel);
|
||
v.extend([o.re, o.im]);
|
||
let plus = Mps::product(&vec![[C::ONE, C::ONE]; 8]);
|
||
let mut ev = Evolution::new(&Chain::ising(8, 1.0, 0.9), &plus, TdvpConfig { dt: 0.2, max_bond: 16, cutoff: 0.0, threads: 1, ..TdvpConfig::default() });
|
||
for _ in 0..3 {
|
||
ev.step();
|
||
}
|
||
v.extend(ev.expect(Op::x()));
|
||
let ff = ising_quench(8, 1.0, 0.9, ev.time());
|
||
v.extend(ff.x);
|
||
v.extend(ff.zz);
|
||
v.push(ff.echo);
|
||
assert_eq!(digest(&v), 0xe1b4_fd28_af23_50cd);
|
||
}
|
||
|
||
#[test]
|
||
fn chemistry_by_dmrg() {
|
||
use quantum_chem::*;
|
||
let mut v = Vec::new();
|
||
let fd = read_fcidump(include_str!("data/lih.fcidump")).unwrap();
|
||
v.push(fci(&fd).unwrap().energy);
|
||
v.push(hartree_fock_energy(&fd));
|
||
let r = ground_state(&fd, &ChemConfig { max_bond: 10, sweeps: 1, threads: 1, ..ChemConfig::default() });
|
||
v.extend(r.sweep_energies);
|
||
v.push(r.discarded);
|
||
v.extend(r.entropies);
|
||
let mpo = molecular_mpo(&fd, &ChemConfig::default());
|
||
v.extend(mpo.dims.iter().map(|&d| d as f64));
|
||
assert_eq!(digest(&v), 0xe955_a4ff_7eda_9960);
|
||
}
|
||
|
||
#[test]
|
||
fn neural_quantum_states() {
|
||
use quantum_nqs::*;
|
||
let mut v = Vec::new();
|
||
let n = 8;
|
||
let model = SpinModel::heisenberg(n, &chain_edges(n, false), 1.0);
|
||
v.push(model.exact_ground_energy(Some(0)).unwrap());
|
||
let rbm = Rbm::new(n, 2, 0.05, 1).with_marshall_sign(checkerboard(n, 1));
|
||
// Exact expectations, then sampled ones, a few steps each.
|
||
let mut exact = Vmc::new(model.clone(), rbm.clone(), VmcConfig { exact: true, magnetisation: Some(0), lr: 0.1, threads: 1, ..VmcConfig::default() });
|
||
for _ in 0..5 {
|
||
let e = exact.step();
|
||
v.extend([e.energy, e.variance]);
|
||
}
|
||
let mut sampled = Vmc::new(SpinModel::ising(n, &chain_edges(n, false), 1.0, 0.8), Rbm::new(n, 1, 0.1, 2), VmcConfig { samples: 200, chains: 4, threads: 1, ..VmcConfig::default() });
|
||
for _ in 0..3 {
|
||
let e = sampled.step();
|
||
v.extend([e.energy, e.variance, e.error, e.acceptance]);
|
||
}
|
||
v.extend(&sampled.rbm.w);
|
||
// A complex machine on the Majumdar–Ghosh ring, which has no sign rule:
|
||
// complex exponentials, logarithms and arctangents in every step.
|
||
let mg = SpinModel::j1j2(n, &chain_edges(n, true), &chain_next_nearest(n, true), 1.0, 0.5);
|
||
let mut cplx = Vmc::new(mg, Rbm::complex(n, 2, 0.05, 3), VmcConfig { exact: true, magnetisation: Some(0), lr: 0.1, threads: 1, ..VmcConfig::default() });
|
||
for _ in 0..4 {
|
||
let e = cplx.step();
|
||
v.extend([e.energy, e.variance]);
|
||
}
|
||
v.extend(cplx.rbm.w.iter().flat_map(|c| [c.re, c.im]));
|
||
// A projected machine on a 10-site ring (momentum π, odd), started near
|
||
// even: the metric goes singular and the eigendecomposition takes over.
|
||
let ring = SpinModel::heisenberg(10, &chain_edges(10, true), 1.0);
|
||
let rbm = Rbm::complex(10, 1, 0.01, 7).with_symmetry(Symmetry::ring(10, true).with_spin_flip(true));
|
||
let mut proj = Vmc::new(ring, rbm, VmcConfig { exact: true, magnetisation: Some(0), lr: 0.1, threads: 1, ..VmcConfig::default() });
|
||
for k in 0..60 {
|
||
let e = proj.step();
|
||
if k % 10 == 9 {
|
||
v.extend([e.energy, e.variance]);
|
||
}
|
||
}
|
||
v.extend(proj.rbm.b.iter().flat_map(|c| [c.re, c.im]));
|
||
assert_eq!(digest(&v), 0x834c_988e_b34e_814f);
|
||
}
|
||
|