Skip to main content

wai_quantum/
quantum_control.rs

1//! Deterministic FILTER-FUNCTION control + NOISE SPECTROSCOPY —
2//! `wai.quantum.cal.spectroscopy` + `wai.quantum.cal.robust_ff`
3//! (extensions/quantum-ops § Calibration, frequency domain).
4//!
5//! The composite-pulse robust control already in [`crate::quantum_cal`] hardens a
6//! gate against a *static* amplitude error. Real devices are limited by
7//! *correlated, low-frequency* noise — 1/f dephasing above all — and the SOTA
8//! tools address it in the frequency domain via the **filter function**: a
9//! control's first-order sensitivity `F(ω)` to dephasing noise, so the coherence
10//! decay is the overlap `χ = (1/π) ∫ S(ω) F(ω)/ω² dω` of the noise PSD `S(ω)`
11//! with the filter. Two matched capabilities fall straight out of that one
12//! integral, and both run **entirely offline against a model** — no hardware
13//! front-end at all, so they are 100% deterministic-Rust and 100% receiptable:
14//!
15//! - **Noise spectroscopy** ([`reconstruct_psd`]) — a bank of dynamical-decoupling
16//!   sequences are narrow-band filters at different centre frequencies; the
17//!   coherence they each retain measures `S(ω)` there. A Richardson–Lucy
18//!   deconvolution over the filter bank reconstructs the hidden PSD.
19//! - **Filter-function robust control** ([`optimize_dd`]) — given a PSD, reshape a
20//!   DD sequence's pulse timing to push its filter's stopband over where the noise
21//!   lives, minimising `χ` (maximising coherence). The optimiser reshapes CPMG
22//!   toward the noise-avoiding sequence for that spectrum.
23//!
24//! The matched pair: spectroscopy signs a PSD; robust control signs a pulse whose
25//! filter provably avoids that PSD, binding the PSD's hash. Everything is pure
26//! fixed-point integer arithmetic (reusing the calibration floor's `sin`/`cos`/
27//! `exp`), byte-identical on every machine, and seals a `wai.quantum.calibration`
28//! receipt via [`crate::quantum_cal::seal_artifacts`].
29//!
30//! Honest boundary: the qubit response is a clean first-order dephasing model, and
31//! the "measurement" is a deterministic simulator (no hardware). What is not
32//! approximate is the reproducibility of the DSP and the receipt over it.
33
34use crate::quantum_cal::{cos_fx, exp_neg_fx, seal_artifacts, sin_fx, CalArtifacts, CAL_FRAC, CAL_ONE};
35use crate::quantum_ops::{content_hash, CalibrationReceipt, GrantRef};
36use ed25519_dalek::SigningKey;
37
38const ONE: i64 = CAL_ONE;
39const FRAC: u32 = CAL_FRAC;
40const PI_FX: i64 = 3_294_199; // round(π · 2^20)
41const INV_PI_FX: i64 = 333_772; // round(2^20 / π)
42
43#[inline]
44fn fmul(a: i64, b: i64) -> i64 {
45    ((a as i128 * b as i128) >> FRAC) as i64
46}
47#[inline]
48fn fdiv(a: i64, b: i64) -> i64 {
49    if b == 0 {
50        return 0;
51    }
52    (((a as i128) << FRAC) / b as i128) as i64
53}
54
55/// Fixed-point natural log for `x ∈ (0, ONE]`, via `ln x = 2·artanh((x−1)/(x+1))`.
56/// Returns a signed value (≤ 0 for `x ≤ 1`). Deterministic.
57fn ln_fx(x: i64) -> i64 {
58    if x <= 0 {
59        return -20 * ONE; // guard: treat as a large negative log
60    }
61    let z = fdiv(x - ONE, x + ONE); // (x-1)/(x+1) ∈ (-1, 0]
62    let z2 = fmul(z, z);
63    let mut term = z;
64    let mut acc = 0i64;
65    let mut k = 1i64;
66    for _ in 0..12 {
67        acc += term / k;
68        term = fmul(term, z2);
69        k += 2;
70    }
71    2 * acc
72}
73
74fn splitmix64(state: &mut u64) -> u64 {
75    *state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
76    let mut z = *state;
77    z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
78    z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
79    z ^ (z >> 31)
80}
81
82// ===========================================================================
83// Frequency grid + noise PSD model
84// ===========================================================================
85
86/// A discrete angular-frequency grid (fixed-point ω values + spacing). The pulse
87/// interval is normalised to `T = 1` (`ONE`), so ω is in units of `1/T`.
88#[derive(Clone, Debug)]
89pub struct NoiseGrid {
90    pub omega: Vec<i64>,
91    pub dw: i64,
92}
93
94impl NoiseGrid {
95    /// Linear grid of `n` points over `[w_min, w_max]` (given as f64 for setup;
96    /// the grid values themselves are fixed-point and drive all later arithmetic).
97    pub fn linear(w_min: f64, w_max: f64, n: usize) -> NoiseGrid {
98        let wmin = (w_min * ONE as f64) as i64;
99        let wmax = (w_max * ONE as f64) as i64;
100        let dw = (wmax - wmin) / (n as i64 - 1).max(1);
101        let omega = (0..n).map(|i| wmin + i as i64 * dw).collect();
102        NoiseGrid { omega, dw }
103    }
104}
105
106/// A `1/f^α`-plus-Lorentzian dephasing PSD on the grid (fixed-point). `a_over_f`
107/// scales the `1/f` floor; a Lorentzian bump of height `bump` sits at `w0` with
108/// half-width `gamma`. This is the hidden spectrum the spectroscopy recovers.
109pub fn psd_model(g: &NoiseGrid, a_over_f: f64, bump: f64, w0: f64, gamma: f64) -> Vec<i64> {
110    let a = (a_over_f * ONE as f64) as i64;
111    let h = (bump * ONE as f64) as i64;
112    let w0f = (w0 * ONE as f64) as i64;
113    let gf = (gamma * ONE as f64) as i64;
114    let g2 = fmul(gf, gf);
115    g.omega
116        .iter()
117        .map(|&w| {
118            let one_over_f = fdiv(a, w); // A / ω
119            let dwn = w - w0f;
120            let lor = fmul(h, fdiv(g2, fmul(dwn, dwn) + g2)); // bump · γ²/((ω−ω0)²+γ²)
121            one_over_f + lor
122        })
123        .collect()
124}
125
126fn hash_psd(psd: &[i64]) -> [u8; 32] {
127    let mut b = Vec::with_capacity(psd.len() * 8 + 8);
128    b.extend_from_slice(b"wai:qc-psd\x01");
129    for &v in psd {
130        b.extend_from_slice(&v.to_le_bytes());
131    }
132    content_hash(&b)
133}
134
135// ===========================================================================
136// DD sequences + filter functions
137// ===========================================================================
138
139/// Free induction (Ramsey) — no π-pulses.
140pub fn dd_free() -> Vec<i64> {
141    Vec::new()
142}
143/// Hahn echo — one π-pulse at T/2.
144pub fn dd_hahn() -> Vec<i64> {
145    vec![ONE / 2]
146}
147/// CPMG-`n` — `n` equally spaced π-pulses at `(k−½)/n`.
148pub fn dd_cpmg(n: usize) -> Vec<i64> {
149    (1..=n)
150        .map(|k| ((2 * k as i64 - 1) * ONE) / (2 * n as i64))
151        .collect()
152}
153/// UDD-`n` — Uhrig spacing `sin²(πk/(2n+2))`, optimal at nulling low-frequency
154/// noise to high order.
155pub fn dd_udd(n: usize) -> Vec<i64> {
156    (1..=n)
157        .map(|k| {
158            let theta = (PI_FX * k as i64) / (2 * n as i64 + 2);
159            let s = sin_fx(theta);
160            fmul(s, s)
161        })
162        .collect()
163}
164
165/// The filter kernel of a DD sequence on the grid: `kern[i] = (1/π)·|ỹ(ω_i)|²·dω`,
166/// so the coherence overlap against a PSD is simply `χ = Σ_i psd[i]·kern[i]`.
167///
168/// `ỹ(ω) = ∫₀ᵀ y(t) e^{iωt} dt` with the toggling-frame switching function
169/// `y(t) = (−1)^k` between pulses; `|ỹ|² = |Σ_k (−1)^k (e^{iωt_{k+1}} − e^{iωt_k})|² / ω²`.
170pub fn filter_kernel(pulses: &[i64], g: &NoiseGrid) -> Vec<i64> {
171    // boundary-augmented times: 0, t_1..t_n, T
172    let mut t = Vec::with_capacity(pulses.len() + 2);
173    t.push(0i64);
174    t.extend_from_slice(pulses);
175    t.push(ONE);
176    g.omega
177        .iter()
178        .map(|&w| {
179            let (mut re, mut im) = (0i64, 0i64);
180            // Σ_{k=0}^{n} (-1)^k (E_{k+1} - E_k)
181            for k in 0..t.len() - 1 {
182                let sign = if k % 2 == 0 { 1i64 } else { -1 };
183                let p1 = fmul(w, t[k + 1]);
184                let p0 = fmul(w, t[k]);
185                re += sign * (cos_fx(p1) - cos_fx(p0));
186                im += sign * (sin_fx(p1) - sin_fx(p0));
187            }
188            // |ỹ|² = (re²+im²)/ω²  (fixed-point, ·ONE); then ·dω·(1/π)
189            let mag2 = re as i128 * re as i128 + im as i128 * im as i128;
190            let w2 = w as i128 * w as i128;
191            let yy = if w2 == 0 { 0 } else { ((mag2 * ONE as i128) / w2) as i64 };
192            fmul(fmul(yy, g.dw), INV_PI_FX)
193        })
194        .collect()
195}
196
197/// Coherence-decay overlap `χ = Σ_i psd[i]·kern[i]` (fixed-point).
198pub fn overlap(psd: &[i64], kern: &[i64]) -> i64 {
199    psd.iter().zip(kern).map(|(&s, &k)| fmul(s, k)).sum()
200}
201
202/// Retained coherence `C = exp(−χ)`.
203pub fn coherence(chi: i64) -> i64 {
204    exp_neg_fx(chi)
205}
206
207// ===========================================================================
208// Noise spectroscopy — reconstruct S(ω) from a DD filter bank
209// ===========================================================================
210
211/// The reconstructed spectrum plus the (hidden) truth it was recovered from.
212#[derive(Clone, Debug, PartialEq, Eq)]
213pub struct SpectroResult {
214    pub omega: Vec<i64>,
215    pub psd_true: Vec<i64>,
216    pub psd_est: Vec<i64>,
217    /// Number of CPMG sequences in the probe bank.
218    pub n_sequences: usize,
219    /// Mean absolute reconstruction error over the grid (fixed-point).
220    pub error_fx: i64,
221}
222
223/// Reconstruct the PSD from a bank of CPMG filters. Each sequence `n = 1..=n_max`
224/// is a narrow-band filter; we "measure" its retained coherence `C_n` under the
225/// hidden `psd_true` (deterministic, with `shots` of sampling noise), take
226/// `χ_n = −ln C_n`, and run `iters` Richardson–Lucy deconvolution steps (which
227/// preserve positivity) to recover `S(ω)`.
228pub fn reconstruct_psd(
229    g: &NoiseGrid,
230    psd_true: &[i64],
231    n_max: usize,
232    shots: u32,
233    iters: u32,
234    seed: u64,
235) -> SpectroResult {
236    // Build the filter bank and the "measured" decay exponents.
237    let kernels: Vec<Vec<i64>> = (1..=n_max).map(|n| filter_kernel(&dd_cpmg(n), g)).collect();
238    let mut chi_meas = Vec::with_capacity(n_max);
239    for (idx, kern) in kernels.iter().enumerate() {
240        let chi_true = overlap(psd_true, kern);
241        let c_true = coherence(chi_true);
242        // sample P(+1) = (1+C)/2 with `shots`, estimate Ĉ, then χ̂ = −ln Ĉ
243        let mut st = seed.wrapping_mul(0x100_0001).wrapping_add(idx as u64 + 1);
244        let p_plus = (ONE + c_true) / 2;
245        let mut hits = 0u64;
246        for _ in 0..shots {
247            let r = (splitmix64(&mut st) >> (64 - FRAC)) as i64;
248            if r < p_plus {
249                hits += 1;
250            }
251        }
252        let c_hat = (2 * (hits as i128 * ONE as i128) / shots.max(1) as i128 - ONE as i128) as i64;
253        let c_hat = c_hat.clamp(ONE / 100, ONE);
254        chi_meas.push(-ln_fx(c_hat));
255    }
256
257    // Richardson–Lucy: S_i ← S_i · [Σ_n K_{n,i}·(χ_n / (K·S)_n)] / [Σ_n K_{n,i}]
258    let ng = g.omega.len();
259    let denom: Vec<i64> = (0..ng).map(|i| kernels.iter().map(|k| k[i]).sum()).collect();
260    let mut est = vec![ONE / 10; ng]; // flat positive start
261    for _ in 0..iters {
262        // predicted χ per sequence with current estimate
263        let pred: Vec<i64> = kernels.iter().map(|k| overlap(&est, k)).collect();
264        let ratio: Vec<i64> = pred
265            .iter()
266            .zip(&chi_meas)
267            .map(|(&p, &m)| if p <= 0 { ONE } else { fdiv(m, p) })
268            .collect();
269        for i in 0..ng {
270            if denom[i] <= 0 {
271                continue;
272            }
273            let num: i64 = kernels.iter().zip(&ratio).map(|(k, &r)| fmul(k[i], r)).sum();
274            let corr = fdiv(num, denom[i]);
275            est[i] = fmul(est[i], corr).max(0);
276        }
277    }
278
279    let error_fx = {
280        let s: i64 = psd_true
281            .iter()
282            .zip(&est)
283            .map(|(&a, &b)| (a - b).abs())
284            .sum();
285        s / ng.max(1) as i64
286    };
287
288    SpectroResult {
289        omega: g.omega.clone(),
290        psd_true: psd_true.to_vec(),
291        psd_est: est,
292        n_sequences: n_max,
293        error_fx,
294    }
295}
296
297impl SpectroResult {
298    pub fn hash(&self) -> [u8; 32] {
299        let mut h = blake3::Hasher::new();
300        h.update(b"wai:qc-spectro\x01");
301        for &v in &self.psd_est {
302            h.update(&v.to_le_bytes());
303        }
304        *h.finalize().as_bytes()
305    }
306    pub fn artifacts(&self) -> CalArtifacts {
307        let mut cfg = Vec::new();
308        for &v in &self.psd_est {
309            cfg.extend_from_slice(&v.to_le_bytes());
310        }
311        let mut ev = Vec::new();
312        for &v in &self.psd_true {
313            ev.extend_from_slice(&v.to_le_bytes());
314        }
315        CalArtifacts {
316            target: "noise-psd:q0".into(),
317            config_bytes: cfg,
318            evidence_kind: "dd_noise_spectroscopy".into(),
319            evidence_bytes: ev,
320            summary: format!("PSD reconstructed from {} CPMG filters", self.n_sequences),
321        }
322    }
323    pub fn seal(&self, signer: &SigningKey, signer_id: &str, joules_micro: u64, grant: GrantRef) -> CalibrationReceipt {
324        seal_artifacts(signer, signer_id, "sim:transmon:q0", &self.artifacts(), joules_micro, grant, None)
325    }
326}
327
328// ===========================================================================
329// Filter-function robust control — reshape a DD sequence to dodge the PSD
330// ===========================================================================
331
332/// A robust DD sequence optimised against a target PSD.
333#[derive(Clone, Debug, PartialEq, Eq)]
334pub struct RobustResult {
335    /// Optimised pulse timing (fixed-point, in `(0,1)`).
336    pub pulses: Vec<i64>,
337    /// Filter overlap χ of the optimised sequence (lower is better).
338    pub chi_opt: i64,
339    /// Filter overlap χ of equal-spaced CPMG (the baseline).
340    pub chi_cpmg: i64,
341    /// Coherence C = exp(−χ) of optimised vs CPMG.
342    pub coh_opt: i64,
343    pub coh_cpmg: i64,
344    /// Hash of the PSD the pulse is robust against (bound into the receipt).
345    pub psd_hash: [u8; 32],
346    /// Filter kernels for display (optimised, CPMG).
347    pub kern_opt: Vec<i64>,
348    pub kern_cpmg: Vec<i64>,
349}
350
351/// Reshape an `n`-pulse DD sequence to minimise the filter overlap `χ` against
352/// `psd`, by deterministic coordinate descent on the pulse positions (starting
353/// from CPMG, each position nudged within its neighbours by a shrinking step).
354pub fn optimize_dd(g: &NoiseGrid, psd: &[i64], n: usize, rounds: u32) -> RobustResult {
355    let cpmg = dd_cpmg(n);
356    let kern_cpmg = filter_kernel(&cpmg, g);
357    let chi_cpmg = overlap(psd, &kern_cpmg);
358
359    let mut pos = cpmg.clone();
360    let chi_of = |p: &[i64]| overlap(psd, &filter_kernel(p, g));
361    let mut best = chi_of(&pos);
362    let mut step = ONE / 8;
363    for _ in 0..rounds {
364        for i in 0..n {
365            let lo = if i == 0 { ONE / 200 } else { pos[i - 1] + ONE / 200 };
366            let hi = if i == n - 1 { ONE - ONE / 200 } else { pos[i + 1] - ONE / 200 };
367            for &dir in &[-1i64, 1] {
368                let cand = (pos[i] + dir * step).clamp(lo, hi);
369                if cand == pos[i] {
370                    continue;
371                }
372                let save = pos[i];
373                pos[i] = cand;
374                let c = chi_of(&pos);
375                if c < best {
376                    best = c;
377                } else {
378                    pos[i] = save;
379                }
380            }
381        }
382        step = (step * 3) / 5; // shrink
383        if step < ONE / 2000 {
384            break;
385        }
386    }
387
388    let kern_opt = filter_kernel(&pos, g);
389    let chi_opt = overlap(psd, &kern_opt);
390    RobustResult {
391        pulses: pos,
392        chi_opt,
393        chi_cpmg,
394        coh_opt: coherence(chi_opt),
395        coh_cpmg: coherence(chi_cpmg),
396        psd_hash: hash_psd(psd),
397        kern_opt,
398        kern_cpmg,
399    }
400}
401
402impl RobustResult {
403    /// Improvement factor χ_cpmg / χ_opt (fixed-point).
404    pub fn improvement_fx(&self) -> i64 {
405        fdiv(self.chi_cpmg, self.chi_opt.max(1))
406    }
407    pub fn hash(&self) -> [u8; 32] {
408        let mut h = blake3::Hasher::new();
409        h.update(b"wai:qc-robust-ff\x01");
410        for &p in &self.pulses {
411            h.update(&p.to_le_bytes());
412        }
413        h.update(&self.chi_opt.to_le_bytes());
414        *h.finalize().as_bytes()
415    }
416    pub fn artifacts(&self) -> CalArtifacts {
417        let mut cfg = Vec::new();
418        for &p in &self.pulses {
419            cfg.extend_from_slice(&p.to_le_bytes());
420        }
421        // evidence binds the PSD hash + achieved overlap
422        let mut ev = Vec::new();
423        ev.extend_from_slice(&self.psd_hash);
424        ev.extend_from_slice(&self.chi_opt.to_le_bytes());
425        CalArtifacts {
426            target: "robust-dd:q0".into(),
427            config_bytes: cfg,
428            evidence_kind: "filter_function_robust_control".into(),
429            evidence_bytes: ev,
430            summary: format!(
431                "{}-pulse DD, χ {}→{} vs CPMG (filter dodges the pinned PSD)",
432                self.pulses.len(),
433                self.chi_cpmg,
434                self.chi_opt
435            ),
436        }
437    }
438    pub fn seal(&self, signer: &SigningKey, signer_id: &str, joules_micro: u64, grant: GrantRef) -> CalibrationReceipt {
439        seal_artifacts(signer, signer_id, "sim:transmon:q0", &self.artifacts(), joules_micro, grant, None)
440    }
441}
442
443#[cfg(test)]
444mod tests {
445    use super::*;
446
447    fn key(s: u8) -> SigningKey {
448        SigningKey::from_bytes(&[s; 32])
449    }
450    fn grid() -> NoiseGrid {
451        NoiseGrid::linear(0.5, 90.0, 180)
452    }
453    fn f(v: i64) -> f64 {
454        v as f64 / ONE as f64
455    }
456
457    #[test]
458    fn ln_and_coherence_roundtrip() {
459        // exp(-χ) then -ln should recover χ within tolerance.
460        for &chi in &[ONE / 10, ONE / 2, ONE, 2 * ONE] {
461            let c = coherence(chi);
462            let back = -ln_fx(c);
463            assert!((back - chi).abs() < ONE / 20, "χ {} → C {} → {}", f(chi), f(c), f(back));
464        }
465    }
466
467    #[test]
468    fn more_pulses_suppress_low_freq_noise() {
469        // Against a 1/f spectrum, an echo must retain more coherence than free
470        // induction, and CPMG-8 more than a single echo. The physics sanity check.
471        let g = grid();
472        let psd = psd_model(&g, 0.02, 0.0, 0.0, 1.0);
473        let chi_free = overlap(&psd, &filter_kernel(&dd_free(), &g));
474        let chi_hahn = overlap(&psd, &filter_kernel(&dd_hahn(), &g));
475        let chi_cpmg8 = overlap(&psd, &filter_kernel(&dd_cpmg(8), &g));
476        assert!(chi_hahn < chi_free, "echo beats free: {} vs {}", chi_hahn, chi_free);
477        assert!(chi_cpmg8 < chi_hahn, "CPMG-8 beats echo: {} vs {}", chi_cpmg8, chi_hahn);
478    }
479
480    #[test]
481    fn spectroscopy_recovers_hidden_bump() {
482        // A Lorentzian bump on a 1/f floor: the reconstruction should place power
483        // near the true bump location (correlation of shapes is positive).
484        let g = grid();
485        let psd = psd_model(&g, 0.015, 0.5, 40.0, 6.0);
486        let r = reconstruct_psd(&g, &psd, 20, 40_000, 40, 0xC0FFEE);
487        // reconstruction error is a small fraction of the PSD scale
488        let mean_psd: i64 = psd.iter().sum::<i64>() / psd.len() as i64;
489        assert!(
490            r.error_fx < mean_psd,
491            "reconstruction error {} should be below mean PSD {}",
492            r.error_fx, mean_psd
493        );
494        // the estimate is positive everywhere (RL preserves positivity)
495        assert!(r.psd_est.iter().all(|&v| v >= 0));
496    }
497
498    #[test]
499    fn spectroscopy_is_deterministic() {
500        let g = grid();
501        let psd = psd_model(&g, 0.02, 0.3, 30.0, 5.0);
502        let a = reconstruct_psd(&g, &psd, 16, 20_000, 30, 7);
503        let b = reconstruct_psd(&g, &psd, 16, 20_000, 30, 7);
504        assert_eq!(a, b);
505    }
506
507    #[test]
508    fn robust_control_beats_cpmg() {
509        // Reshaping the DD timing must not do worse than CPMG, and against a
510        // low-frequency-weighted spectrum it should do strictly better.
511        let g = grid();
512        let psd = psd_model(&g, 0.04, 0.0, 0.0, 1.0);
513        let r = optimize_dd(&g, &psd, 6, 30);
514        assert!(r.chi_opt <= r.chi_cpmg, "optimised {} must not exceed CPMG {}", r.chi_opt, r.chi_cpmg);
515        assert!(r.coh_opt >= r.coh_cpmg);
516    }
517
518    #[test]
519    fn results_seal_and_verify() {
520        let g = grid();
521        let psd = psd_model(&g, 0.02, 0.4, 35.0, 5.0);
522        let spec = reconstruct_psd(&g, &psd, 16, 20_000, 30, 1);
523        let sr = spec.seal(&key(1), "did:key:lab", 1_000_000, GrantRef::unbounded("quantum.calibrate"));
524        assert!(sr.verify());
525
526        let rob = optimize_dd(&g, &psd, 6, 25);
527        let rr = rob.seal(&key(2), "did:key:lab", 1_000_000, GrantRef::unbounded("quantum.calibrate"));
528        assert!(rr.verify());
529        // the robust receipt binds the exact PSD it was optimised against
530        assert!(rob.artifacts().evidence_bytes.starts_with(&hash_psd(&psd)));
531    }
532}
533