1use super::{Channel, OutputBuffer};
12use crate::phase::Phase;
13use num_complex::Complex64;
14
15#[derive(Clone, Copy, Debug)]
23pub struct Clifford1Q {
24 pub support: [u32; 1],
27 pub out_pauli: [u8; 4],
30 pub phase: [Phase; 4],
33}
34
35impl Clifford1Q {
36 pub fn h(qubit: u32) -> Self {
38 Self {
39 support: [qubit],
40 out_pauli: [0, 2, 1, 3],
41 phase: [Phase::ONE, Phase::ONE, Phase::ONE, Phase::MINUS_ONE],
42 }
43 }
44
45 pub fn s(qubit: u32) -> Self {
48 Self {
49 support: [qubit],
50 out_pauli: [0, 3, 2, 1],
51 phase: [Phase::ONE, Phase::ONE, Phase::ONE, Phase::MINUS_ONE],
52 }
53 }
54
55 pub fn x(qubit: u32) -> Self {
57 Self {
58 support: [qubit],
59 out_pauli: [0, 1, 2, 3],
60 phase: [Phase::ONE, Phase::ONE, Phase::MINUS_ONE, Phase::MINUS_ONE],
61 }
62 }
63
64 pub fn y(qubit: u32) -> Self {
66 Self {
67 support: [qubit],
68 out_pauli: [0, 1, 2, 3],
69 phase: [Phase::ONE, Phase::MINUS_ONE, Phase::MINUS_ONE, Phase::ONE],
70 }
71 }
72
73 pub fn z(qubit: u32) -> Self {
75 Self {
76 support: [qubit],
77 out_pauli: [0, 1, 2, 3],
78 phase: [Phase::ONE, Phase::MINUS_ONE, Phase::ONE, Phase::MINUS_ONE],
79 }
80 }
81
82 pub fn adjoint(&self) -> Self {
89 let mut out_pauli = [0u8; 4];
90 let mut phase = [Phase::ONE; 4];
91 for input_idx in 0u8..4 {
92 let f_a = self.out_pauli[input_idx as usize] as usize;
93 let c_a = self.phase[input_idx as usize];
94 out_pauli[f_a] = input_idx;
95 phase[f_a] = Phase::new((4 - c_a.exponent()) & 3);
96 }
97 Self {
98 support: self.support,
99 out_pauli,
100 phase,
101 }
102 }
103}
104
105impl Clifford1Q {
106 #[inline]
110 fn apply_table<const W: usize>(
111 &self,
112 out_pauli: &[u8; 4],
113 phase: &[Phase; 4],
114 input_x: &[u64; W],
115 input_z: &[u64; W],
116 coeff: Complex64,
117 out: &mut OutputBuffer<'_, W>,
118 ) {
119 let q = self.support[0] as usize;
120 debug_assert!(q < 64 * W);
121 let word = q / 64;
122 let bit = q % 64;
123 let mask = 1u64 << bit;
124 let x_bit = ((input_x[word] >> bit) & 1) as u8;
125 let z_bit = ((input_z[word] >> bit) & 1) as u8;
126 let idx = (x_bit | (z_bit << 1)) as usize;
127 let op = out_pauli[idx];
128 let ox = (op & 1) as u64;
129 let oz = ((op >> 1) & 1) as u64;
130 let mut nx = *input_x;
131 let mut nz = *input_z;
132 nx[word] = (nx[word] & !mask) | (ox << bit);
133 nz[word] = (nz[word] & !mask) | (oz << bit);
134 out.push(nx, nz, phase[idx].apply(coeff));
135 }
136}
137
138impl<const W: usize> Channel<W> for Clifford1Q {
139 #[inline]
140 fn max_fanout(&self) -> usize {
141 1
142 }
143
144 #[inline]
145 fn support(&self) -> &[u32] {
146 &self.support
147 }
148
149 #[inline]
150 fn apply(
151 &self,
152 input_x: &[u64; W],
153 input_z: &[u64; W],
154 coeff: Complex64,
155 out: &mut OutputBuffer<'_, W>,
156 ) {
157 self.apply_table(&self.out_pauli, &self.phase, input_x, input_z, coeff, out);
158 }
159
160 #[inline]
161 fn apply_adjoint(
162 &self,
163 input_x: &[u64; W],
164 input_z: &[u64; W],
165 coeff: Complex64,
166 out: &mut OutputBuffer<'_, W>,
167 ) {
168 let adj = self.adjoint();
169 self.apply_table(&adj.out_pauli, &adj.phase, input_x, input_z, coeff, out);
170 }
171}
172
173#[derive(Clone, Copy, Debug)]
179pub struct Clifford2Q {
180 pub support: [u32; 2],
183 pub out_pauli: [u8; 16],
187 pub phase: [Phase; 16],
189}
190
191impl Clifford2Q {
192 pub fn cnot(control: u32, target: u32) -> Self {
196 Self::from_2q_generators(
197 [control, target],
198 (pack4(1, 0, 1, 0), Phase::ONE),
200 (pack4(0, 1, 0, 0), Phase::ONE),
202 (pack4(0, 0, 1, 0), Phase::ONE),
204 (pack4(0, 1, 0, 1), Phase::ONE),
206 )
207 }
208
209 pub fn cz(q0: u32, q1: u32) -> Self {
212 Self::from_2q_generators(
213 [q0, q1],
214 (pack4(1, 0, 0, 1), Phase::ONE),
216 (pack4(0, 1, 0, 0), Phase::ONE),
218 (pack4(0, 1, 1, 0), Phase::ONE),
220 (pack4(0, 0, 0, 1), Phase::ONE),
222 )
223 }
224
225 pub fn swap(q0: u32, q1: u32) -> Self {
228 Self::from_2q_generators(
229 [q0, q1],
230 (pack4(0, 0, 1, 0), Phase::ONE),
232 (pack4(0, 0, 0, 1), Phase::ONE),
234 (pack4(1, 0, 0, 0), Phase::ONE),
236 (pack4(0, 1, 0, 0), Phase::ONE),
238 )
239 }
240
241 fn from_2q_generators(
246 support: [u32; 2],
247 x0_image: (u8, Phase),
248 z0_image: (u8, Phase),
249 x1_image: (u8, Phase),
250 z1_image: (u8, Phase),
251 ) -> Self {
252 let gens = [x0_image, z0_image, x1_image, z1_image];
253 let mut out_pauli = [0u8; 16];
254 let mut phase = [Phase::ONE; 16];
255 for idx in 0..16usize {
256 let bits = [
258 (idx & 1) as u8, ((idx >> 1) & 1) as u8, ((idx >> 2) & 1) as u8, ((idx >> 3) & 1) as u8, ];
263
264 let mut acc_x = [0u64; 1];
271 let mut acc_z = [0u64; 1];
272 let mut acc_phase = Phase::ONE;
273 for (b, (img, ph)) in bits.iter().zip(gens.iter()) {
274 if *b == 1 {
275 let (gx, gz) = unpack4_to_word(*img);
276 let mut acc = crate::pauli_string::PauliString::<1> { x: acc_x, z: acc_z };
277 let other = crate::pauli_string::PauliString::<1> { x: gx, z: gz };
278 let mul_phase = acc.mul_assign(&other);
279 acc_x = acc.x;
280 acc_z = acc.z;
281 acc_phase = acc_phase + mul_phase + *ph;
282 }
283 }
284 let y_count = (bits[0] & bits[1]) + (bits[2] & bits[3]);
289 acc_phase += Phase::new(y_count);
290 out_pauli[idx] = pack4_from_word(acc_x, acc_z, support);
291 phase[idx] = acc_phase;
292 }
293 Self {
294 support,
295 out_pauli,
296 phase,
297 }
298 }
299}
300
301const fn pack4(x0: u8, z0: u8, x1: u8, z1: u8) -> u8 {
303 (x0 & 1) | ((z0 & 1) << 1) | ((x1 & 1) << 2) | ((z1 & 1) << 3)
304}
305
306fn unpack4_to_word(packed: u8) -> ([u64; 1], [u64; 1]) {
310 let x0 = (packed & 1) as u64;
311 let z0 = ((packed >> 1) & 1) as u64;
312 let x1 = ((packed >> 2) & 1) as u64;
313 let z1 = ((packed >> 3) & 1) as u64;
314 let x = x0 | (x1 << 1);
315 let z = z0 | (z1 << 1);
316 ([x], [z])
317}
318
319fn pack4_from_word(x: [u64; 1], z: [u64; 1], _support: [u32; 2]) -> u8 {
324 let x0 = (x[0] & 1) as u8;
325 let z0 = (z[0] & 1) as u8;
326 let x1 = ((x[0] >> 1) & 1) as u8;
327 let z1 = ((z[0] >> 1) & 1) as u8;
328 pack4(x0, z0, x1, z1)
329}
330
331impl<const W: usize> Channel<W> for Clifford2Q {
332 #[inline]
333 fn max_fanout(&self) -> usize {
334 1
335 }
336
337 #[inline]
338 fn support(&self) -> &[u32] {
339 &self.support
340 }
341
342 #[inline]
343 fn apply(
344 &self,
345 input_x: &[u64; W],
346 input_z: &[u64; W],
347 coeff: Complex64,
348 out: &mut OutputBuffer<'_, W>,
349 ) {
350 let q0 = self.support[0] as usize;
351 let q1 = self.support[1] as usize;
352 debug_assert!(q0 < 64 * W);
353 debug_assert!(q1 < 64 * W);
354 debug_assert!(q0 != q1);
355
356 let w0 = q0 / 64;
357 let b0 = q0 % 64;
358 let w1 = q1 / 64;
359 let b1 = q1 % 64;
360 let m0 = 1u64 << b0;
361 let m1 = 1u64 << b1;
362
363 let x0 = ((input_x[w0] >> b0) & 1) as u8;
364 let z0 = ((input_z[w0] >> b0) & 1) as u8;
365 let x1 = ((input_x[w1] >> b1) & 1) as u8;
366 let z1 = ((input_z[w1] >> b1) & 1) as u8;
367 let idx = (x0 | (z0 << 1) | (x1 << 2) | (z1 << 3)) as usize;
368
369 let op = self.out_pauli[idx];
370 let ox0 = (op & 1) as u64;
371 let oz0 = ((op >> 1) & 1) as u64;
372 let ox1 = ((op >> 2) & 1) as u64;
373 let oz1 = ((op >> 3) & 1) as u64;
374
375 let mut nx = *input_x;
376 let mut nz = *input_z;
377 nx[w0] = (nx[w0] & !m0) | (ox0 << b0);
378 nz[w0] = (nz[w0] & !m0) | (oz0 << b0);
379 nx[w1] = (nx[w1] & !m1) | (ox1 << b1);
380 nz[w1] = (nz[w1] & !m1) | (oz1 << b1);
381
382 out.push(nx, nz, self.phase[idx].apply(coeff));
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389 use crate::pauli_string::PauliString;
390
391 #[allow(clippy::type_complexity)]
392 fn alloc_buf<const W: usize>() -> (Vec<[u64; W]>, Vec<[u64; W]>, Vec<Complex64>, usize) {
393 (
394 vec![[0u64; W]; 1],
395 vec![[0u64; W]; 1],
396 vec![Complex64::new(0.0, 0.0); 1],
397 0usize,
398 )
399 }
400
401 fn apply_1q<const W: usize>(
403 gate: &Clifford1Q,
404 input: &PauliString<W>,
405 ) -> (PauliString<W>, Complex64) {
406 let (mut x, mut z, mut c, mut len) = alloc_buf::<W>();
407 let mut buf = OutputBuffer::<W> {
408 x: &mut x,
409 z: &mut z,
410 coeff: &mut c,
411 len: &mut len,
412 };
413 gate.apply(&input.x, &input.z, Complex64::new(1.0, 0.0), &mut buf);
414 assert_eq!(*buf.len, 1);
415 let out = PauliString::<W> { x: x[0], z: z[0] };
416 (out, c[0])
417 }
418
419 fn apply_2q<const W: usize>(
420 gate: &Clifford2Q,
421 input: &PauliString<W>,
422 ) -> (PauliString<W>, Complex64) {
423 let (mut x, mut z, mut c, mut len) = alloc_buf::<W>();
424 let mut buf = OutputBuffer::<W> {
425 x: &mut x,
426 z: &mut z,
427 coeff: &mut c,
428 len: &mut len,
429 };
430 gate.apply(&input.x, &input.z, Complex64::new(1.0, 0.0), &mut buf);
431 assert_eq!(*buf.len, 1);
432 let out = PauliString::<W> { x: x[0], z: z[0] };
433 (out, c[0])
434 }
435
436 fn pauli_y<const W: usize>(qubit: u32) -> PauliString<W> {
437 PauliString::<W>::y(qubit)
438 }
439
440 #[test]
443 fn h_on_qubit_0_w1() {
444 let h = Clifford1Q::h(0);
445 let (out, c) = apply_1q::<1>(&h, &PauliString::<1>::identity());
447 assert_eq!(out, PauliString::<1>::identity());
448 assert_eq!(c, Complex64::new(1.0, 0.0));
449 let (out, c) = apply_1q::<1>(&h, &PauliString::<1>::x(0));
451 assert_eq!(out, PauliString::<1>::z(0));
452 assert_eq!(c, Complex64::new(1.0, 0.0));
453 let (out, c) = apply_1q::<1>(&h, &PauliString::<1>::z(0));
455 assert_eq!(out, PauliString::<1>::x(0));
456 assert_eq!(c, Complex64::new(1.0, 0.0));
457 let (out, c) = apply_1q::<1>(&h, &pauli_y::<1>(0));
459 assert_eq!(out, pauli_y::<1>(0));
460 assert_eq!(c, Complex64::new(-1.0, 0.0));
461 }
462
463 #[test]
464 fn s_on_qubit_0_w1() {
465 let s = Clifford1Q::s(0);
466 let (out, c) = apply_1q::<1>(&s, &PauliString::<1>::x(0));
468 assert_eq!(out, pauli_y::<1>(0));
469 assert_eq!(c, Complex64::new(1.0, 0.0));
470 let (out, c) = apply_1q::<1>(&s, &PauliString::<1>::z(0));
472 assert_eq!(out, PauliString::<1>::z(0));
473 assert_eq!(c, Complex64::new(1.0, 0.0));
474 let (out, c) = apply_1q::<1>(&s, &pauli_y::<1>(0));
476 assert_eq!(out, PauliString::<1>::x(0));
477 assert_eq!(c, Complex64::new(-1.0, 0.0));
478 }
479
480 #[test]
481 fn x_gate_w1() {
482 let g = Clifford1Q::x(0);
483 let (o, c) = apply_1q::<1>(&g, &PauliString::<1>::x(0));
485 assert_eq!(o, PauliString::<1>::x(0));
486 assert_eq!(c, Complex64::new(1.0, 0.0));
487 let (o, c) = apply_1q::<1>(&g, &PauliString::<1>::z(0));
488 assert_eq!(o, PauliString::<1>::z(0));
489 assert_eq!(c, Complex64::new(-1.0, 0.0));
490 let (o, c) = apply_1q::<1>(&g, &pauli_y::<1>(0));
491 assert_eq!(o, pauli_y::<1>(0));
492 assert_eq!(c, Complex64::new(-1.0, 0.0));
493 }
494
495 #[test]
496 fn y_gate_w1() {
497 let g = Clifford1Q::y(0);
498 let (o, c) = apply_1q::<1>(&g, &PauliString::<1>::x(0));
499 assert_eq!(o, PauliString::<1>::x(0));
500 assert_eq!(c, Complex64::new(-1.0, 0.0));
501 let (o, c) = apply_1q::<1>(&g, &PauliString::<1>::z(0));
502 assert_eq!(o, PauliString::<1>::z(0));
503 assert_eq!(c, Complex64::new(-1.0, 0.0));
504 let (o, c) = apply_1q::<1>(&g, &pauli_y::<1>(0));
505 assert_eq!(o, pauli_y::<1>(0));
506 assert_eq!(c, Complex64::new(1.0, 0.0));
507 }
508
509 #[test]
510 fn z_gate_w1() {
511 let g = Clifford1Q::z(0);
512 let (o, c) = apply_1q::<1>(&g, &PauliString::<1>::x(0));
513 assert_eq!(o, PauliString::<1>::x(0));
514 assert_eq!(c, Complex64::new(-1.0, 0.0));
515 let (o, c) = apply_1q::<1>(&g, &PauliString::<1>::z(0));
516 assert_eq!(o, PauliString::<1>::z(0));
517 assert_eq!(c, Complex64::new(1.0, 0.0));
518 let (o, c) = apply_1q::<1>(&g, &pauli_y::<1>(0));
519 assert_eq!(o, pauli_y::<1>(0));
520 assert_eq!(c, Complex64::new(-1.0, 0.0));
521 }
522
523 #[test]
524 fn h_on_qubit_64_w2_word_boundary() {
525 let h = Clifford1Q::h(64);
526 let (out, c) = apply_1q::<2>(&h, &PauliString::<2>::x(64));
528 assert_eq!(out, PauliString::<2>::z(64));
529 assert_eq!(c, Complex64::new(1.0, 0.0));
530 assert_eq!(out.x[0], 0); assert_eq!(out.z[0], 0);
532 let (out, _c) = apply_1q::<2>(&h, &PauliString::<2>::z(64));
534 assert_eq!(out, PauliString::<2>::x(64));
535 }
536
537 #[test]
538 fn support_outside_bits_untouched_w2() {
539 let mut input = PauliString::<2>::x(0);
542 let _ = input.mul_assign(&PauliString::<2>::x(70));
543 let h = Clifford1Q::h(70);
544 let (out, c) = apply_1q::<2>(&h, &input);
545
546 let mut expected = PauliString::<2>::x(0);
548 let _ = expected.mul_assign(&PauliString::<2>::z(70));
549 assert_eq!(out, expected);
550 assert_eq!(c, Complex64::new(1.0, 0.0));
551 }
552
553 #[test]
554 fn h_squared_is_identity() {
555 let h = Clifford1Q::h(0);
557 for input in [
558 PauliString::<1>::identity(),
559 PauliString::<1>::x(0),
560 PauliString::<1>::z(0),
561 pauli_y::<1>(0),
562 ] {
563 let (mid, c1) = apply_1q::<1>(&h, &input);
564 let (out, c2) = apply_1q::<1>(&h, &mid);
565 assert_eq!(out, input, "H·H should be identity on {:?}", input);
566 assert_eq!(
567 c1 * c2,
568 Complex64::new(1.0, 0.0),
569 "phase should square to +1"
570 );
571 }
572 }
573
574 #[test]
578 fn h_x_y_z_are_self_adjoint() {
579 for gate in [
580 Clifford1Q::h(0),
581 Clifford1Q::x(0),
582 Clifford1Q::y(0),
583 Clifford1Q::z(0),
584 ] {
585 let adj = gate.adjoint();
586 assert_eq!(adj.out_pauli, gate.out_pauli);
587 assert_eq!(adj.phase, gate.phase);
588 }
589 }
590
591 #[test]
594 fn s_adjoint_inverts_table_and_is_involutive() {
595 let s = Clifford1Q::s(0);
596 let s_dag = s.adjoint();
597 assert_eq!(s_dag.out_pauli, [0, 3, 2, 1]);
600 assert_eq!(
601 s_dag.phase,
602 [Phase::ONE, Phase::MINUS_ONE, Phase::ONE, Phase::ONE]
603 );
604 let s_again = s_dag.adjoint();
606 assert_eq!(s_again.out_pauli, s.out_pauli);
607 assert_eq!(s_again.phase, s.phase);
608 }
609
610 #[test]
612 fn s_apply_then_apply_adjoint_round_trips() {
613 let s = Clifford1Q::s(0);
614 let x_in = PauliString::<1>::x(0);
615 let (mid, c1) = apply_1q::<1>(&s, &x_in);
616 let (mut bx, mut bz, mut bc, mut len) = alloc_buf::<1>();
618 let mut buf = OutputBuffer::<1> {
619 x: &mut bx,
620 z: &mut bz,
621 coeff: &mut bc,
622 len: &mut len,
623 };
624 s.apply_adjoint(&mid.x, &mid.z, c1, &mut buf);
625 assert_eq!(*buf.len, 1);
626 assert_eq!(bx[0], x_in.x);
627 assert_eq!(bz[0], x_in.z);
628 assert_eq!(bc[0], Complex64::new(1.0, 0.0));
629 }
630
631 fn tensor<const W: usize>(q0: u32, q1: u32, pa: u8, pb: u8) -> PauliString<W> {
637 let mut p = PauliString::<W>::identity();
638 let put = |p: &mut PauliString<W>, q: u32, code: u8| {
639 let g = match code {
640 0 => return,
641 1 => PauliString::<W>::x(q),
642 2 => PauliString::<W>::z(q),
643 3 => PauliString::<W>::y(q),
644 _ => unreachable!(),
645 };
646 let _ = p.mul_assign(&g);
650 };
651 put(&mut p, q0, pa);
652 put(&mut p, q1, pb);
653 p
654 }
655
656 #[test]
657 fn cnot_generator_rules_w1() {
658 let cnot = Clifford2Q::cnot(0, 1);
659 let (o, c) = apply_2q::<1>(&cnot, &tensor::<1>(0, 1, 1, 0));
661 assert_eq!(o, tensor::<1>(0, 1, 1, 1));
662 assert_eq!(c, Complex64::new(1.0, 0.0));
663 let (o, c) = apply_2q::<1>(&cnot, &tensor::<1>(0, 1, 0, 1));
665 assert_eq!(o, tensor::<1>(0, 1, 0, 1));
666 assert_eq!(c, Complex64::new(1.0, 0.0));
667 let (o, c) = apply_2q::<1>(&cnot, &tensor::<1>(0, 1, 2, 0));
669 assert_eq!(o, tensor::<1>(0, 1, 2, 0));
670 assert_eq!(c, Complex64::new(1.0, 0.0));
671 let (o, c) = apply_2q::<1>(&cnot, &tensor::<1>(0, 1, 0, 2));
673 assert_eq!(o, tensor::<1>(0, 1, 2, 2));
674 assert_eq!(c, Complex64::new(1.0, 0.0));
675 }
676
677 fn cnot_reference<const W: usize>(
684 control: u32,
685 target: u32,
686 pa: u8,
687 pb: u8,
688 ) -> (PauliString<W>, Phase) {
689 let (img_a, ph_a) = match pa {
691 0 => (PauliString::<W>::identity(), Phase::ONE),
692 1 => {
693 let mut p = PauliString::<W>::x(control);
695 let _ = p.mul_assign(&PauliString::<W>::x(target));
696 (p, Phase::ONE)
697 }
698 2 => (PauliString::<W>::z(control), Phase::ONE),
699 3 => {
700 let mut p = PauliString::<W>::x(control);
704 let _ = p.mul_assign(&PauliString::<W>::x(target));
705 let mut q = PauliString::<W>::z(control);
706 let mp = p.mul_assign(&q);
707 (p, Phase::I + mp)
709 }
710 _ => unreachable!(),
711 };
712 let (img_b, ph_b) = match pb {
713 0 => (PauliString::<W>::identity(), Phase::ONE),
714 1 => (PauliString::<W>::x(target), Phase::ONE),
715 2 => {
716 let mut p = PauliString::<W>::z(control);
718 let _ = p.mul_assign(&PauliString::<W>::z(target));
719 (p, Phase::ONE)
720 }
721 3 => {
722 let mut p = PauliString::<W>::x(target);
724 let mut q = PauliString::<W>::z(control);
725 let _ = q.mul_assign(&PauliString::<W>::z(target));
726 let mp = p.mul_assign(&q);
727 (p, Phase::I + mp)
728 }
729 _ => unreachable!(),
730 };
731 let mut prod = img_a;
735 let mp = prod.mul_assign(&img_b);
736 (prod, ph_a + ph_b + mp)
737 }
738
739 #[test]
740 fn cnot_full_table_w1() {
741 let cnot = Clifford2Q::cnot(0, 1);
742 for pa in 0u8..4 {
743 for pb in 0u8..4 {
744 let input = tensor::<1>(0, 1, pa, pb);
745 let (got_out, got_c) = apply_2q::<1>(&cnot, &input);
746 let (exp_out, exp_phase) = cnot_reference::<1>(0, 1, pa, pb);
747 assert_eq!(
748 got_out, exp_out,
749 "CNOT output mismatch on (pa={}, pb={})",
750 pa, pb
751 );
752 assert_eq!(
753 got_c,
754 exp_phase.to_complex(),
755 "CNOT phase mismatch on (pa={}, pb={})",
756 pa,
757 pb
758 );
759 }
760 }
761 }
762
763 #[test]
764 fn cnot_word_boundary_w2() {
765 let cnot = Clifford2Q::cnot(63, 64);
769 let input = tensor::<2>(63, 64, 1, 1);
771 let (out, c) = apply_2q::<2>(&cnot, &input);
772 let expected = tensor::<2>(63, 64, 1, 0);
773 assert_eq!(out, expected);
774 assert_eq!(c, Complex64::new(1.0, 0.0));
775 let input = tensor::<2>(63, 64, 3, 1);
777 let (got_out, got_c) = apply_2q::<2>(&cnot, &input);
778 let (exp_out, exp_phase) = cnot_reference::<2>(63, 64, 3, 1);
779 assert_eq!(got_out, exp_out);
780 assert_eq!(got_c, exp_phase.to_complex());
781 }
782
783 #[test]
784 fn cz_symmetry_w1() {
785 let cz_ab = Clifford2Q::cz(0, 1);
787 let cz_ba = Clifford2Q::cz(1, 0);
788 for pa in 0u8..4 {
789 for pb in 0u8..4 {
790 let input = tensor::<1>(0, 1, pa, pb);
791 let (a, ca) = apply_2q::<1>(&cz_ab, &input);
792 let (b, cb) = apply_2q::<1>(&cz_ba, &input);
793 assert_eq!(a, b, "CZ(0,1) and CZ(1,0) disagree on ({}, {})", pa, pb);
794 assert_eq!(ca, cb);
795 }
796 }
797 }
798
799 #[test]
800 fn cz_generator_rules_w1() {
801 let cz = Clifford2Q::cz(0, 1);
802 let (o, c) = apply_2q::<1>(&cz, &tensor::<1>(0, 1, 1, 0));
804 assert_eq!(o, tensor::<1>(0, 1, 1, 2));
805 assert_eq!(c, Complex64::new(1.0, 0.0));
806 let (o, c) = apply_2q::<1>(&cz, &tensor::<1>(0, 1, 0, 1));
808 assert_eq!(o, tensor::<1>(0, 1, 2, 1));
809 assert_eq!(c, Complex64::new(1.0, 0.0));
810 let (o, c) = apply_2q::<1>(&cz, &tensor::<1>(0, 1, 2, 0));
812 assert_eq!(o, tensor::<1>(0, 1, 2, 0));
813 assert_eq!(c, Complex64::new(1.0, 0.0));
814 let (o, c) = apply_2q::<1>(&cz, &tensor::<1>(0, 1, 0, 2));
816 assert_eq!(o, tensor::<1>(0, 1, 0, 2));
817 assert_eq!(c, Complex64::new(1.0, 0.0));
818 }
819
820 #[test]
821 fn swap_table_w1() {
822 let swap = Clifford2Q::swap(0, 1);
823 let (o, c) = apply_2q::<1>(&swap, &tensor::<1>(0, 1, 0, 0));
825 assert_eq!(o, tensor::<1>(0, 1, 0, 0));
826 assert_eq!(c, Complex64::new(1.0, 0.0));
827 let (o, c) = apply_2q::<1>(&swap, &tensor::<1>(0, 1, 1, 0));
828 assert_eq!(o, tensor::<1>(0, 1, 0, 1));
829 assert_eq!(c, Complex64::new(1.0, 0.0));
830 let (o, c) = apply_2q::<1>(&swap, &tensor::<1>(0, 1, 0, 3));
831 assert_eq!(o, tensor::<1>(0, 1, 3, 0));
832 assert_eq!(c, Complex64::new(1.0, 0.0));
833 let (o, c) = apply_2q::<1>(&swap, &tensor::<1>(0, 1, 1, 2));
834 assert_eq!(o, tensor::<1>(0, 1, 2, 1));
835 assert_eq!(c, Complex64::new(1.0, 0.0));
836 }
837}