quil_rs/instruction/
gate_sequence.rs

1use std::collections::{HashMap, HashSet};
2
3use crate::{
4    expression::Expression,
5    instruction::{Gate, GateModifier, Qubit},
6    pickleable_new,
7    quil::Quil,
8};
9
10/// An error that can occur when expanding a gate that has a sequence gate definition.
11#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone)]
12pub enum DefGateSequenceExpansionError {
13    #[error("gate sequence expected {expected} arguments, found {found}")]
14    ParameterCount { expected: usize, found: usize },
15    #[error("cyclic sequence gate definition detected: {0:?}")]
16    CyclicSequenceGateDefinition(Vec<String>),
17    #[error(
18        "SEQUENCE gate expected to be applied to {expected} qubit{}, \
19         but was applied to {found}",
20        if *expected == 1 { "" } else { "s" },
21    )]
22    QubitCount { expected: usize, found: usize },
23    #[error("expected fixed qubit argument, found {}", .0.to_quil_or_debug())]
24    NonFixedQubitArgument(Qubit),
25    #[error(
26        "gate modifiers on invocations of sequence gate definitions are currently unsupported, \
27         but found {0:?}"
28    )]
29    GateModifiersUnsupported(Vec<GateModifier>),
30    /// Gate sequence element is a gate application where the formal qubit must be an argument. Note,
31    /// this error should never occur because gate sequence elements are validated in [`DefGateSequence::try_new`].
32    /// We still prefer returning an error to panicking.
33    #[error("gate sequence elements must reference parameters, but found {}", .0.to_quil_or_debug())]
34    InvalidGateSequenceElementQubit(Qubit),
35    /// Qubit variable in gate sequence is undefined. Note, this error should never occur because gate
36    /// sequence elements are validated in [`DefGateSequence::try_new`]. We still prefer returning
37    /// an error to panicking.
38    #[error("qubit variable {0} in gate sequence is undefined")]
39    UndefinedGateSequenceElementQubit(String),
40}
41
42/// A sequence of gates that make up a defined gate (i.e. with `DEFGATE ... AS SEQUENCE`).
43#[derive(Clone, Debug, PartialEq, Eq, Hash)]
44#[cfg_attr(feature = "stubs", pyo3_stub_gen::derive::gen_stub_pyclass)]
45#[cfg_attr(
46    feature = "python",
47    pyo3::pyclass(module = "quil.instructions", eq, frozen, hash, get_all, subclass)
48)]
49pub struct DefGateSequence {
50    /// The list of qubit variable names in the gate signature.
51    pub(crate) qubits: Vec<String>,
52    /// The list of `Gate` objects that make up the sequence.
53    pub(crate) gates: Vec<Gate>,
54}
55
56pickleable_new! {
57    impl DefGateSequence {
58        /// Creates a new `DefGateSequence` with the given qubits and gates.
59        ///
60        /// `qubits` should be a list of qubit names that the gates in the sequence will act on.
61        /// `gates` should be a list of `Gate` objects that make up the sequence.
62        /// Each gate must reference qubits in the `qubits` list by name.
63        /// They may not specify a fixed qubit.
64        pub fn try_new(qubits: Vec<String>, gates: Vec<Gate>) -> Result<DefGateSequence, DefGateSequenceError> {
65            let (gates, qubits) = validate_defgate_as_sequence_elements(gates, qubits)?;
66            Ok(Self { qubits, gates })
67        }
68    }
69}
70
71impl DefGateSequence {
72    pub(crate) fn expand(
73        &self,
74        gate_parameter_arguments: HashMap<String, Expression>,
75        qubit_arguments: Vec<Qubit>,
76    ) -> Result<Vec<Gate>, DefGateSequenceExpansionError> {
77        if qubit_arguments.len() != self.qubits.len() {
78            return Err(DefGateSequenceExpansionError::QubitCount {
79                expected: self.qubits.len(),
80                found: qubit_arguments.len(),
81            });
82        }
83
84        let fixed_qubit_arguments = qubit_arguments
85            .into_iter()
86            .map(|qubit| {
87                if let Qubit::Fixed(fixed_qubit) = qubit {
88                    Ok(fixed_qubit)
89                } else {
90                    Err(DefGateSequenceExpansionError::NonFixedQubitArgument(qubit))
91                }
92            })
93            .collect::<Result<Vec<u64>, _>>()?;
94
95        let qubit_argument_map: HashMap<_, _> = self
96            .qubits
97            .iter()
98            .zip(fixed_qubit_arguments.iter())
99            .map(|(qubit_variable, fixed_qubit)| {
100                (qubit_variable.clone(), Qubit::Fixed(*fixed_qubit))
101            })
102            .collect();
103
104        self.gates
105            .iter()
106            .map(|gate| {
107                let gate_parameters = gate
108                    .parameters
109                    .iter()
110                    .map(|parameter| parameter.substitute_variables(&gate_parameter_arguments))
111                    .collect::<Vec<_>>();
112
113                let gate_qubits = gate
114                    .qubits
115                    .iter()
116                    .map(|qubit| {
117                        if let Qubit::Variable(qubit_variable) = qubit {
118                            qubit_argument_map.get(qubit_variable)
119                                .map_or_else(||
120                                    Err(DefGateSequenceExpansionError::UndefinedGateSequenceElementQubit(qubit_variable.clone())),
121                                    |qubit| Ok(qubit.clone())
122                                )
123                        } else {
124                            // Spec states that a sequence element is effectively a gate application where the formal qubit must be an argument.
125                            Err(DefGateSequenceExpansionError::InvalidGateSequenceElementQubit(qubit.clone()))
126                        }
127                    })
128                    .collect::<Result<Vec<Qubit>, _>>()?;
129
130                // Note, we don't use `Gate::new` here because the gate name is already validated,
131                // and we've performed all necessary validations on the gate parameters and qubits.
132                Ok(Gate {
133                    name: gate.name.clone(),
134                    parameters: gate_parameters,
135                    qubits: gate_qubits,
136                    modifiers: gate.modifiers.clone(),
137                })
138            })
139            .collect()
140    }
141}
142
143fn validate_defgate_as_sequence_elements(
144    gates: Vec<Gate>,
145    qubit_parameters: Vec<String>,
146) -> Result<(Vec<Gate>, Vec<String>), DefGateSequenceError> {
147    if qubit_parameters.is_empty() {
148        return Err(DefGateSequenceError::AtLeastOneQubitParameterRequired);
149    }
150    let qubit_parameter_set: HashSet<_> = qubit_parameters.iter().collect();
151
152    gates.iter().enumerate().try_for_each(|(i, gate)| {
153        gate.qubits.iter().enumerate().try_for_each(|(j, qubit)| {
154            if let Qubit::Variable(argument) = qubit {
155                if qubit_parameter_set.contains(argument) {
156                    Ok(())
157                } else {
158                    Err(DefGateSequenceError::UndefinedGateSequenceElementQubit {
159                        gate_index: i,
160                        qubit_argument_index: j,
161                        argument_name: argument.clone(),
162                    })
163                }
164            } else {
165                Err(DefGateSequenceError::InvalidGateSequenceElementQubit {
166                    gate_index: i,
167                    qubit_argument_index: j,
168                    qubit: qubit.clone(),
169                })
170            }
171        })
172    })?;
173    Ok((gates, qubit_parameters))
174}
175
176/// An error that can occur when initializing a sequence gate definition.
177#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone)]
178pub enum DefGateSequenceError {
179    #[error(
180        "\"{argument_name}\" is undefined at qubit argument {qubit_argument_index} of gate {gate_index}"
181    )]
182    UndefinedGateSequenceElementQubit {
183        gate_index: usize,
184        qubit_argument_index: usize,
185        argument_name: String,
186    },
187    #[error("DEFGATE AS SEQUENCE elements must be gates with qubit arguments, found {} at qubit argument {qubit_argument_index} of gate {gate_index}", qubit.to_quil_or_debug())]
188    InvalidGateSequenceElementQubit {
189        gate_index: usize,
190        qubit_argument_index: usize,
191        qubit: Qubit,
192    },
193    #[error("DEFGATE AS SEQUENCE must have at least one qubit parameter")]
194    AtLeastOneQubitParameterRequired,
195}