quil_rs/instruction/
calibration.rs

1#[cfg(not(feature = "python"))]
2use optipy::strip_pyo3;
3#[cfg(feature = "stubs")]
4use pyo3_stub_gen::derive::gen_stub_pyclass;
5
6use crate::{
7    instruction::{
8        write_expression_parameter_string, write_instruction_block, Expression, GateModifier,
9        Instruction, Qubit,
10    },
11    pickleable_new,
12    quil::{Quil, INDENT},
13    validation::identifier::{validate_identifier, IdentifierValidationError},
14};
15
16use super::{write_qubit_parameters, Gate};
17
18pub trait CalibrationSignature {
19    type Signature<'a>
20    where
21        Self: 'a;
22
23    fn signature(&self) -> Self::Signature<'_>;
24    fn has_signature(&self, signature: &Self::Signature<'_>) -> bool;
25}
26
27#[derive(Clone, Debug, PartialEq)]
28#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
29#[cfg_attr(
30    feature = "python",
31    pyo3::pyclass(module = "quil.instructions", eq, get_all, set_all, subclass)
32)]
33#[cfg_attr(not(feature = "python"), strip_pyo3)]
34pub struct CalibrationDefinition {
35    #[pyo3(name = "identifier")]
36    pub identifier: CalibrationIdentifier,
37    pub instructions: Vec<Instruction>,
38}
39
40pickleable_new! {
41    impl CalibrationDefinition {
42        /// Builds a new calibration definition.
43        pub fn new(
44            identifier: CalibrationIdentifier,
45            instructions: Vec<Instruction>,
46        );
47    }
48}
49
50impl CalibrationSignature for CalibrationDefinition {
51    type Signature<'a> = <CalibrationIdentifier as CalibrationSignature>::Signature<'a>;
52
53    fn signature(&self) -> Self::Signature<'_> {
54        self.identifier.signature()
55    }
56
57    fn has_signature(&self, signature: &Self::Signature<'_>) -> bool {
58        self.identifier.has_signature(signature)
59    }
60}
61
62impl Quil for CalibrationDefinition {
63    fn write(
64        &self,
65        f: &mut impl std::fmt::Write,
66        fall_back_to_debug: bool,
67    ) -> crate::quil::ToQuilResult<()> {
68        self.identifier.write(f, fall_back_to_debug)?;
69        write!(f, ":")?;
70        for instruction in &self.instructions {
71            write!(f, "\n{INDENT}")?;
72            instruction.write(f, fall_back_to_debug)?;
73        }
74        Ok(())
75    }
76}
77
78/// Unique identifier for a calibration definition within a program
79#[derive(Clone, Debug, PartialEq)]
80#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
81#[cfg_attr(
82    feature = "python",
83    pyo3::pyclass(module = "quil.instructions", eq, get_all, set_all, subclass)
84)]
85pub struct CalibrationIdentifier {
86    /// The modifiers applied to the gate
87    pub modifiers: Vec<GateModifier>,
88
89    /// The name of the gate
90    pub name: String,
91
92    /// The parameters of the gate - these are the variables in the calibration definition
93    pub parameters: Vec<Expression>,
94
95    /// The qubits on which the gate is applied
96    pub qubits: Vec<Qubit>,
97}
98
99impl CalibrationIdentifier {
100    /// Builds a new calibration identifier.
101    ///
102    /// # Errors
103    ///
104    /// Returns an error if the given name isn't a valid Quil identifier.
105    pub fn new(
106        name: String,
107        modifiers: Vec<GateModifier>,
108        parameters: Vec<Expression>,
109        qubits: Vec<Qubit>,
110    ) -> Result<Self, IdentifierValidationError> {
111        validate_identifier(name.as_str())?;
112        Ok(Self {
113            modifiers,
114            name,
115            parameters,
116            qubits,
117        })
118    }
119}
120
121impl CalibrationIdentifier {
122    pub fn matches(&self, gate: &Gate) -> bool {
123        // Filter out non-matching calibrations: check rules 1-4
124        if self.name != gate.name
125            || self.modifiers != gate.modifiers
126            || self.parameters.len() != gate.parameters.len()
127            || self.qubits.len() != gate.qubits.len()
128        {
129            return false;
130        }
131
132        let fixed_qubits_match = self
133            .qubits
134            .iter()
135            .enumerate()
136            .all(|(calibration_index, _)| {
137                match (
138                    &self.qubits[calibration_index],
139                    &gate.qubits[calibration_index],
140                ) {
141                    // Placeholders never match
142                    (Qubit::Placeholder(_), _) | (_, Qubit::Placeholder(_)) => false,
143                    // If they're both fixed, test if they're fixed to the same qubit
144                    (Qubit::Fixed(calibration_fixed_qubit), Qubit::Fixed(gate_fixed_qubit)) => {
145                        calibration_fixed_qubit == gate_fixed_qubit
146                    }
147                    // If the calibration is variable, it matches any fixed qubit
148                    (Qubit::Variable(_), _) => true,
149                    // If the calibration is fixed, but the gate's qubit is variable, it's not a match
150                    (Qubit::Fixed(_), _) => false,
151                }
152            });
153        if !fixed_qubits_match {
154            return false;
155        }
156
157        let fixed_parameters_match =
158            self.parameters
159                .iter()
160                .enumerate()
161                .all(|(calibration_index, _)| {
162                    let calibration_parameters =
163                        self.parameters[calibration_index].clone().into_simplified();
164                    let gate_parameters =
165                        gate.parameters[calibration_index].clone().into_simplified();
166                    match (calibration_parameters, gate_parameters) {
167                        // If the calibration is variable, it matches any fixed qubit
168                        (Expression::Variable(_), _) => true,
169                        // If the calibration is fixed, but the gate's qubit is variable, it's not a match
170                        (calib, gate) => calib == gate,
171                    }
172                });
173        fixed_parameters_match
174    }
175}
176
177impl CalibrationSignature for CalibrationIdentifier {
178    type Signature<'a> = (&'a [GateModifier], &'a str, &'a [Expression], &'a [Qubit]);
179
180    fn signature(&self) -> Self::Signature<'_> {
181        let Self {
182            modifiers,
183            name,
184            parameters,
185            qubits,
186        } = self;
187        (
188            modifiers.as_slice(),
189            name.as_str(),
190            parameters.as_slice(),
191            qubits.as_slice(),
192        )
193    }
194
195    fn has_signature(&self, signature: &Self::Signature<'_>) -> bool {
196        &self.signature() == signature
197    }
198}
199
200impl Quil for CalibrationIdentifier {
201    fn write(
202        &self,
203        f: &mut impl std::fmt::Write,
204        fall_back_to_debug: bool,
205    ) -> crate::quil::ToQuilResult<()> {
206        write!(f, "DEFCAL {}", self.name)?;
207        write_expression_parameter_string(f, fall_back_to_debug, &self.parameters)?;
208        write_qubit_parameters(f, fall_back_to_debug, &self.qubits)?;
209        Ok(())
210    }
211}
212
213#[derive(Clone, Debug, PartialEq)]
214#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
215#[cfg_attr(
216    feature = "python",
217    pyo3::pyclass(module = "quil.instructions", eq, get_all, set_all, subclass)
218)]
219pub struct MeasureCalibrationDefinition {
220    pub identifier: MeasureCalibrationIdentifier,
221    pub instructions: Vec<Instruction>,
222}
223
224pickleable_new! {
225    impl MeasureCalibrationDefinition {
226        pub fn new(identifier: MeasureCalibrationIdentifier, instructions: Vec<Instruction>);
227    }
228}
229
230impl CalibrationSignature for MeasureCalibrationDefinition {
231    type Signature<'a> = <MeasureCalibrationIdentifier as CalibrationSignature>::Signature<'a>;
232
233    fn signature(&self) -> Self::Signature<'_> {
234        self.identifier.signature()
235    }
236
237    fn has_signature(&self, signature: &Self::Signature<'_>) -> bool {
238        self.identifier.has_signature(signature)
239    }
240}
241
242impl Quil for MeasureCalibrationDefinition {
243    fn write(
244        &self,
245        f: &mut impl std::fmt::Write,
246        fall_back_to_debug: bool,
247    ) -> crate::quil::ToQuilResult<()> {
248        self.identifier.write(f, fall_back_to_debug)?;
249        writeln!(f, ":")?;
250
251        write_instruction_block(f, fall_back_to_debug, &self.instructions)?;
252        writeln!(f)?;
253        Ok(())
254    }
255}
256
257/// A unique identifier for a measurement calibration definition within a program
258#[derive(Clone, Debug, PartialEq)]
259#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
260#[cfg_attr(
261    feature = "python",
262    pyo3::pyclass(module = "quil.instructions", eq, get_all, set_all, subclass)
263)]
264pub struct MeasureCalibrationIdentifier {
265    /// The Quil-T name of the measurement, if any.
266    pub name: Option<String>,
267
268    /// The qubit which is being measured.
269    pub qubit: Qubit,
270
271    /// The name the definition uses for the variable it will write the measurement result to, if
272    /// this is a measurement for record.
273    ///
274    /// If this is missing, this is a calibration for a measurement for effect.
275    pub target: Option<String>,
276}
277
278impl MeasureCalibrationIdentifier {
279    pub const fn new(name: Option<String>, qubit: Qubit, target: Option<String>) -> Self {
280        Self {
281            name,
282            qubit,
283            target,
284        }
285    }
286}
287
288impl CalibrationSignature for MeasureCalibrationIdentifier {
289    type Signature<'a> = (Option<&'a str>, &'a Qubit, Option<&'a str>);
290
291    fn signature(&self) -> Self::Signature<'_> {
292        let Self {
293            name,
294            qubit,
295            target,
296        } = self;
297
298        (name.as_deref(), qubit, target.as_deref())
299    }
300
301    fn has_signature(&self, signature: &Self::Signature<'_>) -> bool {
302        &self.signature() == signature
303    }
304}
305
306impl Quil for MeasureCalibrationIdentifier {
307    fn write(
308        &self,
309        f: &mut impl std::fmt::Write,
310        fall_back_to_debug: bool,
311    ) -> crate::quil::ToQuilResult<()> {
312        let Self {
313            name,
314            qubit,
315            target,
316        } = self;
317
318        write!(f, "DEFCAL MEASURE")?;
319        if let Some(name) = name {
320            write!(f, "!{name}")?;
321        }
322        write!(f, " ")?;
323        qubit.write(f, fall_back_to_debug)?;
324        if let Some(target) = target {
325            write!(f, " {target}")?;
326        }
327
328        Ok(())
329    }
330}
331
332#[cfg(test)]
333mod test_measure_calibration_definition {
334    use super::MeasureCalibrationDefinition;
335    use crate::expression::Expression;
336    use crate::instruction::calibration::MeasureCalibrationIdentifier;
337    use crate::instruction::{Gate, Instruction, Qubit};
338    use crate::quil::Quil;
339    use insta::assert_snapshot;
340    use rstest::rstest;
341
342    #[rstest]
343    #[case(
344        "With Fixed Qubit",
345        MeasureCalibrationDefinition {
346            identifier: MeasureCalibrationIdentifier {
347                name: None,
348                qubit: Qubit::Fixed(0),
349                target: Some("theta".to_string()),
350            },
351            instructions: vec![Instruction::Gate(Gate {
352                name: "X".to_string(),
353                parameters: vec![Expression::Variable("theta".to_string())],
354                qubits: vec![Qubit::Fixed(0)],
355                modifiers: vec![],
356
357            })]},
358    )]
359    #[case(
360        "Named With Fixed Qubit",
361        MeasureCalibrationDefinition {
362            identifier: MeasureCalibrationIdentifier {
363                name: Some("midcircuit".to_string()),
364                qubit: Qubit::Fixed(0),
365                target: Some("theta".to_string()),
366            },
367            instructions: vec![Instruction::Gate(Gate {
368                name: "X".to_string(),
369                parameters: vec![Expression::Variable("theta".to_string())],
370                qubits: vec![Qubit::Fixed(0)],
371                modifiers: vec![],
372
373            })]},
374    )]
375    #[case(
376        "Effect With Fixed Qubit",
377        MeasureCalibrationDefinition {
378            identifier: MeasureCalibrationIdentifier {
379                name: None,
380                qubit: Qubit::Fixed(0),
381                target: None,
382            },
383            instructions: vec![Instruction::Gate(Gate {
384                name: "X".to_string(),
385                parameters: vec![Expression::PiConstant()],
386                qubits: vec![Qubit::Fixed(0)],
387                modifiers: vec![],
388
389            })]},
390    )]
391    #[case(
392        "Named Effect With Fixed Qubit",
393        MeasureCalibrationDefinition {
394            identifier: MeasureCalibrationIdentifier {
395                name: Some("midcircuit".to_string()),
396                qubit: Qubit::Fixed(0),
397                target: None,
398            },
399            instructions: vec![Instruction::Gate(Gate {
400                name: "X".to_string(),
401                parameters: vec![Expression::PiConstant()],
402                qubits: vec![Qubit::Fixed(0)],
403                modifiers: vec![],
404
405            })]},
406    )]
407    #[case(
408        "With Variable Qubit",
409        MeasureCalibrationDefinition {
410            identifier: MeasureCalibrationIdentifier {
411                name: None,
412                qubit: Qubit::Variable("q".to_string()),
413                target: Some("theta".to_string()),
414            },
415            instructions: vec![Instruction::Gate(Gate {
416                name: "X".to_string(),
417                parameters: vec![Expression::Variable("theta".to_string())],
418                qubits: vec![Qubit::Variable("q".to_string())],
419                modifiers: vec![],
420            })]},
421    )]
422    #[case(
423        "Named With Variable Qubit",
424        MeasureCalibrationDefinition {
425            identifier: MeasureCalibrationIdentifier {
426                name: Some("midcircuit".to_string()),
427                qubit: Qubit::Variable("q".to_string()),
428                target: Some("theta".to_string()),
429            },
430            instructions: vec![Instruction::Gate(Gate {
431                name: "X".to_string(),
432                parameters: vec![Expression::Variable("theta".to_string())],
433                qubits: vec![Qubit::Variable("q".to_string())],
434                modifiers: vec![],
435            })]},
436    )]
437    #[case(
438        "Effect Variable Qubit",
439        MeasureCalibrationDefinition {
440            identifier: MeasureCalibrationIdentifier {
441                name: None,
442                qubit: Qubit::Variable("q".to_string()),
443                target: None,
444            },
445            instructions: vec![Instruction::Gate(Gate {
446                name: "X".to_string(),
447                parameters: vec![Expression::PiConstant()],
448                qubits: vec![Qubit::Variable("q".to_string())],
449                modifiers: vec![],
450            })]},
451    )]
452    #[case(
453        "Named Effect Variable Qubit",
454        MeasureCalibrationDefinition {
455            identifier: MeasureCalibrationIdentifier {
456                name: Some("midcircuit".to_string()),
457                qubit: Qubit::Variable("q".to_string()),
458                target: None,
459            },
460            instructions: vec![Instruction::Gate(Gate {
461                name: "X".to_string(),
462                parameters: vec![Expression::PiConstant()],
463                qubits: vec![Qubit::Variable("q".to_string())],
464                modifiers: vec![],
465            })]},
466    )]
467    fn test_display(
468        #[case] description: &str,
469        #[case] measure_cal_def: MeasureCalibrationDefinition,
470    ) {
471        insta::with_settings!({
472            snapshot_suffix => description,
473        }, {
474            assert_snapshot!(measure_cal_def.to_quil_or_debug())
475        })
476    }
477}