1use crate::{
2 expression::Expression,
3 imag,
4 instruction::{write_expression_parameter_string, write_parameter_string, write_qubits, Qubit},
5 pickleable_new,
6 quil::{write_join_quil, Quil, INDENT},
7 real,
8 validation::identifier::{
9 validate_identifier, validate_user_identifier, IdentifierValidationError,
10 },
11};
12use ndarray::{array, linalg::kron, Array2};
13use num_complex::Complex64;
14use once_cell::sync::Lazy;
15use std::{
16 cmp::Ordering,
17 collections::{HashMap, HashSet},
18};
19
20#[cfg(feature = "stubs")]
21use pyo3_stub_gen::derive::{
22 gen_stub_pyclass, gen_stub_pyclass_complex_enum, gen_stub_pyclass_enum,
23};
24
25#[derive(Clone, Debug, PartialEq, Eq, Hash)]
27#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
28#[cfg_attr(
29 feature = "python",
30 pyo3::pyclass(module = "quil.instructions", eq, frozen, hash, get_all, subclass)
31)]
32pub struct Gate {
33 pub name: String,
34 pub parameters: Vec<Expression>,
35 pub qubits: Vec<Qubit>,
36 pub modifiers: Vec<GateModifier>,
37}
38
39#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
41#[cfg_attr(feature = "stubs", gen_stub_pyclass_enum)]
42#[cfg_attr(
43 feature = "python",
44 pyo3::pyclass(
45 module = "quil.instructions",
46 eq,
47 frozen,
48 hash,
49 rename_all = "SCREAMING_SNAKE_CASE"
50 )
51)]
52pub enum GateModifier {
53 Controlled,
56 Dagger,
58 Forked,
61}
62
63#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
64pub enum GateError {
65 #[error("invalid name: {0}")]
66 InvalidIdentifier(#[from] IdentifierValidationError),
67
68 #[error("a gate must operate on 1 or more qubits")]
69 EmptyQubits,
70
71 #[error("expected {expected} parameters, but got {actual}")]
72 ForkedParameterLength { expected: usize, actual: usize },
73
74 #[error("expected the number of Pauli term arguments, {actual}, to match the length of the Pauli word, {expected}")]
75 PauliTermArgumentLength { expected: usize, actual: usize },
76
77 #[error("the Pauli term arguments {mismatches:?}, are not in the defined argument list: {expected_arguments:?}")]
78 PauliSumArgumentMismatch {
79 mismatches: Vec<String>,
80 expected_arguments: Vec<String>,
81 },
82
83 #[error("unknown gate `{name}` to turn into {} matrix ", if *parameterized { "parameterized" } else { "constant" })]
84 UndefinedGate { name: String, parameterized: bool },
85
86 #[error("expected {expected} parameters, was given {actual}")]
87 MatrixArgumentLength { expected: usize, actual: usize },
88
89 #[error(
90 "cannot produce a matrix for a gate `{name}` with non-constant parameters {parameters:?}"
91 )]
92 MatrixNonConstantParams {
93 name: String,
94 parameters: Vec<Expression>,
95 },
96
97 #[error("cannot produce a matrix for gate `{name}` with variable qubit {qubit}", qubit=.qubit.to_quil_or_debug())]
98 MatrixVariableQubit { name: String, qubit: Qubit },
99
100 #[error("forked gate `{name}` has an odd number of parameters: {parameters:?}")]
101 ForkedGateOddNumParams {
102 name: String,
103 parameters: Vec<Expression>,
104 },
105
106 #[error("cannot produce a matrix for a gate `{name}` with unresolved qubit placeholders")]
107 UnresolvedQubitPlaceholder { name: String },
108}
109
110pub type Matrix = Array2<Complex64>;
112
113impl Gate {
114 pub fn new(
120 name: &str,
121 parameters: Vec<Expression>,
122 qubits: Vec<Qubit>,
123 modifiers: Vec<GateModifier>,
124 ) -> Result<Self, GateError> {
125 if qubits.is_empty() {
126 return Err(GateError::EmptyQubits);
127 }
128
129 validate_identifier(name).map_err(GateError::InvalidIdentifier)?;
130
131 Ok(Self {
132 name: name.to_string(),
133 parameters,
134 qubits,
135 modifiers,
136 })
137 }
138}
139
140impl Gate {
141 #[must_use]
143 pub fn dagger(mut self) -> Self {
144 self.modifiers.insert(0, GateModifier::Dagger);
145 self
146 }
147
148 #[must_use]
150 pub fn controlled(mut self, control_qubit: Qubit) -> Self {
151 self.qubits.insert(0, control_qubit);
152 self.modifiers.insert(0, GateModifier::Controlled);
153 self
154 }
155
156 pub fn forked(
163 mut self,
164 fork_qubit: Qubit,
165 alt_params: Vec<Expression>,
166 ) -> Result<Self, GateError> {
167 if alt_params.len() != self.parameters.len() {
168 return Err(GateError::ForkedParameterLength {
169 expected: self.parameters.len(),
170 actual: alt_params.len(),
171 });
172 }
173 self.modifiers.insert(0, GateModifier::Forked);
174 self.qubits.insert(0, fork_qubit);
175 self.parameters.extend(alt_params);
176 Ok(self)
177 }
178
179 #[allow(clippy::wrong_self_convention)] pub fn to_unitary(&mut self, n_qubits: u64) -> Result<Matrix, GateError> {
189 let qubits = self
190 .qubits
191 .iter()
192 .map(|q| match q {
193 Qubit::Variable(_) => Err(GateError::MatrixVariableQubit {
194 name: self.name.clone(),
195 qubit: q.clone(),
196 }),
197 Qubit::Placeholder(_) => Err(GateError::UnresolvedQubitPlaceholder {
198 name: self.name.clone(),
199 }),
200 Qubit::Fixed(i) => Ok(*i),
201 })
202 .collect::<Result<Vec<_>, _>>()?;
203 Ok(lifted_gate_matrix(&gate_matrix(self)?, &qubits, n_qubits))
204 }
205}
206
207fn lifted_gate_matrix(matrix: &Matrix, qubits: &[u64], n_qubits: u64) -> Matrix {
215 let (perm, start) = permutation_arbitrary(qubits, n_qubits);
216 let v = qubit_adjacent_lifted_gate(start, matrix, n_qubits);
217 perm.t().mapv(|c| c.conj()).dot(&v.dot(&perm))
218}
219
220fn gate_matrix(gate: &mut Gate) -> Result<Matrix, GateError> {
236 static ZERO: Lazy<Matrix> =
237 Lazy::new(|| array![[real!(1.0), real!(0.0)], [real!(0.0), real!(0.0)]]);
238 static ONE: Lazy<Matrix> =
239 Lazy::new(|| array![[real!(0.0), real!(0.0)], [real!(0.0), real!(1.0)]]);
240 if let Some(modifier) = gate.modifiers.pop() {
241 match modifier {
242 GateModifier::Controlled => {
243 gate.qubits = gate.qubits[1..].to_vec();
244 let matrix = gate_matrix(gate)?;
245 Ok(kron(&ZERO, &Array2::eye(matrix.shape()[0])) + kron(&ONE, &matrix))
246 }
247 GateModifier::Dagger => gate_matrix(gate).map(|g| g.t().mapv(|c| c.conj())),
248 GateModifier::Forked => {
249 let param_index = gate.parameters.len();
250 if param_index & 1 != 0 {
251 Err(GateError::ForkedGateOddNumParams {
252 name: gate.name.clone(),
253 parameters: gate.parameters.clone(),
254 })
255 } else {
256 gate.qubits = gate.qubits[1..].to_vec();
258 let (p0, p1) = gate.parameters[..].split_at(param_index / 2);
259 let mut child0 = gate.clone();
260 child0.parameters = p0.to_vec();
261 let mat0 = gate_matrix(&mut child0)?;
262 gate.parameters = p1.to_vec();
263 let mat1 = gate_matrix(gate)?;
264 Ok(kron(&ZERO, &mat0) + kron(&ONE, &mat1))
265 }
266 }
267 }
268 } else if gate.parameters.is_empty() {
269 CONSTANT_GATE_MATRICES
270 .get(&gate.name)
271 .cloned()
272 .ok_or_else(|| GateError::UndefinedGate {
273 name: gate.name.clone(),
274 parameterized: false,
275 })
276 } else {
277 match gate.parameters.len() {
278 1 => {
279 if let Expression::Number(x) = gate.parameters[0].clone().into_simplified() {
280 PARAMETERIZED_GATE_MATRICES
281 .get(&gate.name)
282 .map(|f| f(x))
283 .ok_or_else(|| GateError::UndefinedGate {
284 name: gate.name.clone(),
285 parameterized: true,
286 })
287 } else {
288 Err(GateError::MatrixNonConstantParams {
289 name: gate.name.clone(),
290 parameters: gate.parameters.clone(),
291 })
292 }
293 }
294 actual => Err(GateError::MatrixArgumentLength {
295 expected: 1,
296 actual,
297 }),
298 }
299 }
300}
301
302fn permutation_arbitrary(qubit_inds: &[u64], n_qubits: u64) -> (Matrix, u64) {
324 let mut perm = Array2::eye(2usize.pow(n_qubits as u32));
326 let mut sorted_inds = qubit_inds.to_vec();
328 sorted_inds.sort();
329 let med_i = qubit_inds.len() / 2;
330 let med = sorted_inds[med_i];
331 let start = med - med_i as u64;
334 if qubit_inds.len() > 1 {
335 let final_map = (start..start + qubit_inds.len() as u64)
338 .rev()
339 .collect::<Vec<_>>();
340
341 let mut qubit_arr = (0..n_qubits).collect::<Vec<_>>(); let mut made_it = false;
349 let mut right = true;
350 while !made_it {
351 let array = if right {
352 (0..qubit_inds.len()).collect::<Vec<_>>()
353 } else {
354 (0..qubit_inds.len()).rev().collect()
355 };
356
357 for i in array {
358 let j = qubit_arr
359 .iter()
360 .position(|&q| q == qubit_inds[i])
361 .expect("These arrays cover the same range.");
362 let pmod = two_swap_helper(j as u64, final_map[i], n_qubits, &mut qubit_arr);
363 perm = pmod.dot(&perm);
364 if (final_map[final_map.len() - 1]..final_map[0] + 1)
365 .rev()
366 .zip(qubit_inds)
367 .all(|(f, &q)| qubit_arr[f as usize] == q)
368 {
369 made_it = true;
370 break;
371 }
372 }
373 right = !right;
374 }
375 }
376 (perm, start)
377}
378
379fn two_swap_helper(j: u64, k: u64, n_qubits: u64, qubit_map: &mut [u64]) -> Matrix {
392 let mut perm = Array2::eye(2usize.pow(n_qubits as u32));
393 let swap = CONSTANT_GATE_MATRICES
394 .get("SWAP")
395 .expect("Key should exist by design.");
396 match Ord::cmp(&j, &k) {
397 Ordering::Equal => {}
398 Ordering::Greater => {
399 for i in (k + 1..=j).rev() {
401 perm = qubit_adjacent_lifted_gate(i - 1, swap, n_qubits).dot(&perm);
402 qubit_map.swap(i as usize, (i - 1) as usize);
403 }
404 }
405 Ordering::Less => {
406 for i in j..k {
408 perm = qubit_adjacent_lifted_gate(i, swap, n_qubits).dot(&perm);
409 qubit_map.swap(i as usize, (i + 1) as usize);
410 }
411 }
412 }
413 perm
414}
415
416fn qubit_adjacent_lifted_gate(i: u64, matrix: &Matrix, n_qubits: u64) -> Matrix {
435 let bottom_matrix = Array2::eye(2usize.pow(i as u32));
436 let gate_size = (matrix.shape()[0] as f64).log2().floor() as u64;
437 let top_qubits = n_qubits - i - gate_size;
439 let top_matrix = Array2::eye(2usize.pow(top_qubits as u32));
440 kron(&top_matrix, &kron(matrix, &bottom_matrix))
441}
442
443static CONSTANT_GATE_MATRICES: Lazy<HashMap<String, Matrix>> = Lazy::new(|| {
447 let _0 = real!(0.0);
448 let _1 = real!(1.0);
449 let _i = imag!(1.0);
450 let _1_sqrt_2 = real!(std::f64::consts::FRAC_1_SQRT_2);
451 HashMap::from([
452 ("I".to_string(), Array2::eye(2)),
453 ("X".to_string(), array![[_0, _1], [_1, _0]]),
454 ("Y".to_string(), array![[_0, -_i], [_i, _0]]),
455 ("Z".to_string(), array![[_1, _0], [_0, -_1]]),
456 ("H".to_string(), array![[_1, _1], [_1, -_1]] * _1_sqrt_2),
457 (
458 "CNOT".to_string(),
459 array![
460 [_1, _0, _0, _0],
461 [_0, _1, _0, _0],
462 [_0, _0, _0, _1],
463 [_0, _0, _1, _0]
464 ],
465 ),
466 (
467 "CCNOT".to_string(),
468 array![
469 [_1, _0, _0, _0, _0, _0, _0, _0],
470 [_0, _1, _0, _0, _0, _0, _0, _0],
471 [_0, _0, _1, _0, _0, _0, _0, _0],
472 [_0, _0, _0, _1, _0, _0, _0, _0],
473 [_0, _0, _0, _0, _1, _0, _0, _0],
474 [_0, _0, _0, _0, _0, _1, _0, _0],
475 [_0, _0, _0, _0, _0, _0, _0, _1],
476 [_0, _0, _0, _0, _0, _0, _1, _0],
477 ],
478 ),
479 ("S".to_string(), array![[_1, _0], [_0, _i]]),
480 (
481 "T".to_string(),
482 array![[_1, _0], [_0, Complex64::cis(std::f64::consts::FRAC_PI_4)]],
483 ),
484 ("CZ".to_string(), {
485 let mut cz = Array2::eye(4);
486 cz[[3, 3]] = -_1;
487 cz
488 }),
489 (
490 "SWAP".to_string(),
491 array![
492 [_1, _0, _0, _0],
493 [_0, _0, _1, _0],
494 [_0, _1, _0, _0],
495 [_0, _0, _0, _1],
496 ],
497 ),
498 (
499 "CSWAP".to_string(),
500 array![
501 [_1, _0, _0, _0, _0, _0, _0, _0],
502 [_0, _1, _0, _0, _0, _0, _0, _0],
503 [_0, _0, _1, _0, _0, _0, _0, _0],
504 [_0, _0, _0, _1, _0, _0, _0, _0],
505 [_0, _0, _0, _0, _1, _0, _0, _0],
506 [_0, _0, _0, _0, _0, _0, _1, _0],
507 [_0, _0, _0, _0, _0, _1, _0, _0],
508 [_0, _0, _0, _0, _0, _0, _0, _1],
509 ],
510 ),
511 (
512 "ISWAP".to_string(),
513 array![
514 [_1, _0, _0, _0],
515 [_0, _0, _i, _0],
516 [_0, _i, _0, _0],
517 [_0, _0, _0, _1],
518 ],
519 ),
520 ])
521});
522
523type ParameterizedMatrix = fn(Complex64) -> Matrix;
524
525static PARAMETERIZED_GATE_MATRICES: Lazy<HashMap<String, ParameterizedMatrix>> = Lazy::new(|| {
529 HashMap::from([
531 (
532 "RX".to_string(),
533 (|theta: Complex64| {
534 let _i = imag!(1.0);
535 let t = theta / 2.0;
536 array![[t.cos(), -_i * t.sin()], [-_i * t.sin(), t.cos()]]
537 }) as ParameterizedMatrix,
538 ),
539 (
540 "RY".to_string(),
541 (|theta: Complex64| {
542 let t = theta / 2.0;
543 array![[t.cos(), -t.sin()], [t.sin(), t.cos()]]
544 }) as ParameterizedMatrix,
545 ),
546 (
547 "RZ".to_string(),
548 (|theta: Complex64| {
549 let t = theta / 2.0;
550 array![[t.cos(), -t.sin()], [t.sin(), t.cos()]]
551 }) as ParameterizedMatrix,
552 ),
553 (
554 "PHASE".to_string(),
555 (|alpha: Complex64| {
556 let mut p = Array2::eye(2);
557 p[[1, 1]] = alpha.cos() + imag!(1.0) * alpha.sin();
558 p
559 }) as ParameterizedMatrix,
560 ),
561 (
562 "CPHASE00".to_string(),
563 (|alpha: Complex64| {
564 let mut p = Array2::eye(4);
565 p[[0, 0]] = alpha.cos() + imag!(1.0) * alpha.sin();
566 p
567 }) as ParameterizedMatrix,
568 ),
569 (
570 "CPHASE01".to_string(),
571 (|alpha: Complex64| {
572 let mut p = Array2::eye(4);
573 p[[1, 1]] = alpha.cos() + imag!(1.0) * alpha.sin();
574 p
575 }) as ParameterizedMatrix,
576 ),
577 (
578 "CPHASE10".to_string(),
579 (|alpha: Complex64| {
580 let mut p = Array2::eye(4);
581 p[[2, 2]] = alpha.cos() + imag!(1.0) * alpha.sin();
582 p
583 }) as ParameterizedMatrix,
584 ),
585 (
586 "CPHASE".to_string(),
587 (|alpha: Complex64| {
588 let mut p = Array2::eye(4);
589 p[[3, 3]] = alpha.cos() + imag!(1.0) * alpha.sin();
590 p
591 }) as ParameterizedMatrix,
592 ),
593 (
594 "PSWAP".to_string(),
595 (|theta: Complex64| {
596 let (_0, _1, _c) = (real!(0.0), real!(1.0), theta.cos() + theta);
597 array![
598 [_1, _0, _0, _0],
599 [_0, _0, _c, _0],
600 [_0, _c, _0, _0],
601 [_0, _0, _0, _1],
602 ]
603 }) as ParameterizedMatrix,
604 ),
605 ])
606});
607
608impl Quil for Gate {
609 fn write(
610 &self,
611 f: &mut impl std::fmt::Write,
612 fall_back_to_debug: bool,
613 ) -> crate::quil::ToQuilResult<()> {
614 for modifier in &self.modifiers {
615 modifier.write(f, fall_back_to_debug)?;
616 write!(f, " ")?;
617 }
618
619 write!(f, "{}", self.name)?;
620 write_expression_parameter_string(f, fall_back_to_debug, &self.parameters)?;
621 write_qubits(f, fall_back_to_debug, &self.qubits)
622 }
623}
624
625impl Quil for GateModifier {
626 fn write(
627 &self,
628 f: &mut impl std::fmt::Write,
629 _fall_back_to_debug: bool,
630 ) -> crate::quil::ToQuilResult<()> {
631 match self {
632 Self::Controlled => write!(f, "CONTROLLED"),
633 Self::Dagger => write!(f, "DAGGER"),
634 Self::Forked => write!(f, "FORKED"),
635 }
636 .map_err(Into::into)
637 }
638}
639
640#[cfg(test)]
641mod test_gate_into_matrix {
642 use super::{
643 lifted_gate_matrix, permutation_arbitrary, qubit_adjacent_lifted_gate, two_swap_helper,
644 Expression::Number, Gate, GateModifier::*, Matrix, ParameterizedMatrix, Qubit::Fixed,
645 CONSTANT_GATE_MATRICES, PARAMETERIZED_GATE_MATRICES,
646 };
647 use crate::{imag, real};
648 use approx::assert_abs_diff_eq;
649 use ndarray::{array, linalg::kron, Array2};
650 use num_complex::Complex64;
651 use once_cell::sync::Lazy;
652 use rstest::rstest;
653
654 static _0: Complex64 = real!(0.0);
655 static _1: Complex64 = real!(1.0);
656 static _I: Complex64 = imag!(1.0);
657 static PI: Complex64 = real!(std::f64::consts::PI);
658 static PI_4: Complex64 = real!(std::f64::consts::FRAC_PI_4);
659 static SWAP: Lazy<Matrix> = Lazy::new(|| CONSTANT_GATE_MATRICES.get("SWAP").cloned().unwrap());
660 static X: Lazy<Matrix> = Lazy::new(|| array![[_0, _1], [_1, _0]]);
661 static P0: Lazy<Matrix> = Lazy::new(|| array![[_1, _0], [_0, _0]]);
662 static P1: Lazy<Matrix> = Lazy::new(|| array![[_0, _0], [_0, _1]]);
663 static CNOT: Lazy<Matrix> = Lazy::new(|| CONSTANT_GATE_MATRICES.get("CNOT").cloned().unwrap());
664 static ISWAP: Lazy<Matrix> =
665 Lazy::new(|| CONSTANT_GATE_MATRICES.get("ISWAP").cloned().unwrap());
666 static H: Lazy<Matrix> = Lazy::new(|| CONSTANT_GATE_MATRICES.get("H").cloned().unwrap());
667 static RZ: Lazy<ParameterizedMatrix> =
668 Lazy::new(|| PARAMETERIZED_GATE_MATRICES.get("RZ").cloned().unwrap());
669 static CCNOT: Lazy<Matrix> =
670 Lazy::new(|| CONSTANT_GATE_MATRICES.get("CCNOT").cloned().unwrap());
671 static CZ: Lazy<Matrix> = Lazy::new(|| CONSTANT_GATE_MATRICES.get("CZ").cloned().unwrap());
672
673 #[rstest]
674 #[case(0, 2, &SWAP)]
675 #[case(0, 3, &kron(&Array2::eye(2), &SWAP))]
676 #[case(0, 4, &kron(&Array2::eye(4), &SWAP))]
677 #[case(1, 3, &kron(&SWAP, &Array2::eye(2)))]
678 #[case(1, 4, &kron(&Array2::eye(2), &kron(&SWAP, &Array2::eye(2))))]
679 #[case(2, 4, &kron(&Array2::eye(1), &kron(&SWAP, &Array2::eye(4))))]
680 #[case(8, 10, &kron(&Array2::eye(1), &kron(&SWAP, &Array2::eye(2usize.pow(8)))))]
681 fn test_qubit_adjacent_lifted_gate(
682 #[case] i: u64,
683 #[case] n_qubits: u64,
684 #[case] expected: &Matrix,
685 ) {
686 let result = qubit_adjacent_lifted_gate(i, &SWAP, n_qubits);
687 assert_abs_diff_eq!(result, expected);
688 }
689
690 #[rstest]
692 #[case(0, 0, 2, &mut[0, 1], &[0, 1], &Array2::eye(4))]
693 #[case(0, 1, 2, &mut[0, 1], &[1, 0], &array![[_1, _0, _0, _0],
694 [_0, _0, _1, _0],
695 [_0, _1, _0, _0],
696 [_0, _0, _0, _1]])]
697 #[case(0, 1, 2, &mut[1, 0], &[0, 1], &array![[_1, _0, _0, _0],
698 [_0, _0, _1, _0],
699 [_0, _1, _0, _0],
700 [_0, _0, _0, _1]])]
701 #[case(1, 0, 2, &mut[0, 1], &[1, 0], &array![[_1, _0, _0, _0],
702 [_0, _0, _1, _0],
703 [_0, _1, _0, _0],
704 [_0, _0, _0, _1]])]
705 #[case(1, 0, 2, &mut[1, 0], &[0, 1], &array![[_1, _0, _0, _0],
706 [_0, _0, _1, _0],
707 [_0, _1, _0, _0],
708 [_0, _0, _0, _1]])]
709 #[case(0, 1, 3, &mut[0, 1, 2], &[1, 0, 2], &array![[_1, _0, _0, _0, _0, _0, _0, _0],
710 [_0, _0, _1, _0, _0, _0, _0, _0],
711 [_0, _1, _0, _0, _0, _0, _0, _0],
712 [_0, _0, _0, _1, _0, _0, _0, _0],
713 [_0, _0, _0, _0, _1, _0, _0, _0],
714 [_0, _0, _0, _0, _0, _0, _1, _0],
715 [_0, _0, _0, _0, _0, _1, _0, _0],
716 [_0, _0, _0, _0, _0, _0, _0, _1]])]
717
718 fn test_two_swap_helper(
719 #[case] j: u64,
720 #[case] k: u64,
721 #[case] n_qubits: u64,
722 #[case] qubit_map: &mut [u64],
723 #[case] expected_qubit_map: &[u64],
724 #[case] expected_matrix: &Matrix,
725 ) {
726 let result = two_swap_helper(j, k, n_qubits, qubit_map);
727 assert_eq!(qubit_map, expected_qubit_map);
728 assert_abs_diff_eq!(result, expected_matrix);
729 }
730
731 #[rstest]
733 #[case(&[0], 1, 0, &Array2::eye(2))]
734 #[case(&[0, 1], 2, 0, &array![[_1, _0, _0, _0],
735 [_0, _0, _1, _0],
736 [_0, _1, _0, _0],
737 [_0, _0, _0, _1]])]
738 #[case(&[1, 0], 2, 0, &Array2::eye(4))]
739 #[case(&[0, 2], 3, 1, &array![[_1, _0, _0, _0, _0, _0, _0, _0],
740 [_0, _0, _1, _0, _0, _0, _0, _0],
741 [_0, _0, _0, _0, _1, _0, _0, _0],
742 [_0, _0, _0, _0, _0, _0, _1, _0],
743 [_0, _1, _0, _0, _0, _0, _0, _0],
744 [_0, _0, _0, _1, _0, _0, _0, _0],
745 [_0, _0, _0, _0, _0, _1, _0, _0],
746 [_0, _0, _0, _0, _0, _0, _0, _1]])]
747 #[case(&[1, 2], 3, 1, &array![[_1, _0, _0, _0, _0, _0, _0, _0],
748 [_0, _1, _0, _0, _0, _0, _0, _0],
749 [_0, _0, _0, _0, _1, _0, _0, _0],
750 [_0, _0, _0, _0, _0, _1, _0, _0],
751 [_0, _0, _1, _0, _0, _0, _0, _0],
752 [_0, _0, _0, _1, _0, _0, _0, _0],
753 [_0, _0, _0, _0, _0, _0, _1, _0],
754 [_0, _0, _0, _0, _0, _0, _0, _1]])]
755 #[case(&[0, 1, 2], 3, 0, &array![[_1, _0, _0, _0, _0, _0, _0, _0],
756 [_0, _0, _0, _0, _1, _0, _0, _0],
757 [_0, _0, _1, _0, _0, _0, _0, _0],
758 [_0, _0, _0, _0, _0, _0, _1, _0],
759 [_0, _1, _0, _0, _0, _0, _0, _0],
760 [_0, _0, _0, _0, _0, _1, _0, _0],
761 [_0, _0, _0, _1, _0, _0, _0, _0],
762 [_0, _0, _0, _0, _0, _0, _0, _1]])]
763 fn test_permutation_arbitrary(
764 #[case] qubit_inds: &[u64],
765 #[case] n_qubits: u64,
766 #[case] expected_start: u64,
767 #[case] expected_matrix: &Matrix,
768 ) {
769 let (result_matrix, result_start) = permutation_arbitrary(qubit_inds, n_qubits);
770 assert_abs_diff_eq!(result_matrix, expected_matrix);
771 assert_eq!(result_start, expected_start);
772 }
773
774 #[rstest]
775 #[case(&CNOT, &mut [1, 0], 2, &(kron(&P0, &Array2::eye(2)) + kron(&P1, &X)))]
776 #[case(&CNOT, &mut [0, 1], 2, &(kron(&Array2::eye(2), &P0) + kron(&X, &P1)))]
777 #[case(&CNOT, &mut [2, 1], 3, &(kron(&CNOT, &Array2::eye(2))))]
778 #[case(&ISWAP, &mut [0, 1], 3, &kron(&Array2::eye(2), &ISWAP))]
779 #[case(&ISWAP, &mut [1, 0], 3, &kron(&Array2::eye(2), &ISWAP))]
780 #[case(&ISWAP, &mut [1, 2], 4, &kron(&Array2::eye(2), &kron(&ISWAP, &Array2::eye(2))))]
781 #[case(&ISWAP, &mut [3, 2], 4, &kron(&ISWAP, &Array2::eye(4)))]
782 #[case(&ISWAP, &mut [2, 3], 4, &kron(&ISWAP, &Array2::eye(4)))]
783 #[case(&H, &mut [0], 4, &kron(&Array2::eye(8), &H))]
784 #[case(&H, &mut [1], 4, &kron(&Array2::eye(4), &kron(&H, &Array2::eye(2))))]
785 #[case(&H, &mut [2], 4, &kron(&Array2::eye(2), &kron(&H, &Array2::eye(4))))]
786 #[case(&H, &mut [3], 4, &kron(&H, &Array2::eye(8)))]
787 #[case(&H, &mut [0], 5, &kron(&Array2::eye(16), &H))]
788 #[case(&H, &mut [1], 5, &kron(&Array2::eye(8), &kron(&H, &Array2::eye(2))))]
789 #[case(&H, &mut [2], 5, &kron(&Array2::eye(4), &kron(&H, &Array2::eye(4))))]
790 #[case(&H, &mut [3], 5, &kron(&Array2::eye(2), &kron(&H, &Array2::eye(8))))]
791 #[case(&H, &mut [4], 5, &kron(&H, &Array2::eye(16)))]
792 fn test_lifted_gate_matrix(
793 #[case] matrix: &Matrix,
794 #[case] indices: &mut [u64],
795 #[case] n_qubits: u64,
796 #[case] expected: &Matrix,
797 ) {
798 assert_abs_diff_eq!(lifted_gate_matrix(matrix, indices, n_qubits), expected);
799 }
800
801 #[rstest]
802 #[case(&mut Gate::new("H", vec![], vec![Fixed(0)], vec![]).unwrap(), 4, &kron(&Array2::eye(8), &H))]
803 #[case(&mut Gate::new("RZ", vec![Number(PI_4)], vec![Fixed(0)], vec![Dagger]).unwrap(), 1, &RZ(-PI_4))]
804 #[case(&mut Gate::new("X", vec![], vec![Fixed(0)], vec![Dagger]).unwrap().controlled(Fixed(1)), 2, &CNOT)]
805 #[case(
806 &mut Gate::new("X", vec![], vec![Fixed(0)], vec![]).unwrap().dagger().controlled(Fixed(1)).dagger().dagger().controlled(Fixed(2)),
807 3,
808 &CCNOT
809 )]
810 #[case(
811 &mut Gate::new("PHASE", vec![Number(_0)], vec![Fixed(1)], vec![]).unwrap().forked(Fixed(0), vec![Number(PI)]).unwrap(),
812 2,
813 &lifted_gate_matrix(&CZ, &[0, 1], 2)
814 )]
815 fn test_to_unitary(#[case] gate: &mut Gate, #[case] n_qubits: u64, #[case] expected: &Matrix) {
816 let result = gate.to_unitary(n_qubits);
817 assert!(result.is_ok());
818 assert_abs_diff_eq!(result.as_ref().unwrap(), expected);
819 }
820}
821
822#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, strum::Display, strum::EnumString)]
823#[cfg_attr(feature = "stubs", gen_stub_pyclass_enum)]
824#[cfg_attr(
825 feature = "python",
826 pyo3::pyclass(
827 module = "quil.instructions",
828 eq,
829 frozen,
830 hash,
831 rename_all = "SCREAMING_SNAKE_CASE"
832 )
833)]
834#[strum(serialize_all = "UPPERCASE")]
835pub enum PauliGate {
836 I,
837 X,
838 Y,
839 Z,
840}
841
842#[derive(Clone, Debug, PartialEq, Eq, Hash)]
843#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
844#[cfg_attr(
845 feature = "python",
846 pyo3::pyclass(module = "quil.instructions", eq, frozen, hash, get_all, subclass)
847)]
848pub struct PauliTerm {
849 pub arguments: Vec<(PauliGate, String)>,
850 pub expression: Expression,
851}
852
853pickleable_new! {
854 impl PauliTerm {
855 pub fn new(arguments: Vec<(PauliGate, String)>, expression: Expression);
856 }
857}
858
859impl PauliTerm {
860 pub(crate) fn word(&self) -> impl Iterator<Item = &PauliGate> {
861 self.arguments.iter().map(|(gate, _)| gate)
862 }
863
864 pub(crate) fn arguments(&self) -> impl Iterator<Item = &String> {
865 self.arguments.iter().map(|(_, argument)| argument)
866 }
867}
868
869#[derive(Clone, Debug, PartialEq, Eq, Hash)]
870#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
871#[cfg_attr(
872 feature = "python",
873 pyo3::pyclass(module = "quil.instructions", eq, frozen, hash, get_all, subclass)
874)]
875pub struct PauliSum {
876 pub arguments: Vec<String>,
877 pub terms: Vec<PauliTerm>,
878}
879
880pickleable_new! {
881 impl PauliSum {
882 pub fn new(arguments: Vec<String>, terms: Vec<PauliTerm>) -> Result<PauliSum, GateError> {
883 let diff = terms
884 .iter()
885 .flat_map(|t| t.arguments())
886 .collect::<HashSet<_>>()
887 .difference(&arguments.iter().collect::<HashSet<_>>())
888 .copied()
889 .collect::<Vec<_>>();
890
891 if !diff.is_empty() {
892 return Err(GateError::PauliSumArgumentMismatch {
893 mismatches: diff.into_iter().cloned().collect(),
894 expected_arguments: arguments,
895 });
896 }
897
898 Ok(Self { arguments, terms })
899 }
900 }
901}
902
903#[derive(Clone, Debug, PartialEq, Eq, Hash)]
905#[cfg_attr(feature = "stubs", gen_stub_pyclass_complex_enum)]
906#[cfg_attr(
907 feature = "python",
908 pyo3::pyclass(module = "quil.instructions", eq, frozen, hash)
909)]
910pub enum GateSpecification {
911 Matrix(Vec<Vec<Expression>>),
913 Permutation(Vec<u64>),
915 PauliSum(PauliSum),
918}
919
920impl Quil for GateSpecification {
921 fn write(
922 &self,
923 f: &mut impl std::fmt::Write,
924 fall_back_to_debug: bool,
925 ) -> crate::quil::ToQuilResult<()> {
926 match self {
927 GateSpecification::Matrix(matrix) => {
928 for row in matrix {
929 write!(f, "{INDENT}")?;
930 write_join_quil(f, fall_back_to_debug, row.iter(), ", ", "")?;
931 writeln!(f)?;
932 }
933 }
934 GateSpecification::Permutation(permutation) => {
935 write!(f, "{INDENT}")?;
936 if let Some(i) = permutation.first() {
937 write!(f, "{i}")?;
938 }
939 for i in permutation.iter().skip(1) {
940 write!(f, ", {i}")?;
941 }
942 writeln!(f)?;
943 }
944 GateSpecification::PauliSum(pauli_sum) => {
945 for term in &pauli_sum.terms {
946 write!(f, "{INDENT}")?;
947 for word in term.word() {
948 write!(f, "{word}")?;
949 }
950 write!(f, "(")?;
951 term.expression.write(f, fall_back_to_debug)?;
952 write!(f, ")")?;
953 for argument in term.arguments() {
954 write!(f, " {argument}")?;
955 }
956 writeln!(f)?;
957 }
958 }
959 }
960 Ok(())
961 }
962}
963
964#[derive(Clone, Debug, PartialEq, Eq, Hash)]
966#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
967#[cfg_attr(
968 feature = "python",
969 pyo3::pyclass(module = "quil.instructions", eq, frozen, hash, get_all, subclass)
970)]
971pub struct GateDefinition {
972 pub name: String,
973 pub parameters: Vec<String>,
974 pub specification: GateSpecification,
975}
976
977pickleable_new! {
978 impl GateDefinition {
979 pub fn new(
980 name: String,
981 parameters: Vec<String>,
982 specification: GateSpecification,
983 ) -> Result<GateDefinition, GateError> {
984 validate_user_identifier(&name)?;
985 Ok(Self {
986 name,
987 parameters,
988 specification,
989 })
990 }
991 }
992}
993
994impl Quil for GateDefinition {
995 fn write(
996 &self,
997 f: &mut impl std::fmt::Write,
998 fall_back_to_debug: bool,
999 ) -> crate::quil::ToQuilResult<()> {
1000 write!(f, "DEFGATE {}", self.name,)?;
1001 write_parameter_string(f, &self.parameters)?;
1002 match &self.specification {
1003 GateSpecification::Matrix(_) => writeln!(f, " AS MATRIX:")?,
1004 GateSpecification::Permutation(_) => writeln!(f, " AS PERMUTATION:")?,
1005 GateSpecification::PauliSum(sum) => {
1006 for arg in &sum.arguments {
1007 write!(f, " {arg}")?;
1008 }
1009 writeln!(f, " AS PAULI-SUM:")?
1010 }
1011 }
1012 self.specification.write(f, fall_back_to_debug)?;
1013 Ok(())
1014 }
1015}
1016
1017#[cfg(test)]
1018mod test_gate_definition {
1019 use super::{GateDefinition, GateSpecification, PauliGate, PauliSum, PauliTerm};
1020 use crate::expression::{
1021 Expression, ExpressionFunction, FunctionCallExpression, InfixExpression, InfixOperator,
1022 PrefixExpression, PrefixOperator,
1023 };
1024 use crate::quil::Quil;
1025 use crate::{imag, real};
1026 use insta::assert_snapshot;
1027 use internment::ArcIntern;
1028 use rstest::rstest;
1029
1030 #[rstest]
1031 #[case(
1032 "Permutation GateDefinition",
1033 GateDefinition{
1034 name: "PermGate".to_string(),
1035 parameters: vec![],
1036 specification: GateSpecification::Permutation(vec![0, 1, 2, 3, 4, 5, 7, 6]),
1037
1038 }
1039 )]
1040 #[case(
1041 "Parameterized GateDefinition",
1042 GateDefinition{
1043 name: "ParamGate".to_string(),
1044 parameters: vec!["theta".to_string()],
1045 specification: GateSpecification::Matrix(vec![
1046 vec![
1047 Expression::FunctionCall(FunctionCallExpression {
1048 function: crate::expression::ExpressionFunction::Cosine,
1049 expression: ArcIntern::new(Expression::Infix(InfixExpression {
1050 left: ArcIntern::new(Expression::Variable("theta".to_string())),
1051 operator: InfixOperator::Slash,
1052 right: ArcIntern::new(Expression::Number(real!(2.0))),
1053 })),
1054 }),
1055 Expression::Infix(InfixExpression {
1056 left: ArcIntern::new(Expression::Prefix(PrefixExpression {
1057 operator: PrefixOperator::Minus,
1058 expression: ArcIntern::new(Expression::Number(imag!(1f64)))
1059 })),
1060 operator: InfixOperator::Star,
1061 right: ArcIntern::new(Expression::FunctionCall(FunctionCallExpression {
1062 function: ExpressionFunction::Sine,
1063 expression: ArcIntern::new(Expression::Infix(InfixExpression {
1064 left: ArcIntern::new(Expression::Variable("theta".to_string())),
1065 operator: InfixOperator::Slash,
1066 right: ArcIntern::new(Expression::Number(real!(2.0))),
1067 })),
1068 })),
1069 })
1070 ],
1071 vec![
1072 Expression::Infix(InfixExpression {
1073 left: ArcIntern::new(Expression::Prefix(PrefixExpression {
1074 operator: PrefixOperator::Minus,
1075 expression: ArcIntern::new(Expression::Number(imag!(1f64)))
1076 })),
1077 operator: InfixOperator::Star,
1078 right: ArcIntern::new(Expression::FunctionCall(FunctionCallExpression {
1079 function: ExpressionFunction::Sine,
1080 expression: ArcIntern::new(Expression::Infix(InfixExpression {
1081 left: ArcIntern::new(Expression::Variable("theta".to_string())),
1082 operator: InfixOperator::Slash,
1083 right: ArcIntern::new(Expression::Number(real!(2.0))),
1084 })),
1085 })),
1086 }),
1087 Expression::FunctionCall(FunctionCallExpression {
1088 function: crate::expression::ExpressionFunction::Cosine,
1089 expression: ArcIntern::new(Expression::Infix(InfixExpression {
1090 left: ArcIntern::new(Expression::Variable("theta".to_string())),
1091 operator: InfixOperator::Slash,
1092 right: ArcIntern::new(Expression::Number(real!(2.0))),
1093 })),
1094 }),
1095 ],
1096 ]),
1097
1098 }
1099 )]
1100 #[case(
1101 "Pauli Sum GateDefinition",
1102 GateDefinition{
1103 name: "PauliSumGate".to_string(),
1104 parameters: vec!["theta".to_string()],
1105 specification: GateSpecification::PauliSum(PauliSum{arguments: vec!["p".to_string(), "q".to_string()], terms: vec![
1106 PauliTerm {
1107 arguments: vec![(PauliGate::Z, "p".to_string()), (PauliGate::Z, "q".to_string())],
1108 expression: Expression::Infix(InfixExpression {
1109 left: ArcIntern::new(Expression::Prefix(PrefixExpression {
1110 operator: PrefixOperator::Minus,
1111 expression: ArcIntern::new(Expression::Variable("theta".to_string()))
1112 })),
1113 operator: InfixOperator::Slash,
1114 right: ArcIntern::new(Expression::Number(real!(4.0)))
1115 }),
1116 },
1117 PauliTerm {
1118 arguments: vec![(PauliGate::Y, "p".to_string())],
1119 expression: Expression::Infix(InfixExpression {
1120 left: ArcIntern::new(Expression::Variable("theta".to_string())),
1121 operator: InfixOperator::Slash,
1122 right: ArcIntern::new(Expression::Number(real!(4.0)))
1123 }),
1124 },
1125 PauliTerm {
1126 arguments: vec![(PauliGate::X, "q".to_string())],
1127 expression: Expression::Infix(InfixExpression {
1128 left: ArcIntern::new(Expression::Variable("theta".to_string())),
1129 operator: InfixOperator::Slash,
1130 right: ArcIntern::new(Expression::Number(real!(4.0)))
1131 }),
1132 },
1133 ]})
1134 }
1135 )]
1136 fn test_display(#[case] description: &str, #[case] gate_def: GateDefinition) {
1137 insta::with_settings!({
1138 snapshot_suffix => description,
1139 }, {
1140 assert_snapshot!(gate_def.to_quil_or_debug())
1141 })
1142 }
1143}
1144
1145#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1147pub enum GateType {
1148 Matrix,
1149 Permutation,
1150 PauliSum,
1151}
1152
1153impl Quil for GateType {
1154 fn write(
1155 &self,
1156 f: &mut impl std::fmt::Write,
1157 _fall_back_to_debug: bool,
1158 ) -> crate::quil::ToQuilResult<()> {
1159 match self {
1160 Self::Matrix => write!(f, "MATRIX"),
1161 Self::Permutation => write!(f, "PERMUTATION"),
1162 Self::PauliSum => write!(f, "PAULI-SUM"),
1163 }
1164 .map_err(Into::into)
1165 }
1166}