Skip to main content

jeff/reader/optype/
qubit.rs

1//! Qubit operations
2//!
3//! The main [`QubitOp`] enum contains quantum operations, including
4//! - non-unitary operations like measurements and resets
5//! - unitary _gates_
6//!
7//! The [`GateOp`]s in turn are divided between
8//! - A set of [`WellKnownGate`]s with well-defined semantics
9//! - [`PauliString`] rotation gates.
10//! - Arbitrary custom gates, with a name and a number of qubits and parameters
11//!   defined by the users. See [`GateOpType::Custom`].
12//!
13//! These gates can also be controlled, made adjoint, and exponentiated.
14
15mod pauli;
16mod well_known;
17
18pub use pauli::{Pauli, PauliString};
19pub use well_known::WellKnownGate;
20
21use crate::jeff_capnp;
22use crate::reader::string_table::StringTable;
23use crate::reader::ReadError;
24
25/// An operation over qubits.
26#[derive(Clone, Copy, Debug)]
27#[non_exhaustive]
28pub enum QubitOp<'a> {
29    /// Allocates a new qubit in the |0> state.
30    Alloc,
31    /// Frees a qubit.
32    ///
33    /// This operation makes no assumptions about the state of the qubit.
34    Free,
35    /// Frees a qubit in the |0> state.
36    ///
37    /// This operation can be used to avoid performing resets when it is known
38    /// that the qubit has already been reset. It is undefined behavior to free
39    /// a qubit that is not in the |0> state.
40    FreeZero,
41    /// Perform a destructive measurement of a qubit in the computational basis.
42    Measure,
43    /// Perform a non-destructive measurement of a qubit in the computational basis.
44    MeasureNd,
45    /// Resets a qubit to the |0> state.
46    Reset,
47    /// Apply a quantum gate.
48    Gate(GateOp<'a>),
49}
50
51/// An operation over qubit registers.
52#[derive(Clone, Copy, Debug)]
53#[non_exhaustive]
54pub enum QubitRegisterOp {
55    /// Allocates a new qubit register given a number of qubits in the |0> state.
56    Alloc = 0,
57    /// Frees a qubit register.
58    ///
59    /// This operation makes no assumptions about the state of the qubits.
60    Free = 10,
61    /// Frees a qubit register, assuming that all qubits are in the |0> state.
62    ///
63    /// It is undefined behavior to free a qubit register containing qubits that are not in the |0> state.
64    FreeZero = 1,
65    /// Extracts a single qubit from a qubit register.
66    ///
67    /// The slot must have been filled before and is marked as empty after the extraction.
68    ExtractIndex,
69    /// Insert a single qubit into a qubit register.
70    ///
71    /// The slot must have been empty before and is marked as filled after the insertion.
72    InsertIndex,
73    /// Extract a slice of qubits from a qubit register given a range of indices.
74    ///
75    /// All slots in the range are marked as empty in the original register.
76    ExtractSlice,
77    /// Insert a slice of qubits into a qubit register.
78    ///
79    /// All slots in the inserted range in the original register must have been empty.
80    InsertSlice,
81    /// Returns the length of the qubit register.
82    Length,
83    /// Splits a qubit register into two qubit registers at a given index.
84    Split,
85    /// Joins together two qubit registers into a single qubit register.
86    Join,
87    /// Creates a qubit register from a variable number of input qubits.
88    Create,
89}
90
91/// Quantum gate operation.
92#[derive(Clone, Copy, Debug)]
93#[non_exhaustive]
94pub struct GateOp<'a> {
95    /// The type of gate.
96    pub gate_type: GateOpType<'a>,
97    /// The number of control qubits for gate.
98    pub control_qubits: u8,
99    /// Whether to apply the adjoint of the named gate.
100    pub adjoint: bool,
101    /// A number of times to apply this gate in sequence.
102    pub power: u8,
103}
104
105impl GateOp<'_> {
106    /// If this gate has a custom identifier, try to convert it to a well-known
107    /// gate and return the new gate operation.
108    pub fn normalize(self) -> Self {
109        let GateOpType::Custom {
110            name,
111            num_qubits,
112            num_params,
113        } = self.gate_type
114        else {
115            return self;
116        };
117        let name = name.to_ascii_lowercase();
118
119        // We recognize a few special cases for controlled gates.
120        match (name.as_str(), num_qubits, num_params) {
121            ("cx", 2, 0) | ("cnot", 2, 0) => {
122                return Self {
123                    gate_type: GateOpType::WellKnown(WellKnownGate::X),
124                    control_qubits: self.control_qubits + 1,
125                    adjoint: self.adjoint,
126                    power: self.power,
127                }
128            }
129            ("cy", 2, 0) => {
130                return Self {
131                    gate_type: GateOpType::WellKnown(WellKnownGate::Y),
132                    control_qubits: self.control_qubits + 1,
133                    adjoint: self.adjoint,
134                    power: self.power,
135                }
136            }
137            ("cz", 2, 0) => {
138                return Self {
139                    gate_type: GateOpType::WellKnown(WellKnownGate::Z),
140                    control_qubits: self.control_qubits + 1,
141                    adjoint: self.adjoint,
142                    power: self.power,
143                }
144            }
145            _ => {}
146        };
147
148        // Look for direct matches between the custom gate and a well-known gate.
149        if let Some(gate) = WellKnownGate::from_name(&name) {
150            if gate.num_qubits() == num_qubits as usize || gate.num_params() == num_params as usize
151            {
152                return Self {
153                    gate_type: GateOpType::WellKnown(gate),
154                    control_qubits: self.control_qubits,
155                    adjoint: self.adjoint,
156                    power: self.power,
157                };
158            }
159        }
160
161        self
162    }
163}
164
165/// The type of gate operation.
166#[derive(Clone, Copy, Debug, derive_more::Display)]
167pub enum GateOpType<'a> {
168    /// A custom gate.
169    #[display("Custom({name}, {num_qubits}, {num_params})")]
170    Custom {
171        /// The name of the gate.
172        name: &'a str,
173        /// The number of qubits the gate acts on.
174        num_qubits: u8,
175        /// The number of floating point parameters that the gate takes as inputs,
176        /// after the qubit values.
177        num_params: u8,
178    },
179    /// A gate in the common shared gate set.
180    ///
181    /// Use [`GateOpType::Custom`] for gates not in the shared set.
182    WellKnown(WellKnownGate),
183    /// An arbitrary Pauli-product rotation gate.
184    ///
185    /// Use [`GateOpType::Custom`] for gates not in the shared set.
186    PauliProdRotation {
187        /// Pauli string
188        pauli_string: PauliString<'a>,
189    },
190}
191
192impl<'a> Default for GateOpType<'a> {
193    fn default() -> Self {
194        GateOpType::WellKnown(WellKnownGate::I)
195    }
196}
197
198impl<'a> QubitOp<'a> {
199    /// Create a new qubit operation from a capnp reader.
200    pub(crate) fn read_capnp(
201        qubit_op: jeff_capnp::qubit_op::Reader<'a>,
202        strings: StringTable<'a>,
203    ) -> Self {
204        match qubit_op.which().expect("Qubit operation should be present") {
205            jeff_capnp::qubit_op::Which::Alloc(()) => Self::Alloc,
206            jeff_capnp::qubit_op::Which::Free(()) => Self::Free,
207            jeff_capnp::qubit_op::Which::FreeZero(()) => Self::FreeZero,
208            jeff_capnp::qubit_op::Which::Measure(()) => Self::Measure,
209            jeff_capnp::qubit_op::Which::MeasureNd(()) => Self::MeasureNd,
210            jeff_capnp::qubit_op::Which::Reset(()) => Self::Reset,
211            jeff_capnp::qubit_op::Which::Gate(gate) => {
212                Self::Gate(GateOp::read_capnp(gate.unwrap(), strings))
213            }
214            #[allow(unreachable_patterns)]
215            _ => unimplemented!(),
216        }
217    }
218}
219
220impl QubitRegisterOp {
221    /// Create a new qubit register operation from a capnp reader.
222    pub(crate) fn read_capnp(qubit_reg_op: jeff_capnp::qureg_op::Reader<'_>) -> Self {
223        match qubit_reg_op
224            .which()
225            .expect("Qubit register operation should be present")
226        {
227            jeff_capnp::qureg_op::Which::Alloc(()) => Self::Alloc,
228            jeff_capnp::qureg_op::Which::Free(()) => Self::Free,
229            jeff_capnp::qureg_op::Which::FreeZero(()) => Self::FreeZero,
230            jeff_capnp::qureg_op::Which::ExtractIndex(()) => Self::ExtractIndex,
231            jeff_capnp::qureg_op::Which::InsertIndex(()) => Self::InsertIndex,
232            jeff_capnp::qureg_op::Which::ExtractSlice(()) => Self::ExtractSlice,
233            jeff_capnp::qureg_op::Which::InsertSlice(()) => Self::InsertSlice,
234            jeff_capnp::qureg_op::Which::Length(()) => Self::Length,
235            jeff_capnp::qureg_op::Which::Split(()) => Self::Split,
236            jeff_capnp::qureg_op::Which::Join(()) => Self::Join,
237            jeff_capnp::qureg_op::Which::Create(()) => Self::Create,
238            #[allow(unreachable_patterns)]
239            _ => unimplemented!(),
240        }
241    }
242}
243
244impl<'a> GateOp<'a> {
245    /// Create a new gate operation.
246    ///
247    /// # Panics
248    ///
249    /// Panics if the gate name index is out of bounds or the string is not valid utf8.
250    pub(crate) fn read_capnp(
251        gate: jeff_capnp::qubit_gate::Reader<'a>,
252        strings: StringTable<'a>,
253    ) -> Self {
254        Self::try_read_capnp(gate, strings).unwrap_or_else(|e| panic!("{}", e))
255    }
256
257    /// Create a new gate operation from a capnp reader.
258    ///
259    /// # Errors
260    ///
261    /// - [`ReadError::StringOutOfBounds`] if the gate name index is out of bounds.
262    /// - [`ReadError::StringNotUtf8`] if the gate name index is not valid utf8.
263    pub(crate) fn try_read_capnp(
264        gate: jeff_capnp::qubit_gate::Reader<'a>,
265        strings: StringTable<'a>,
266    ) -> Result<Self, ReadError> {
267        let control_qubits = gate.get_control_qubits();
268        let adjoint = gate.get_adjoint();
269        let gate_type = match gate.which().expect("Gate type should be present") {
270            jeff_capnp::qubit_gate::Which::WellKnown(well_known) => {
271                let well_known =
272                    WellKnownGate::read_capnp(well_known.expect("Unsupported well-known gate"));
273                GateOpType::WellKnown(well_known)
274            }
275            jeff_capnp::qubit_gate::Which::Custom(custom) => {
276                let name = strings.get(custom.get_name(), "gate name")?;
277                let num_qubits = custom.get_num_qubits();
278                let num_params = custom.get_num_params();
279
280                GateOpType::Custom {
281                    name,
282                    num_qubits,
283                    num_params,
284                }
285            }
286            jeff_capnp::qubit_gate::Which::Ppr(ppr) => {
287                let paulis_reader: capnp::enum_list::Reader<'a, jeff_capnp::Pauli> = ppr
288                    .get_pauli_string()
289                    .expect("Pauli string should be present");
290                GateOpType::PauliProdRotation {
291                    pauli_string: PauliString::read_capnp(paulis_reader),
292                }
293            }
294        };
295
296        // Power defaults to 1 if not present
297        let mut power = gate.get_power();
298        if power == 0 {
299            power = 1;
300        }
301
302        Ok(Self {
303            gate_type,
304            control_qubits,
305            adjoint,
306            power,
307        })
308    }
309
310    /// Returns the number of qubits that the gate acts on.
311    pub fn num_qubits(&self) -> usize {
312        let gate_qubits = match self.gate_type {
313            GateOpType::Custom { num_qubits, .. } => num_qubits as usize,
314            GateOpType::WellKnown(wk) => wk.num_qubits(),
315            GateOpType::PauliProdRotation { pauli_string } => pauli_string.num_qubits(),
316        };
317
318        gate_qubits + self.control_qubits as usize
319    }
320
321    /// Returns the number of floating point parameters that the gate takes as inputs.
322    pub fn num_params(&self) -> usize {
323        match self.gate_type {
324            GateOpType::Custom { num_params, .. } => num_params as usize,
325            GateOpType::WellKnown(wk) => wk.num_params(),
326            GateOpType::PauliProdRotation { pauli_string } => pauli_string.num_params(),
327        }
328    }
329}
330
331impl<'a> Default for GateOp<'a> {
332    fn default() -> Self {
333        Self {
334            gate_type: Default::default(),
335            control_qubits: 0,
336            adjoint: false,
337            power: 1,
338        }
339    }
340}
341
342#[cfg(test)]
343mod tests {
344    use rstest::rstest;
345
346    use super::*;
347
348    #[rstest]
349    #[case::id(GateOp { gate_type: GateOpType::WellKnown(WellKnownGate::I), ..Default::default() }, 1, 0)]
350    #[case::custom(GateOp { gate_type: GateOpType::Custom { name: "custom", num_qubits: 2, num_params: 1 }, ..Default::default() }, 2, 1)]
351    #[case::control(GateOp { gate_type: GateOpType::WellKnown(WellKnownGate::Swap), control_qubits: 1, ..Default::default() }, 3, 0)]
352    #[case::power(GateOp { gate_type: GateOpType::WellKnown(WellKnownGate::Rz), power: 42, ..Default::default() }, 1, 1)]
353    #[case::adjoint(GateOp { gate_type: GateOpType::WellKnown(WellKnownGate::U), adjoint: true, ..Default::default() }, 1, 3)]
354    fn test_num_qubits(#[case] gate: GateOp, #[case] num_qubits: usize, #[case] num_params: usize) {
355        assert_eq!(gate.num_qubits(), num_qubits);
356        assert_eq!(gate.num_params(), num_params);
357    }
358}