use crate::quantum_ops::{content_hash, CompileReceipt, GrantRef};
use ed25519_dalek::SigningKey;
fn splitmix64(state: &mut u64) -> u64 {
*state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = *state;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Gate {
H(u32),
S(u32),
Cx(u32, u32),
Swap(u32, u32),
}
impl Gate {
fn tag(&self) -> u8 {
match self {
Gate::H(_) => 1,
Gate::S(_) => 2,
Gate::Cx(_, _) => 3,
Gate::Swap(_, _) => 4,
}
}
fn qubits(&self) -> (u32, u32) {
match *self {
Gate::H(q) | Gate::S(q) => (q, q),
Gate::Cx(a, b) | Gate::Swap(a, b) => (a, b),
}
}
}
pub fn circuit_bytes(gates: &[Gate], n: u32) -> Vec<u8> {
let mut b = Vec::with_capacity(gates.len() * 9 + 8);
b.extend_from_slice(b"wai:qc-circuit\x01");
b.extend_from_slice(&n.to_le_bytes());
for g in gates {
let (p, q) = g.qubits();
b.push(g.tag());
b.extend_from_slice(&p.to_le_bytes());
b.extend_from_slice(&q.to_le_bytes());
}
b
}
pub fn routed_bytes(gates: &[Gate], n: u32, final_map: &[u32]) -> Vec<u8> {
let mut b = circuit_bytes(gates, n);
b.extend_from_slice(b"\x01map\x01");
for &m in final_map {
b.extend_from_slice(&m.to_le_bytes());
}
b
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Tableau {
pub n: u32,
rows: Vec<(u32, u32, u8)>,
pub work: u64,
}
#[inline]
fn bit(v: u32, q: u32) -> u32 {
(v >> q) & 1
}
#[inline]
fn setbit(v: u32, q: u32, b: u32) -> u32 {
(v & !(1 << q)) | (b << q)
}
impl Tableau {
pub fn identity(n: u32) -> Tableau {
let mut rows = Vec::with_capacity(2 * n as usize);
for i in 0..n {
rows.push((1 << i, 0, 0)); }
for i in 0..n {
rows.push((0, 1 << i, 0)); }
Tableau { n, rows, work: 0 }
}
fn apply(&mut self, g: &Gate) {
match *g {
Gate::H(q) => {
for r in self.rows.iter_mut() {
let (x, z, s) = *r;
let xq = bit(x, q);
let zq = bit(z, q);
let ns = s ^ (xq & zq) as u8;
*r = (setbit(x, q, zq), setbit(z, q, xq), ns);
}
}
Gate::S(q) => {
for r in self.rows.iter_mut() {
let (x, z, s) = *r;
let xq = bit(x, q);
let zq = bit(z, q);
let ns = s ^ (xq & zq) as u8;
*r = (x, z ^ (xq << q), ns);
}
}
Gate::Cx(c, t) => {
for r in self.rows.iter_mut() {
let (x, z, s) = *r;
let xc = bit(x, c);
let zc = bit(z, c);
let xt = bit(x, t);
let zt = bit(z, t);
let ns = s ^ (xc & zt & (xt ^ zc ^ 1)) as u8;
*r = (x ^ (xc << t), z ^ (zt << c), ns);
}
}
Gate::Swap(a, b) => {
for r in self.rows.iter_mut() {
let (mut x, mut z, s) = *r;
let xa = bit(x, a);
let xb = bit(x, b);
x = setbit(setbit(x, a, xb), b, xa);
let za = bit(z, a);
let zb = bit(z, b);
z = setbit(setbit(z, a, zb), b, za);
*r = (x, z, s);
}
}
}
self.work += self.rows.len() as u64;
}
pub fn of(gates: &[Gate], n: u32) -> Tableau {
let mut t = Tableau::identity(n);
for g in gates {
t.apply(g);
}
t
}
pub fn equivalent(&self, other: &Tableau) -> bool {
self.n == other.n && self.rows == other.rows
}
}
#[derive(Clone, Debug)]
pub struct Coupling {
pub n: u32,
adj: Vec<Vec<u32>>,
}
impl Coupling {
pub fn from_edges(n: u32, edges: &[(u32, u32)]) -> Coupling {
let mut adj = vec![Vec::new(); n as usize];
for &(a, b) in edges {
adj[a as usize].push(b);
adj[b as usize].push(a);
}
Coupling { n, adj }
}
pub fn line(n: u32) -> Coupling {
let e: Vec<(u32, u32)> = (0..n.saturating_sub(1)).map(|i| (i, i + 1)).collect();
Coupling::from_edges(n, &e)
}
pub fn ring(n: u32) -> Coupling {
let mut e: Vec<(u32, u32)> = (0..n.saturating_sub(1)).map(|i| (i, i + 1)).collect();
if n > 2 {
e.push((n - 1, 0));
}
Coupling::from_edges(n, &e)
}
fn coupled(&self, a: u32, b: u32) -> bool {
self.adj[a as usize].contains(&b)
}
fn path(&self, a: u32, b: u32) -> Vec<u32> {
if a == b {
return vec![a];
}
let n = self.n as usize;
let mut prev = vec![u32::MAX; n];
let mut seen = vec![false; n];
let mut q = std::collections::VecDeque::new();
q.push_back(a);
seen[a as usize] = true;
while let Some(u) = q.pop_front() {
if u == b {
break;
}
for &v in &self.adj[u as usize] {
if !seen[v as usize] {
seen[v as usize] = true;
prev[v as usize] = u;
q.push_back(v);
}
}
}
let mut path = vec![b];
let mut cur = b;
while cur != a {
cur = prev[cur as usize];
if cur == u32::MAX {
return Vec::new(); }
path.push(cur);
}
path.reverse();
path
}
pub fn bytes(&self) -> Vec<u8> {
let mut b = Vec::new();
b.extend_from_slice(b"wai:qc-coupling\x01");
b.extend_from_slice(&self.n.to_le_bytes());
for (a, nb) in self.adj.iter().enumerate() {
for &c in nb {
if (a as u32) < c {
b.extend_from_slice(&(a as u32).to_le_bytes());
b.extend_from_slice(&c.to_le_bytes());
}
}
}
b
}
}
#[derive(Clone, Debug)]
pub struct RouteResult {
pub routed: Vec<Gate>,
pub final_map: Vec<u32>,
pub swaps: u32,
}
pub fn route(gates: &[Gate], coupling: &Coupling) -> RouteResult {
let n = coupling.n;
let mut phys: Vec<u32> = (0..n).collect();
let mut logq: Vec<u32> = (0..n).collect();
let mut routed = Vec::new();
let mut swaps = 0u32;
let do_swap = |routed: &mut Vec<Gate>, phys: &mut [u32], logq: &mut [u32], p: u32, q: u32| {
routed.push(Gate::Swap(p, q));
let (lp, lq) = (logq[p as usize], logq[q as usize]);
logq[p as usize] = lq;
logq[q as usize] = lp;
phys[lp as usize] = q;
phys[lq as usize] = p;
};
for g in gates {
match *g {
Gate::H(l) => routed.push(Gate::H(phys[l as usize])),
Gate::S(l) => routed.push(Gate::S(phys[l as usize])),
Gate::Cx(a, b) | Gate::Swap(a, b) => {
let (pa, pb) = (phys[a as usize], phys[b as usize]);
if !coupling.coupled(pa, pb) {
let path = coupling.path(pa, pb);
for w in 0..path.len().saturating_sub(2) {
do_swap(&mut routed, &mut phys, &mut logq, path[w], path[w + 1]);
swaps += 1;
}
}
let (pa, pb) = (phys[a as usize], phys[b as usize]);
match *g {
Gate::Cx(_, _) => routed.push(Gate::Cx(pa, pb)),
_ => do_swap(&mut routed, &mut phys, &mut logq, pa, pb),
}
}
}
}
RouteResult { routed, final_map: phys, swaps }
}
fn restore_swaps(final_map: &[u32], n: u32) -> Vec<Gate> {
let mut logq = vec![0u32; n as usize];
for (l, &p) in final_map.iter().enumerate() {
logq[p as usize] = l as u32;
}
let mut phys: Vec<u32> = final_map.to_vec();
let mut out = Vec::new();
for p in 0..n as usize {
while logq[p] != p as u32 {
let want = p as u32; let cur_phys = phys[want as usize];
out.push(Gate::Swap(p as u32, cur_phys));
let (lp, lc) = (logq[p], logq[cur_phys as usize]);
logq[p] = lc;
logq[cur_phys as usize] = lp;
phys[lp as usize] = cur_phys;
phys[lc as usize] = p as u32;
}
}
out
}
pub fn verify_routing(source: &[Gate], route: &RouteResult, n: u32) -> (bool, u64) {
let mut full = route.routed.clone();
full.extend(restore_swaps(&route.final_map, n));
let t_routed = Tableau::of(&full, n);
let t_source = Tableau::of(source, n);
let work = t_routed.work + t_source.work;
(t_source.equivalent(&t_routed), work)
}
pub fn random_clifford(n: u32, depth: u32, seed: u64) -> Vec<Gate> {
let mut st = seed.wrapping_mul(0x2545_F491).wrapping_add(1);
let mut g = Vec::with_capacity(depth as usize);
for _ in 0..depth {
match splitmix64(&mut st) % 3 {
0 => g.push(Gate::H((splitmix64(&mut st) % n as u64) as u32)),
1 => g.push(Gate::S((splitmix64(&mut st) % n as u64) as u32)),
_ => {
let a = (splitmix64(&mut st) % n as u64) as u32;
let mut b = (splitmix64(&mut st) % n as u64) as u32;
if b == a {
b = (b + 1) % n;
}
g.push(Gate::Cx(a, b));
}
}
}
g
}
#[allow(clippy::too_many_arguments)]
pub fn compile_and_seal(
signer: &SigningKey,
signer_id: &str,
source: &[Gate],
coupling: &Coupling,
joules_micro: u64,
grant: GrantRef,
) -> (RouteResult, bool, CompileReceipt) {
let n = coupling.n;
let r = route(source, coupling);
let (equivalent, work) = verify_routing(source, &r, n);
let receipt = CompileReceipt::seal(
signer,
signer_id,
"clifford-tableau",
content_hash(&circuit_bytes(source, n)),
content_hash(&routed_bytes(&r.routed, n, &r.final_map)),
content_hash(&coupling.bytes()),
equivalent,
r.swaps,
work,
joules_micro,
grant,
None,
);
(r, equivalent, receipt)
}
#[cfg(test)]
mod tests {
use super::*;
fn key(s: u8) -> SigningKey {
SigningKey::from_bytes(&[s; 32])
}
#[test]
fn hh_is_identity() {
let t = Tableau::of(&[Gate::H(0), Gate::H(0)], 2);
assert!(t.equivalent(&Tableau::identity(2)));
}
#[test]
fn s_to_the_fourth_is_identity() {
let t = Tableau::of(&[Gate::S(0), Gate::S(0), Gate::S(0), Gate::S(0)], 1);
assert!(t.equivalent(&Tableau::identity(1)));
}
#[test]
fn swap_equals_three_cnots() {
let a = Tableau::of(&[Gate::Swap(0, 1)], 2);
let b = Tableau::of(&[Gate::Cx(0, 1), Gate::Cx(1, 0), Gate::Cx(0, 1)], 2);
assert!(a.equivalent(&b));
}
#[test]
fn distinct_circuits_are_not_equivalent() {
let a = Tableau::of(&[Gate::H(0)], 2);
let b = Tableau::of(&[Gate::S(0)], 2);
assert!(!a.equivalent(&b));
}
#[test]
fn routing_preserves_equivalence() {
let n = 5;
let coupling = Coupling::line(n);
for seed in 0..40u64 {
let src = random_clifford(n, 40, seed.wrapping_mul(0x9E37));
let r = route(&src, &coupling);
let (equiv, _) = verify_routing(&src, &r, n);
assert!(equiv, "routed circuit must be equivalent (seed {seed})");
for g in &r.routed {
if let Gate::Cx(a, b) | Gate::Swap(a, b) = *g {
assert!(coupling.coupled(a, b), "routed 2q gate must be local");
}
}
}
}
#[test]
fn tamper_breaks_equivalence() {
let n = 5;
let coupling = Coupling::line(n);
let src = random_clifford(n, 40, 0xBEEF);
let mut r = route(&src, &coupling);
r.routed.pop();
let (equiv, _) = verify_routing(&src, &r, n);
assert!(!equiv, "a tampered routed circuit must NOT verify as equivalent");
}
#[test]
fn compile_seals_verifying_receipt() {
let coupling = Coupling::ring(6);
let src = random_clifford(6, 60, 7);
let (r, equiv, rec) = compile_and_seal(
&key(1), "did:key:lab", &src, &coupling, 300_000, GrantRef::unbounded("quantum.compile"),
);
assert!(equiv);
assert!(rec.verify());
assert!(rec.equivalent);
assert!(rec.source_matches(&circuit_bytes(&src, 6)));
assert!(rec.target_matches(&routed_bytes(&r.routed, 6, &r.final_map)));
}
#[test]
fn routing_is_deterministic() {
let coupling = Coupling::line(6);
let src = random_clifford(6, 50, 123);
let a = route(&src, &coupling);
let b = route(&src, &coupling);
assert_eq!(a.routed, b.routed);
assert_eq!(a.final_map, b.final_map);
}
}