Skip to main content

wai_quantum/
quantum_compile.rs

1//! Deterministic circuit COMPILATION + equivalence proof — `wai.quantum.compile`
2//! (extensions/quantum-ops § Compilation).
3//!
4//! A quantum compiler routes a circuit onto a device's coupling graph — inserting
5//! SWAPs so every two-qubit gate acts on physically-connected qubits — and remaps
6//! qubits. Whether the routed circuit still computes the same thing is a
7//! correctness obligation that today is checked, if at all, by an unsigned,
8//! non-reproducible tool. This module makes it a **signed certificate**: a
9//! deterministic router plus a byte-exact **Clifford stabilizer-tableau**
10//! equivalence check, sealed into a [`crate::quantum_ops::CompileReceipt`].
11//!
12//! Two Clifford circuits implement the same unitary (up to global phase) iff they
13//! conjugate every Pauli identically — i.e. they map the `2n` generators
14//! `{X_i, Z_i}` to the same signed Paulis. The tableau tracks those `2n` images
15//! under `H`, `S`, `CX`, `SWAP` by the Aaronson–Gottesman update rules; equivalence
16//! is a row-by-row comparison. Routing preserves semantics up to the final qubit
17//! permutation, so the certificate checks `routed ++ (swaps restoring identity) ≡
18//! source` — all through the same deterministic simulator, no hand-rolled Pauli
19//! bookkeeping. Byte-identical on every machine; the verdict reproduces from the
20//! circuits alone.
21//!
22//! Honest boundary: the equivalence check is exact for **Clifford** circuits (the
23//! stabilizer formalism is complete there); general-circuit equivalence is
24//! co-NP-hard and out of scope. That is the standard, honest scope for a
25//! tableau-based checker — and it covers routing/mapping correctness, where the
26//! transformation is Clifford (SWAPs + relabeling) regardless of the payload.
27
28use crate::quantum_ops::{content_hash, CompileReceipt, GrantRef};
29use ed25519_dalek::SigningKey;
30
31fn splitmix64(state: &mut u64) -> u64 {
32    *state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
33    let mut z = *state;
34    z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
35    z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
36    z ^ (z >> 31)
37}
38
39// ===========================================================================
40// Circuits
41// ===========================================================================
42
43/// A Clifford gate on named qubits.
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub enum Gate {
46    H(u32),
47    S(u32),
48    Cx(u32, u32),
49    Swap(u32, u32),
50}
51
52impl Gate {
53    fn tag(&self) -> u8 {
54        match self {
55            Gate::H(_) => 1,
56            Gate::S(_) => 2,
57            Gate::Cx(_, _) => 3,
58            Gate::Swap(_, _) => 4,
59        }
60    }
61    fn qubits(&self) -> (u32, u32) {
62        match *self {
63            Gate::H(q) | Gate::S(q) => (q, q),
64            Gate::Cx(a, b) | Gate::Swap(a, b) => (a, b),
65        }
66    }
67}
68
69/// Canonical bytes of a circuit (`n` qubits) — what a receipt's circuit hashes bind.
70pub fn circuit_bytes(gates: &[Gate], n: u32) -> Vec<u8> {
71    let mut b = Vec::with_capacity(gates.len() * 9 + 8);
72    b.extend_from_slice(b"wai:qc-circuit\x01");
73    b.extend_from_slice(&n.to_le_bytes());
74    for g in gates {
75        let (p, q) = g.qubits();
76        b.push(g.tag());
77        b.extend_from_slice(&p.to_le_bytes());
78        b.extend_from_slice(&q.to_le_bytes());
79    }
80    b
81}
82
83/// Canonical bytes of a routed circuit together with its final qubit mapping.
84pub fn routed_bytes(gates: &[Gate], n: u32, final_map: &[u32]) -> Vec<u8> {
85    let mut b = circuit_bytes(gates, n);
86    b.extend_from_slice(b"\x01map\x01");
87    for &m in final_map {
88        b.extend_from_slice(&m.to_le_bytes());
89    }
90    b
91}
92
93// ===========================================================================
94// Clifford stabilizer tableau
95// ===========================================================================
96
97/// The images of the `2n` Pauli generators under a Clifford circuit: rows
98/// `0..n` are the images of `X_i`, rows `n..2n` the images of `Z_i`, each a signed
99/// Pauli `(x, z, sign)`. Two circuits are equivalent (up to global phase) iff their
100/// tableaux are equal.
101#[derive(Clone, Debug, PartialEq, Eq)]
102pub struct Tableau {
103    pub n: u32,
104    /// `2n` rows of `(x_bits, z_bits, sign)`.
105    rows: Vec<(u32, u32, u8)>,
106    /// Generator-update work performed (for the receipt).
107    pub work: u64,
108}
109
110#[inline]
111fn bit(v: u32, q: u32) -> u32 {
112    (v >> q) & 1
113}
114#[inline]
115fn setbit(v: u32, q: u32, b: u32) -> u32 {
116    (v & !(1 << q)) | (b << q)
117}
118
119impl Tableau {
120    /// The identity tableau on `n` qubits.
121    pub fn identity(n: u32) -> Tableau {
122        let mut rows = Vec::with_capacity(2 * n as usize);
123        for i in 0..n {
124            rows.push((1 << i, 0, 0)); // X_i
125        }
126        for i in 0..n {
127            rows.push((0, 1 << i, 0)); // Z_i
128        }
129        Tableau { n, rows, work: 0 }
130    }
131
132    fn apply(&mut self, g: &Gate) {
133        match *g {
134            Gate::H(q) => {
135                for r in self.rows.iter_mut() {
136                    let (x, z, s) = *r;
137                    let xq = bit(x, q);
138                    let zq = bit(z, q);
139                    let ns = s ^ (xq & zq) as u8;
140                    *r = (setbit(x, q, zq), setbit(z, q, xq), ns);
141                }
142            }
143            Gate::S(q) => {
144                for r in self.rows.iter_mut() {
145                    let (x, z, s) = *r;
146                    let xq = bit(x, q);
147                    let zq = bit(z, q);
148                    let ns = s ^ (xq & zq) as u8;
149                    *r = (x, z ^ (xq << q), ns);
150                }
151            }
152            Gate::Cx(c, t) => {
153                for r in self.rows.iter_mut() {
154                    let (x, z, s) = *r;
155                    let xc = bit(x, c);
156                    let zc = bit(z, c);
157                    let xt = bit(x, t);
158                    let zt = bit(z, t);
159                    let ns = s ^ (xc & zt & (xt ^ zc ^ 1)) as u8;
160                    *r = (x ^ (xc << t), z ^ (zt << c), ns);
161                }
162            }
163            Gate::Swap(a, b) => {
164                for r in self.rows.iter_mut() {
165                    let (mut x, mut z, s) = *r;
166                    let xa = bit(x, a);
167                    let xb = bit(x, b);
168                    x = setbit(setbit(x, a, xb), b, xa);
169                    let za = bit(z, a);
170                    let zb = bit(z, b);
171                    z = setbit(setbit(z, a, zb), b, za);
172                    *r = (x, z, s);
173                }
174            }
175        }
176        self.work += self.rows.len() as u64;
177    }
178
179    /// Build the tableau of a circuit.
180    pub fn of(gates: &[Gate], n: u32) -> Tableau {
181        let mut t = Tableau::identity(n);
182        for g in gates {
183            t.apply(g);
184        }
185        t
186    }
187
188    /// Do two circuits implement the same Clifford (up to global phase)?
189    pub fn equivalent(&self, other: &Tableau) -> bool {
190        self.n == other.n && self.rows == other.rows
191    }
192}
193
194// ===========================================================================
195// Coupling graph + routing
196// ===========================================================================
197
198/// A hardware coupling graph on `n` qubits (undirected adjacency).
199#[derive(Clone, Debug)]
200pub struct Coupling {
201    pub n: u32,
202    adj: Vec<Vec<u32>>,
203}
204
205impl Coupling {
206    pub fn from_edges(n: u32, edges: &[(u32, u32)]) -> Coupling {
207        let mut adj = vec![Vec::new(); n as usize];
208        for &(a, b) in edges {
209            adj[a as usize].push(b);
210            adj[b as usize].push(a);
211        }
212        Coupling { n, adj }
213    }
214    /// A line `0-1-…-(n-1)`.
215    pub fn line(n: u32) -> Coupling {
216        let e: Vec<(u32, u32)> = (0..n.saturating_sub(1)).map(|i| (i, i + 1)).collect();
217        Coupling::from_edges(n, &e)
218    }
219    /// A ring (line plus the wrap edge).
220    pub fn ring(n: u32) -> Coupling {
221        let mut e: Vec<(u32, u32)> = (0..n.saturating_sub(1)).map(|i| (i, i + 1)).collect();
222        if n > 2 {
223            e.push((n - 1, 0));
224        }
225        Coupling::from_edges(n, &e)
226    }
227    fn coupled(&self, a: u32, b: u32) -> bool {
228        self.adj[a as usize].contains(&b)
229    }
230    /// Shortest path `a → b` (inclusive) via BFS. Deterministic (neighbors in
231    /// insertion order).
232    fn path(&self, a: u32, b: u32) -> Vec<u32> {
233        if a == b {
234            return vec![a];
235        }
236        let n = self.n as usize;
237        let mut prev = vec![u32::MAX; n];
238        let mut seen = vec![false; n];
239        let mut q = std::collections::VecDeque::new();
240        q.push_back(a);
241        seen[a as usize] = true;
242        while let Some(u) = q.pop_front() {
243            if u == b {
244                break;
245            }
246            for &v in &self.adj[u as usize] {
247                if !seen[v as usize] {
248                    seen[v as usize] = true;
249                    prev[v as usize] = u;
250                    q.push_back(v);
251                }
252            }
253        }
254        let mut path = vec![b];
255        let mut cur = b;
256        while cur != a {
257            cur = prev[cur as usize];
258            if cur == u32::MAX {
259                return Vec::new(); // disconnected
260            }
261            path.push(cur);
262        }
263        path.reverse();
264        path
265    }
266
267    pub fn bytes(&self) -> Vec<u8> {
268        let mut b = Vec::new();
269        b.extend_from_slice(b"wai:qc-coupling\x01");
270        b.extend_from_slice(&self.n.to_le_bytes());
271        for (a, nb) in self.adj.iter().enumerate() {
272            for &c in nb {
273                if (a as u32) < c {
274                    b.extend_from_slice(&(a as u32).to_le_bytes());
275                    b.extend_from_slice(&c.to_le_bytes());
276                }
277            }
278        }
279        b
280    }
281}
282
283/// The result of routing a logical circuit onto a coupling graph.
284#[derive(Clone, Debug)]
285pub struct RouteResult {
286    /// The routed circuit, on physical qubits (includes inserted SWAPs).
287    pub routed: Vec<Gate>,
288    /// Final mapping `final_map[logical] = physical`.
289    pub final_map: Vec<u32>,
290    /// SWAP gates inserted.
291    pub swaps: u32,
292}
293
294/// Route a logical circuit onto `coupling` by inserting SWAPs (moving a qubit
295/// along the shortest path to its partner). Deterministic.
296pub fn route(gates: &[Gate], coupling: &Coupling) -> RouteResult {
297    let n = coupling.n;
298    // phys[l] = physical location of logical l; log[p] = logical at physical p.
299    let mut phys: Vec<u32> = (0..n).collect();
300    let mut logq: Vec<u32> = (0..n).collect();
301    let mut routed = Vec::new();
302    let mut swaps = 0u32;
303
304    let do_swap = |routed: &mut Vec<Gate>, phys: &mut [u32], logq: &mut [u32], p: u32, q: u32| {
305        routed.push(Gate::Swap(p, q));
306        let (lp, lq) = (logq[p as usize], logq[q as usize]);
307        logq[p as usize] = lq;
308        logq[q as usize] = lp;
309        phys[lp as usize] = q;
310        phys[lq as usize] = p;
311    };
312
313    for g in gates {
314        match *g {
315            Gate::H(l) => routed.push(Gate::H(phys[l as usize])),
316            Gate::S(l) => routed.push(Gate::S(phys[l as usize])),
317            Gate::Cx(a, b) | Gate::Swap(a, b) => {
318                let (pa, pb) = (phys[a as usize], phys[b as usize]);
319                if !coupling.coupled(pa, pb) {
320                    let path = coupling.path(pa, pb);
321                    // walk logical-a along the path until it sits adjacent to pb
322                    for w in 0..path.len().saturating_sub(2) {
323                        do_swap(&mut routed, &mut phys, &mut logq, path[w], path[w + 1]);
324                        swaps += 1;
325                    }
326                }
327                let (pa, pb) = (phys[a as usize], phys[b as usize]);
328                match *g {
329                    Gate::Cx(_, _) => routed.push(Gate::Cx(pa, pb)),
330                    _ => do_swap(&mut routed, &mut phys, &mut logq, pa, pb),
331                }
332            }
333        }
334    }
335    RouteResult { routed, final_map: phys, swaps }
336}
337
338/// The SWAP network that restores the identity mapping from `final_map`
339/// (`final_map[l] = physical`). Appended to a routed circuit, it "unroutes" it.
340fn restore_swaps(final_map: &[u32], n: u32) -> Vec<Gate> {
341    // logq[p] = logical currently at physical p
342    let mut logq = vec![0u32; n as usize];
343    for (l, &p) in final_map.iter().enumerate() {
344        logq[p as usize] = l as u32;
345    }
346    let mut phys: Vec<u32> = final_map.to_vec();
347    let mut out = Vec::new();
348    for p in 0..n as usize {
349        // we want logical p at physical p
350        while logq[p] != p as u32 {
351            let want = p as u32; // logical we want here
352            let cur_phys = phys[want as usize];
353            // swap physical p with cur_phys
354            out.push(Gate::Swap(p as u32, cur_phys));
355            let (lp, lc) = (logq[p], logq[cur_phys as usize]);
356            logq[p] = lc;
357            logq[cur_phys as usize] = lp;
358            phys[lp as usize] = cur_phys;
359            phys[lc as usize] = p as u32;
360        }
361    }
362    out
363}
364
365/// Prove a routed circuit is equivalent to its source: `routed ++ restore ≡ source`
366/// as Cliffords. Returns `(equivalent, total_work)`.
367pub fn verify_routing(source: &[Gate], route: &RouteResult, n: u32) -> (bool, u64) {
368    let mut full = route.routed.clone();
369    full.extend(restore_swaps(&route.final_map, n));
370    let t_routed = Tableau::of(&full, n);
371    let t_source = Tableau::of(source, n);
372    let work = t_routed.work + t_source.work;
373    (t_source.equivalent(&t_routed), work)
374}
375
376// ===========================================================================
377// Random Clifford circuits + sealing
378// ===========================================================================
379
380/// A deterministic random Clifford circuit on `n` qubits with `depth` gates —
381/// CX gates on arbitrary (possibly non-adjacent) qubit pairs, so routing is needed.
382pub fn random_clifford(n: u32, depth: u32, seed: u64) -> Vec<Gate> {
383    let mut st = seed.wrapping_mul(0x2545_F491).wrapping_add(1);
384    let mut g = Vec::with_capacity(depth as usize);
385    for _ in 0..depth {
386        match splitmix64(&mut st) % 3 {
387            0 => g.push(Gate::H((splitmix64(&mut st) % n as u64) as u32)),
388            1 => g.push(Gate::S((splitmix64(&mut st) % n as u64) as u32)),
389            _ => {
390                let a = (splitmix64(&mut st) % n as u64) as u32;
391                let mut b = (splitmix64(&mut st) % n as u64) as u32;
392                if b == a {
393                    b = (b + 1) % n;
394                }
395                g.push(Gate::Cx(a, b));
396            }
397        }
398    }
399    g
400}
401
402/// A full route + verify + seal: route `source` onto `coupling`, prove equivalence,
403/// and seal a `CompileReceipt` binding source, routed circuit (+ mapping), and
404/// coupling to the verdict.
405#[allow(clippy::too_many_arguments)]
406pub fn compile_and_seal(
407    signer: &SigningKey,
408    signer_id: &str,
409    source: &[Gate],
410    coupling: &Coupling,
411    joules_micro: u64,
412    grant: GrantRef,
413) -> (RouteResult, bool, CompileReceipt) {
414    let n = coupling.n;
415    let r = route(source, coupling);
416    let (equivalent, work) = verify_routing(source, &r, n);
417    let receipt = CompileReceipt::seal(
418        signer,
419        signer_id,
420        "clifford-tableau",
421        content_hash(&circuit_bytes(source, n)),
422        content_hash(&routed_bytes(&r.routed, n, &r.final_map)),
423        content_hash(&coupling.bytes()),
424        equivalent,
425        r.swaps,
426        work,
427        joules_micro,
428        grant,
429        None,
430    );
431    (r, equivalent, receipt)
432}
433
434#[cfg(test)]
435mod tests {
436    use super::*;
437
438    fn key(s: u8) -> SigningKey {
439        SigningKey::from_bytes(&[s; 32])
440    }
441
442    #[test]
443    fn hh_is_identity() {
444        let t = Tableau::of(&[Gate::H(0), Gate::H(0)], 2);
445        assert!(t.equivalent(&Tableau::identity(2)));
446    }
447
448    #[test]
449    fn s_to_the_fourth_is_identity() {
450        let t = Tableau::of(&[Gate::S(0), Gate::S(0), Gate::S(0), Gate::S(0)], 1);
451        assert!(t.equivalent(&Tableau::identity(1)));
452    }
453
454    #[test]
455    fn swap_equals_three_cnots() {
456        let a = Tableau::of(&[Gate::Swap(0, 1)], 2);
457        let b = Tableau::of(&[Gate::Cx(0, 1), Gate::Cx(1, 0), Gate::Cx(0, 1)], 2);
458        assert!(a.equivalent(&b));
459    }
460
461    #[test]
462    fn distinct_circuits_are_not_equivalent() {
463        let a = Tableau::of(&[Gate::H(0)], 2);
464        let b = Tableau::of(&[Gate::S(0)], 2);
465        assert!(!a.equivalent(&b));
466    }
467
468    #[test]
469    fn routing_preserves_equivalence() {
470        // The heart of it: route a random Clifford circuit onto a line, and the
471        // routed circuit must be provably equivalent to the source.
472        let n = 5;
473        let coupling = Coupling::line(n);
474        for seed in 0..40u64 {
475            let src = random_clifford(n, 40, seed.wrapping_mul(0x9E37));
476            let r = route(&src, &coupling);
477            let (equiv, _) = verify_routing(&src, &r, n);
478            assert!(equiv, "routed circuit must be equivalent (seed {seed})");
479            // every 2-qubit gate in the routed circuit is on a coupled pair
480            for g in &r.routed {
481                if let Gate::Cx(a, b) | Gate::Swap(a, b) = *g {
482                    assert!(coupling.coupled(a, b), "routed 2q gate must be local");
483                }
484            }
485        }
486    }
487
488    #[test]
489    fn tamper_breaks_equivalence() {
490        // A corrupted routed circuit must be caught by the check.
491        let n = 5;
492        let coupling = Coupling::line(n);
493        let src = random_clifford(n, 40, 0xBEEF);
494        let mut r = route(&src, &coupling);
495        // drop the last gate — no longer equivalent
496        r.routed.pop();
497        let (equiv, _) = verify_routing(&src, &r, n);
498        assert!(!equiv, "a tampered routed circuit must NOT verify as equivalent");
499    }
500
501    #[test]
502    fn compile_seals_verifying_receipt() {
503        let coupling = Coupling::ring(6);
504        let src = random_clifford(6, 60, 7);
505        let (r, equiv, rec) = compile_and_seal(
506            &key(1), "did:key:lab", &src, &coupling, 300_000, GrantRef::unbounded("quantum.compile"),
507        );
508        assert!(equiv);
509        assert!(rec.verify());
510        assert!(rec.equivalent);
511        assert!(rec.source_matches(&circuit_bytes(&src, 6)));
512        assert!(rec.target_matches(&routed_bytes(&r.routed, 6, &r.final_map)));
513    }
514
515    #[test]
516    fn routing_is_deterministic() {
517        let coupling = Coupling::line(6);
518        let src = random_clifford(6, 50, 123);
519        let a = route(&src, &coupling);
520        let b = route(&src, &coupling);
521        assert_eq!(a.routed, b.routed);
522        assert_eq!(a.final_map, b.final_map);
523    }
524}