1use crate::ijson::{self, Json, RANGE_LIMIT};
58
59pub const FRAC: u32 = 30;
63pub const ONE: i64 = 1 << FRAC;
65const ROUND: i128 = 1 << (FRAC - 1);
66pub const DYADIC_MAX: usize = 32;
69
70const DOMAIN_STATEVECTOR: &[u8] = b"wai:quantum-statevector\x01";
72const DOMAIN_CIRCUIT: &[u8] = b"wai:quantum-circuit\x01";
73const DOMAIN_HISTOGRAM: &[u8] = b"wai:quantum-histogram\x01";
74
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
83pub struct Amp {
84 pub re: i64,
85 pub im: i64,
86}
87
88#[inline]
92pub fn fxmul(a: i64, b: i64) -> i64 {
93 ((a as i128 * b as i128 + ROUND) >> FRAC) as i64
94}
95
96#[inline]
100pub fn sqrt_fx(v: i64) -> i64 {
101 if v <= 0 {
102 return 0;
103 }
104 (((v as u128) << FRAC).isqrt()) as i64
105}
106
107impl Amp {
108 pub const ZERO: Amp = Amp { re: 0, im: 0 };
110 pub const ONE: Amp = Amp { re: ONE, im: 0 };
112
113 #[inline]
114 pub fn add(self, o: Amp) -> Amp {
115 Amp { re: self.re + o.re, im: self.im + o.im }
116 }
117
118 #[inline]
120 pub fn mul(self, o: Amp) -> Amp {
121 Amp {
122 re: fxmul(self.re, o.re) - fxmul(self.im, o.im),
123 im: fxmul(self.re, o.im) + fxmul(self.im, o.re),
124 }
125 }
126
127 #[inline]
129 pub fn conj(self) -> Amp {
130 Amp { re: self.re, im: -self.im }
131 }
132
133 #[inline]
136 pub fn norm2(self) -> i128 {
137 self.re as i128 * self.re as i128 + self.im as i128 * self.im as i128
138 }
139}
140
141pub const PHASE_V1: [Amp; 33] = [
162 Amp::ZERO,
163 Amp { re: -1073741824, im: 0 }, Amp { re: 0, im: 1073741824 }, Amp { re: 759250124, im: 759250124 }, Amp { re: 992008094, im: 410903207 }, Amp { re: 1053110175, im: 209476638 }, Amp { re: 1068571463, im: 105245102 }, Amp { re: 1072448454, im: 52686012 }, Amp { re: 1073418432, im: 26350953 }, Amp { re: 1073660972, im: 13176485 }, Amp { re: 1073721610, im: 6588405 }, Amp { re: 1073736770, im: 3294284 }, Amp { re: 1073740560, im: 1647223 }, Amp { re: 1073741507, im: 823774 }, Amp { re: 1073741744, im: 411887 }, Amp { re: 1073741803, im: 207243 }, Amp { re: 1073741818, im: 103621 }, Amp { re: 1073741822, im: 56755 }, Amp { re: 1073741823, im: 32768 }, Amp { re: 1073741823, im: 0 }, Amp { re: 1073741823, im: 0 }, Amp { re: 1073741823, im: 0 }, Amp { re: 1073741823, im: 0 }, Amp { re: 1073741823, im: 0 }, Amp { re: 1073741823, im: 0 }, Amp { re: 1073741823, im: 0 }, Amp { re: 1073741823, im: 0 }, Amp { re: 1073741823, im: 0 }, Amp { re: 1073741823, im: 0 }, Amp { re: 1073741823, im: 0 }, Amp { re: 1073741823, im: 0 }, Amp { re: 1073741823, im: 0 }, Amp { re: 1073741823, im: 0 }, ];
196
197pub const PHASE_V2: [Amp; 33] = [
213 Amp::ZERO,
214 Amp { re: -1073741824, im: 0 }, Amp { re: 0, im: 1073741824 }, Amp { re: 759250125, im: 759250125 }, Amp { re: 992008094, im: 410903207 }, Amp { re: 1053110176, im: 209476638 }, Amp { re: 1068571464, im: 105245103 }, Amp { re: 1072448455, im: 52686014 }, Amp { re: 1073418433, im: 26350943 }, Amp { re: 1073660973, im: 13176464 }, Amp { re: 1073721611, im: 6588356 }, Amp { re: 1073736771, im: 3294193 }, Amp { re: 1073740561, im: 1647099 }, Amp { re: 1073741508, im: 823550 }, Amp { re: 1073741745, im: 411775 }, Amp { re: 1073741804, im: 205887 }, Amp { re: 1073741819, im: 102944 }, Amp { re: 1073741823, im: 51472 }, Amp { re: 1073741824, im: 25736 }, Amp { re: 1073741824, im: 12868 }, Amp { re: 1073741824, im: 6434 }, Amp { re: 1073741824, im: 3217 }, Amp { re: 1073741824, im: 1608 }, Amp { re: 1073741824, im: 804 }, Amp { re: 1073741824, im: 402 }, Amp { re: 1073741824, im: 201 }, Amp { re: 1073741824, im: 101 }, Amp { re: 1073741824, im: 50 }, Amp { re: 1073741824, im: 25 }, Amp { re: 1073741824, im: 13 }, Amp { re: 1073741824, im: 6 }, Amp { re: 1073741824, im: 3 }, Amp { re: 1073741824, im: 2 }, ];
247
248#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
260#[non_exhaustive]
261pub enum Gateset {
262 V1,
264 #[default]
266 V2,
267}
268
269impl Gateset {
270 pub const ALL: [Gateset; 2] = [Gateset::V1, Gateset::V2];
272
273 pub const fn capability(self) -> &'static str {
275 match self {
276 Gateset::V1 => "wai.quantum.circuit",
277 Gateset::V2 => "wai.quantum.circuit2",
278 }
279 }
280
281 pub const fn magic(self) -> [u8; 4] {
283 match self {
284 Gateset::V1 => *b"WQC1",
285 Gateset::V2 => *b"WQC2",
286 }
287 }
288
289 pub const fn ext(self) -> &'static str {
292 match self {
293 Gateset::V1 => "wai.quantum.circuit/1",
294 Gateset::V2 => "wai.quantum.circuit2/1",
295 }
296 }
297
298 pub const fn numeric(self) -> &'static str {
300 match self {
301 Gateset::V1 => "wai.det.fixed64",
302 Gateset::V2 => "wai.det.amp64",
303 }
304 }
305
306 pub const fn name(self) -> &'static str {
309 match self {
310 Gateset::V1 => "cliffordT+dyadicP",
311 Gateset::V2 => "cliffordT+dyadicP.rn",
312 }
313 }
314
315 pub const fn table(self) -> &'static [Amp; 33] {
317 match self {
318 Gateset::V1 => &PHASE_V1,
319 Gateset::V2 => &PHASE_V2,
320 }
321 }
322
323 #[inline]
326 pub fn phase(self, k: usize) -> Amp {
327 self.table()[k]
328 }
329
330 #[inline]
333 pub fn inv_sqrt2(self) -> i64 {
334 self.table()[3].re
335 }
336
337 pub fn contract_bytes(self, n_qubits: u8) -> Vec<u8> {
341 format!(
342 "{{\"ext\":\"{}\",\"n_qubits\":{},\"numeric\":\"{}\",\"frac\":{},\"gateset\":\"{}\"}}",
343 self.ext(),
344 n_qubits,
345 self.numeric(),
346 FRAC,
347 self.name(),
348 )
349 .into_bytes()
350 }
351
352 pub fn table_digest(self) -> [u8; 32] {
355 let mut h = blake3::Hasher::new();
356 for a in &self.table()[1..] {
357 h.update(&a.re.to_le_bytes());
358 h.update(&a.im.to_le_bytes());
359 }
360 *h.finalize().as_bytes()
361 }
362
363 pub fn from_magic(m: &[u8]) -> Option<Gateset> {
365 Gateset::ALL.into_iter().find(|g| m == g.magic().as_slice())
366 }
367
368 pub fn from_capability(s: &str) -> Option<Gateset> {
370 Gateset::ALL.into_iter().find(|g| s == g.capability())
371 }
372}
373
374#[derive(Clone, Copy, Debug, PartialEq, Eq)]
380pub enum BaseGate {
381 I,
382 X,
383 Y,
384 Z,
385 H,
386 S,
387 Sdg,
388 T,
389 Tdg,
390 P,
392}
393
394impl BaseGate {
395 pub(crate) fn opcode(self) -> u8 {
396 match self {
397 BaseGate::I => 0,
398 BaseGate::X => 1,
399 BaseGate::Y => 2,
400 BaseGate::Z => 3,
401 BaseGate::H => 4,
402 BaseGate::S => 5,
403 BaseGate::Sdg => 6,
404 BaseGate::T => 7,
405 BaseGate::Tdg => 8,
406 BaseGate::P => 9,
407 }
408 }
409 fn from_opcode(b: u8) -> Option<BaseGate> {
410 Some(match b {
411 0 => BaseGate::I,
412 1 => BaseGate::X,
413 2 => BaseGate::Y,
414 3 => BaseGate::Z,
415 4 => BaseGate::H,
416 5 => BaseGate::S,
417 6 => BaseGate::Sdg,
418 7 => BaseGate::T,
419 8 => BaseGate::Tdg,
420 9 => BaseGate::P,
421 _ => return None,
422 })
423 }
424
425 pub(crate) fn matrix(self, param: u16, gs: Gateset) -> [[Amp; 2]; 2] {
429 let z = Amp::ZERO;
430 let one = Amp::ONE;
431 let i = Amp { re: 0, im: ONE };
432 let neg_i = Amp { re: 0, im: -ONE };
433 let s = Amp { re: gs.inv_sqrt2(), im: 0 };
434 let neg_s = Amp { re: -gs.inv_sqrt2(), im: 0 };
435 let neg_one = Amp { re: -ONE, im: 0 };
436 match self {
437 BaseGate::I => [[one, z], [z, one]],
438 BaseGate::X => [[z, one], [one, z]],
439 BaseGate::Y => [[z, neg_i], [i, z]],
440 BaseGate::Z => [[one, z], [z, neg_one]],
441 BaseGate::H => [[s, s], [s, neg_s]],
442 BaseGate::S => [[one, z], [z, i]],
443 BaseGate::Sdg => [[one, z], [z, neg_i]],
444 BaseGate::T => [[one, z], [z, gs.phase(3)]],
445 BaseGate::Tdg => [[one, z], [z, gs.phase(3).conj()]],
446 BaseGate::P => [[one, z], [z, gs.phase(param as usize)]],
447 }
448 }
449}
450
451#[derive(Clone, Debug, PartialEq, Eq)]
455pub struct Gate {
456 pub base: BaseGate,
457 pub controls: Vec<u8>,
458 pub target: u8,
459 pub param: u16,
460}
461
462#[derive(Clone, Debug, PartialEq, Eq)]
477#[non_exhaustive]
478pub struct Circuit {
479 pub n_qubits: u8,
480 pub ops: Vec<Gate>,
481 pub gateset: Gateset,
482}
483
484#[derive(Debug, PartialEq, Eq)]
486#[non_exhaustive]
487pub enum QuantumError {
488 Malformed(String),
494 Invalid(String),
500 Contract(String),
504 TooManyQubits(u8),
506}
507
508impl QuantumError {
509 pub fn kind(&self) -> &'static str {
512 match self {
513 QuantumError::Malformed(_) => "malformed",
514 QuantumError::Invalid(_) => "invalid",
515 QuantumError::Contract(_) => "contract",
516 QuantumError::TooManyQubits(_) => "too-many-qubits",
517 }
518 }
519}
520
521impl std::fmt::Display for QuantumError {
522 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
523 match self {
524 QuantumError::Malformed(e) => write!(f, "malformed WQC: {e}"),
525 QuantumError::Invalid(e) => write!(f, "invalid circuit: {e}"),
526 QuantumError::Contract(e) => write!(f, "contract mismatch: {e}"),
527 QuantumError::TooManyQubits(n) => write!(f, "too many qubits: {n}"),
528 }
529 }
530}
531impl std::error::Error for QuantumError {}
532
533pub const MAX_QUBITS: u8 = 26;
537
538impl Circuit {
539 pub fn new(n_qubits: u8) -> Self {
541 Circuit::with_gateset(n_qubits, Gateset::V2)
542 }
543
544 pub fn with_gateset(n_qubits: u8, gateset: Gateset) -> Self {
546 Circuit { n_qubits, ops: Vec::new(), gateset }
547 }
548
549 pub fn empty_like(&self) -> Self {
552 Circuit::with_gateset(self.n_qubits, self.gateset)
553 }
554
555 fn push(&mut self, base: BaseGate, controls: Vec<u8>, target: u8, param: u16) -> &mut Self {
556 self.ops.push(Gate { base, controls, target, param });
557 self
558 }
559
560 pub fn x(&mut self, q: u8) -> &mut Self { self.push(BaseGate::X, vec![], q, 0) }
562 pub fn y(&mut self, q: u8) -> &mut Self { self.push(BaseGate::Y, vec![], q, 0) }
563 pub fn z(&mut self, q: u8) -> &mut Self { self.push(BaseGate::Z, vec![], q, 0) }
564 pub fn h(&mut self, q: u8) -> &mut Self { self.push(BaseGate::H, vec![], q, 0) }
565 pub fn s(&mut self, q: u8) -> &mut Self { self.push(BaseGate::S, vec![], q, 0) }
566 pub fn t(&mut self, q: u8) -> &mut Self { self.push(BaseGate::T, vec![], q, 0) }
567 pub fn p(&mut self, k: u16, q: u8) -> &mut Self { self.push(BaseGate::P, vec![], q, k) }
569 pub fn cx(&mut self, c: u8, t: u8) -> &mut Self { self.push(BaseGate::X, vec![c], t, 0) }
570 pub fn cz(&mut self, c: u8, t: u8) -> &mut Self { self.push(BaseGate::Z, vec![c], t, 0) }
571 pub fn cp(&mut self, k: u16, c: u8, t: u8) -> &mut Self { self.push(BaseGate::P, vec![c], t, k) }
573 pub fn ccx(&mut self, c0: u8, c1: u8, t: u8) -> &mut Self {
575 self.push(BaseGate::X, vec![c0, c1], t, 0)
576 }
577 pub fn swap(&mut self, a: u8, b: u8) -> &mut Self {
579 self.cx(a, b).cx(b, a).cx(a, b)
580 }
581
582 pub fn qft(n: u8) -> Circuit {
587 let mut c = Circuit::new(n);
588 for j in 0..n {
589 c.h(j);
590 for l in (j + 1)..n {
591 let m = (l - j + 1) as u16;
593 c.cp(m, l, j);
594 }
595 }
596 for j in 0..(n / 2) {
597 c.swap(j, n - 1 - j);
598 }
599 c
600 }
601
602 pub fn validate(&self) -> Result<(), QuantumError> {
611 let n = self.n_qubits;
612 let strict = match self.gateset {
613 Gateset::V1 => false,
614 Gateset::V2 => true,
615 };
616 if u32::try_from(self.ops.len()).is_err() {
617 return Err(QuantumError::Invalid(format!("{} ops, more than u32::MAX", self.ops.len())));
618 }
619 for g in &self.ops {
620 if g.target >= n {
621 return Err(QuantumError::Invalid(format!("target {} ≥ n_qubits {}", g.target, n)));
622 }
623 if g.controls.len() > usize::from(u8::MAX) {
624 return Err(QuantumError::Invalid(format!("{} controls, more than 255", g.controls.len())));
625 }
626 let mut seen = [0u64; 4];
628 for &c in &g.controls {
629 if c >= n {
630 return Err(QuantumError::Invalid(format!("control {c} ≥ n_qubits {n}")));
631 }
632 if c == g.target {
633 return Err(QuantumError::Invalid(format!("control {c} equals target")));
634 }
635 if strict {
636 let (word, bit) = (usize::from(c >> 6), 1u64 << (c & 63));
637 if seen[word] & bit != 0 {
638 return Err(QuantumError::Invalid(format!("control {c} repeated")));
639 }
640 seen[word] |= bit;
641 }
642 }
643 if g.base == BaseGate::P {
644 let k = g.param as usize;
645 if k < 1 || k > DYADIC_MAX {
646 return Err(QuantumError::Invalid(format!("P(k) with k={k} out of 1..={DYADIC_MAX}")));
647 }
648 } else if strict && g.param != 0 {
649 return Err(QuantumError::Invalid(format!("{:?} with param {}; only P takes one", g.base, g.param)));
650 }
651 }
652 Ok(())
653 }
654
655 pub fn simulate(&self) -> Result<StateVector, QuantumError> {
659 self.simulate_from(0)
660 }
661
662 pub fn simulate_from(&self, start: usize) -> Result<StateVector, QuantumError> {
664 self.validate()?;
665 if self.n_qubits > MAX_QUBITS {
666 return Err(QuantumError::TooManyQubits(self.n_qubits));
667 }
668 let dim = 1usize << self.n_qubits;
669 let mut amps = vec![Amp::ZERO; dim];
670 amps[start % dim] = Amp::ONE;
671 for g in &self.ops {
672 apply(&mut amps, self.n_qubits, g, self.gateset);
673 }
674 Ok(StateVector { n_qubits: self.n_qubits, amps })
675 }
676}
677
678fn apply(amps: &mut [Amp], n: u8, g: &Gate, gs: Gateset) {
711 if matches!(g.base, BaseGate::I) {
712 return;
713 }
714 let m = g.base.matrix(g.param, gs);
715 let tbit = 1usize << g.target;
716 let ctrl_mask: usize = g.controls.iter().fold(0usize, |acc, &c| acc | (1usize << c));
717 let dim = 1usize << n;
718
719 let is_zero = |a: Amp| a.re == 0 && a.im == 0;
720 let is_one = |a: Amp| a.re == ONE && a.im == 0;
721
722 if is_one(m[0][0]) && is_zero(m[0][1]) && is_zero(m[1][0]) {
725 let ph = m[1][1];
726 let negate = ph.re == -ONE && ph.im == 0;
727 let mut i = 0usize;
728 while i < dim {
729 if i & tbit != 0 && (i & ctrl_mask) == ctrl_mask {
730 amps[i] = if negate {
731 Amp { re: -amps[i].re, im: -amps[i].im }
732 } else {
733 ph.mul(amps[i])
734 };
735 }
736 i += 1;
737 }
738 return;
739 }
740
741 if is_zero(m[0][0]) && is_zero(m[1][1]) {
743 let (a01, a10) = (m[0][1], m[1][0]);
744 let plain_swap = is_one(a01) && is_one(a10);
745 let mut i = 0usize;
746 while i < dim {
747 if i & tbit == 0 && (i & ctrl_mask) == ctrl_mask {
748 let j = i | tbit;
749 if plain_swap {
750 amps.swap(i, j);
751 } else {
752 let (a0, a1) = (amps[i], amps[j]);
753 amps[i] = a01.mul(a1);
754 amps[j] = a10.mul(a0);
755 }
756 }
757 i += 1;
758 }
759 return;
760 }
761
762 if m[0][0].im == 0 && m[0][1].im == 0 && m[1][0].im == 0 && m[1][1].im == 0 {
767 let (p00, p01) = (m[0][0].re, m[0][1].re);
768 let (p10, p11) = (m[1][0].re, m[1][1].re);
769 let mut i = 0usize;
770 while i < dim {
771 if i & tbit == 0 && (i & ctrl_mask) == ctrl_mask {
772 let j = i | tbit;
773 let a0 = amps[i];
774 let a1 = amps[j];
775 amps[i] = Amp {
776 re: fxmul(p00, a0.re) + fxmul(p01, a1.re),
777 im: fxmul(p00, a0.im) + fxmul(p01, a1.im),
778 };
779 amps[j] = Amp {
780 re: fxmul(p10, a0.re) + fxmul(p11, a1.re),
781 im: fxmul(p10, a0.im) + fxmul(p11, a1.im),
782 };
783 }
784 i += 1;
785 }
786 return;
787 }
788
789 let mut i = 0usize;
790 while i < dim {
791 if i & tbit == 0 && (i & ctrl_mask) == ctrl_mask {
792 let j = i | tbit;
793 let a0 = amps[i];
794 let a1 = amps[j];
795 amps[i] = m[0][0].mul(a0).add(m[0][1].mul(a1));
796 amps[j] = m[1][0].mul(a0).add(m[1][1].mul(a1));
797 }
798 i += 1;
799 }
800}
801
802#[derive(Clone, Debug, PartialEq, Eq)]
808pub struct StateVector {
809 pub n_qubits: u8,
810 pub amps: Vec<Amp>,
811}
812
813fn splitmix64(s: &mut u64) -> u64 {
816 *s = s.wrapping_add(0x9E37_79B9_7F4A_7C15);
817 let mut z = *s;
818 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
819 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
820 z ^ (z >> 31)
821}
822
823impl StateVector {
824 pub fn basis(n: u8, k: usize) -> StateVector {
826 let dim = 1usize << n;
827 let mut amps = vec![Amp::ZERO; dim];
828 amps[k % dim] = Amp::ONE;
829 StateVector { n_qubits: n, amps }
830 }
831
832 pub fn canonical_bytes(&self) -> Vec<u8> {
835 let mut out = Vec::with_capacity(self.amps.len() * 16);
836 for a in &self.amps {
837 out.extend_from_slice(&a.re.to_le_bytes());
838 out.extend_from_slice(&a.im.to_le_bytes());
839 }
840 out
841 }
842
843 pub fn statevector_hash(&self) -> [u8; 32] {
847 let mut h = blake3::Hasher::new();
848 h.update(DOMAIN_STATEVECTOR);
849 h.update(&[self.n_qubits]);
850 h.update(&FRAC.to_le_bytes());
851 h.update(&self.canonical_bytes());
852 *h.finalize().as_bytes()
853 }
854
855 pub fn prob_weights(&self) -> Vec<i128> {
859 self.amps.iter().map(|a| a.norm2()).collect()
860 }
861
862 pub fn sample_shots(&self, seed: u64, shots: u64) -> Vec<u64> {
866 let weights = self.prob_weights();
867 let mut cum = Vec::with_capacity(weights.len());
869 let mut total: i128 = 0;
870 for w in &weights {
871 total += *w;
872 cum.push(total);
873 }
874 let mut counts = vec![0u64; self.amps.len()];
875 if total <= 0 {
876 return counts;
877 }
878 let mut state = seed;
879 for _ in 0..shots {
880 let r = (splitmix64(&mut state) as u128 % total as u128) as i128;
881 let idx = match cum.binary_search_by(|c| {
883 if *c <= r { std::cmp::Ordering::Less } else { std::cmp::Ordering::Greater }
884 }) {
885 Ok(i) | Err(i) => i,
886 };
887 let slot = idx.min(counts.len() - 1);
888 counts[slot] += 1;
889 }
890 counts
891 }
892
893 pub fn histogram_hash(&self, seed: u64, shots: u64) -> [u8; 32] {
896 let counts = self.sample_shots(seed, shots);
897 let mut h = blake3::Hasher::new();
898 h.update(DOMAIN_HISTOGRAM);
899 h.update(&[self.n_qubits]);
900 h.update(&seed.to_le_bytes());
901 h.update(&shots.to_le_bytes());
902 for c in &counts {
903 h.update(&c.to_le_bytes());
904 }
905 *h.finalize().as_bytes()
906 }
907
908 pub fn fidelity_fx(&self, other: &StateVector) -> i64 {
915 assert_eq!(self.amps.len(), other.amps.len(), "fidelity needs equal dimension");
916 let mut re: i128 = 0;
917 let mut im: i128 = 0;
918 for (a, b) in self.amps.iter().zip(&other.amps) {
919 let p = a.conj().mul(*b);
921 re += p.re as i128;
922 im += p.im as i128;
923 }
924 ((re * re + im * im) >> FRAC) as i64
926 }
927}
928
929#[derive(Clone, Copy, Debug, PartialEq, Eq)]
936pub struct Measure {
937 pub seed: u64,
938 pub shots: u64,
939}
940
941const SECT_CONTRACT: u8 = 0x01;
942const SECT_OPLOG: u8 = 0x02;
943const SECT_MEASURE: u8 = 0x03;
944
945fn measure_bytes(m: Measure) -> Vec<u8> {
948 format!("{{\"seed\":{},\"shots\":{},\"basis\":\"computational\"}}", m.seed, m.shots).into_bytes()
949}
950
951fn oplog_bytes(c: &Circuit) -> Vec<u8> {
955 let n_ops = u32::try_from(c.ops.len()).expect("validate bounds the op count by u32::MAX");
956 let mut out = Vec::new();
957 out.extend_from_slice(&n_ops.to_le_bytes());
958 for g in &c.ops {
959 out.push(g.base.opcode());
960 out.push(u8::try_from(g.controls.len()).expect("validate bounds a gate's controls by 255"));
961 out.push(g.target);
962 out.extend_from_slice(&g.param.to_le_bytes());
963 out.extend_from_slice(&g.controls);
964 }
965 out
966}
967
968fn oplog_capacity(n_ops: u32, len: usize) -> usize {
972 usize::try_from(n_ops).unwrap_or(usize::MAX).min(len.saturating_sub(4) / 5)
973}
974
975fn parse_oplog(b: &[u8]) -> Result<(Vec<Gate>, usize), QuantumError> {
979 if b.len() < 4 {
980 return Err(QuantumError::Malformed("oplog header".into()));
981 }
982 let n_ops = u32::from_le_bytes([b[0], b[1], b[2], b[3]]);
983 let mut ops = Vec::with_capacity(oplog_capacity(n_ops, b.len()));
984 let mut pos = 4;
985 for _ in 0..n_ops {
986 if b.len() - pos < 5 {
987 return Err(QuantumError::Malformed("op header".into()));
988 }
989 let base = BaseGate::from_opcode(b[pos])
990 .ok_or_else(|| QuantumError::Malformed(format!("opcode {}", b[pos])))?;
991 let n_ctrl = usize::from(b[pos + 1]);
992 let target = b[pos + 2];
993 let param = u16::from_le_bytes([b[pos + 3], b[pos + 4]]);
994 pos += 5;
995 if b.len() - pos < n_ctrl {
996 return Err(QuantumError::Malformed("op controls".into()));
997 }
998 let controls = b[pos..pos + n_ctrl].to_vec();
999 pos += n_ctrl;
1000 ops.push(Gate { base, controls, target, param });
1001 }
1002 Ok((ops, pos))
1003}
1004
1005fn section_range(blob_start: usize, off: u32, len: u32, total: usize) -> Option<std::ops::Range<usize>> {
1009 let start = blob_start.checked_add(usize::try_from(off).ok()?)?;
1010 let end = start.checked_add(usize::try_from(len).ok()?)?;
1011 (end <= total).then_some(start..end)
1012}
1013
1014fn shown(v: Option<&Json>) -> String {
1017 let Some(v) = v else {
1018 return "missing".to_string();
1019 };
1020 let s = v.to_string();
1021 match s.char_indices().nth(64) {
1022 Some((cut, _)) => format!("{}…", &s[..cut]),
1023 None => s,
1024 }
1025}
1026
1027fn read_contract(gateset: Gateset, raw: &[u8]) -> Result<u8, QuantumError> {
1036 let obj = ijson::parse(raw).map_err(|e| QuantumError::Contract(format!("the contract is not I-JSON: {e}")))?;
1037 if !matches!(obj, Json::Object(_)) {
1038 return Err(QuantumError::Contract(format!("the contract is not a JSON object: {}", shown(Some(&obj)))));
1039 }
1040 let n_qubits = || {
1041 obj.get("n_qubits")
1042 .and_then(Json::as_u64)
1043 .and_then(|n| u8::try_from(n).ok())
1044 .ok_or_else(|| {
1045 QuantumError::Malformed(format!("n_qubits {} is not an integer in 0..=255", shown(obj.get("n_qubits"))))
1046 })
1047 };
1048 match gateset {
1049 Gateset::V1 => {
1050 let text = |key: &str, value: &str| {
1051 if obj.get(key).and_then(Json::as_str) == Some(value) {
1052 Ok(())
1053 } else {
1054 Err(QuantumError::Contract(format!("{key} is {}, not {value:?}", shown(obj.get(key)))))
1055 }
1056 };
1057 text("ext", gateset.ext())?;
1058 text("numeric", gateset.numeric())?;
1059 if obj.get("frac").and_then(Json::as_u64) != Some(u64::from(FRAC)) {
1060 return Err(QuantumError::Contract(format!("frac is {}, not {FRAC}", shown(obj.get("frac")))));
1061 }
1062 text("gateset", gateset.name())?;
1063 n_qubits()
1064 }
1065 Gateset::V2 => {
1066 let n = n_qubits()?;
1067 if raw != gateset.contract_bytes(n).as_slice() {
1068 return Err(QuantumError::Contract(format!(
1069 "not the canonical {} contract for {n} qubits",
1070 gateset.capability()
1071 )));
1072 }
1073 Ok(n)
1074 }
1075 }
1076}
1077
1078fn read_measure(gateset: Gateset, raw: &[u8]) -> Result<Measure, QuantumError> {
1084 let mv = ijson::parse(raw).map_err(|e| QuantumError::Malformed(format!("the measurement is not I-JSON: {e}")))?;
1085 let field = |key: &str| mv.get(key).and_then(Json::as_u64);
1086 match gateset {
1087 Gateset::V1 => Ok(Measure { seed: field("seed").unwrap_or(0), shots: field("shots").unwrap_or(0) }),
1088 Gateset::V2 => {
1089 let u64_field = |key: &str| {
1090 field(key).ok_or_else(|| QuantumError::Malformed(format!("measure {key} is {}, not a u64", shown(mv.get(key)))))
1091 };
1092 let m = Measure { seed: u64_field("seed")?, shots: u64_field("shots")? };
1093 if raw != measure_bytes(m).as_slice() {
1094 return Err(QuantumError::Malformed("not the canonical measure bytes".into()));
1095 }
1096 Ok(m)
1097 }
1098 }
1099}
1100
1101pub fn to_wqc(c: &Circuit, measure: Option<Measure>) -> Result<Vec<u8>, QuantumError> {
1114 c.validate()?;
1115 let mut sections = vec![(SECT_CONTRACT, c.gateset.contract_bytes(c.n_qubits)), (SECT_OPLOG, oplog_bytes(c))];
1116 if let Some(m) = measure {
1117 sections.push((SECT_MEASURE, measure_bytes(m)));
1118 }
1119
1120 let mut out = Vec::new();
1121 out.extend_from_slice(&c.gateset.magic());
1122 out.extend_from_slice(&(sections.len() as u16).to_le_bytes());
1124 let mut off: u64 = 0;
1125 for (kind, data) in §ions {
1126 let (Ok(off32), Ok(len32)) = (u32::try_from(off), u32::try_from(data.len())) else {
1127 return Err(QuantumError::Invalid(format!(
1128 "section {kind:#04x}, {} bytes at offset {off}, does not fit the container's u32 offsets",
1129 data.len()
1130 )));
1131 };
1132 out.push(*kind);
1133 out.extend_from_slice(&off32.to_le_bytes());
1134 out.extend_from_slice(&len32.to_le_bytes());
1135 off += u64::from(len32);
1136 }
1137 for (_, data) in §ions {
1138 out.extend_from_slice(data);
1139 }
1140 Ok(out)
1141}
1142
1143pub fn from_wqc(bytes: &[u8]) -> Result<(Circuit, Option<Measure>), QuantumError> {
1171 if bytes.len() < 6 {
1172 return Err(QuantumError::Malformed("magic".into()));
1173 }
1174 let gateset = Gateset::from_magic(&bytes[0..4]).ok_or_else(|| QuantumError::Malformed("magic".into()))?;
1175 let strict = match gateset {
1176 Gateset::V1 => false,
1177 Gateset::V2 => true,
1178 };
1179
1180 let n_sections = usize::from(u16::from_le_bytes([bytes[4], bytes[5]]));
1181 let table_start = 6;
1182 let blob_start = n_sections
1183 .checked_mul(9)
1184 .and_then(|len| len.checked_add(table_start))
1185 .filter(|&end| end <= bytes.len())
1186 .ok_or_else(|| QuantumError::Malformed("section table".into()))?;
1187 let u32_at = |p: usize| u32::from_le_bytes([bytes[p], bytes[p + 1], bytes[p + 2], bytes[p + 3]]);
1188 let mut contract: Option<&[u8]> = None;
1189 let mut oplog: Option<&[u8]> = None;
1190 let mut measure_raw: Option<&[u8]> = None;
1191 for s in 0..n_sections {
1192 let p = table_start + s * 9;
1194 let kind = bytes[p];
1195 let range = section_range(blob_start, u32_at(p + 1), u32_at(p + 5), bytes.len())
1196 .ok_or_else(|| QuantumError::Malformed(format!("section {kind:#04x} bounds")))?;
1197 let slot = match kind {
1198 SECT_CONTRACT => &mut contract,
1199 SECT_OPLOG => &mut oplog,
1200 SECT_MEASURE => &mut measure_raw,
1201 _ => continue, };
1203 if slot.replace(&bytes[range]).is_some() {
1204 return Err(QuantumError::Malformed(format!("duplicate section {kind:#04x}")));
1205 }
1206 }
1207 let contract = contract.ok_or_else(|| QuantumError::Malformed("missing contract".into()))?;
1208 let oplog = oplog.ok_or_else(|| QuantumError::Malformed("missing oplog".into()))?;
1209
1210 let n_qubits = read_contract(gateset, contract)?;
1211
1212 let (ops, used) = parse_oplog(oplog)?;
1213 if strict && used != oplog.len() {
1214 return Err(QuantumError::Malformed(format!("{} bytes after the last op", oplog.len() - used)));
1215 }
1216
1217 let circuit = Circuit { n_qubits, ops, gateset };
1218 circuit.validate()?;
1219
1220 let measure = measure_raw.map(|raw| read_measure(gateset, raw)).transpose()?;
1221 Ok((circuit, measure))
1222}
1223
1224pub fn circuit_hash(c: &Circuit) -> Result<[u8; 32], QuantumError> {
1234 c.validate()?;
1235 let mut h = blake3::Hasher::new();
1236 h.update(DOMAIN_CIRCUIT);
1237 h.update(&c.gateset.contract_bytes(c.n_qubits));
1238 h.update(&oplog_bytes(c));
1239 Ok(*h.finalize().as_bytes())
1240}
1241
1242fn wqc_table(magic: &[u8; 4], table: &[(u8, u32, u32)], blob: &[u8]) -> Vec<u8> {
1246 let mut out = magic.to_vec();
1247 out.extend_from_slice(&u16::try_from(table.len()).expect("a short table").to_le_bytes());
1248 for &(kind, off, len) in table {
1249 out.push(kind);
1250 out.extend_from_slice(&off.to_le_bytes());
1251 out.extend_from_slice(&len.to_le_bytes());
1252 }
1253 out.extend_from_slice(blob);
1254 out
1255}
1256
1257fn wqc_raw(magic: &[u8; 4], sections: &[(u8, &[u8])]) -> Vec<u8> {
1260 let (mut table, mut blob) = (Vec::new(), Vec::new());
1261 for &(kind, data) in sections {
1262 let at = |n: usize| u32::try_from(n).expect("a small section");
1263 table.push((kind, at(blob.len()), at(data.len())));
1264 blob.extend_from_slice(data);
1265 }
1266 wqc_table(magic, &table, &blob)
1267}
1268
1269fn op(opcode: u8, controls: &[u8], target: u8, param: u16) -> Vec<u8> {
1271 let mut out = vec![opcode, u8::try_from(controls.len()).expect("a few controls"), target];
1272 out.extend_from_slice(¶m.to_le_bytes());
1273 out.extend_from_slice(controls);
1274 out
1275}
1276
1277fn oplog_of(ops: &[Vec<u8>]) -> Vec<u8> {
1279 let mut out = u32::try_from(ops.len()).expect("a few ops").to_le_bytes().to_vec();
1280 for o in ops {
1281 out.extend_from_slice(o);
1282 }
1283 out
1284}
1285
1286
1287fn with_members(contract: &[u8], members: &[u8]) -> Vec<u8> {
1289 let (body, close) = contract.split_at(contract.len() - 1);
1290 assert_eq!(close, b"}", "a contract is an object");
1291 [body, b",", members, b"}"].concat()
1292}
1293
1294fn swap(bytes: &[u8], from: &str, to: &str) -> Vec<u8> {
1296 let s = std::str::from_utf8(bytes).expect("a contract is text");
1297 assert!(s.contains(from), "{from:?} is not in {s}");
1298 s.replacen(from, to, 1).into_bytes()
1299}
1300
1301pub fn reject_corpus() -> Vec<(&'static str, Vec<u8>, &'static str)> {
1308 let bell = oplog_of(&[op(4, &[], 0, 0), op(1, &[0], 1, 0)]);
1309 let canon1 = Gateset::V1.contract_bytes(2);
1310 let canon2 = Gateset::V2.contract_bytes(2);
1311 let v1 = |contract: &[u8]| wqc_raw(b"WQC1", &[(SECT_CONTRACT, contract), (SECT_OPLOG, &bell)]);
1312 let v1_oplog = |oplog: &[u8]| wqc_raw(b"WQC1", &[(SECT_CONTRACT, &canon1), (SECT_OPLOG, oplog)]);
1313 let v2 = |contract: &[u8], oplog: &[u8], measure: Option<&[u8]>| {
1314 let mut sections = vec![(SECT_CONTRACT, contract), (SECT_OPLOG, oplog)];
1315 sections.extend(measure.map(|m| (SECT_MEASURE, m)));
1316 wqc_raw(b"WQC2", §ions)
1317 };
1318 let v2_contract = |contract: &[u8]| v2(contract, &bell, None);
1319 let v2_oplog = |oplog: &[u8]| v2(&canon2, oplog, None);
1320 let v2_measure = |measure: &[u8]| v2(&canon2, &bell, Some(measure));
1321 let mut magic_wqc3 = v2_contract(&canon2);
1322 magic_wqc3[0..4].copy_from_slice(b"WQC3");
1323 let n_ops_u32max = [u32::MAX.to_le_bytes().as_slice(), &op(4, &[], 0, 0)].concat();
1325 let wrap = wqc_table(
1327 b"WQC1",
1328 &[(SECT_CONTRACT, 0, u32::try_from(canon1.len()).expect("a short contract")), (SECT_OPLOG, 0xFFFF_FFF0, 0x20)],
1329 &[canon1.as_slice(), &bell].concat(),
1330 );
1331 let mut table_past_end = v1(&canon1);
1333 table_past_end.truncate(6 + 9);
1334 let at = |n: usize| u32::try_from(n).expect("a short section");
1335 let (c1, b1) = (at(canon1.len()), at(bell.len()));
1336 let both = [canon1.as_slice(), &bell].concat();
1337 let not_utf8 = [&canon1[..canon1.len() - 1], b",\"note\":\"\xff\"}"].concat();
1339 let truncated_controls = [1u32.to_le_bytes().as_slice(), &[1, 2, 1, 0, 0, 0]].concat();
1341 let measure = b"{\"seed\":1,\"shots\":10,\"basis\":\"computational\"}";
1342 let deep = [&canon1[..canon1.len() - 1], b",\"x\":", &b"[".repeat(127), &b"]".repeat(127), b"}"].concat();
1345 let reordered2 = format!(
1346 r#"{{"n_qubits":2,"ext":"{}","numeric":"{}","frac":30,"gateset":"{}"}}"#,
1347 Gateset::V2.ext(),
1348 Gateset::V2.numeric(),
1349 Gateset::V2.name()
1350 );
1351 vec![
1352 ("magic_wqc3", magic_wqc3, "malformed"),
1353 ("v1_ext_other", v1(&swap(&canon1, "circuit/1", "circuit/2")), "contract"),
1354 ("v1_numeric_other", v1(&swap(&canon1, "wai.det.fixed64", "wai.det.amp64")), "contract"),
1355 ("v1_frac_31", v1(&swap(&canon1, "\"frac\":30", "\"frac\":31")), "contract"),
1356 ("v1_frac_float", v1(&swap(&canon1, "\"frac\":30", "\"frac\":30.0")), "contract"),
1357 ("v1_gateset_rn", v1(&swap(&canon1, "dyadicP\"", "dyadicP.rn\"")), "contract"),
1358 ("v1_contract2_in_wqc1", v1(&canon2), "contract"),
1359 ("v1_fields_missing", v1(br#"{"n_qubits":3}"#), "contract"),
1360 ("v1_contract_not_object", v1(&[b"[".as_slice(), &canon1, b"]"].concat()), "contract"),
1361 ("v1_n_qubits_256", v1(&swap(&canon1, "\"n_qubits\":2", "\"n_qubits\":256")), "malformed"),
1362 ("v1_n_qubits_258", v1(&swap(&canon1, "\"n_qubits\":2", "\"n_qubits\":258")), "malformed"),
1363 (
1364 "v1_contract_twice",
1365 wqc_raw(b"WQC1", &[(SECT_CONTRACT, &canon1), (SECT_OPLOG, &bell), (SECT_CONTRACT, &Gateset::V1.contract_bytes(3))]),
1366 "malformed",
1367 ),
1368 (
1369 "v1_oplog_twice",
1370 wqc_raw(b"WQC1", &[(SECT_CONTRACT, &canon1), (SECT_OPLOG, &bell), (SECT_OPLOG, &oplog_of(&[op(1, &[], 0, 0)]))]),
1371 "malformed",
1372 ),
1373 ("v1_contract_duplicate_member", v1(&swap(&canon1, "\"frac\":30", "\"frac\":30,\"frac\":30")), "contract"),
1375 ("v1_contract_lone_surrogate", v1(&swap(&canon1, "}", ",\"note\":\"\\ud800\"}")), "contract"),
1376 ("v1_contract_too_deep", v1(&deep), "contract"),
1377 ("v1_n_qubits_minus0", v1(&swap(&canon1, "\"n_qubits\":2", "\"n_qubits\":-0")), "malformed"),
1378 ("v1_contract_lone_low_surrogate", v1(&with_members(&canon1, br#""note":"\udc00""#)), "contract"),
1379 ("v1_contract_noncharacter", v1(&with_members(&canon1, br#""note":"\ufdd0""#)), "contract"),
1380 ("v1_contract_raw_noncharacter", v1(&with_members(&canon1, "\"note\":\"\u{ffff}\"".as_bytes())), "contract"),
1381 (
1382 "v1_contract_integer_rounds_to_infinity",
1383 v1(&with_members(&canon1, format!("\"x\":{RANGE_LIMIT}").as_bytes())),
1384 "contract",
1385 ),
1386 ("v1_contract_number_rounds_to_infinity", v1(&with_members(&canon1, br#""x":1.7976931348623159e308"#)), "contract"),
1387 (
1389 "v1_contract_long_literal",
1390 v1(&with_members(&canon1, format!("\"x\":0.{}1e655669", "0".repeat(655_359)).as_bytes())),
1391 "contract",
1392 ),
1393 ("v1_contract_bom", v1(&[b"\xef\xbb\xbf".as_slice(), &canon1].concat()), "contract"),
1394 ("v1_contract_nan", v1(&with_members(&canon1, br#""x":NaN"#)), "contract"),
1395 ("v1_contract_control_character", v1(&with_members(&canon1, b"\"note\":\"a\x01b\"")), "contract"),
1396 (
1398 "v1_measure_duplicate_member",
1399 wqc_raw(b"WQC1", &[(SECT_CONTRACT, &canon1), (SECT_OPLOG, &bell), (SECT_MEASURE, br#"{"seed":1,"seed":2,"shots":10}"#)]),
1400 "malformed",
1401 ),
1402 ("v1_contract_not_utf8", v1(¬_utf8), "contract"),
1403 ("v1_contract_number_out_of_range", v1(&swap(&canon1, "}", ",\"x\":1e400}")), "contract"),
1404 ("v1_n_qubits_fraction", v1(&swap(&canon1, "\"n_qubits\":2", "\"n_qubits\":2.0")), "malformed"),
1405 ("v1_n_qubits_missing", v1(&swap(&canon1, "\"n_qubits\":2,", "")), "malformed"),
1406 (
1407 "v1_measure_not_json",
1408 wqc_raw(b"WQC1", &[(SECT_CONTRACT, &canon1), (SECT_OPLOG, &bell), (SECT_MEASURE, br#"{"seed":1,"#)]),
1409 "malformed",
1410 ),
1411 ("short_payload", b"WQC2\x00".to_vec(), "malformed"),
1413 ("table_past_end", table_past_end, "malformed"),
1414 ("section_past_end", wqc_table(b"WQC1", &[(SECT_CONTRACT, 0, c1), (SECT_OPLOG, c1, b1 + 1)], &both), "malformed"),
1415 (
1416 "unknown_section_past_end",
1417 wqc_table(b"WQC1", &[(SECT_CONTRACT, 0, c1), (SECT_OPLOG, c1, b1), (0x7F, c1 + b1, 1)], &both),
1418 "malformed",
1419 ),
1420 ("no_contract", wqc_raw(b"WQC1", &[(SECT_OPLOG, &bell)]), "malformed"),
1421 ("no_oplog", wqc_raw(b"WQC1", &[(SECT_CONTRACT, &canon1)]), "malformed"),
1422 ("v1_oplog_short", v1_oplog(&[0, 0, 0]), "malformed"),
1424 ("v1_op_truncated_controls", v1_oplog(&truncated_controls), "malformed"),
1425 ("v1_opcode_10", v1_oplog(&oplog_of(&[op(10, &[], 0, 0)])), "malformed"),
1426 ("v1_target_out_of_range", v1_oplog(&oplog_of(&[op(4, &[], 2, 0)])), "invalid"),
1427 ("v1_control_out_of_range", v1_oplog(&oplog_of(&[op(1, &[2], 1, 0)])), "invalid"),
1428 ("v1_control_is_target", v1_oplog(&oplog_of(&[op(1, &[1], 1, 0)])), "invalid"),
1429 ("v1_phase_0", v1_oplog(&oplog_of(&[op(9, &[], 0, 0)])), "invalid"),
1430 ("v1_phase_33", v1_oplog(&oplog_of(&[op(9, &[], 0, 33)])), "invalid"),
1431 ("v1_n_ops_u32max", v1_oplog(&n_ops_u32max), "malformed"),
1432 ("v1_offset_wrap", wrap, "malformed"),
1433 ("v2_contract_whitespace", v2_contract(&swap(&canon2, "\"n_qubits\":2", "\"n_qubits\": 2")), "contract"),
1434 ("v2_contract_key_order", v2_contract(reordered2.as_bytes()), "contract"),
1435 ("v2_contract_extra_member", v2_contract(&swap(&canon2, "}", ",\"note\":\"x\"}")), "contract"),
1436 ("v2_contract1_in_wqc2", v2_contract(&canon1), "contract"),
1437 ("v2_n_qubits_256", v2_contract(&swap(&canon2, "\"n_qubits\":2", "\"n_qubits\":256")), "malformed"),
1438 ("v2_contract_not_object", v2_contract(&[b"[".as_slice(), &canon2, b"]"].concat()), "contract"),
1439 (
1442 "v2_n_qubits_rounds_to_infinity",
1443 v2_contract(&swap(&canon2, "\"n_qubits\":2", "\"n_qubits\":1.7976931348623159e308")),
1444 "contract",
1445 ),
1446 (
1447 "v2_n_qubits_largest_finite",
1448 v2_contract(&swap(&canon2, "\"n_qubits\":2", "\"n_qubits\":1.7976931348623158e308")),
1449 "malformed",
1450 ),
1451 ("v2_phase_0", v2_oplog(&oplog_of(&[op(9, &[], 0, 0)])), "invalid"),
1452 (
1453 "v2_measure_twice",
1454 wqc_raw(b"WQC2", &[(SECT_CONTRACT, &canon2), (SECT_OPLOG, &bell), (SECT_MEASURE, measure), (SECT_MEASURE, measure)]),
1455 "malformed",
1456 ),
1457 ("v2_measure_seed_signed", v2_measure(br#"{"seed":-1,"shots":10,"basis":"computational"}"#), "malformed"),
1458 (
1459 "v2_duplicate_controls",
1460 v2(&Gateset::V2.contract_bytes(3), &oplog_of(&[op(1, &[0, 0], 2, 0)]), None),
1461 "invalid",
1462 ),
1463 ("v2_param_on_h", v2_oplog(&oplog_of(&[op(4, &[], 0, 7), op(1, &[0], 1, 0)])), "invalid"),
1464 ("v2_oplog_trailing_byte", v2_oplog(&[bell.as_slice(), &[0]].concat()), "malformed"),
1465 ("v2_opcode_10", v2_oplog(&oplog_of(&[op(10, &[], 0, 0)])), "malformed"),
1466 ("v2_measure_spaced", v2_measure(br#"{"seed": 1, "shots": 10, "basis": "computational"}"#), "malformed"),
1467 ("v2_measure_no_shots", v2_measure(br#"{"seed":1,"basis":"computational"}"#), "malformed"),
1468 ("v2_measure_basis_x", v2_measure(br#"{"seed":1,"shots":10,"basis":"x"}"#), "malformed"),
1469 ]
1470}
1471
1472pub fn accept_corpus() -> Vec<(&'static str, Vec<u8>, Circuit, Option<Measure>)> {
1480 let bell_ops = |gateset| Circuit {
1481 n_qubits: 2,
1482 ops: vec![
1483 Gate { base: BaseGate::H, controls: vec![], target: 0, param: 0 },
1484 Gate { base: BaseGate::X, controls: vec![0], target: 1, param: 0 },
1485 ],
1486 gateset,
1487 };
1488 let (bell1, bell2) = (bell_ops(Gateset::V1), bell_ops(Gateset::V2));
1489 let wide = |c: &Circuit| Circuit { n_qubits: 255, ..c.clone() };
1490 let bell = oplog_of(&[op(4, &[], 0, 0), op(1, &[0], 1, 0)]);
1491 let canon1 = Gateset::V1.contract_bytes(2);
1492 let canon2 = Gateset::V2.contract_bytes(2);
1493 let v1 = |contract: &[u8]| wqc_raw(b"WQC1", &[(SECT_CONTRACT, contract), (SECT_OPLOG, &bell)]);
1494 let v1_measure = |measure: &[u8]| wqc_raw(b"WQC1", &[(SECT_CONTRACT, &canon1), (SECT_OPLOG, &bell), (SECT_MEASURE, measure)]);
1495 let m = |seed, shots| Some(Measure { seed, shots });
1496 let reordered = format!(
1497 r#"{{"gateset":"{}","frac":30,"numeric":"{}","n_qubits":2,"ext":"{}"}}"#,
1498 Gateset::V1.name(),
1499 Gateset::V1.numeric(),
1500 Gateset::V1.ext()
1501 );
1502 let spaced = String::from_utf8(canon1.clone()).expect("a contract is text").replace(':', " : ").replace(',', " ,\n\t");
1503 let spaced = format!(" \r\n{spaced}\r\n ");
1504 let depth = format!(r#""x":{}{}"#, "[".repeat(126), "]".repeat(126));
1505 let largest_integer = format!("{}1", &RANGE_LIMIT[..RANGE_LIMIT.len() - 1]);
1506 let unknown = |magic: &[u8; 4], contract: &[u8]| {
1507 wqc_raw(magic, &[(0x7F, b"later"), (SECT_CONTRACT, contract), (SECT_OPLOG, &bell), (0x04, b"")])
1508 };
1509 let mut param_on_h = bell1.clone();
1510 param_on_h.ops[0].param = 7;
1511 let mut repeated_control = bell1.clone();
1512 repeated_control.ops[1].controls = vec![0, 0];
1513 vec![
1514 ("v1_members_reordered", v1(reordered.as_bytes()), bell1.clone(), None),
1515 ("v1_whitespace", v1(spaced.as_bytes()), bell1.clone(), None),
1516 (
1517 "v1_further_members",
1518 v1(&with_members(&canon1, br#""note":"x","list":[0,-1,2.5,-3e-2,1E+2,true,false,null,{},[]],"obj":{"a":{"b":[]}},"again":{"note":1}"#)),
1519 bell1.clone(),
1520 None,
1521 ),
1522 (
1523 "v1_escapes_and_characters",
1524 v1(&with_members(&canon1, "\"note\":\"\\\"\\\\\\/\\b\\f\\n\\r\\t\\u00e9\\ud83d\\ude00\\udbff\\udffd\u{e9}\u{fdcf}\u{fdf0}\u{fffd}\u{10fffd}\"".as_bytes())),
1525 bell1.clone(),
1526 None,
1527 ),
1528 ("v1_depth_127", v1(&with_members(&canon1, depth.as_bytes())), bell1.clone(), None),
1529 (
1530 "v1_numbers_largest_finite",
1531 v1(&with_members(&canon1, format!(r#""x":1.7976931348623158e308,"y":-1.7976931348623157e308,"z":{largest_integer},"w":-{largest_integer}"#).as_bytes())),
1532 bell1.clone(),
1533 None,
1534 ),
1535 (
1538 "v1_numbers_long_literals",
1539 v1(&with_members(&canon1, format!("\"x\":1{}e-655360,\"y\":1e{}1", "0".repeat(655_360), "0".repeat(4300)).as_bytes())),
1540 bell1.clone(),
1541 None,
1542 ),
1543 (
1544 "v1_numbers_toward_zero",
1545 v1(&with_members(&canon1, br#""x":1e-400,"y":-0,"z":0e99999999999999999999,"w":-0.0E-0"#)),
1546 bell1.clone(),
1547 None,
1548 ),
1549 ("v1_n_qubits_255", v1(&Gateset::V1.contract_bytes(255)), wide(&bell1), None),
1550 ("v1_measure_lax", v1_measure(br#" { "basis" : "x" , "shots" : 10 , "seed" : 1 } "#), bell1.clone(), m(1, 10)),
1551 ("v1_measure_missing_shots", v1_measure(br#"{"seed":1,"basis":"computational"}"#), bell1.clone(), m(1, 0)),
1552 (
1553 "v1_measure_u64_max",
1554 v1_measure(br#"{"seed":18446744073709551615,"shots":18446744073709551615}"#),
1555 bell1.clone(),
1556 m(u64::MAX, u64::MAX),
1557 ),
1558 (
1559 "v1_measure_not_u64",
1560 v1_measure(br#"{"seed":18446744073709551616,"shots":-0,"x":2.5}"#),
1561 bell1.clone(),
1562 m(0, 0),
1563 ),
1564 ("v1_measure_other_types", v1_measure(br#"{"seed":"1","shots":1e1,"basis":null}"#), bell1.clone(), m(0, 0)),
1565 ("v1_measure_not_object", v1_measure(b"[1,2]"), bell1.clone(), m(0, 0)),
1566 (
1567 "v1_oplog_trailing_bytes",
1568 wqc_raw(b"WQC1", &[(SECT_CONTRACT, &canon1), (SECT_OPLOG, &[bell.as_slice(), &[0, 1, 2]].concat())]),
1569 bell1.clone(),
1570 None,
1571 ),
1572 (
1573 "v1_param_on_h",
1574 wqc_raw(b"WQC1", &[(SECT_CONTRACT, &canon1), (SECT_OPLOG, &oplog_of(&[op(4, &[], 0, 7), op(1, &[0], 1, 0)]))]),
1575 param_on_h,
1576 None,
1577 ),
1578 (
1579 "v1_repeated_control",
1580 wqc_raw(b"WQC1", &[(SECT_CONTRACT, &canon1), (SECT_OPLOG, &oplog_of(&[op(4, &[], 0, 0), op(1, &[0, 0], 1, 0)]))]),
1581 repeated_control,
1582 None,
1583 ),
1584 ("v1_unknown_sections", unknown(b"WQC1", &canon1), bell1.clone(), None),
1585 ("v2_unknown_sections", unknown(b"WQC2", &canon2), bell2.clone(), None),
1586 (
1587 "v2_n_qubits_255",
1588 wqc_raw(b"WQC2", &[(SECT_CONTRACT, &Gateset::V2.contract_bytes(255)), (SECT_OPLOG, &bell)]),
1589 wide(&bell2),
1590 None,
1591 ),
1592 (
1593 "v2_measure_u64_max",
1594 wqc_raw(
1595 b"WQC2",
1596 &[
1597 (SECT_CONTRACT, &canon2),
1598 (SECT_OPLOG, &bell),
1599 (SECT_MEASURE, br#"{"seed":18446744073709551615,"shots":18446744073709551615,"basis":"computational"}"#),
1600 ],
1601 ),
1602 bell2,
1603 m(u64::MAX, u64::MAX),
1604 ),
1605 ]
1606}
1607
1608#[cfg(test)]
1609mod tests {
1610 use super::*;
1611 fn apply_reference(amps: &mut [Amp], n: u8, g: &Gate, gs: Gateset) {
1616 let m = g.base.matrix(g.param, gs);
1617 let tbit = 1usize << g.target;
1618 let ctrl_mask: usize = g.controls.iter().fold(0usize, |acc, &c| acc | (1usize << c));
1619 let dim = 1usize << n;
1620 let mut i = 0usize;
1621 while i < dim {
1622 if i & tbit == 0 && (i & ctrl_mask) == ctrl_mask {
1623 let j = i | tbit;
1624 let a0 = amps[i];
1625 let a1 = amps[j];
1626 amps[i] = m[0][0].mul(a0).add(m[0][1].mul(a1));
1627 amps[j] = m[1][0].mul(a0).add(m[1][1].mul(a1));
1628 }
1629 i += 1;
1630 }
1631 }
1632
1633
1634 #[test]
1639 #[ignore]
1640 fn probe_apply_speedup() {
1641 use std::time::Instant;
1642 let n = 20u8;
1643 let dim = 1usize << n;
1644 let reps = 5;
1645 let gates = 60;
1646 println!("\\n n={n} best of {reps}, interleaved");
1647 println!(" gate reference optimised speedup");
1648 for kind in ["h", "t", "z", "s", "x", "cx", "cz", "y"] {
1649 let mut ops: Vec<Gate> = Vec::new();
1650 for i in 0..gates {
1651 let q = (i % n as usize) as u8;
1652 let o = (q + 1) % n;
1653 ops.push(match kind {
1654 "h" => Gate { base: BaseGate::H, controls: vec![], target: q, param: 0 },
1655 "t" => Gate { base: BaseGate::T, controls: vec![], target: q, param: 0 },
1656 "z" => Gate { base: BaseGate::Z, controls: vec![], target: q, param: 0 },
1657 "s" => Gate { base: BaseGate::S, controls: vec![], target: q, param: 0 },
1658 "x" => Gate { base: BaseGate::X, controls: vec![], target: q, param: 0 },
1659 "y" => Gate { base: BaseGate::Y, controls: vec![], target: q, param: 0 },
1660 "cx" => Gate { base: BaseGate::X, controls: vec![o], target: q, param: 0 },
1661 _ => Gate { base: BaseGate::Z, controls: vec![o], target: q, param: 0 },
1662 });
1663 }
1664 let (mut best_ref, mut best_opt) = (u128::MAX, u128::MAX);
1665 for _ in 0..reps {
1666 let mut a = vec![Amp::ZERO; dim];
1667 a[0] = Amp::ONE;
1668 let t0 = Instant::now();
1669 for g in &ops { apply_reference(&mut a, n, g, Gateset::V2); }
1670 best_ref = best_ref.min(t0.elapsed().as_nanos());
1671 std::hint::black_box(&a);
1672
1673 let mut b = vec![Amp::ZERO; dim];
1674 b[0] = Amp::ONE;
1675 let t1 = Instant::now();
1676 for g in &ops { apply(&mut b, n, g, Gateset::V2); }
1677 best_opt = best_opt.min(t1.elapsed().as_nanos());
1678 std::hint::black_box(&b);
1679 }
1680 let (r, o) = (best_ref as f64 / gates as f64, best_opt as f64 / gates as f64);
1681 println!(" {kind:<5} {r:>10.0} ns {o:>10.0} ns {:>9.2}x", r / o);
1682 }
1683 }
1684
1685 #[test]
1692 fn optimised_apply_is_bit_identical_to_the_general_path() {
1693 let mut st = 0x1234_5678_9ABC_DEF0u64;
1694 let mut rnd = |m: usize| {
1695 st = st.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
1696 ((st >> 33) as usize) % m.max(1)
1697 };
1698 for (case, gs) in Gateset::ALL.into_iter().flat_map(|gs| std::iter::repeat_n(gs, 300)).enumerate() {
1699 let n = 3 + rnd(3) as u8;
1700 let dim = 1usize << n;
1701 let mut c = Circuit::with_gateset(n, gs);
1702 for _ in 0..24 {
1703 let t = rnd(n as usize) as u8;
1704 let mut other = rnd(n as usize) as u8;
1705 if other == t {
1706 other = (t + 1) % n;
1707 }
1708 match rnd(13) {
1709 0 => { c.x(t); }
1710 1 => { c.y(t); }
1711 2 => { c.z(t); }
1712 3 => { c.h(t); }
1713 4 => { c.s(t); }
1714 5 => { c.t(t); }
1715 6 => { c.p(1 + rnd(DYADIC_MAX) as u16, t); }
1716 7 => { c.cx(other, t); }
1717 8 => { c.cz(other, t); }
1718 9 => { c.cp(1 + rnd(DYADIC_MAX) as u16, other, t); }
1719 10 => c.ops.push(Gate { base: BaseGate::Sdg, controls: vec![], target: t, param: 0 }),
1720 11 => c.ops.push(Gate { base: BaseGate::Tdg, controls: vec![], target: t, param: 0 }),
1721 _ => c.ops.push(Gate { base: BaseGate::I, controls: vec![], target: t, param: 0 }),
1722 }
1723 }
1724 if n >= 3 {
1727 c.ops.push(Gate { base: BaseGate::Y, controls: vec![0], target: n - 1, param: 0 });
1728 c.ccx(0, 1, n - 1);
1729 }
1730
1731 let mut want = vec![Amp::ZERO; dim];
1732 want[0] = Amp::ONE;
1733 for g in &c.ops {
1734 apply_reference(&mut want, n, g, gs);
1735 }
1736 let got = c.simulate().unwrap();
1737 assert_eq!(got.amps, want, "case {case}: optimised apply diverged on {} gates, n={n}, {gs:?}", c.ops.len());
1738 }
1739 }
1740
1741
1742 const TOL: i64 = ONE / 10_000;
1745
1746 fn approx(a: i64, b: i64, tol: i64) -> bool {
1747 (a - b).abs() <= tol
1748 }
1749
1750 fn hx(b: &[u8]) -> String {
1751 b.iter().map(|x| format!("{x:02x}")).collect()
1752 }
1753
1754 fn dyadic_ladder(gs: Gateset) -> Circuit {
1759 let mut c = Circuit::with_gateset(2, gs);
1760 c.x(0).y(1).z(1).s(1);
1761 c.push(BaseGate::Sdg, vec![], 1, 0);
1762 c.t(1);
1763 c.push(BaseGate::Tdg, vec![], 1, 0);
1764 c.push(BaseGate::I, vec![], 1, 0);
1765 for _ in 0..2 {
1766 for k in 1..=DYADIC_MAX as u16 {
1767 c.cp(k, 0, 1);
1768 }
1769 }
1770 c
1771 }
1772
1773 #[test]
1778 fn phase_table_v1_is_frozen() {
1779 let mut t = [Amp::ZERO; DYADIC_MAX + 1];
1782 t[1] = Amp { re: -ONE, im: 0 };
1783 t[2] = Amp { re: 0, im: ONE };
1784 for k in 3..=DYADIC_MAX {
1785 let c = t[k - 1].re;
1786 t[k] = Amp { re: sqrt_fx((ONE + c) / 2), im: sqrt_fx((ONE - c) / 2) };
1787 }
1788 assert_eq!(t, PHASE_V1, "PHASE_V1 is not the circuit/1 recurrence");
1789 assert_eq!(
1790 hx(&Gateset::V1.table_digest()),
1791 "3fa9805ff96feb819a1cf371fedcd3b5228f93eddf933d0edcf401cbf8c28466"
1792 );
1793 for k in 19..=DYADIC_MAX {
1795 assert_eq!(PHASE_V1[k], Amp { re: ONE - 1, im: 0 }, "PHASE_V1[{k}]");
1796 }
1797 assert_eq!(Gateset::V1.inv_sqrt2(), 759_250_124);
1798 let sv = dyadic_ladder(Gateset::V1).simulate().unwrap();
1801 assert_eq!(
1802 hx(&sv.statevector_hash()),
1803 "9754af24edd026601297a0f3334eb7171174978841a2938c9b372db942a3851d"
1804 );
1805 }
1806
1807 #[test]
1817 fn phase_table_v2_is_certified_nearest() {
1818 const S: u128 = 1 << 62;
1819 let mul = |a: u128, b: u128| a.checked_mul(b).expect("the enclosure overflowed u128");
1820 let ceil_isqrt = |x: u128| {
1821 let r = x.isqrt();
1822 if r * r == x { r } else { r + 1 }
1823 };
1824 let r = |x: u128| ((x + (1 << 31)) >> 32) as i64;
1826 let far_from_tie = |x: u128| ((x & 0xFFFF_FFFF) as i128 - (1i128 << 31)).abs() > 1 << 20;
1827
1828 let (mut c_lo, mut c_hi, mut s_lo, mut s_hi) = (0u128, 0u128, S, S);
1830 for k in 3..=DYADIC_MAX {
1831 let c_lo2 = (mul(S + c_lo, S) >> 1).isqrt();
1832 let c_hi2 = ceil_isqrt(mul(S + c_hi, S).div_ceil(2));
1833 let s_lo2 = mul(s_lo, S) / (2 * c_hi2);
1834 let s_hi2 = mul(s_hi, S).div_ceil(2 * c_lo2);
1835 assert!(c_lo2 <= c_hi2 && s_lo2 <= s_hi2, "P({k}): an empty enclosure");
1836 let want = PHASE_V2[k];
1837 assert_eq!((r(c_lo2), r(c_hi2)), (want.re, want.re), "re P({k}) is not certified");
1838 assert_eq!((r(s_lo2), r(s_hi2)), (want.im, want.im), "im P({k}) is not certified");
1839 for x in [c_lo2, c_hi2, s_lo2, s_hi2] {
1840 assert!(far_from_tie(x), "P({k}): an endpoint lies within 2^20 ulps of a tie");
1841 }
1842 (c_lo, c_hi, s_lo, s_hi) = (c_lo2, c_hi2, s_lo2, s_hi2);
1843 }
1844 assert_eq!(PHASE_V2[0], Amp::ZERO);
1845 assert_eq!(PHASE_V2[1], Amp { re: -ONE, im: 0 });
1846 assert_eq!(PHASE_V2[2], Amp { re: 0, im: ONE });
1847 assert_eq!(
1848 hx(&Gateset::V2.table_digest()),
1849 "d34cb793715348e7c92db027c0bc3dddb6008f5730470bafaed978ea7fb23510"
1850 );
1851 assert_eq!(Gateset::V2.inv_sqrt2(), 759_250_125);
1852 }
1853
1854 #[cfg(any(
1862 feature = "quantum_spd",
1863 feature = "quantum_pauli",
1864 feature = "quantum_mps",
1865 feature = "quantum_vml",
1866 feature = "quantum_phasor",
1867 feature = "quantum_kernel",
1868 feature = "quantum_tn",
1869 feature = "quantum_sv",
1870 feature = "quantum_tdvp",
1871 feature = "quantum_nqs"
1872 ))]
1873 #[test]
1874 fn every_v2_phase_is_within_one_unit_of_ideal() {
1875 const BOUND: f64 = 0.5 + 1e-6;
1877 let deviation = |t: &[Amp; 33]| -> Vec<(usize, f64)> {
1879 (1..=DYADIC_MAX)
1880 .map(|k| {
1881 let (s, c) = crate::repro::sin_cos(std::f64::consts::TAU / (1u64 << k) as f64);
1882 let one = ONE as f64;
1883 let dre = (t[k].re as f64 - c * one).abs();
1884 let dim = (t[k].im as f64 - s * one).abs();
1885 (k, dre.max(dim))
1886 })
1887 .collect()
1888 };
1889 let mut worst = 0.0f64;
1890 for (k, d) in deviation(&PHASE_V2) {
1891 assert!(d <= BOUND, "PHASE_V2[{k}] is {d} units from the ideal");
1892 worst = worst.max(d);
1893 }
1894 assert!(worst < 1.0);
1895 let v1_bad: Vec<usize> =
1898 deviation(&PHASE_V1).into_iter().filter(|&(_, d)| d > BOUND).map(|(k, _)| k).collect();
1899 assert!(v1_bad.contains(&8), "the check passed circuit/1's P(8): {v1_bad:?}");
1900 assert!((8..=DYADIC_MAX).all(|k| v1_bad.contains(&k)), "circuit/1 failures: {v1_bad:?}");
1901 }
1902
1903 #[test]
1907 fn table_identities_per_gateset() {
1908 for gs in Gateset::ALL {
1909 assert_eq!(gs.phase(1), Amp { re: -ONE, im: 0 }, "{gs:?} P(1)");
1910 assert_eq!(gs.phase(2), Amp { re: 0, im: ONE }, "{gs:?} P(2)");
1911 assert_eq!(gs.phase(3).re, gs.phase(3).im, "{gs:?} T");
1912 assert_eq!(gs.phase(3).re, gs.inv_sqrt2(), "{gs:?} 1/√2");
1913 assert_eq!(gs.table()[0], Amp::ZERO, "{gs:?} index 0");
1914 let units: i128 = match gs {
1915 Gateset::V1 => 3,
1916 Gateset::V2 => 1,
1917 };
1918 for k in 1..=DYADIC_MAX {
1919 let d = gs.phase(k).norm2() - (ONE as i128) * (ONE as i128);
1921 assert!(
1922 d.abs() <= units << FRAC,
1923 "{gs:?}: |P({k})|² − 1 = {} units",
1924 d as f64 / ONE as f64
1925 );
1926 }
1927 }
1928 }
1929
1930 #[test]
1934 fn gateset_identifiers_are_consistent() {
1935 assert_eq!(Gateset::default(), Gateset::V2);
1936 for gs in Gateset::ALL {
1937 assert_eq!(Gateset::from_magic(&gs.magic()), Some(gs));
1938 assert_eq!(Gateset::from_capability(gs.capability()), Some(gs));
1939 assert_eq!(gs.ext(), format!("{}/1", gs.capability()));
1940 }
1941 assert_eq!(Gateset::from_magic(b"WQC3"), None);
1942 assert_eq!(Gateset::from_magic(b"WQC"), None);
1943 assert_eq!(Gateset::from_magic(b"WQC1\0"), None);
1944 assert_eq!(Gateset::from_capability("wai.quantum.circuit/1"), None);
1945 assert_eq!(Gateset::from_capability("wai.quantum.circuit3"), None);
1946 assert_eq!(
1947 Gateset::V1.contract_bytes(5),
1948 br#"{"ext":"wai.quantum.circuit/1","n_qubits":5,"numeric":"wai.det.fixed64","frac":30,"gateset":"cliffordT+dyadicP"}"#
1949 );
1950 assert_eq!(
1951 Gateset::V2.contract_bytes(5),
1952 br#"{"ext":"wai.quantum.circuit2/1","n_qubits":5,"numeric":"wai.det.amp64","frac":30,"gateset":"cliffordT+dyadicP.rn"}"#
1953 );
1954 assert_eq!((Gateset::V1.contract_bytes(5).len(), Gateset::V2.contract_bytes(5).len()), (112, 114));
1955 for n in 0..=u8::MAX {
1956 assert_eq!(Gateset::V1.contract_bytes(n), serde_contract_v1(n), "n_qubits {n}");
1957 }
1958 }
1959
1960 fn serde_contract_v1(n_qubits: u8) -> Vec<u8> {
1964 serde_json::to_vec(&serde_json::json!({
1965 "ext": "wai.quantum.circuit/1",
1966 "n_qubits": n_qubits,
1967 "numeric": "wai.det.fixed64",
1968 "frac": FRAC,
1969 "gateset": "cliffordT+dyadicP",
1970 }))
1971 .unwrap()
1972 }
1973
1974 fn serde_measure(m: Measure) -> Vec<u8> {
1976 serde_json::to_vec(&serde_json::json!({ "seed": m.seed, "shots": m.shots, "basis": "computational" })).unwrap()
1977 }
1978
1979 #[test]
1980 fn simulation_is_deterministic_byte_for_byte() {
1981 let c = Circuit::qft(6);
1983 let a = c.simulate().unwrap();
1984 let b = c.simulate().unwrap();
1985 assert_eq!(a.amps, b.amps, "quantum sim must be byte-exact reproducible");
1986 assert_eq!(a.statevector_hash(), b.statevector_hash());
1987 }
1988
1989 #[test]
1990 fn bell_state() {
1991 let mut c = Circuit::new(2);
1993 c.h(0).cx(0, 1);
1994 let sv = c.simulate().unwrap();
1995 let s = c.gateset.inv_sqrt2();
1996 assert!(approx(sv.amps[0b00].re, s, TOL));
1997 assert_eq!(sv.amps[0b01], Amp::ZERO);
1998 assert_eq!(sv.amps[0b10], Amp::ZERO);
1999 assert!(approx(sv.amps[0b11].re, s, TOL));
2000 assert!(approx(sv.fidelity_fx(&sv), ONE, TOL));
2002 }
2003
2004 #[test]
2005 fn ghz_state() {
2006 let mut c = Circuit::new(3);
2008 c.h(0).cx(0, 1).cx(1, 2);
2009 let sv = c.simulate().unwrap();
2010 let s = c.gateset.inv_sqrt2();
2011 assert!(approx(sv.amps[0b000].re, s, TOL));
2012 assert!(approx(sv.amps[0b111].re, s, TOL));
2013 for k in 1..7 {
2014 assert_eq!(sv.amps[k], Amp::ZERO, "index {k} should be empty");
2015 }
2016 }
2017
2018 #[test]
2019 fn qft_of_zero_is_uniform_superposition() {
2020 let n = 4u8;
2022 let dim = 1usize << n;
2023 let sv = Circuit::qft(n).simulate().unwrap();
2024 let want2 = ONE / dim as i64;
2027 for k in 0..dim {
2028 let mag2 = ((sv.amps[k].norm2()) >> FRAC) as i64;
2029 assert!(approx(mag2, want2, ONE / 1000), "amp {k} magnitude^2 {mag2} != {want2}");
2030 }
2031 }
2032
2033 #[test]
2034 fn orthogonal_states_have_zero_fidelity() {
2035 let a = StateVector::basis(3, 0b000);
2036 let b = StateVector::basis(3, 0b111);
2037 assert_eq!(a.fidelity_fx(&b), 0);
2038 assert!(approx(a.fidelity_fx(&a), ONE, 1));
2039 }
2040
2041 #[test]
2042 fn shot_histogram_is_deterministic() {
2043 let mut c = Circuit::new(2);
2045 c.h(0).cx(0, 1);
2046 let sv = c.simulate().unwrap();
2047 let h1 = sv.histogram_hash(0xC0FFEE, 10_000);
2048 let h2 = sv.histogram_hash(0xC0FFEE, 10_000);
2049 assert_eq!(h1, h2, "pinned-seed histogram must be reproducible");
2050 let counts = sv.sample_shots(0xC0FFEE, 10_000);
2051 assert_eq!(counts[0b01], 0);
2052 assert_eq!(counts[0b10], 0);
2053 assert!(counts[0b00] > 4000 && counts[0b00] < 6000, "≈ half on |00⟩: {}", counts[0b00]);
2054 assert_eq!(counts[0b00] + counts[0b11], 10_000);
2055 }
2056
2057 fn corpus_circuits(gs: Gateset) -> [(&'static str, Circuit, Option<Measure>); 4] {
2060 let mut bell = Circuit::with_gateset(2, gs);
2061 bell.h(0).cx(0, 1);
2062 let mut ghz3 = Circuit::with_gateset(3, gs);
2063 ghz3.h(0).cx(0, 1).cx(1, 2);
2064 let qft = |n| {
2065 let mut c = Circuit::qft(n);
2066 c.gateset = gs;
2067 c
2068 };
2069 [
2070 ("bell", bell, Some(Measure { seed: 12_648_430, shots: 100_000 })),
2071 ("ghz3", ghz3, None),
2072 ("qft3", qft(3), None),
2073 ("qft5", qft(5), None),
2074 ]
2075 }
2076
2077 #[test]
2081 fn circuit1_files_are_written_as_before() {
2082 let want = [
2084 ("bell", 216, "97b294a85afe628060a51f12e7f9c2104ebc0b7d04ef36ff15b154ed1c54728e",
2085 "894d9a9582b19f27b8d08d06938357237fc00f7f30bf4bfc2be0c04fa5e47a4e"),
2086 ("ghz3", 157, "8f2d924416a19bc96407f36669ca3f55bcd6a57b3256bd16a669dc96bd3fb490",
2087 "34e44f7d68037cbb3aee677f56b894dc48df6d408aa068a16d3b18076e5e7c5c"),
2088 ("qft3", 191, "e1a7e49e2804130d802d8e927f63d6633b966c8e6b7cd3eb7278e01fd1f884c5",
2089 "7e8414be4410557a05b52bcc1084e1c5f93b6caa0f28fa45f4d2ab661efd2a60"),
2090 ("qft5", 261, "92b4e1497f52109922a0f2ea7ec8de2b1d9e92f155dafbf6e5f69eca4427878c",
2091 "f36a9af99a251cae6f42144b2be3c57523f15e1e2ab3492cf15cac709fc048be"),
2092 ];
2093 for ((name, c, m), (file, len, digest, hash)) in corpus_circuits(Gateset::V1).into_iter().zip(want) {
2094 assert_eq!(name, file);
2095 let bytes = to_wqc(&c, m).unwrap();
2096 assert_eq!(bytes.len(), len, "{name}.wqc");
2097 assert_eq!(hx(blake3::hash(&bytes).as_bytes()), digest, "{name}.wqc");
2098 assert_eq!(hx(&circuit_hash(&c).unwrap()), hash, "{name}");
2099 }
2100 let values = [0, 1, 42, 1024, 100_000, 12_648_430, u64::from(u32::MAX), u64::MAX];
2101 for seed in values {
2102 for shots in values {
2103 let m = Measure { seed, shots };
2104 assert_eq!(measure_bytes(m), serde_measure(m), "{m:?}");
2105 }
2106 }
2107 }
2108
2109 #[test]
2116 fn the_conformance_corpora_are_pinned() {
2117 type Row = (&'static str, usize, usize, &'static str, &'static str);
2119 let v1: [Row; 7] = [
2120 ("bell", 216, 2, "894d9a9582b19f27b8d08d06938357237fc00f7f30bf4bfc2be0c04fa5e47a4e",
2121 "bd5df69662f4e61878d17baee1f3cf5705c3726d6740d1a4bf7be5133c099efb"),
2122 ("ghz3", 157, 3, "34e44f7d68037cbb3aee677f56b894dc48df6d408aa068a16d3b18076e5e7c5c",
2123 "08c5e6fb50b91a00d28795506cc5d99ce1e9a3a8b4c41e4bfd05f5d829af9542"),
2124 ("qft3", 191, 9, "7e8414be4410557a05b52bcc1084e1c5f93b6caa0f28fa45f4d2ab661efd2a60",
2125 "0fe1e5b8e2ac8e9afbfa926b3877967ebfead92efb7ed8e1ff6b117668926d6e"),
2126 ("qft5", 261, 21, "f36a9af99a251cae6f42144b2be3c57523f15e1e2ab3492cf15cac709fc048be",
2127 "c268d77f2ddf84fecb028e80ce989123912870f04e5b9a1e3d885c9ce235c27d"),
2128 ("qft5_x22", 276, 24, "40c847fe9f85b6d79d245b6e65420197e6bbedb9c0b1ab77baaf1cfe077ff4f5",
2129 "95a2d2fd41f6a13c19562729a4b553ee12db6adfaa361b5061c16d801354d0cd"),
2130 ("dyadic_ladder", 564, 72, "2fa5b02c952dcaf55ab4cfd2ea5b3fae999ac912a32a4e06dd2b6ee96bb58d4f",
2131 "9754af24edd026601297a0f3334eb7171174978841a2938c9b372db942a3851d"),
2132 ("qft8_ones", 460, 56, "5ca94d3a960b8ec65a1eb802699f1ed8e20f00e7fbd9297849f0c60047404a1a",
2133 "fd683498744552e9e2e8657a2bdc244e4f03d4ee4dd292c9705ba38b19fb709c"),
2134 ];
2135 let v2: [Row; 7] = [
2136 ("bell", 218, 2, "6f6199943059fc4d90554d7c33f1ac69723603f5128962a6e3cac9c765484d2b",
2137 "db68a50b08a8c2eabc89a881db9fcb130fff89dc36182d4c7f90da5bf4720b15"),
2138 ("ghz3", 159, 3, "c5911b898a899c999ce01730bb825c84678569493ac513760e15dcbccd84227f",
2139 "51850f28817200e9e03980b60f34879c7bd0147a07b0c1746528b801cae2e2f8"),
2140 ("qft3", 193, 9, "c23a1b1e31b87705999cbc54866f4c9e3e4c4d2b274d6260672121abb5ca89b8",
2141 "70fe25ec22008f86076328b2c0908ede778e6d8227e563e8c7dcd89f7e844bf2"),
2142 ("qft5", 263, 21, "8559c83769ac8c0cb3aabc64672233aa569cd4ce92aa6545f1449982b9aa8b8c",
2143 "50b01fab0bfee966e553a947aea6aa09cebf8f21ba201cd548d703048d66f2ba"),
2144 ("qft5_x22", 278, 24, "c126604030b16f4f1e88e6683894f9316262f49b3419be06ab74c7dbd2441651",
2145 "5def2fca1d29e9e2b9ca2020cd2fa2dc056d07ce2e21b6e101b6116a03efd61c"),
2146 ("dyadic_ladder", 566, 72, "8a6dcf0d41ecefd1a9761fef49b385d7dffe7543a9382efda02bfe4f7a0896fe",
2147 "28bdb7265c10d798012ac78b604327700acd8cdac0e1f88ed4045f8b0b78bc7c"),
2148 ("qft8_ones", 462, 56, "286f044750b19937f6e7bf6f534053aa74a21777516c773dbd7c254df7301b34",
2149 "e059d43a574780d819a872f7904bf96918ba5880e1c7c32b38159306acd559fe"),
2150 ];
2151 for (gs, rows) in [(Gateset::V1, v1), (Gateset::V2, v2)] {
2152 let x_then_qft = |n: u8, xs: &[u8]| {
2154 let mut c = Circuit::with_gateset(n, gs);
2155 for &q in xs {
2156 c.x(q);
2157 }
2158 c.ops.extend(Circuit::qft(n).ops);
2159 c
2160 };
2161 let mut vectors: Vec<(&str, Circuit, Option<Measure>)> = corpus_circuits(gs).into();
2162 vectors.push(("qft5_x22", x_then_qft(5, &[1, 2, 4]), None));
2163 vectors.push(("dyadic_ladder", dyadic_ladder(gs), None));
2164 vectors.push(("qft8_ones", x_then_qft(8, &[0, 1, 2, 3, 4, 5, 6, 7]), None));
2165 for ((name, c, m), (file, len, n_ops, circuit, statevector)) in vectors.into_iter().zip(rows) {
2166 assert_eq!(name, file);
2167 assert_eq!(c.gateset, gs, "{name}");
2168 assert_eq!(to_wqc(&c, m).unwrap().len(), len, "{gs:?} {name}.wqc");
2169 assert_eq!(c.ops.len(), n_ops, "{gs:?} {name}");
2170 assert_eq!(hx(&circuit_hash(&c).unwrap()), circuit, "{gs:?} {name}");
2171 let sv = c.simulate().unwrap();
2172 assert_eq!(hx(&sv.statevector_hash()), statevector, "{gs:?} {name}");
2173 if let Some(Measure { seed, shots }) = m {
2176 assert_eq!(name, "bell");
2177 assert_eq!(
2178 hx(&sv.histogram_hash(seed, shots)),
2179 "0893f48ef32ca9f2865f6617537f0a4a5334f67e3c944bba845beb2f377acdb3",
2180 "{gs:?} {name}"
2181 );
2182 }
2183 }
2184 }
2185 }
2186
2187 #[test]
2195 fn wqc_round_trips() {
2196 for gs in Gateset::ALL {
2197 for (name, c, m) in corpus_circuits(gs) {
2198 for m in [None, m.or(Some(Measure { seed: 42, shots: 1024 }))] {
2199 let bytes = to_wqc(&c, m).unwrap();
2200 assert_eq!(bytes[0..4], gs.magic(), "{gs:?} {name}");
2201 let (c2, m2) = from_wqc(&bytes).unwrap();
2202 assert_eq!((&c2, m2), (&c, m), "{gs:?} {name}");
2203 assert_eq!(circuit_hash(&c2).unwrap(), circuit_hash(&c).unwrap(), "{gs:?} {name}");
2204 assert_eq!(c2.simulate().unwrap(), c.simulate().unwrap(), "{gs:?} {name}");
2205 }
2206 }
2207 }
2208 let want = [
2211 ("bell", 218, "6f6199943059fc4d90554d7c33f1ac69723603f5128962a6e3cac9c765484d2b"),
2212 ("ghz3", 159, "c5911b898a899c999ce01730bb825c84678569493ac513760e15dcbccd84227f"),
2213 ("qft3", 193, "c23a1b1e31b87705999cbc54866f4c9e3e4c4d2b274d6260672121abb5ca89b8"),
2214 ("qft5", 263, "8559c83769ac8c0cb3aabc64672233aa569cd4ce92aa6545f1449982b9aa8b8c"),
2215 ];
2216 for ((name, c, m), (file, len, hash)) in corpus_circuits(Gateset::V2).into_iter().zip(want) {
2217 assert_eq!(name, file);
2218 assert_eq!(to_wqc(&c, m).unwrap().len(), len, "circuit2 {name}.wqc");
2219 assert_eq!(hx(&circuit_hash(&c).unwrap()), hash, "circuit2 {name}");
2220 }
2221
2222 let [(_, bell, _), ..] = corpus_circuits(Gateset::V1);
2225 let accepted = accept_corpus();
2226 assert_eq!(accepted[0].2, bell);
2227 let names: std::collections::BTreeSet<_> = accepted.iter().map(|c| c.0).collect();
2228 assert_eq!(names.len(), accepted.len(), "case names repeat");
2229 for (name, file, circuit, measure) in accepted {
2230 let (back, m) = from_wqc(&file).unwrap_or_else(|e| panic!("{name}: {e}"));
2231 assert_eq!((&back, m), (&circuit, measure), "{name}");
2232 }
2233 }
2234
2235 #[test]
2242 fn container_rejects() {
2243 let bell = oplog_of(&[op(4, &[], 0, 0), op(1, &[0], 1, 0)]);
2244 let (canon1, canon2) = (Gateset::V1.contract_bytes(2), Gateset::V2.contract_bytes(2));
2245 let measure = measure_bytes(Measure { seed: 1, shots: 10 });
2246 for file in [
2248 wqc_raw(b"WQC1", &[(SECT_CONTRACT, &canon1), (SECT_OPLOG, &bell)]),
2249 wqc_raw(b"WQC2", &[(SECT_CONTRACT, &canon2), (SECT_OPLOG, &bell)]),
2250 wqc_raw(b"WQC2", &[(SECT_CONTRACT, &canon2), (SECT_OPLOG, &bell), (SECT_MEASURE, &measure)]),
2251 ] {
2252 from_wqc(&file).unwrap();
2253 }
2254
2255 let cases = reject_corpus();
2256 let names: std::collections::BTreeSet<_> = cases.iter().map(|c| c.0).collect();
2257 assert_eq!(names.len(), cases.len(), "case names repeat");
2258 let kinds: std::collections::BTreeSet<_> = cases.iter().map(|c| c.2).collect();
2259 assert_eq!(kinds, std::collections::BTreeSet::from(["contract", "invalid", "malformed"]));
2260 for (name, file, kind) in &cases {
2261 match from_wqc(file) {
2262 Ok(c) => panic!("{name} was accepted: {c:?}"),
2263 Err(e) => assert_eq!(e.kind(), *kind, "{name}: {e}"),
2264 }
2265 }
2266 for (name, rule) in [
2268 ("v1_contract_duplicate_member", "duplicate member"),
2269 ("v1_contract_lone_surrogate", "surrogate"),
2270 ("v1_contract_lone_low_surrogate", "surrogate"),
2271 ("v1_contract_too_deep", "depth"),
2272 ("v1_contract_not_utf8", "utf-8"),
2273 ("v1_contract_number_out_of_range", "number range"),
2274 ("v1_contract_integer_rounds_to_infinity", "number range"),
2275 ("v1_contract_number_rounds_to_infinity", "number range"),
2276 ("v1_contract_noncharacter", "noncharacter"),
2277 ("v1_contract_raw_noncharacter", "noncharacter"),
2278 ("v1_contract_bom", "syntax"),
2279 ("v1_contract_nan", "syntax"),
2280 ("v1_contract_control_character", "syntax"),
2281 ("v2_n_qubits_rounds_to_infinity", "number range"),
2282 ("v1_contract_long_literal", "number range"),
2283 ("v1_measure_duplicate_member", "duplicate member"),
2284 ("v1_measure_not_json", "syntax"),
2285 ] {
2286 let (_, file, _) = cases.iter().find(|c| c.0 == name).unwrap_or_else(|| panic!("no case {name}"));
2287 let e = from_wqc(file).unwrap_err().to_string();
2288 assert!(e.contains(&format!("I-JSON: {rule} at byte")), "{name}: {e}");
2289 }
2290
2291 let structural: [(&str, Vec<u8>, &str); 9] = [
2293 ("short", b"WQC1\x00".to_vec(), "malformed"),
2294 ("table_cut", wqc_raw(b"WQC1", &[(SECT_CONTRACT, &canon1), (SECT_OPLOG, &bell)])[..20].to_vec(), "malformed"),
2295 ("no_contract", wqc_raw(b"WQC2", &[(SECT_OPLOG, &bell)]), "malformed"),
2296 ("no_oplog", wqc_raw(b"WQC2", &[(SECT_CONTRACT, &canon2)]), "malformed"),
2297 ("past_end", wqc_table(b"WQC1", &[(SECT_CONTRACT, 0, 112), (SECT_OPLOG, 112, 16)], &[canon1.as_slice(), &bell].concat()), "malformed"),
2298 ("contract_not_json", wqc_raw(b"WQC1", &[(SECT_CONTRACT, b"{\"ext\":"), (SECT_OPLOG, &bell)]), "contract"),
2299 (
2300 "v2_n_qubits_256",
2301 wqc_raw(b"WQC2", &[(SECT_CONTRACT, &swap(&canon2, "\"n_qubits\":2", "\"n_qubits\":256")), (SECT_OPLOG, &bell)]),
2302 "malformed",
2303 ),
2304 (
2305 "v1_n_qubits_negative",
2306 wqc_raw(b"WQC1", &[(SECT_CONTRACT, &swap(&canon1, "\"n_qubits\":2", "\"n_qubits\":-2")), (SECT_OPLOG, &bell)]),
2307 "malformed",
2308 ),
2309 (
2310 "v1_measure_not_json",
2311 wqc_raw(b"WQC1", &[(SECT_CONTRACT, &canon1), (SECT_OPLOG, &bell), (SECT_MEASURE, b"seed=1")]),
2312 "malformed",
2313 ),
2314 ];
2315 for (name, file, kind) in &structural {
2316 assert_eq!(from_wqc(file).map(|_| ()).unwrap_err().kind(), *kind, "{name}");
2317 }
2318 let long = swap(&canon1, "wai.quantum.circuit/1", &"é".repeat(10_000));
2320 let e = from_wqc(&wqc_raw(b"WQC1", &[(SECT_CONTRACT, &long), (SECT_OPLOG, &bell)])).unwrap_err();
2321 assert_eq!(e.kind(), "contract");
2322 assert!(e.to_string().chars().count() < 160, "{e}");
2323
2324 let first = |sections: &[(u8, &[u8])]| from_wqc(&wqc_raw(b"WQC2", sections)).unwrap_err().kind();
2328 let canon3 = Gateset::V2.contract_bytes(3);
2329 let spaced = swap(&canon3, ":3", ": 3");
2330 let repeats = oplog_of(&[op(1, &[0, 0], 2, 0)]);
2331 assert_eq!(first(&[(SECT_CONTRACT, &spaced), (SECT_OPLOG, &repeats), (SECT_OPLOG, &repeats)]), "malformed");
2332 assert_eq!(first(&[(SECT_CONTRACT, &spaced), (SECT_OPLOG, &[0, 0])]), "contract");
2333 assert_eq!(first(&[(SECT_CONTRACT, &canon3), (SECT_OPLOG, &[repeats.as_slice(), &[0]].concat())]), "malformed");
2334 assert_eq!(first(&[(SECT_CONTRACT, &canon3), (SECT_OPLOG, &repeats), (SECT_MEASURE, b"[]")]), "invalid");
2335
2336 for gs in Gateset::ALL {
2339 let mut c = Circuit::with_gateset(2, gs);
2340 c.ops.push(Gate { base: BaseGate::X, controls: vec![0; 256], target: 1, param: 0 });
2341 let e = c.validate().unwrap_err();
2342 assert_eq!(e.kind(), "invalid", "{gs:?}");
2343 assert_eq!(to_wqc(&c, None).unwrap_err(), e, "{gs:?}");
2344 assert_eq!(circuit_hash(&c).unwrap_err(), e, "{gs:?}");
2345 }
2346 let mut wide = Circuit::with_gateset(255, Gateset::V2);
2348 wide.ops.push(Gate { base: BaseGate::X, controls: (0..254).collect(), target: 254, param: 0 });
2349 let bytes = to_wqc(&wide, None).unwrap();
2350 let (back, _) = from_wqc(&bytes).unwrap();
2351 assert_eq!(back, wide);
2352 assert_eq!(circuit_hash(&back).unwrap(), circuit_hash(&wide).unwrap());
2353
2354 assert_eq!(oplog_capacity(u32::MAX, 9), 1);
2357 assert_eq!(oplog_capacity(u32::MAX, 3), 0);
2358 assert_eq!(oplog_capacity(3, 1 << 20), 3);
2359 let n_ops_u32max = [u32::MAX.to_le_bytes().as_slice(), &op(4, &[], 0, 0)].concat();
2360 assert_eq!(parse_oplog(&n_ops_u32max).unwrap_err(), QuantumError::Malformed("op header".into()));
2361
2362 assert_eq!(section_range(24, 0, 112, 136), Some(24..136));
2365 assert_eq!(section_range(24, 0, 113, 136), None);
2366 assert_eq!(section_range(usize::MAX - 8, 0xFFFF_FFF0, 0x20, usize::MAX), None);
2367 assert_eq!(section_range(usize::MAX - 0x10, 0x8, 0x20, usize::MAX), None);
2368 assert_eq!(section_range(usize::MAX - 0x40, 0x10, 0x20, usize::MAX), Some(usize::MAX - 0x30..usize::MAX - 0x10));
2369 }
2370
2371
2372 #[test]
2373 fn validation_rejects_bad_indices() {
2374 let mut c = Circuit::new(2);
2375 c.push(BaseGate::X, vec![], 5, 0); assert!(matches!(c.validate(), Err(QuantumError::Invalid(_))));
2377
2378 let mut c = Circuit::new(2);
2379 c.push(BaseGate::X, vec![1], 1, 0); assert!(matches!(c.validate(), Err(QuantumError::Invalid(_))));
2381
2382 let mut c = Circuit::new(1);
2383 c.push(BaseGate::P, vec![], 0, 999); assert!(matches!(c.validate(), Err(QuantumError::Invalid(_))));
2385
2386 for gs in Gateset::ALL {
2389 let mut c = Circuit::with_gateset(2, gs);
2390 c.push(BaseGate::X, vec![0; 256], 1, 0);
2391 assert!(matches!(c.validate(), Err(QuantumError::Invalid(_))), "{gs:?}");
2392 }
2393 let mut c = Circuit::with_gateset(2, Gateset::V1);
2394 c.push(BaseGate::X, vec![0; 255], 1, 0);
2395 assert_eq!(c.validate(), Ok(()));
2396 }
2397
2398 #[test]
2401 fn circuits_carry_their_gateset() {
2402 assert_eq!(Circuit::new(3).gateset, Gateset::V2);
2403 assert_eq!(Circuit::qft(3).gateset, Gateset::V2);
2404 for gs in Gateset::ALL {
2405 let mut c = Circuit::with_gateset(3, gs);
2406 assert_eq!((c.n_qubits, c.gateset), (3, gs));
2407 c.h(0).cx(0, 1);
2408 let e = c.empty_like();
2409 assert_eq!((e.n_qubits, e.gateset, e.ops.len()), (3, gs, 0));
2410 }
2411 let v2 = Circuit::qft(3);
2412 let mut v1 = v2.clone();
2413 v1.gateset = Gateset::V1;
2414 assert_ne!(v1, v2);
2415 assert_ne!(v1.simulate().unwrap(), v2.simulate().unwrap());
2416 }
2417
2418 #[test]
2419 fn error_kinds_name_their_class() {
2420 let cases = [
2421 (QuantumError::Malformed("m".into()), "malformed", "malformed WQC: m"),
2422 (QuantumError::Invalid("i".into()), "invalid", "invalid circuit: i"),
2423 (QuantumError::Contract("c".into()), "contract", "contract mismatch: c"),
2424 (QuantumError::TooManyQubits(27), "too-many-qubits", "too many qubits: 27"),
2425 ];
2426 for (e, kind, shown) in cases {
2427 assert_eq!((e.kind(), e.to_string().as_str()), (kind, shown));
2428 }
2429 }
2430
2431 #[test]
2434 fn phase_is_copied_exactly() {
2435 for gs in Gateset::ALL {
2436 for k in 1..=DYADIC_MAX as u16 {
2437 let mut c = Circuit::with_gateset(1, gs);
2438 c.x(0).p(k, 0);
2439 assert_eq!(c.simulate().unwrap().amps, [Amp::ZERO, gs.phase(k.into())], "{gs:?} P({k})");
2440 let mut c = Circuit::with_gateset(2, gs);
2441 c.x(0).x(1).cp(k, 0, 1);
2442 let amps = c.simulate().unwrap().amps;
2443 assert_eq!(amps[0b11], gs.phase(k.into()), "{gs:?} CP({k})");
2444 assert!(amps[..0b11].iter().all(|a| *a == Amp::ZERO), "{gs:?} CP({k})");
2445 }
2446 }
2447 }
2448
2449 #[test]
2453 fn repeated_small_phase() {
2454 let reps = 1usize << 18;
2455 let run = |gs| {
2456 let mut c = Circuit::with_gateset(1, gs);
2457 c.x(0);
2458 c.ops.extend(std::iter::repeat_n(Gate { base: BaseGate::P, controls: vec![], target: 0, param: 19 }, reps));
2459 let amps = c.simulate().unwrap().amps;
2460 assert_eq!(amps[0], Amp::ZERO);
2461 amps[1]
2462 };
2463 assert_eq!(run(Gateset::V1), Amp { re: 1_073_479_680, im: 0 });
2464 assert_eq!(run(Gateset::V1).re, ONE - reps as i64);
2465 let v2 = run(Gateset::V2);
2466 let angle = (v2.im as f64).atan2(v2.re as f64);
2467 assert!((angle.abs() - std::f64::consts::PI).abs() < 1e-4, "circuit2 turned {angle} rad, not π");
2468 let norm = (v2.norm2() as f64).sqrt() / ONE as f64;
2470 assert!((norm - 1.0).abs() < 1e-4, "|amp| = {norm}");
2471 }
2472
2473 #[test]
2477 fn v2_rejects_what_v1_tolerates() {
2478 let use_gate = |gs, n, g: Gate| {
2479 let mut c = Circuit::with_gateset(n, gs);
2480 c.ops.push(g);
2481 c
2482 };
2483 let mut lax = vec![Gate { base: BaseGate::X, controls: vec![0, 0], target: 1, param: 0 }];
2484 for base in [BaseGate::I, BaseGate::X, BaseGate::Y, BaseGate::Z, BaseGate::H, BaseGate::S, BaseGate::Sdg, BaseGate::T, BaseGate::Tdg] {
2485 lax.push(Gate { base, controls: vec![], target: 0, param: 1 });
2486 }
2487 lax.push(Gate { base: BaseGate::H, controls: vec![], target: 0, param: u16::MAX });
2488 for g in lax {
2489 let v1 = use_gate(Gateset::V1, 2, g.clone());
2490 assert_eq!(v1.validate(), Ok(()), "{g:?}");
2491 assert!(v1.simulate().is_ok(), "{g:?}");
2492 let v2 = use_gate(Gateset::V2, 2, g.clone());
2493 let e = v2.validate().unwrap_err();
2494 assert_eq!(e.kind(), "invalid", "{g:?}");
2495 assert_eq!(v2.simulate().unwrap_err(), e, "{g:?}");
2496 }
2497 let mut p = Circuit::new(1);
2499 p.p(5, 0);
2500 assert_eq!(p.validate(), Ok(()));
2501 let all: Vec<u8> = (0..254).collect();
2503 assert_eq!(use_gate(Gateset::V2, 255, Gate { base: BaseGate::X, controls: all.clone(), target: 254, param: 0 }).validate(), Ok(()));
2504 let mut repeat = all.clone();
2505 repeat.push(253);
2506 let g = Gate { base: BaseGate::X, controls: repeat, target: 254, param: 0 };
2507 assert_eq!(use_gate(Gateset::V1, 255, g.clone()).validate(), Ok(()));
2508 assert!(matches!(use_gate(Gateset::V2, 255, g).validate(), Err(QuantumError::Invalid(_))));
2509 }
2510
2511 #[cfg(any(
2515 feature = "quantum_spd",
2516 feature = "quantum_pauli",
2517 feature = "quantum_mps",
2518 feature = "quantum_vml",
2519 feature = "quantum_phasor",
2520 feature = "quantum_kernel",
2521 feature = "quantum_tn",
2522 feature = "quantum_sv",
2523 feature = "quantum_tdvp",
2524 feature = "quantum_nqs"
2525 ))]
2526 mod against_ideal {
2527 use super::*;
2528
2529 type C = (f64, f64);
2530
2531 fn cmul(a: C, b: C) -> C {
2532 (a.0 * b.0 - a.1 * b.1, a.0 * b.1 + a.1 * b.0)
2533 }
2534
2535 fn cadd(a: C, b: C) -> C {
2536 (a.0 + b.0, a.1 + b.1)
2537 }
2538
2539 fn root(k: u16) -> C {
2541 let (s, c) = crate::repro::sin_cos(std::f64::consts::TAU / (1u64 << k) as f64);
2542 (c, s)
2543 }
2544
2545 fn matrix(base: BaseGate, param: u16) -> [[C; 2]; 2] {
2547 let (z, one, i, neg_i) = ((0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (0.0, -1.0));
2548 let h = std::f64::consts::FRAC_1_SQRT_2;
2549 let t = root(3);
2550 match base {
2551 BaseGate::I => [[one, z], [z, one]],
2552 BaseGate::X => [[z, one], [one, z]],
2553 BaseGate::Y => [[z, neg_i], [i, z]],
2554 BaseGate::Z => [[one, z], [z, (-1.0, 0.0)]],
2555 BaseGate::H => [[(h, 0.0), (h, 0.0)], [(h, 0.0), (-h, 0.0)]],
2556 BaseGate::S => [[one, z], [z, i]],
2557 BaseGate::Sdg => [[one, z], [z, neg_i]],
2558 BaseGate::T => [[one, z], [z, t]],
2559 BaseGate::Tdg => [[one, z], [z, (t.0, -t.1)]],
2560 BaseGate::P => [[one, z], [z, root(param)]],
2561 }
2562 }
2563
2564 fn simulate(c: &Circuit) -> Vec<C> {
2566 let dim = 1usize << c.n_qubits;
2567 let mut a = vec![(0.0, 0.0); dim];
2568 a[0] = (1.0, 0.0);
2569 for g in &c.ops {
2570 let m = matrix(g.base, g.param);
2571 let tbit = 1usize << g.target;
2572 let mask = g.controls.iter().fold(0usize, |acc, &q| acc | (1usize << q));
2573 for i in 0..dim {
2574 if i & tbit == 0 && i & mask == mask {
2575 let j = i | tbit;
2576 let (a0, a1) = (a[i], a[j]);
2577 a[i] = cadd(cmul(m[0][0], a0), cmul(m[0][1], a1));
2578 a[j] = cadd(cmul(m[1][0], a0), cmul(m[1][1], a1));
2579 }
2580 }
2581 }
2582 a
2583 }
2584
2585 #[test]
2589 fn qft14_accuracy() {
2590 let n = 14u8;
2591 let mut c = Circuit::new(n);
2592 for q in 0..n {
2593 c.x(q);
2594 }
2595 c.ops.extend(Circuit::qft(n).ops);
2596 let want = simulate(&c);
2597 let error = |gs| {
2598 let mut c = c.clone();
2599 c.gateset = gs;
2600 let one = ONE as f64;
2601 c.simulate()
2602 .unwrap()
2603 .amps
2604 .iter()
2605 .zip(&want)
2606 .map(|(a, w)| {
2607 let (dre, dim) = (a.re as f64 - w.0 * one, a.im as f64 - w.1 * one);
2608 (dre * dre + dim * dim).sqrt()
2609 })
2610 .fold(0.0f64, f64::max)
2611 };
2612 let (v2, v1) = (error(Gateset::V2), error(Gateset::V1));
2613 assert!(v2 <= 3.0, "circuit2 QFT-14 is {v2} units from the ideal");
2614 assert!(v1 > 8.0, "circuit/1 QFT-14 is only {v1} units from the ideal");
2615 }
2616
2617 #[test]
2623 fn one_v2_gate_is_within_two_units() {
2624 let mut gates = vec![(BaseGate::H, 0u16), (BaseGate::T, 0), (BaseGate::Tdg, 0)];
2625 gates.extend((1..=DYADIC_MAX as u16).map(|k| (BaseGate::P, k)));
2626 let worst = |gs| {
2627 let mut s = 0x5EED_0000_0000_0008u64;
2628 let mut uniform = || (splitmix64(&mut s) >> 11) as f64 / (1u64 << 53) as f64 * 2.0 - 1.0;
2629 let one = ONE as f64;
2630 let mut worst = 0.0f64;
2631 for &(base, param) in &gates {
2632 let m = matrix(base, param);
2633 for _ in 0..400 {
2634 let z = [uniform(), uniform(), uniform(), uniform()];
2635 let norm = z.iter().map(|x| x * x).sum::<f64>().sqrt();
2636 let fixed = |x: f64| (x / norm * one) as i64;
2637 let a = [Amp { re: fixed(z[0]), im: fixed(z[1]) }, Amp { re: fixed(z[2]), im: fixed(z[3]) }];
2638 let mut got = a;
2639 apply(&mut got, 1, &Gate { base, controls: vec![], target: 0, param }, gs);
2640 let af = a.map(|x| (x.re as f64 / one, x.im as f64 / one));
2641 for r in 0..2 {
2642 let w = cadd(cmul(m[r][0], af[0]), cmul(m[r][1], af[1]));
2643 worst = worst.max((got[r].re as f64 - w.0 * one).abs()).max((got[r].im as f64 - w.1 * one).abs());
2644 }
2645 }
2646 }
2647 worst
2648 };
2649 assert_eq!(gates.len() * 400, 14_000);
2650 let v2 = worst(Gateset::V2);
2651 assert!(v2 <= 2.0, "a circuit2 gate is {v2} units from the ideal");
2652 let v1 = worst(Gateset::V1);
2653 assert!(v1 > 2.0, "the check passed circuit/1: {v1}");
2654 }
2655 }
2656}