quil_rs/instruction/
gate.rs

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/// A struct encapsulating all the properties of a Quil Quantum Gate.
26#[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/// An enum of all the possible modifiers on a quil [`Gate`]
40#[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    /// The `CONTROLLED` modifier makes the gate take an extra [`Qubit`] parameter as a control
54    /// qubit.
55    Controlled,
56    /// The `DAGGER` modifier does a complex-conjugate transpose on the [`Gate`].
57    Dagger,
58    /// The `FORKED` modifier allows an alternate set of parameters to be used based on the state
59    /// of a qubit.
60    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
110/// Matrix version of a gate.
111pub type Matrix = Array2<Complex64>;
112
113impl Gate {
114    /// Build a new gate
115    ///
116    /// # Errors
117    ///
118    /// Returns an error if the given name isn't a valid Quil identifier or if no qubits are given.
119    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    /// Apply a DAGGER modifier to the gate
142    #[must_use]
143    pub fn dagger(mut self) -> Self {
144        self.modifiers.insert(0, GateModifier::Dagger);
145        self
146    }
147
148    /// Apply a CONTROLLED modifier to the gate
149    #[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    /// Apply a FORKED modifier to the gate
157    ///
158    /// # Errors
159    ///
160    /// Returns an error if the number of provided alternate parameters don't
161    /// equal the number of existing parameters.
162    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    /// Lift a Gate to the full `n_qubits`-qubit Hilbert space.
180    ///
181    /// # Errors
182    ///
183    /// Returns an error if any of the parameters of this gate are non-constant, if any of the
184    /// qubits are variable, if the name of this gate is unknown, or if there are an unexpected
185    /// number of parameters.
186    // TODO (#464): This method can lead to overflow.
187    #[allow(clippy::wrong_self_convention)] // It's a Breaking Change to change it now.
188    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
207/// Lift a unitary matrix to act on the specified qubits in a full `n_qubits`-qubit Hilbert
208/// space.
209///
210/// For 1-qubit gates, this is easy and can be achieved with appropriate kronning of identity
211/// matrices. For 2-qubit gates acting on adjacent qubit indices, it is also easy. However, for a
212/// multiqubit gate acting on non-adjactent qubit indices, we must first apply a permutation matrix
213/// to make the qubits adjacent and then apply the inverse permutation.
214fn 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
220/// Recursively handle a gate, with all modifiers.
221///
222/// The main source of complexity is in handling FORKED gates. Given a gate with modifiers, such as
223/// `FORKED CONTROLLED FORKED RX(a,b,c,d) 0 1 2 3`, we get a tree, as in
224///
225/// ```text
226///
227///               FORKED CONTROLLED FORKED RX(a,b,c,d) 0 1 2 3
228///                 /                                      \
229///    CONTROLLED FORKED RX(a,b) 1 2 3       CONTROLLED FORKED RX(c,d) 1 2 3
230///                |                                        |
231///         FORKED RX(a,b) 2 3                      FORKED RX(c,d) 2 3
232///          /          \                            /          \
233///      RX(a) 3      RX(b) 3                    RX(c) 3      RX(d) 3
234/// ```
235fn 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                    // Some mutability dancing to keep the borrow checker happy
257                    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
302/// Generate the permutation matrix that permutes an arbitrary number of single-particle Hilbert
303/// spaces into adjacent positions.
304///
305///
306/// Transposes the qubit indices in the order they are passed to a contiguous region in the
307/// complete Hilbert space, in increasing qubit index order (preserving the order they are passed
308/// in).
309///
310/// Gates are usually defined as `GATE 0 1 2`, with such an argument ordering dictating the layout
311/// of the matrix corresponding to GATE. If such an instruction is given, actual qubits (0, 1, 2)
312/// need to be swapped into the positions (2, 1, 0), because the lifting operation taking the 8 x 8
313/// matrix of GATE is done in the little-endian (reverse) addressed qubit space.
314///
315/// For example, suppose I have a Quil command CCNOT 20 15 10. The median of the qubit indices is
316/// 15 - hence, we permute qubits [20, 15, 10] into the final map [16, 15, 14] to minimize the
317/// number of swaps needed, and so we can directly operate with the final CCNOT, when lifted from
318/// indices [16, 15, 14] to the complete Hilbert space.
319///
320/// Notes: assumes qubit indices are unique.
321///
322/// Done in preparation for arbitrary gate application on adjacent qubits.
323fn permutation_arbitrary(qubit_inds: &[u64], n_qubits: u64) -> (Matrix, u64) {
324    // Begin construction of permutation
325    let mut perm = Array2::eye(2usize.pow(n_qubits as u32));
326    // First, sort the list and find the median.
327    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    // The starting position of all specified Hilbert spaces begins at the qubit at (median -
332    // med_i)
333    let start = med - med_i as u64;
334    if qubit_inds.len() > 1 {
335        // Array of final indices the arguments are mapped to, from high index to low index, left to
336        // right ordering
337        let final_map = (start..start + qubit_inds.len() as u64)
338            .rev()
339            .collect::<Vec<_>>();
340
341        // Note that the lifting operation takes a k-qubit gate operating on the qubits i+k-1, i+k-2,
342        // ... i (left to right). two_swap_helper can be used to build the permutation matrix by
343        // filling out the final map by sweeping over the qubit_inds from left to right and back again,
344        // swapping qubits into position. we loop over the qubit_inds until the final mapping matches
345        // the argument.
346        let mut qubit_arr = (0..n_qubits).collect::<Vec<_>>(); // Current qubit indexing
347
348        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
379/// Generate the permutation matrix that permutes two single-particle Hilbert spaces into adjacent
380/// positions.
381///
382/// ALWAYS swaps j TO k. Recall that Hilbert spaces are ordered in decreasing qubit index order.
383/// Hence, j > k implies that j is to the left of k.
384///
385/// End results:
386///     j == k: nothing happens
387///     j > k: Swap j right to k, until j at ind (k) and k at ind (k+1).
388///     j < k: Swap j left to k, until j at ind (k) and k at ind (k-1).
389///
390/// Done in preparation for arbitrary 2-qubit gate application on ADJACENT qubits.
391fn 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            // swap j right to k, until j at ind (k) and k at ind (k+1)
400            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            // swap j left to k, until j at ind (k) and k at ind (k-1)
407            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
416/// Lifts input k-qubit gate on adjacent qubits starting from qubit i to complete Hilbert space of
417/// dimension 2 ** `num_qubits`.
418///
419/// Ex: 1-qubit gate, lifts from qubit i
420/// Ex: 2-qubit gate, lifts from qubits (i+1, i)
421/// Ex: 3-qubit gate, lifts from qubits (i+2, i+1, i), operating in that order
422///
423/// In general, this takes a k-qubit gate (2D matrix 2^k x 2^k) and lifts it to the complete
424/// Hilbert space of dim 2^num_qubits, as defined by the right-to-left tensor product (1) in
425/// arXiv:1608.03355.
426///
427/// Developer note: Quil and the QVM like qubits to be ordered such that qubit 0 is on the right.
428/// Therefore, in `qubit_adjacent_lifted_gate`, `lifted_pauli`, and `lifted_state_operator`, we
429/// build up the lifted matrix by performing the kronecker product from right to left.
430///
431/// Note that while the qubits are addressed in decreasing order, starting with num_qubit - 1 on
432/// the left and ending with qubit 0 on the right (in a little-endian fashion), gates are still
433/// lifted to apply on qubits in increasing index (right-to-left) order.
434fn 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    // TODO (#464): This can overflow and lead to silent errors in release builds.
438    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
443/// Gates matrices that don't use any parameters.
444///
445/// https://github.com/quil-lang/quil/blob/master/spec/Quil.md#standard-gates
446static 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
525/// Gates matrices that use parameters.
526///
527/// https://github.com/quil-lang/quil/blob/master/spec/Quil.md#standard-gates
528static PARAMETERIZED_GATE_MATRICES: Lazy<HashMap<String, ParameterizedMatrix>> = Lazy::new(|| {
529    // Unfortunately, Complex::cis takes a _float_ argument.
530    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    // test cases via pyquil.simulation.tools.two_swap_helper
691    #[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    // test cases via pyquil.simulation.tools.permutation_arbitrary
732    #[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/// An enum representing a the specification of a [`GateDefinition`] for a given [`GateType`]
904#[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    /// A matrix of [`Expression`]s representing a unitary operation for a [`GateType::Matrix`].
912    Matrix(Vec<Vec<Expression>>),
913    /// A vector of integers that defines the permutation used for a [`GateType::Permutation`]
914    Permutation(Vec<u64>),
915    /// A Hermitian operator specified as a Pauli sum, a sum of combinations of Pauli operators,
916    /// used for a [`GateType::PauliSum`]
917    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/// A struct encapsulating a quil Gate Definition
965#[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/// The type of a [`GateDefinition`]
1146#[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}