tket/
ops.rs

1use std::sync::{Arc, Weak};
2
3use crate::extension::bool::bool_type;
4use crate::extension::rotation::rotation_type;
5use crate::extension::sympy::SympyOpDef;
6use crate::extension::{TKET_EXTENSION, TKET_EXTENSION_ID as EXTENSION_ID};
7use hugr::ops::custom::ExtensionOp;
8use hugr::types::Type;
9use hugr::{
10    extension::{
11        prelude::{bool_t, option_type, qb_t},
12        simple_op::{try_from_name, MakeOpDef, MakeRegisteredOp},
13        ExtensionId, OpDef, SignatureFunc,
14    },
15    ops::OpType,
16    type_row,
17    types::Signature,
18};
19
20use derive_more::Display;
21use serde::{Deserialize, Serialize};
22use smol_str::ToSmolStr;
23use strum::{EnumIter, EnumString, IntoStaticStr};
24
25#[derive(
26    Clone,
27    Copy,
28    Debug,
29    Serialize,
30    Deserialize,
31    Hash,
32    PartialEq,
33    Eq,
34    PartialOrd,
35    Ord,
36    EnumIter,
37    IntoStaticStr,
38    EnumString,
39)]
40#[allow(missing_docs)]
41#[non_exhaustive]
42/// Simple enum of tket quantum operations.
43pub enum TketOp {
44    H,
45    CX,
46    CY,
47    CZ,
48    CRz,
49    T,
50    Tdg,
51    S,
52    Sdg,
53    X,
54    Y,
55    Z,
56    Rx,
57    Ry,
58    Rz,
59    Toffoli,
60    Measure,
61    MeasureFree,
62    QAlloc,
63    TryQAlloc,
64    QFree,
65    Reset,
66    V,
67    Vdg,
68}
69
70impl TketOp {
71    /// Expose the operation names directly in TketOp
72    pub fn exposed_name(&self) -> smol_str::SmolStr {
73        <TketOp as Into<OpType>>::into(*self).to_smolstr()
74    }
75
76    /// Wraps the operation in an [`ExtensionOp`]
77    pub fn into_extension_op(self) -> ExtensionOp {
78        <Self as MakeRegisteredOp>::to_extension_op(self)
79            .expect("Failed to convert to extension op.")
80    }
81}
82
83/// Whether an op is a given TketOp.
84pub fn op_matches(op: &OpType, tket_op: TketOp) -> bool {
85    op.to_string() == tket_op.exposed_name()
86}
87
88#[derive(
89    Clone, Copy, Debug, Serialize, Deserialize, EnumIter, Display, PartialEq, PartialOrd, EnumString,
90)]
91#[allow(missing_docs)]
92/// Simple enum representation of Pauli matrices.
93pub enum Pauli {
94    I,
95    X,
96    Y,
97    Z,
98}
99
100impl Pauli {
101    /// Check if this pauli commutes with another.
102    pub fn commutes_with(&self, other: Self) -> bool {
103        *self == Pauli::I || other == Pauli::I || *self == other
104    }
105}
106impl MakeOpDef for TketOp {
107    fn opdef_id(&self) -> hugr::ops::OpName {
108        <&'static str>::from(self).into()
109    }
110
111    fn init_signature(&self, _extension_ref: &std::sync::Weak<hugr::Extension>) -> SignatureFunc {
112        use TketOp::*;
113        match self {
114            H | T | S | V | X | Y | Z | Tdg | Sdg | Vdg | Reset => Signature::new_endo(qb_t()),
115            CX | CZ | CY => Signature::new_endo(vec![qb_t(); 2]),
116            Toffoli => Signature::new_endo(vec![qb_t(); 3]),
117            Measure => Signature::new(qb_t(), vec![qb_t(), bool_t()]),
118            MeasureFree => Signature::new(qb_t(), bool_type()),
119            Rz | Rx | Ry => Signature::new(vec![qb_t(), rotation_type()], qb_t()),
120            CRz => Signature::new(vec![qb_t(), qb_t(), rotation_type()], vec![qb_t(); 2]),
121            QAlloc => Signature::new(type_row![], qb_t()),
122            TryQAlloc => Signature::new(type_row![], Type::from(option_type(qb_t()))),
123            QFree => Signature::new(qb_t(), type_row![]),
124        }
125        .into()
126    }
127
128    fn extension(&self) -> ExtensionId {
129        EXTENSION_ID.to_owned()
130    }
131
132    fn post_opdef(&self, def: &mut OpDef) {
133        def.add_misc(
134            "commutation",
135            serde_json::to_value(self.qubit_commutation()).unwrap(),
136        );
137    }
138
139    fn from_def(op_def: &OpDef) -> Result<Self, hugr::extension::simple_op::OpLoadError> {
140        try_from_name(op_def.name(), op_def.extension_id())
141    }
142
143    fn extension_ref(&self) -> Weak<hugr::Extension> {
144        Arc::downgrade(&TKET_EXTENSION)
145    }
146}
147
148impl MakeRegisteredOp for TketOp {
149    fn extension_id(&self) -> ExtensionId {
150        EXTENSION_ID.to_owned()
151    }
152
153    fn extension_ref(&self) -> Weak<hugr::Extension> {
154        Arc::<hugr::Extension>::downgrade(&TKET_EXTENSION)
155    }
156}
157
158impl TketOp {
159    pub(crate) fn qubit_commutation(&self) -> Vec<(usize, Pauli)> {
160        use TketOp::*;
161
162        match self {
163            X | V | Vdg | Rx => vec![(0, Pauli::X)],
164            Y => vec![(0, Pauli::Y)],
165            T | Z | S | Tdg | Sdg | Rz | Measure => vec![(0, Pauli::Z)],
166            CX => vec![(0, Pauli::Z), (1, Pauli::X)],
167            CZ => vec![(0, Pauli::Z), (1, Pauli::Z)],
168            // by default, no commutation
169            _ => vec![],
170        }
171    }
172
173    /// Check if this op is a quantum op.
174    pub fn is_quantum(&self) -> bool {
175        use TketOp::*;
176        match self {
177            H | CX | T | S | V | X | Y | Z | Tdg | Sdg | Vdg | Rz | Rx | Toffoli | Ry | CZ | CY
178            | CRz => true,
179            Measure | MeasureFree | QAlloc | TryQAlloc | QFree | Reset => false,
180        }
181    }
182}
183
184/// Initialize a new custom symbolic expression constant op from a string.
185pub fn symbolic_constant_op(arg: String) -> OpType {
186    SympyOpDef.with_expr(arg).into()
187}
188
189#[cfg(test)]
190pub(crate) mod test {
191
192    use std::str::FromStr;
193    use std::sync::Arc;
194
195    use hugr::builder::{DFGBuilder, Dataflow, DataflowHugr};
196    use hugr::extension::prelude::{option_type, qb_t};
197    use hugr::extension::simple_op::{MakeExtensionOp, MakeOpDef};
198    use hugr::extension::{prelude::UnwrapBuilder as _, OpDef};
199    use hugr::types::Signature;
200    use hugr::{type_row, CircuitUnit, HugrView};
201    use itertools::Itertools;
202    use rstest::{fixture, rstest};
203    use strum::IntoEnumIterator;
204
205    use super::TketOp;
206    use crate::circuit::Circuit;
207    use crate::extension::bool::bool_type;
208    use crate::extension::{TKET_EXTENSION as EXTENSION, TKET_EXTENSION_ID as EXTENSION_ID};
209    use crate::utils::build_simple_circuit;
210    use crate::Pauli;
211    fn get_opdef(op: TketOp) -> Option<&'static Arc<OpDef>> {
212        EXTENSION.get_op(&op.op_id())
213    }
214    #[test]
215    fn create_extension() {
216        assert_eq!(EXTENSION.name(), &EXTENSION_ID);
217
218        for o in TketOp::iter() {
219            assert_eq!(TketOp::from_def(get_opdef(o).unwrap()), Ok(o));
220        }
221    }
222
223    #[fixture]
224    pub(crate) fn t2_bell_circuit() -> Circuit {
225        let h = build_simple_circuit(2, |circ| {
226            circ.append(TketOp::H, [0])?;
227            circ.append(TketOp::CX, [0, 1])?;
228            Ok(())
229        });
230
231        h.unwrap()
232    }
233
234    #[rstest]
235    fn check_t2_bell(t2_bell_circuit: Circuit) {
236        assert_eq!(t2_bell_circuit.commands().count(), 2);
237    }
238
239    #[test]
240    fn ancilla_circ() {
241        let h = build_simple_circuit(1, |circ| {
242            let empty: [CircuitUnit; 0] = []; // requires type annotation
243            let ancilla = circ.append_with_outputs(TketOp::QAlloc, empty)?[0];
244            let ancilla = circ.append_with_outputs(TketOp::Reset, [ancilla])?[0];
245
246            let ancilla = circ.append_with_outputs(
247                TketOp::CX,
248                [CircuitUnit::Linear(0), CircuitUnit::Wire(ancilla)],
249            )?[0];
250            let ancilla = circ.append_with_outputs(TketOp::Measure, [ancilla])?[0];
251            circ.append_and_consume(TketOp::QFree, [ancilla])?;
252
253            Ok(())
254        })
255        .unwrap();
256
257        // 5 commands: alloc, reset, cx, measure, free
258        assert_eq!(h.commands().count(), 5);
259    }
260
261    #[test]
262    fn try_qalloc_measure_free() {
263        let mut b = DFGBuilder::new(Signature::new(type_row![], bool_type())).unwrap();
264
265        let try_q = b
266            .add_dataflow_op(TketOp::TryQAlloc, [])
267            .unwrap()
268            .out_wire(0);
269        let [q] = b.build_unwrap_sum(1, option_type(qb_t()), try_q).unwrap();
270        let measured = b
271            .add_dataflow_op(TketOp::MeasureFree, [q])
272            .unwrap()
273            .out_wire(0);
274        let h = b.finish_hugr_with_outputs([measured]).unwrap();
275
276        let top_ops = h
277            .children(h.entrypoint())
278            .map(|n| h.get_optype(n))
279            .collect_vec();
280
281        assert_eq!(top_ops.len(), 5);
282        // first two are I/O
283        assert_eq!(
284            TketOp::from_op(top_ops[2].as_extension_op().unwrap()).unwrap(),
285            TketOp::TryQAlloc
286        );
287        assert!(top_ops[3].is_conditional());
288        assert_eq!(
289            TketOp::from_op(top_ops[4].as_extension_op().unwrap()).unwrap(),
290            TketOp::MeasureFree
291        );
292    }
293    #[test]
294    fn tket_op_properties() {
295        for op in TketOp::iter() {
296            // The exposed name should start with "tket.quantum."
297            assert!(op.exposed_name().starts_with(&EXTENSION_ID.to_string()));
298
299            let ext_op = op.into_extension_op();
300            assert_eq!(ext_op.args(), &[]);
301            assert_eq!(ext_op.def().extension_id(), &EXTENSION_ID);
302            let name = ext_op.def().name();
303            assert_eq!(TketOp::from_str(name), Ok(op));
304        }
305
306        // Other calls
307        assert!(TketOp::H.is_quantum());
308        assert!(!TketOp::Measure.is_quantum());
309
310        for (op, pauli) in [
311            (TketOp::X, Pauli::X),
312            (TketOp::Y, Pauli::Y),
313            (TketOp::Z, Pauli::Z),
314        ]
315        .iter()
316        {
317            assert_eq!(op.qubit_commutation(), &[(0, *pauli)]);
318        }
319    }
320}