1use std::sync::OnceLock;
47
48pub const FRAC: u32 = 30;
52pub const ONE: i64 = 1 << FRAC;
54const ROUND: i128 = 1 << (FRAC - 1);
55pub const DYADIC_MAX: usize = 32;
58
59const DOMAIN_STATEVECTOR: &[u8] = b"wai:quantum-statevector\x01";
61const DOMAIN_CIRCUIT: &[u8] = b"wai:quantum-circuit\x01";
62const DOMAIN_HISTOGRAM: &[u8] = b"wai:quantum-histogram\x01";
63
64pub const CAPABILITY: &str = "wai.quantum.circuit";
66
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
75pub struct Amp {
76 pub re: i64,
77 pub im: i64,
78}
79
80#[inline]
84pub fn fxmul(a: i64, b: i64) -> i64 {
85 ((a as i128 * b as i128 + ROUND) >> FRAC) as i64
86}
87
88#[inline]
92pub fn sqrt_fx(v: i64) -> i64 {
93 if v <= 0 {
94 return 0;
95 }
96 (((v as u128) << FRAC).isqrt()) as i64
97}
98
99impl Amp {
100 pub const ZERO: Amp = Amp { re: 0, im: 0 };
102 pub const ONE: Amp = Amp { re: ONE, im: 0 };
104
105 #[inline]
106 pub fn add(self, o: Amp) -> Amp {
107 Amp { re: self.re + o.re, im: self.im + o.im }
108 }
109
110 #[inline]
112 pub fn mul(self, o: Amp) -> Amp {
113 Amp {
114 re: fxmul(self.re, o.re) - fxmul(self.im, o.im),
115 im: fxmul(self.re, o.im) + fxmul(self.im, o.re),
116 }
117 }
118
119 #[inline]
121 pub fn conj(self) -> Amp {
122 Amp { re: self.re, im: -self.im }
123 }
124
125 #[inline]
128 pub fn norm2(self) -> i128 {
129 self.re as i128 * self.re as i128 + self.im as i128 * self.im as i128
130 }
131}
132
133fn dyadic_table() -> &'static [Amp] {
139 static TABLE: OnceLock<Vec<Amp>> = OnceLock::new();
140 TABLE.get_or_init(|| {
141 let mut t = vec![Amp::ZERO; DYADIC_MAX + 1];
142 t[1] = Amp { re: -ONE, im: 0 };
144 t[2] = Amp { re: 0, im: ONE };
146 for k in 3..=DYADIC_MAX {
150 let c = t[k - 1].re;
151 t[k] = Amp {
152 re: sqrt_fx((ONE + c) / 2),
153 im: sqrt_fx((ONE - c) / 2),
154 };
155 }
156 t
157 })
158}
159
160pub fn inv_sqrt2() -> i64 {
163 dyadic_table()[3].re
164}
165
166pub fn phase(k: usize) -> Amp {
168 dyadic_table()[k]
169}
170
171#[derive(Clone, Copy, Debug, PartialEq, Eq)]
177pub enum BaseGate {
178 I,
179 X,
180 Y,
181 Z,
182 H,
183 S,
184 Sdg,
185 T,
186 Tdg,
187 P,
189}
190
191impl BaseGate {
192 fn opcode(self) -> u8 {
193 match self {
194 BaseGate::I => 0,
195 BaseGate::X => 1,
196 BaseGate::Y => 2,
197 BaseGate::Z => 3,
198 BaseGate::H => 4,
199 BaseGate::S => 5,
200 BaseGate::Sdg => 6,
201 BaseGate::T => 7,
202 BaseGate::Tdg => 8,
203 BaseGate::P => 9,
204 }
205 }
206 fn from_opcode(b: u8) -> Option<BaseGate> {
207 Some(match b {
208 0 => BaseGate::I,
209 1 => BaseGate::X,
210 2 => BaseGate::Y,
211 3 => BaseGate::Z,
212 4 => BaseGate::H,
213 5 => BaseGate::S,
214 6 => BaseGate::Sdg,
215 7 => BaseGate::T,
216 8 => BaseGate::Tdg,
217 9 => BaseGate::P,
218 _ => return None,
219 })
220 }
221
222 fn matrix(self, param: u16) -> [[Amp; 2]; 2] {
224 let z = Amp::ZERO;
225 let one = Amp::ONE;
226 let i = Amp { re: 0, im: ONE };
227 let neg_i = Amp { re: 0, im: -ONE };
228 let s = Amp { re: inv_sqrt2(), im: 0 };
229 let neg_s = Amp { re: -inv_sqrt2(), im: 0 };
230 let neg_one = Amp { re: -ONE, im: 0 };
231 match self {
232 BaseGate::I => [[one, z], [z, one]],
233 BaseGate::X => [[z, one], [one, z]],
234 BaseGate::Y => [[z, neg_i], [i, z]],
235 BaseGate::Z => [[one, z], [z, neg_one]],
236 BaseGate::H => [[s, s], [s, neg_s]],
237 BaseGate::S => [[one, z], [z, i]],
238 BaseGate::Sdg => [[one, z], [z, neg_i]],
239 BaseGate::T => [[one, z], [z, phase(3)]],
240 BaseGate::Tdg => [[one, z], [z, phase(3).conj()]],
241 BaseGate::P => [[one, z], [z, phase(param as usize)]],
242 }
243 }
244}
245
246#[derive(Clone, Debug, PartialEq, Eq)]
250pub struct Gate {
251 pub base: BaseGate,
252 pub controls: Vec<u8>,
253 pub target: u8,
254 pub param: u16,
255}
256
257#[derive(Clone, Debug, PartialEq, Eq)]
264pub struct Circuit {
265 pub n_qubits: u8,
266 pub ops: Vec<Gate>,
267}
268
269#[derive(Debug, PartialEq, Eq)]
271pub enum QuantumError {
272 Malformed(String),
274 Invalid(String),
277 TooManyQubits(u8),
279}
280
281impl std::fmt::Display for QuantumError {
282 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
283 match self {
284 QuantumError::Malformed(e) => write!(f, "malformed WQC: {e}"),
285 QuantumError::Invalid(e) => write!(f, "invalid circuit: {e}"),
286 QuantumError::TooManyQubits(n) => write!(f, "too many qubits: {n}"),
287 }
288 }
289}
290impl std::error::Error for QuantumError {}
291
292pub const MAX_QUBITS: u8 = 26;
296
297impl Circuit {
298 pub fn new(n_qubits: u8) -> Self {
299 Circuit { n_qubits, ops: Vec::new() }
300 }
301
302 fn push(&mut self, base: BaseGate, controls: Vec<u8>, target: u8, param: u16) -> &mut Self {
303 self.ops.push(Gate { base, controls, target, param });
304 self
305 }
306
307 pub fn x(&mut self, q: u8) -> &mut Self { self.push(BaseGate::X, vec![], q, 0) }
309 pub fn y(&mut self, q: u8) -> &mut Self { self.push(BaseGate::Y, vec![], q, 0) }
310 pub fn z(&mut self, q: u8) -> &mut Self { self.push(BaseGate::Z, vec![], q, 0) }
311 pub fn h(&mut self, q: u8) -> &mut Self { self.push(BaseGate::H, vec![], q, 0) }
312 pub fn s(&mut self, q: u8) -> &mut Self { self.push(BaseGate::S, vec![], q, 0) }
313 pub fn t(&mut self, q: u8) -> &mut Self { self.push(BaseGate::T, vec![], q, 0) }
314 pub fn p(&mut self, k: u16, q: u8) -> &mut Self { self.push(BaseGate::P, vec![], q, k) }
316 pub fn cx(&mut self, c: u8, t: u8) -> &mut Self { self.push(BaseGate::X, vec![c], t, 0) }
317 pub fn cz(&mut self, c: u8, t: u8) -> &mut Self { self.push(BaseGate::Z, vec![c], t, 0) }
318 pub fn cp(&mut self, k: u16, c: u8, t: u8) -> &mut Self { self.push(BaseGate::P, vec![c], t, k) }
320 pub fn ccx(&mut self, c0: u8, c1: u8, t: u8) -> &mut Self {
322 self.push(BaseGate::X, vec![c0, c1], t, 0)
323 }
324 pub fn swap(&mut self, a: u8, b: u8) -> &mut Self {
326 self.cx(a, b).cx(b, a).cx(a, b)
327 }
328
329 pub fn qft(n: u8) -> Circuit {
333 let mut c = Circuit::new(n);
334 for j in 0..n {
335 c.h(j);
336 for l in (j + 1)..n {
337 let m = (l - j + 1) as u16;
339 c.cp(m, l, j);
340 }
341 }
342 for j in 0..(n / 2) {
343 c.swap(j, n - 1 - j);
344 }
345 c
346 }
347
348 pub fn validate(&self) -> Result<(), QuantumError> {
350 let n = self.n_qubits;
351 for g in &self.ops {
352 if g.target >= n {
353 return Err(QuantumError::Invalid(format!("target {} ≥ n_qubits {}", g.target, n)));
354 }
355 for &c in &g.controls {
356 if c >= n {
357 return Err(QuantumError::Invalid(format!("control {c} ≥ n_qubits {n}")));
358 }
359 if c == g.target {
360 return Err(QuantumError::Invalid(format!("control {c} equals target")));
361 }
362 }
363 if g.base == BaseGate::P {
364 let k = g.param as usize;
365 if k < 1 || k > DYADIC_MAX {
366 return Err(QuantumError::Invalid(format!("P(k) with k={k} out of 1..={DYADIC_MAX}")));
367 }
368 }
369 }
370 Ok(())
371 }
372
373 pub fn simulate(&self) -> Result<StateVector, QuantumError> {
376 self.simulate_from(0)
377 }
378
379 pub fn simulate_from(&self, start: usize) -> Result<StateVector, QuantumError> {
381 self.validate()?;
382 if self.n_qubits > MAX_QUBITS {
383 return Err(QuantumError::TooManyQubits(self.n_qubits));
384 }
385 let dim = 1usize << self.n_qubits;
386 let mut amps = vec![Amp::ZERO; dim];
387 amps[start % dim] = Amp::ONE;
388 for g in &self.ops {
389 apply(&mut amps, self.n_qubits, g);
390 }
391 Ok(StateVector { n_qubits: self.n_qubits, amps })
392 }
393}
394
395fn apply(amps: &mut [Amp], n: u8, g: &Gate) {
424 if matches!(g.base, BaseGate::I) {
425 return;
426 }
427 let m = g.base.matrix(g.param);
428 let tbit = 1usize << g.target;
429 let ctrl_mask: usize = g.controls.iter().fold(0usize, |acc, &c| acc | (1usize << c));
430 let dim = 1usize << n;
431
432 let is_zero = |a: Amp| a.re == 0 && a.im == 0;
433 let is_one = |a: Amp| a.re == ONE && a.im == 0;
434
435 if is_one(m[0][0]) && is_zero(m[0][1]) && is_zero(m[1][0]) {
438 let ph = m[1][1];
439 let negate = ph.re == -ONE && ph.im == 0;
440 let mut i = 0usize;
441 while i < dim {
442 if i & tbit != 0 && (i & ctrl_mask) == ctrl_mask {
443 amps[i] = if negate {
444 Amp { re: -amps[i].re, im: -amps[i].im }
445 } else {
446 ph.mul(amps[i])
447 };
448 }
449 i += 1;
450 }
451 return;
452 }
453
454 if is_zero(m[0][0]) && is_zero(m[1][1]) {
456 let (a01, a10) = (m[0][1], m[1][0]);
457 let plain_swap = is_one(a01) && is_one(a10);
458 let mut i = 0usize;
459 while i < dim {
460 if i & tbit == 0 && (i & ctrl_mask) == ctrl_mask {
461 let j = i | tbit;
462 if plain_swap {
463 amps.swap(i, j);
464 } else {
465 let (a0, a1) = (amps[i], amps[j]);
466 amps[i] = a01.mul(a1);
467 amps[j] = a10.mul(a0);
468 }
469 }
470 i += 1;
471 }
472 return;
473 }
474
475 if m[0][0].im == 0 && m[0][1].im == 0 && m[1][0].im == 0 && m[1][1].im == 0 {
480 let (p00, p01) = (m[0][0].re, m[0][1].re);
481 let (p10, p11) = (m[1][0].re, m[1][1].re);
482 let mut i = 0usize;
483 while i < dim {
484 if i & tbit == 0 && (i & ctrl_mask) == ctrl_mask {
485 let j = i | tbit;
486 let a0 = amps[i];
487 let a1 = amps[j];
488 amps[i] = Amp {
489 re: fxmul(p00, a0.re) + fxmul(p01, a1.re),
490 im: fxmul(p00, a0.im) + fxmul(p01, a1.im),
491 };
492 amps[j] = Amp {
493 re: fxmul(p10, a0.re) + fxmul(p11, a1.re),
494 im: fxmul(p10, a0.im) + fxmul(p11, a1.im),
495 };
496 }
497 i += 1;
498 }
499 return;
500 }
501
502 let mut i = 0usize;
503 while i < dim {
504 if i & tbit == 0 && (i & ctrl_mask) == ctrl_mask {
505 let j = i | tbit;
506 let a0 = amps[i];
507 let a1 = amps[j];
508 amps[i] = m[0][0].mul(a0).add(m[0][1].mul(a1));
509 amps[j] = m[1][0].mul(a0).add(m[1][1].mul(a1));
510 }
511 i += 1;
512 }
513}
514
515#[derive(Clone, Debug, PartialEq, Eq)]
521pub struct StateVector {
522 pub n_qubits: u8,
523 pub amps: Vec<Amp>,
524}
525
526fn splitmix64(s: &mut u64) -> u64 {
529 *s = s.wrapping_add(0x9E37_79B9_7F4A_7C15);
530 let mut z = *s;
531 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
532 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
533 z ^ (z >> 31)
534}
535
536impl StateVector {
537 pub fn basis(n: u8, k: usize) -> StateVector {
539 let dim = 1usize << n;
540 let mut amps = vec![Amp::ZERO; dim];
541 amps[k % dim] = Amp::ONE;
542 StateVector { n_qubits: n, amps }
543 }
544
545 pub fn canonical_bytes(&self) -> Vec<u8> {
548 let mut out = Vec::with_capacity(self.amps.len() * 16);
549 for a in &self.amps {
550 out.extend_from_slice(&a.re.to_le_bytes());
551 out.extend_from_slice(&a.im.to_le_bytes());
552 }
553 out
554 }
555
556 pub fn statevector_hash(&self) -> [u8; 32] {
560 let mut h = blake3::Hasher::new();
561 h.update(DOMAIN_STATEVECTOR);
562 h.update(&[self.n_qubits]);
563 h.update(&FRAC.to_le_bytes());
564 h.update(&self.canonical_bytes());
565 *h.finalize().as_bytes()
566 }
567
568 pub fn prob_weights(&self) -> Vec<i128> {
572 self.amps.iter().map(|a| a.norm2()).collect()
573 }
574
575 pub fn sample_shots(&self, seed: u64, shots: u64) -> Vec<u64> {
579 let weights = self.prob_weights();
580 let mut cum = Vec::with_capacity(weights.len());
582 let mut total: i128 = 0;
583 for w in &weights {
584 total += *w;
585 cum.push(total);
586 }
587 let mut counts = vec![0u64; self.amps.len()];
588 if total <= 0 {
589 return counts;
590 }
591 let mut state = seed;
592 for _ in 0..shots {
593 let r = (splitmix64(&mut state) as u128 % total as u128) as i128;
594 let idx = match cum.binary_search_by(|c| {
596 if *c <= r { std::cmp::Ordering::Less } else { std::cmp::Ordering::Greater }
597 }) {
598 Ok(i) | Err(i) => i,
599 };
600 let slot = idx.min(counts.len() - 1);
601 counts[slot] += 1;
602 }
603 counts
604 }
605
606 pub fn histogram_hash(&self, seed: u64, shots: u64) -> [u8; 32] {
609 let counts = self.sample_shots(seed, shots);
610 let mut h = blake3::Hasher::new();
611 h.update(DOMAIN_HISTOGRAM);
612 h.update(&[self.n_qubits]);
613 h.update(&seed.to_le_bytes());
614 h.update(&shots.to_le_bytes());
615 for c in &counts {
616 h.update(&c.to_le_bytes());
617 }
618 *h.finalize().as_bytes()
619 }
620
621 pub fn fidelity_fx(&self, other: &StateVector) -> i64 {
628 assert_eq!(self.amps.len(), other.amps.len(), "fidelity needs equal dimension");
629 let mut re: i128 = 0;
630 let mut im: i128 = 0;
631 for (a, b) in self.amps.iter().zip(&other.amps) {
632 let p = a.conj().mul(*b);
634 re += p.re as i128;
635 im += p.im as i128;
636 }
637 ((re * re + im * im) >> FRAC) as i64
639 }
640}
641
642#[derive(Clone, Copy, Debug, PartialEq, Eq)]
649pub struct Measure {
650 pub seed: u64,
651 pub shots: u64,
652}
653
654const SECT_CONTRACT: u8 = 0x01;
655const SECT_OPLOG: u8 = 0x02;
656const SECT_MEASURE: u8 = 0x03;
657
658fn contract_json(n_qubits: u8) -> Vec<u8> {
659 let v = serde_json::json!({
661 "ext": "wai.quantum.circuit/1",
662 "n_qubits": n_qubits,
663 "numeric": "wai.det.fixed64",
664 "frac": FRAC,
665 "gateset": "cliffordT+dyadicP",
666 });
667 serde_json::to_vec(&v).expect("contract json")
668}
669
670fn oplog_bytes(c: &Circuit) -> Vec<u8> {
671 let mut out = Vec::new();
673 out.extend_from_slice(&(c.ops.len() as u32).to_le_bytes());
674 for g in &c.ops {
675 out.push(g.base.opcode());
676 out.push(g.controls.len() as u8);
677 out.push(g.target);
678 out.extend_from_slice(&g.param.to_le_bytes());
679 out.extend_from_slice(&g.controls);
680 }
681 out
682}
683
684fn parse_oplog(n_qubits: u8, b: &[u8]) -> Result<Vec<Gate>, QuantumError> {
685 if b.len() < 4 {
686 return Err(QuantumError::Malformed("oplog header".into()));
687 }
688 let n_ops = u32::from_le_bytes([b[0], b[1], b[2], b[3]]) as usize;
689 let mut pos = 4;
690 let mut ops = Vec::with_capacity(n_ops);
691 for _ in 0..n_ops {
692 if pos + 5 > b.len() {
693 return Err(QuantumError::Malformed("op header".into()));
694 }
695 let base = BaseGate::from_opcode(b[pos])
696 .ok_or_else(|| QuantumError::Malformed(format!("opcode {}", b[pos])))?;
697 let n_ctrl = b[pos + 1] as usize;
698 let target = b[pos + 2];
699 let param = u16::from_le_bytes([b[pos + 3], b[pos + 4]]);
700 pos += 5;
701 if pos + n_ctrl > b.len() {
702 return Err(QuantumError::Malformed("op controls".into()));
703 }
704 let controls = b[pos..pos + n_ctrl].to_vec();
705 pos += n_ctrl;
706 ops.push(Gate { base, controls, target, param });
707 }
708 let _ = n_qubits;
709 Ok(ops)
710}
711
712pub fn to_wqc(c: &Circuit, measure: Option<Measure>) -> Vec<u8> {
714 let contract = contract_json(c.n_qubits);
715 let oplog = oplog_bytes(c);
716 let measure_bytes = measure.map(|m| {
717 let v = serde_json::json!({ "seed": m.seed, "shots": m.shots, "basis": "computational" });
718 serde_json::to_vec(&v).expect("measure json")
719 });
720
721 let mut sections: Vec<(u8, Vec<u8>)> = vec![(SECT_CONTRACT, contract), (SECT_OPLOG, oplog)];
722 if let Some(mb) = measure_bytes {
723 sections.push((SECT_MEASURE, mb));
724 }
725
726 let mut out = Vec::new();
727 out.extend_from_slice(b"WQC1");
728 out.extend_from_slice(&(sections.len() as u16).to_le_bytes());
729 let mut off: u32 = 0;
731 for (kind, data) in §ions {
732 out.push(*kind);
733 out.extend_from_slice(&off.to_le_bytes());
734 out.extend_from_slice(&(data.len() as u32).to_le_bytes());
735 off += data.len() as u32;
736 }
737 for (_, data) in §ions {
738 out.extend_from_slice(data);
739 }
740 out
741}
742
743pub fn from_wqc(bytes: &[u8]) -> Result<(Circuit, Option<Measure>), QuantumError> {
745 if bytes.len() < 6 || &bytes[0..4] != b"WQC1" {
746 return Err(QuantumError::Malformed("magic".into()));
747 }
748 let n_sections = u16::from_le_bytes([bytes[4], bytes[5]]) as usize;
749 let table_start = 6;
750 let table_len = n_sections * 9;
751 if bytes.len() < table_start + table_len {
752 return Err(QuantumError::Malformed("section table".into()));
753 }
754 let blob_start = table_start + table_len;
755 let mut contract: Option<&[u8]> = None;
756 let mut oplog: Option<&[u8]> = None;
757 let mut measure_raw: Option<&[u8]> = None;
758 for s in 0..n_sections {
759 let p = table_start + s * 9;
760 let kind = bytes[p];
761 let off = u32::from_le_bytes([bytes[p + 1], bytes[p + 2], bytes[p + 3], bytes[p + 4]]) as usize;
762 let len = u32::from_le_bytes([bytes[p + 5], bytes[p + 6], bytes[p + 7], bytes[p + 8]]) as usize;
763 let start = blob_start + off;
764 if start + len > bytes.len() {
765 return Err(QuantumError::Malformed("section bounds".into()));
766 }
767 let data = &bytes[start..start + len];
768 match kind {
769 SECT_CONTRACT => contract = Some(data),
770 SECT_OPLOG => oplog = Some(data),
771 SECT_MEASURE => measure_raw = Some(data),
772 _ => {} }
774 }
775 let contract = contract.ok_or_else(|| QuantumError::Malformed("missing contract".into()))?;
776 let oplog = oplog.ok_or_else(|| QuantumError::Malformed("missing oplog".into()))?;
777
778 let cv: serde_json::Value =
779 serde_json::from_slice(contract).map_err(|e| QuantumError::Malformed(format!("contract json: {e}")))?;
780 let n_qubits = cv["n_qubits"].as_u64().ok_or_else(|| QuantumError::Malformed("n_qubits".into()))? as u8;
781
782 let ops = parse_oplog(n_qubits, oplog)?;
783 let circuit = Circuit { n_qubits, ops };
784 circuit.validate()?;
785
786 let measure = match measure_raw {
787 Some(mb) => {
788 let mv: serde_json::Value =
789 serde_json::from_slice(mb).map_err(|e| QuantumError::Malformed(format!("measure json: {e}")))?;
790 Some(Measure {
791 seed: mv["seed"].as_u64().unwrap_or(0),
792 shots: mv["shots"].as_u64().unwrap_or(0),
793 })
794 }
795 None => None,
796 };
797 Ok((circuit, measure))
798}
799
800pub fn circuit_hash(c: &Circuit) -> [u8; 32] {
803 let mut h = blake3::Hasher::new();
804 h.update(DOMAIN_CIRCUIT);
805 h.update(&contract_json(c.n_qubits));
806 h.update(&oplog_bytes(c));
807 *h.finalize().as_bytes()
808}
809
810#[cfg(test)]
811mod tests {
812 use super::*;
813 fn apply_reference(amps: &mut [Amp], n: u8, g: &Gate) {
818 let m = g.base.matrix(g.param);
819 let tbit = 1usize << g.target;
820 let ctrl_mask: usize = g.controls.iter().fold(0usize, |acc, &c| acc | (1usize << c));
821 let dim = 1usize << n;
822 let mut i = 0usize;
823 while i < dim {
824 if i & tbit == 0 && (i & ctrl_mask) == ctrl_mask {
825 let j = i | tbit;
826 let a0 = amps[i];
827 let a1 = amps[j];
828 amps[i] = m[0][0].mul(a0).add(m[0][1].mul(a1));
829 amps[j] = m[1][0].mul(a0).add(m[1][1].mul(a1));
830 }
831 i += 1;
832 }
833 }
834
835
836 #[test]
841 #[ignore]
842 fn probe_apply_speedup() {
843 use std::time::Instant;
844 let n = 20u8;
845 let dim = 1usize << n;
846 let reps = 5;
847 let gates = 60;
848 println!("\\n n={n} best of {reps}, interleaved");
849 println!(" gate reference optimised speedup");
850 for kind in ["h", "t", "z", "s", "x", "cx", "cz", "y"] {
851 let mut ops: Vec<Gate> = Vec::new();
852 for i in 0..gates {
853 let q = (i % n as usize) as u8;
854 let o = (q + 1) % n;
855 ops.push(match kind {
856 "h" => Gate { base: BaseGate::H, controls: vec![], target: q, param: 0 },
857 "t" => Gate { base: BaseGate::T, controls: vec![], target: q, param: 0 },
858 "z" => Gate { base: BaseGate::Z, controls: vec![], target: q, param: 0 },
859 "s" => Gate { base: BaseGate::S, controls: vec![], target: q, param: 0 },
860 "x" => Gate { base: BaseGate::X, controls: vec![], target: q, param: 0 },
861 "y" => Gate { base: BaseGate::Y, controls: vec![], target: q, param: 0 },
862 "cx" => Gate { base: BaseGate::X, controls: vec![o], target: q, param: 0 },
863 _ => Gate { base: BaseGate::Z, controls: vec![o], target: q, param: 0 },
864 });
865 }
866 let (mut best_ref, mut best_opt) = (u128::MAX, u128::MAX);
867 for _ in 0..reps {
868 let mut a = vec![Amp::ZERO; dim];
869 a[0] = Amp::ONE;
870 let t0 = Instant::now();
871 for g in &ops { apply_reference(&mut a, n, g); }
872 best_ref = best_ref.min(t0.elapsed().as_nanos());
873 std::hint::black_box(&a);
874
875 let mut b = vec![Amp::ZERO; dim];
876 b[0] = Amp::ONE;
877 let t1 = Instant::now();
878 for g in &ops { apply(&mut b, n, g); }
879 best_opt = best_opt.min(t1.elapsed().as_nanos());
880 std::hint::black_box(&b);
881 }
882 let (r, o) = (best_ref as f64 / gates as f64, best_opt as f64 / gates as f64);
883 println!(" {kind:<5} {r:>10.0} ns {o:>10.0} ns {:>9.2}x", r / o);
884 }
885 }
886
887 #[test]
892 fn optimised_apply_is_bit_identical_to_the_general_path() {
893 let mut st = 0x1234_5678_9ABC_DEF0u64;
894 let mut rnd = |m: usize| {
895 st = st.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
896 ((st >> 33) as usize) % m.max(1)
897 };
898 for _ in 0..300 {
899 let n = 3 + rnd(3) as u8;
900 let dim = 1usize << n;
901 let mut c = Circuit::new(n);
902 for _ in 0..24 {
903 let t = rnd(n as usize) as u8;
904 let mut other = rnd(n as usize) as u8;
905 if other == t {
906 other = (t + 1) % n;
907 }
908 match rnd(13) {
909 0 => { c.x(t); }
910 1 => { c.y(t); }
911 2 => { c.z(t); }
912 3 => { c.h(t); }
913 4 => { c.s(t); }
914 5 => { c.t(t); }
915 6 => { c.p(1 + rnd(5) as u16, t); }
916 7 => { c.cx(other, t); }
917 8 => { c.cz(other, t); }
918 9 => { c.cp(1 + rnd(4) as u16, other, t); }
919 10 => c.ops.push(Gate { base: BaseGate::Sdg, controls: vec![], target: t, param: 0 }),
920 11 => c.ops.push(Gate { base: BaseGate::Tdg, controls: vec![], target: t, param: 0 }),
921 _ => c.ops.push(Gate { base: BaseGate::I, controls: vec![], target: t, param: 0 }),
922 }
923 }
924 if n >= 3 {
927 c.ops.push(Gate { base: BaseGate::Y, controls: vec![0], target: n - 1, param: 0 });
928 c.ccx(0, 1, n - 1);
929 }
930
931 let mut want = vec![Amp::ZERO; dim];
932 want[0] = Amp::ONE;
933 for g in &c.ops {
934 apply_reference(&mut want, n, g);
935 }
936 let got = c.simulate().unwrap();
937 assert_eq!(got.amps, want, "optimised apply diverged on {} gates, n={n}", c.ops.len());
938 }
939 }
940
941
942 const TOL: i64 = ONE / 10_000;
945
946 fn approx(a: i64, b: i64, tol: i64) -> bool {
947 (a - b).abs() <= tol
948 }
949
950 #[test]
951 fn dyadic_table_is_unit_modulus() {
952 for k in 1..=DYADIC_MAX {
954 let a = phase(k);
955 let n2 = ((a.norm2()) >> FRAC) as i64; assert!(approx(n2, ONE, ONE / 1000), "|e^(2πi/2^{k})|^2 = {n2} not ≈ ONE");
957 }
958 assert_eq!(phase(1), Amp { re: -ONE, im: 0 }); assert_eq!(phase(2), Amp { re: 0, im: ONE }); assert_eq!(phase(3).re, inv_sqrt2());
962 assert_eq!(phase(3).im, inv_sqrt2());
963 }
964
965 #[test]
966 fn simulation_is_deterministic_byte_for_byte() {
967 let c = Circuit::qft(6);
969 let a = c.simulate().unwrap();
970 let b = c.simulate().unwrap();
971 assert_eq!(a.amps, b.amps, "quantum sim must be byte-exact reproducible");
972 assert_eq!(a.statevector_hash(), b.statevector_hash());
973 }
974
975 #[test]
976 fn bell_state() {
977 let mut c = Circuit::new(2);
979 c.h(0).cx(0, 1);
980 let sv = c.simulate().unwrap();
981 let s = inv_sqrt2();
982 assert!(approx(sv.amps[0b00].re, s, TOL));
983 assert_eq!(sv.amps[0b01], Amp::ZERO);
984 assert_eq!(sv.amps[0b10], Amp::ZERO);
985 assert!(approx(sv.amps[0b11].re, s, TOL));
986 assert!(approx(sv.fidelity_fx(&sv), ONE, TOL));
988 }
989
990 #[test]
991 fn ghz_state() {
992 let mut c = Circuit::new(3);
994 c.h(0).cx(0, 1).cx(1, 2);
995 let sv = c.simulate().unwrap();
996 let s = inv_sqrt2();
997 assert!(approx(sv.amps[0b000].re, s, TOL));
998 assert!(approx(sv.amps[0b111].re, s, TOL));
999 for k in 1..7 {
1000 assert_eq!(sv.amps[k], Amp::ZERO, "index {k} should be empty");
1001 }
1002 }
1003
1004 #[test]
1005 fn qft_of_zero_is_uniform_superposition() {
1006 let n = 4u8;
1008 let dim = 1usize << n;
1009 let sv = Circuit::qft(n).simulate().unwrap();
1010 let want2 = ONE / dim as i64;
1013 for k in 0..dim {
1014 let mag2 = ((sv.amps[k].norm2()) >> FRAC) as i64;
1015 assert!(approx(mag2, want2, ONE / 1000), "amp {k} magnitude^2 {mag2} != {want2}");
1016 }
1017 }
1018
1019 #[test]
1020 fn orthogonal_states_have_zero_fidelity() {
1021 let a = StateVector::basis(3, 0b000);
1022 let b = StateVector::basis(3, 0b111);
1023 assert_eq!(a.fidelity_fx(&b), 0);
1024 assert!(approx(a.fidelity_fx(&a), ONE, 1));
1025 }
1026
1027 #[test]
1028 fn shot_histogram_is_deterministic() {
1029 let mut c = Circuit::new(2);
1031 c.h(0).cx(0, 1);
1032 let sv = c.simulate().unwrap();
1033 let h1 = sv.histogram_hash(0xC0FFEE, 10_000);
1034 let h2 = sv.histogram_hash(0xC0FFEE, 10_000);
1035 assert_eq!(h1, h2, "pinned-seed histogram must be reproducible");
1036 let counts = sv.sample_shots(0xC0FFEE, 10_000);
1037 assert_eq!(counts[0b01], 0);
1038 assert_eq!(counts[0b10], 0);
1039 assert!(counts[0b00] > 4000 && counts[0b00] < 6000, "≈ half on |00⟩: {}", counts[0b00]);
1040 assert_eq!(counts[0b00] + counts[0b11], 10_000);
1041 }
1042
1043 #[test]
1044 fn wqc_round_trips() {
1045 let c = Circuit::qft(5);
1046 let m = Some(Measure { seed: 42, shots: 1024 });
1047 let bytes = to_wqc(&c, m);
1048 assert_eq!(&bytes[0..4], b"WQC1");
1049 let (c2, m2) = from_wqc(&bytes).unwrap();
1050 assert_eq!(c, c2, "circuit must round-trip through WQC");
1051 assert_eq!(m, m2);
1052 assert_eq!(circuit_hash(&c), circuit_hash(&c2));
1054 assert_eq!(c.simulate().unwrap().amps, c2.simulate().unwrap().amps);
1055 }
1056
1057
1058 #[test]
1059 fn validation_rejects_bad_indices() {
1060 let mut c = Circuit::new(2);
1061 c.push(BaseGate::X, vec![], 5, 0); assert!(matches!(c.validate(), Err(QuantumError::Invalid(_))));
1063
1064 let mut c = Circuit::new(2);
1065 c.push(BaseGate::X, vec![1], 1, 0); assert!(matches!(c.validate(), Err(QuantumError::Invalid(_))));
1067
1068 let mut c = Circuit::new(1);
1069 c.push(BaseGate::P, vec![], 0, 999); assert!(matches!(c.validate(), Err(QuantumError::Invalid(_))));
1071 }
1072}