quil_rs/program/
calibration.rs

1// Copyright 2021 Rigetti Computing
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::HashMap;
16use std::iter::FusedIterator;
17use std::ops::Range;
18
19use itertools::{Either, Itertools as _};
20#[cfg(feature = "stubs")]
21use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pyclass_complex_enum, gen_stub_pymethods};
22
23use crate::instruction::{CalibrationIdentifier, MeasureCalibrationIdentifier};
24use crate::quil::Quil;
25use crate::{
26    expression::Expression,
27    instruction::{
28        CalibrationDefinition, Capture, Delay, Fence, FrameIdentifier, Gate, Instruction,
29        MeasureCalibrationDefinition, Measurement, Pulse, Qubit, RawCapture, SetFrequency,
30        SetPhase, SetScale, ShiftFrequency, ShiftPhase,
31    },
32};
33
34use super::source_map::{SourceMap, SourceMapEntry, SourceMapIndexable};
35use super::{CalibrationSet, InstructionIndex, ProgramError};
36
37#[cfg(not(feature = "python"))]
38use optipy::strip_pyo3;
39
40/// A collection of Quil calibrations (`DEFCAL` instructions) with utility methods.
41///
42/// This exposes the semantics similar to [`CalibrationSet`] to Python users,
43/// so see the documentation there for more information.
44#[derive(Clone, Debug, Default, PartialEq)]
45#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
46#[cfg_attr(
47    feature = "python",
48    pyo3::pyclass(name = "CalibrationSet", module = "quil.program", eq, subclass)
49)]
50pub struct Calibrations {
51    pub calibrations: CalibrationSet<CalibrationDefinition>,
52    pub measure_calibrations: CalibrationSet<MeasureCalibrationDefinition>,
53}
54
55#[cfg_attr(feature = "stubs", gen_stub_pymethods)]
56#[cfg_attr(feature = "python", pyo3::pymethods)]
57#[cfg_attr(not(feature = "python"), strip_pyo3)]
58impl Calibrations {
59    /// Return the count of contained calibrations.
60    #[pyo3(name = "__len__")]
61    pub fn len(&self) -> usize {
62        self.calibrations.len()
63    }
64
65    /// Return true if this contains no data.
66    pub fn is_empty(&self) -> bool {
67        self.calibrations.is_empty()
68    }
69
70    /// Insert a [`CalibrationDefinition`] into the set.
71    ///
72    /// If a calibration with the same [signature][crate::instruction::CalibrationSignature] already
73    /// exists in the set, it will be replaced and the old calibration will be returned.
74    pub fn insert_calibration(
75        &mut self,
76        calibration: CalibrationDefinition,
77    ) -> Option<CalibrationDefinition> {
78        self.calibrations.replace(calibration)
79    }
80
81    /// Insert a [`MeasureCalibrationDefinition`] into the set.
82    ///
83    /// If a calibration with the same [signature][crate::instruction::CalibrationSignature] already
84    /// exists in the set, it will be replaced and the old calibration will be returned.
85    pub fn insert_measurement_calibration(
86        &mut self,
87        calibration: MeasureCalibrationDefinition,
88    ) -> Option<MeasureCalibrationDefinition> {
89        self.measure_calibrations.replace(calibration)
90    }
91
92    /// Append another [`CalibrationSet`] onto this one.
93    ///
94    /// Calibrations with conflicting [`CalibrationSignature`]s are overwritten by the ones in the
95    /// given set.
96    pub fn extend(&mut self, other: Calibrations) {
97        self.calibrations.extend(other.calibrations);
98        self.measure_calibrations.extend(other.measure_calibrations);
99    }
100
101    /// Return the Quil instructions which describe the contained calibrations.
102    pub fn to_instructions(&self) -> Vec<Instruction> {
103        self.iter_calibrations()
104            .cloned()
105            .map(Instruction::CalibrationDefinition)
106            .chain(
107                self.iter_measure_calibrations()
108                    .cloned()
109                    .map(Instruction::MeasureCalibrationDefinition),
110            )
111            .collect()
112    }
113}
114
115struct MatchedCalibration<'a> {
116    pub calibration: &'a CalibrationDefinition,
117    pub fixed_qubit_count: usize,
118}
119
120impl<'a> MatchedCalibration<'a> {
121    pub fn new(calibration: &'a CalibrationDefinition) -> Self {
122        Self {
123            calibration,
124            fixed_qubit_count: calibration
125                .identifier
126                .qubits
127                .iter()
128                .filter(|q| match q {
129                    Qubit::Fixed(_) => true,
130                    Qubit::Placeholder(_) | Qubit::Variable(_) => false,
131                })
132                .count(),
133        }
134    }
135}
136
137/// The product of expanding an instruction using a calibration.
138#[derive(Clone, Debug, PartialEq)]
139pub struct CalibrationExpansionOutput {
140    /// The new instructions resulting from the expansion
141    pub new_instructions: Vec<Instruction>,
142
143    /// Details about the expansion process.
144    pub detail: CalibrationExpansion,
145}
146
147/// Details about the expansion of a calibration.
148#[derive(Clone, Debug, PartialEq)]
149#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
150#[cfg_attr(feature = "python", pyo3::pyclass(module = "quil.program", eq, frozen))]
151#[cfg_attr(not(feature = "python"), strip_pyo3)]
152pub struct CalibrationExpansion {
153    /// The calibration used to expand the instruction.
154    #[pyo3(get)]
155    pub(crate) calibration_used: CalibrationSource,
156
157    /// The target instruction indices produced by the expansion.
158    pub(crate) range: Range<InstructionIndex>,
159
160    /// A map of source locations to the expansions they produced.
161    pub(crate) expansions: SourceMap<InstructionIndex, CalibrationExpansion>,
162}
163
164impl CalibrationExpansion {
165    /// Remove the given target index from all entries, recursively.
166    ///
167    /// This is to be used when the given index is removed from the target program
168    /// in the process of calibration expansion (for example, a `DECLARE`).
169    pub(crate) fn remove_target_index(&mut self, target_index: InstructionIndex) {
170        // Adjust the start of the range if the target index is before the range
171        if self.range.start >= target_index {
172            self.range.start = self.range.start.map(|v| v.saturating_sub(1));
173        }
174
175        // Adjust the end of the range if the target index is before the end of the range
176        if self.range.end > target_index {
177            self.range.end = self.range.end.map(|v| v.saturating_sub(1));
178        }
179
180        // Then walk through all entries expanded for this calibration and remove the
181        // index as well. This is needed when a recursively-expanded instruction contains
182        // an instruction which is excised from the overall calibration.
183        if let Some(target_within_expansion) = target_index.0.checked_sub(self.range.start.0) {
184            self.expansions.entries.retain_mut(
185                |entry: &mut SourceMapEntry<InstructionIndex, CalibrationExpansion>| {
186                    entry
187                        .target_location
188                        .remove_target_index(InstructionIndex(target_within_expansion));
189
190                    !entry.target_location.range.is_empty()
191                },
192            );
193        }
194    }
195
196    pub fn calibration_used(&self) -> &CalibrationSource {
197        &self.calibration_used
198    }
199
200    pub fn range(&self) -> &Range<InstructionIndex> {
201        &self.range
202    }
203
204    pub fn expansions(&self) -> &SourceMap<InstructionIndex, CalibrationExpansion> {
205        &self.expansions
206    }
207}
208
209impl SourceMapIndexable<InstructionIndex> for CalibrationExpansion {
210    fn intersects(&self, other: &InstructionIndex) -> bool {
211        self.range.contains(other)
212    }
213}
214
215impl SourceMapIndexable<CalibrationSource> for CalibrationExpansion {
216    fn intersects(&self, other: &CalibrationSource) -> bool {
217        self.calibration_used() == other
218    }
219}
220
221/// The result of an attempt to expand an instruction within a [`Program`]
222#[derive(Clone, Debug, PartialEq)]
223#[cfg_attr(feature = "stubs", gen_stub_pyclass_complex_enum)]
224#[cfg_attr(feature = "python", pyo3::pyclass(module = "quil.program", eq, frozen))]
225pub enum MaybeCalibrationExpansion {
226    /// The instruction was expanded into others
227    Expanded(CalibrationExpansion),
228
229    /// The instruction was not expanded, but was simply copied over into the target program at the given instruction index
230    Unexpanded(InstructionIndex),
231}
232
233impl SourceMapIndexable<InstructionIndex> for MaybeCalibrationExpansion {
234    fn intersects(&self, other: &InstructionIndex) -> bool {
235        match self {
236            MaybeCalibrationExpansion::Expanded(expansion) => expansion.intersects(other),
237            MaybeCalibrationExpansion::Unexpanded(index) => index == other,
238        }
239    }
240}
241
242impl SourceMapIndexable<CalibrationSource> for MaybeCalibrationExpansion {
243    fn intersects(&self, other: &CalibrationSource) -> bool {
244        match self {
245            MaybeCalibrationExpansion::Expanded(expansion) => expansion.intersects(other),
246            MaybeCalibrationExpansion::Unexpanded(_) => false,
247        }
248    }
249}
250
251/// The source of a calibration, either a [`CalibrationIdentifier`] or a
252/// [`MeasureCalibrationIdentifier`].
253#[derive(Clone, Debug, PartialEq)]
254#[cfg_attr(feature = "stubs", gen_stub_pyclass_complex_enum)]
255#[cfg_attr(feature = "python", pyo3::pyclass(module = "quil.program", eq, frozen))]
256pub enum CalibrationSource {
257    /// Describes a `DEFCAL` instruction
258    Calibration(CalibrationIdentifier),
259
260    /// Describes a `DEFCAL MEASURE` instruction
261    MeasureCalibration(MeasureCalibrationIdentifier),
262}
263
264impl From<CalibrationIdentifier> for CalibrationSource {
265    fn from(value: CalibrationIdentifier) -> Self {
266        Self::Calibration(value)
267    }
268}
269
270impl From<MeasureCalibrationIdentifier> for CalibrationSource {
271    fn from(value: MeasureCalibrationIdentifier) -> Self {
272        Self::MeasureCalibration(value)
273    }
274}
275
276impl Calibrations {
277    /// Iterate over all [`CalibrationDefinition`]s in the set
278    pub fn iter_calibrations(
279        &self,
280    ) -> impl DoubleEndedIterator<Item = &CalibrationDefinition> + FusedIterator {
281        self.calibrations.iter()
282    }
283
284    /// Iterate over all [`MeasureCalibrationDefinition`]s calibrations in the set
285    pub fn iter_measure_calibrations(
286        &self,
287    ) -> impl DoubleEndedIterator<Item = &MeasureCalibrationDefinition> + FusedIterator {
288        self.measure_calibrations.iter()
289    }
290
291    /// Given an instruction, return the instructions to which it is expanded if there is a match.
292    /// Recursively calibrate instructions, returning an error if a calibration directly or indirectly
293    /// expands into itself.
294    ///
295    /// Return only the expanded instructions; for more information about the expansion process,
296    /// see [`Self::expand_with_detail`].
297    pub fn expand(
298        &self,
299        instruction: &Instruction,
300        previous_calibrations: &[Instruction],
301    ) -> Result<Option<Vec<Instruction>>, ProgramError> {
302        self.expand_inner(instruction, previous_calibrations, false)
303            .map(|expansion| expansion.map(|expansion| expansion.new_instructions))
304    }
305
306    /// Given an instruction, return the instructions to which it is expanded if there is a match.
307    /// Recursively calibrate instructions, returning an error if a calibration directly or indirectly
308    /// expands into itself.
309    ///
310    /// Also return information about the expansion.
311    pub fn expand_with_detail(
312        &self,
313        instruction: &Instruction,
314        previous_calibrations: &[Instruction],
315    ) -> Result<Option<CalibrationExpansionOutput>, ProgramError> {
316        self.expand_inner(instruction, previous_calibrations, true)
317    }
318
319    /// Expand an instruction, returning an error if a calibration directly or indirectly
320    /// expands into itself. Return `None` if there are no matching calibrations in `self`.
321    ///
322    /// # Arguments
323    ///
324    /// * `instruction` - The instruction to expand.
325    /// * `previous_calibrations` - The calibrations that were invoked to yield this current instruction.
326    /// * `build_source_map` - Whether to build a source map of the expansion.
327    fn expand_inner(
328        &self,
329        instruction: &Instruction,
330        previous_calibrations: &[Instruction],
331        build_source_map: bool,
332    ) -> Result<Option<CalibrationExpansionOutput>, ProgramError> {
333        if previous_calibrations.contains(instruction) {
334            return Err(ProgramError::RecursiveCalibration(instruction.clone()));
335        }
336        let expansion_result = match instruction {
337            Instruction::Gate(gate) => {
338                let matching_calibration = self.get_match_for_gate(gate);
339
340                match matching_calibration {
341                    Some(calibration) => {
342                        let mut qubit_expansions: HashMap<&String, Qubit> = HashMap::new();
343                        for (index, calibration_qubit) in
344                            calibration.identifier.qubits.iter().enumerate()
345                        {
346                            if let Qubit::Variable(identifier) = calibration_qubit {
347                                qubit_expansions.insert(identifier, gate.qubits[index].clone());
348                            }
349                        }
350
351                        // Variables used within the calibration's definition should be replaced with the actual expressions used by the gate.
352                        // That is, `DEFCAL RX(%theta): ...` should have `%theta` replaced by `pi` throughout if it's used to expand `RX(pi)`.
353                        let variable_expansions: HashMap<String, Expression> = calibration
354                            .identifier
355                            .parameters
356                            .iter()
357                            .zip(gate.parameters.iter())
358                            .filter_map(|(calibration_expression, gate_expression)| {
359                                if let Expression::Variable(variable_name) = calibration_expression
360                                {
361                                    Some((variable_name.clone(), gate_expression.clone()))
362                                } else {
363                                    None
364                                }
365                            })
366                            .collect();
367
368                        let mut instructions = calibration.instructions.clone();
369
370                        for instruction in instructions.iter_mut() {
371                            match instruction {
372                                Instruction::Gate(Gate { qubits, .. })
373                                | Instruction::Delay(Delay { qubits, .. })
374                                | Instruction::Capture(Capture {
375                                    frame: FrameIdentifier { qubits, .. },
376                                    ..
377                                })
378                                | Instruction::RawCapture(RawCapture {
379                                    frame: FrameIdentifier { qubits, .. },
380                                    ..
381                                })
382                                | Instruction::SetFrequency(SetFrequency {
383                                    frame: FrameIdentifier { qubits, .. },
384                                    ..
385                                })
386                                | Instruction::SetPhase(SetPhase {
387                                    frame: FrameIdentifier { qubits, .. },
388                                    ..
389                                })
390                                | Instruction::SetScale(SetScale {
391                                    frame: FrameIdentifier { qubits, .. },
392                                    ..
393                                })
394                                | Instruction::ShiftFrequency(ShiftFrequency {
395                                    frame: FrameIdentifier { qubits, .. },
396                                    ..
397                                })
398                                | Instruction::ShiftPhase(ShiftPhase {
399                                    frame: FrameIdentifier { qubits, .. },
400                                    ..
401                                })
402                                | Instruction::Pulse(Pulse {
403                                    frame: FrameIdentifier { qubits, .. },
404                                    ..
405                                })
406                                | Instruction::Fence(Fence { qubits }) => {
407                                    // Swap all qubits for their concrete implementations
408                                    for qubit in qubits {
409                                        match qubit {
410                                            Qubit::Variable(name) => {
411                                                if let Some(expansion) = qubit_expansions.get(name)
412                                                {
413                                                    *qubit = expansion.clone();
414                                                }
415                                            }
416                                            Qubit::Fixed(_) | Qubit::Placeholder(_) => {}
417                                        }
418                                    }
419                                }
420                                _ => {}
421                            }
422
423                            instruction.apply_to_expressions(|expr| {
424                                *expr = expr.substitute_variables(&variable_expansions);
425                            })
426                        }
427
428                        Some((
429                            instructions,
430                            CalibrationSource::Calibration(calibration.identifier.clone()),
431                        ))
432                    }
433                    None => None,
434                }
435            }
436            Instruction::Measurement(measurement) => {
437                let matching_calibration = self.get_match_for_measurement(measurement);
438
439                match matching_calibration {
440                    Some(calibration) => {
441                        let mut instructions = calibration.instructions.clone();
442                        for instruction in instructions.iter_mut() {
443                            match instruction {
444                                Instruction::Pragma(pragma) => {
445                                    if pragma.name == "LOAD-MEMORY"
446                                        && pragma.data == calibration.identifier.target
447                                    {
448                                        if let Some(target) = &measurement.target {
449                                            pragma.data = Some(target.to_quil_or_debug())
450                                        }
451                                    }
452                                }
453                                Instruction::Capture(capture) => {
454                                    if let Some(target) = &measurement.target {
455                                        capture.memory_reference = target.clone()
456                                    }
457                                }
458                                _ => {}
459                            }
460                        }
461                        Some((
462                            instructions,
463                            CalibrationSource::MeasureCalibration(calibration.identifier.clone()),
464                        ))
465                    }
466                    None => None,
467                }
468            }
469            _ => None,
470        };
471
472        // Add this instruction to the breadcrumb trail before recursion
473        let mut calibration_path = Vec::with_capacity(previous_calibrations.len() + 1);
474        calibration_path.push(instruction.clone());
475        calibration_path.extend_from_slice(previous_calibrations);
476
477        self.recursively_expand_inner(expansion_result, &calibration_path, build_source_map)
478    }
479
480    fn recursively_expand_inner(
481        &self,
482        expansion_result: Option<(Vec<Instruction>, CalibrationSource)>,
483        calibration_path: &[Instruction],
484        build_source_map: bool,
485    ) -> Result<Option<CalibrationExpansionOutput>, ProgramError> {
486        Ok(match expansion_result {
487            Some((instructions, matched_calibration)) => {
488                let mut recursively_expanded_instructions = CalibrationExpansionOutput {
489                    new_instructions: Vec::new(),
490                    detail: CalibrationExpansion {
491                        calibration_used: matched_calibration,
492                        range: InstructionIndex(0)..InstructionIndex(0),
493                        expansions: SourceMap::default(),
494                    },
495                };
496
497                for (expanded_index, instruction) in instructions.into_iter().enumerate() {
498                    let expanded_instructions =
499                        self.expand_inner(&instruction, calibration_path, build_source_map)?;
500                    match expanded_instructions {
501                        Some(mut output) => {
502                            if build_source_map {
503                                let range_start = InstructionIndex(
504                                    recursively_expanded_instructions.new_instructions.len(),
505                                );
506
507                                recursively_expanded_instructions
508                                    .new_instructions
509                                    .extend(output.new_instructions);
510
511                                let range_end = InstructionIndex(
512                                    recursively_expanded_instructions.new_instructions.len(),
513                                );
514                                output.detail.range = range_start..range_end;
515
516                                recursively_expanded_instructions
517                                    .detail
518                                    .expansions
519                                    .entries
520                                    .push(SourceMapEntry {
521                                        source_location: InstructionIndex(expanded_index),
522                                        target_location: output.detail,
523                                    });
524                            } else {
525                                recursively_expanded_instructions
526                                    .new_instructions
527                                    .extend(output.new_instructions);
528                            }
529                        }
530                        None => {
531                            recursively_expanded_instructions
532                                .new_instructions
533                                .push(instruction);
534                        }
535                    };
536                }
537
538                if build_source_map {
539                    // While this appears to be duplicated information at this point, it's useful when multiple
540                    // source mappings are merged together.
541                    recursively_expanded_instructions.detail.range = InstructionIndex(0)
542                        ..InstructionIndex(
543                            recursively_expanded_instructions.new_instructions.len(),
544                        );
545                }
546
547                Some(recursively_expanded_instructions)
548            }
549            None => None,
550        })
551    }
552
553    /// Returns the last-specified [`MeasureCalibrationDefinition`] that matches the target
554    /// qubit (if any), or otherwise the last-specified one that specified no qubit.
555    ///
556    /// If multiple calibrations match the measurement, the precedence is as follows:
557    ///
558    ///   1. Match fixed qubit.
559    ///   2. Match variable qubit.
560    ///   3. Match no qubit.
561    ///
562    /// In the case of multiple calibrations with equal precedence, the last one wins.
563    pub fn get_match_for_measurement(
564        &self,
565        measurement: &Measurement,
566    ) -> Option<&MeasureCalibrationDefinition> {
567        /// Utility type: when collecting from an iterator, return only the first value it produces.
568        struct First<T>(Option<T>);
569
570        impl<T> Default for First<T> {
571            fn default() -> Self {
572                Self(None)
573            }
574        }
575
576        impl<A> Extend<A> for First<A> {
577            fn extend<T: IntoIterator<Item = A>>(&mut self, iter: T) {
578                if self.0.is_none() {
579                    self.0 = iter.into_iter().next()
580                }
581            }
582        }
583
584        let Measurement {
585            name,
586            qubit,
587            target,
588        } = measurement;
589
590        // Find the last matching measurement calibration, but prefer an exact qubit match to a
591        // wildcard qubit match.
592        let (First(exact), First(wildcard)) = self
593            .iter_measure_calibrations()
594            .rev()
595            .filter_map(|calibration| {
596                let identifier = &calibration.identifier;
597
598                if !(name == &identifier.name && target.is_some() == identifier.target.is_some()) {
599                    return None;
600                }
601
602                match &identifier.qubit {
603                    fixed @ Qubit::Fixed(_) if qubit == fixed => Some((calibration, true)),
604                    Qubit::Variable(_) => Some((calibration, false)),
605                    Qubit::Fixed(_) | Qubit::Placeholder(_) => None,
606                }
607            })
608            .partition_map(|(calibration, exact)| {
609                if exact {
610                    Either::Left(calibration)
611                } else {
612                    Either::Right(calibration)
613                }
614            });
615
616        exact.or(wildcard)
617    }
618
619    /// Return the final calibration which matches the gate per the QuilT specification:
620    ///
621    /// A calibration matches a gate if:
622    /// 1. It has the same name
623    /// 2. It has the same modifiers
624    /// 3. It has the same qubit count (any mix of fixed & variable)
625    /// 4. It has the same parameter count (both specified and unspecified)
626    /// 5. All fixed qubits in the calibration definition match those in the gate
627    /// 6. All specified parameters in the calibration definition match those in the gate
628    pub fn get_match_for_gate(&self, gate: &Gate) -> Option<&CalibrationDefinition> {
629        let mut matched_calibration: Option<MatchedCalibration> = None;
630
631        for calibration in self
632            .iter_calibrations()
633            .filter(|calibration| calibration.identifier.matches(gate))
634        {
635            matched_calibration = match matched_calibration {
636                None => Some(MatchedCalibration::new(calibration)),
637                Some(previous_match) => {
638                    let potential_match = MatchedCalibration::new(calibration);
639                    if potential_match.fixed_qubit_count >= previous_match.fixed_qubit_count {
640                        Some(potential_match)
641                    } else {
642                        Some(previous_match)
643                    }
644                }
645            }
646        }
647
648        matched_calibration.map(|m| m.calibration)
649    }
650
651    /// Return the Quil instructions which describe the contained calibrations, consuming the [`CalibrationSet`]
652    pub fn into_instructions(self) -> Vec<Instruction> {
653        self.calibrations
654            .into_iter()
655            .map(Instruction::CalibrationDefinition)
656            .chain(
657                self.measure_calibrations
658                    .into_iter()
659                    .map(Instruction::MeasureCalibrationDefinition),
660            )
661            .collect()
662    }
663}
664
665#[cfg(test)]
666mod tests {
667    use std::str::FromStr;
668
669    use crate::program::calibration::{CalibrationSource, MeasureCalibrationIdentifier};
670    use crate::program::source_map::{SourceMap, SourceMapEntry};
671    use crate::program::{InstructionIndex, Program};
672    use crate::quil::Quil;
673
674    use insta::assert_snapshot;
675    use rstest::rstest;
676
677    use super::{CalibrationExpansion, CalibrationExpansionOutput, CalibrationIdentifier};
678
679    #[rstest]
680    #[case(
681        "Calibration-Param-Precedence",
682        concat!(
683            "DEFCAL RX(%theta) %qubit:\n",
684            "    PULSE 1 \"xy\" gaussian(duration: 1, fwhm: 2, t0: 3)\n",
685            "DEFCAL RX(%theta) 0:\n",
686            "    PULSE 2 \"xy\" gaussian(duration: 1, fwhm: 2, t0: 3)\n",
687            "DEFCAL RX(pi/2) 0:\n",
688            "    PULSE 3 \"xy\" gaussian(duration: 1, fwhm: 2, t0: 3)\n",
689            "RX(pi/2) 1\n",
690            "RX(pi) 0\n",
691            "RX(pi/2) 0\n"
692        ),
693    )]
694    #[case(
695        "Calibration-Simple",
696        concat!(
697            "DEFCAL X 0:\n",
698            "    PULSE 0 \"xy\" gaussian(duration: 1, fwhm: 2, t0: 3)\n",
699            "X 0\n",
700        ),
701    )]
702    #[case(
703        "Calibration-Literal-Parameter",
704        concat!(
705            "DEFCAL RX(3.141592653589793) 0:\n",
706            "    NOP\n",
707            "RX(3.141592653589793) 0\n",
708        ),
709    )]
710    #[case(
711        "Calibration-Instruction-Match",
712        concat!(
713            "DEFCAL X 0:\n",
714            "    Y 0\n",
715            "DEFCAL Y 0:\n",
716            "    PULSE 0 \"xy\" gaussian(duration: 1, fwhm: 2, t0: 3)\n",
717            "X 0\n"
718        ),
719    )]
720    #[case(
721        "Measure-Calibration",
722        concat!(
723            "DEFCAL MEASURE 0 addr:\n",
724            "    PRAGMA INCORRECT_ORDERING\n",
725            "DEFCAL MEASURE 0 addr:\n",
726            "    PRAGMA CORRECT\n",
727            "DEFCAL MEASURE q addr:\n",
728            "    PRAGMA INCORRECT_PRECEDENCE\n",
729            "DEFCAL MEASURE 1 addr:\n",
730            "    PRAGMA INCORRECT_QUBIT\n",
731            "DEFCAL MEASURE q:\n",
732            "    PRAGMA INCORRECT_RECORD_VS_EFFECT\n",
733            "MEASURE 0 ro\n",
734        ),
735    )]
736    #[case(
737        "Calibration-Variable-Qubit",
738        concat!("DEFCAL I %q:\n", "    DELAY q 4e-8\n", "I 0\n",),
739    )]
740    #[case(
741        "Precedence-Fixed-Match",
742        concat!(
743            "DEFCAL MEASURE q:\n",
744            "    PRAGMA INCORRECT_RECORD_VS_EFFECT\n",
745            "DEFCAL MEASURE b addr:\n",
746            "    PRAGMA INCORRECT_PRECEDENCE\n",
747            "DEFCAL MEASURE 0 addr:\n",
748            "    PRAGMA INCORRECT_ORDER\n",
749            "DEFCAL MEASURE 0 addr:\n",
750            "    PRAGMA CORRECT\n",
751            "DEFCAL MEASURE q addr:\n",
752            "    PRAGMA INCORRECT_PRECEDENCE\n",
753            "DEFCAL MEASURE 1 addr:\n",
754            "    PRAGMA INCORRECT_QUBIT\n",
755            "MEASURE 0 ro\n",
756        )
757    )]
758    #[case(
759        "Precedence-Variable-Match",
760        concat!(
761            "DEFCAL MEASURE q:\n",
762            "    PRAGMA INCORRECT_RECORD_VS_EFFECT\n",
763            "DEFCAL MEASURE b addr:\n",
764            "    PRAGMA INCORRECT_ORDER\n",
765            "DEFCAL MEASURE q addr:\n",
766            "    PRAGMA CORRECT\n",
767            "MEASURE 0 ro\n",
768        )
769    )]
770    #[case(
771        "Precedence-No-Target-Match",
772        concat!(
773            "DEFCAL MEASURE b:\n",
774            "    PRAGMA INCORRECT_SAME_NAME_SHOULD_NOT_APPEAR_IN_OUTPUT\n",
775            "DEFCAL MEASURE b:\n",
776            "    PRAGMA INCORRECT_ORDER\n",
777            "DEFCAL MEASURE q:\n",
778            "    PRAGMA CORRECT\n",
779            "MEASURE 0\n",
780        )
781    )]
782    #[case(
783        "Precedence-Prefer-Exact-Qubit-Match",
784        concat!(
785            "DEFCAL MEASURE 0 addr:\n",
786            "    PRAGMA CORRECT\n",
787            "DEFCAL MEASURE q addr:\n",
788            "    PRAGMA INCORRECT_PRECEDENCE\n",
789            "MEASURE 0 ro\n",
790        )
791    )]
792    #[case(
793        "Precedence-Prefer-Exact-Qubit-Match-No-Target",
794        concat!(
795            "DEFCAL MEASURE 0:\n",
796            "    PRAGMA CORRECT\n",
797            "DEFCAL MEASURE q:\n",
798            "    PRAGMA INCORRECT_PRECEDENCE\n",
799            "MEASURE 0\n",
800        )
801    )]
802    #[case(
803        "Precedence-Require-No-Name-Match",
804        concat!(
805            "DEFCAL MEASURE q addr:\n",
806            "    PRAGMA CORRECT\n",
807            "DEFCAL MEASURE!wrong-name 0 addr:\n",
808            "    PRAGMA INCORRECT_NAME\n",
809            "DEFCAL MEASURE!midcircuit 0 addr:\n",
810            "    PRAGMA INCORRECT_NAME\n",
811            "MEASURE 0 ro\n",
812        )
813    )]
814    #[case(
815        "Precedence-Require-Name-Match",
816        concat!(
817            "DEFCAL MEASURE!midcircuit q addr:\n",
818            "    PRAGMA CORRECT\n",
819            "DEFCAL MEASURE!wrong-name 0 addr:\n",
820            "    PRAGMA INCORRECT_NAME\n",
821            "DEFCAL MEASURE 0 addr:\n",
822            "    PRAGMA INCORRECT_NAME\n",
823            "MEASURE!midcircuit 0 ro\n",
824        )
825    )]
826    #[case(
827        "ShiftPhase",
828        concat!(
829            "DEFCAL RZ(%theta) %q:\n",
830            "    SHIFT-PHASE %q \"rf\" -%theta\n",
831            "RZ(pi) 0\n",
832        )
833    )]
834    #[case(
835        "FenceVariableQubit",
836        concat!(
837            "DEFCAL FENCES q0 q1:\n",
838            "    FENCE q0\n",
839            "    FENCE q1\n",
840            "FENCES 0 1\n",
841        )
842    )]
843    fn test_expansion(#[case] description: &str, #[case] input: &str) {
844        let program = Program::from_str(input).unwrap();
845        let calibrated_program = program.expand_calibrations().unwrap();
846        insta::with_settings!({
847            snapshot_suffix => description,
848        }, {
849            assert_snapshot!(calibrated_program.to_quil_or_debug())
850        })
851    }
852
853    /// Assert that instruction expansion yields the expected [`SourceMap`] and resulting instructions.
854    #[test]
855    fn expand_with_detail_recursive() {
856        let input = r#"
857DEFCAL X 0:
858    Y 0
859    MEASURE 0 ro
860    Y 0
861
862DEFCAL Y 0:
863    NOP
864    Z 0
865
866DEFCAL Z 0:
867    WAIT
868
869DEFCAL MEASURE 0 addr:
870    HALT
871
872X 0
873"#;
874
875        let program = Program::from_str(input).unwrap();
876        let instruction = program.instructions.last().unwrap();
877        let expansion = program
878            .calibrations
879            .expand_with_detail(instruction, &[])
880            .unwrap();
881        let expected = CalibrationExpansionOutput {
882            new_instructions: vec![
883                crate::instruction::Instruction::Nop(),
884                crate::instruction::Instruction::Wait(),
885                crate::instruction::Instruction::Halt(),
886                crate::instruction::Instruction::Nop(),
887                crate::instruction::Instruction::Wait(),
888            ],
889            detail: CalibrationExpansion {
890                calibration_used: CalibrationSource::Calibration(CalibrationIdentifier {
891                    modifiers: vec![],
892                    name: "X".to_string(),
893                    parameters: vec![],
894                    qubits: vec![crate::instruction::Qubit::Fixed(0)],
895                }),
896                range: InstructionIndex(0)..InstructionIndex(5),
897                expansions: SourceMap {
898                    entries: vec![
899                        SourceMapEntry {
900                            source_location: InstructionIndex(0),
901                            target_location: CalibrationExpansion {
902                                calibration_used: CalibrationSource::Calibration(
903                                    CalibrationIdentifier {
904                                        modifiers: vec![],
905                                        name: "Y".to_string(),
906                                        parameters: vec![],
907                                        qubits: vec![crate::instruction::Qubit::Fixed(0)],
908                                    },
909                                ),
910                                range: InstructionIndex(0)..InstructionIndex(2),
911                                expansions: SourceMap {
912                                    entries: vec![SourceMapEntry {
913                                        source_location: InstructionIndex(1),
914                                        target_location: CalibrationExpansion {
915                                            calibration_used: CalibrationSource::Calibration(
916                                                CalibrationIdentifier {
917                                                    modifiers: vec![],
918                                                    name: "Z".to_string(),
919                                                    parameters: vec![],
920                                                    qubits: vec![crate::instruction::Qubit::Fixed(
921                                                        0,
922                                                    )],
923                                                },
924                                            ),
925                                            range: InstructionIndex(1)..InstructionIndex(2),
926                                            expansions: SourceMap::default(),
927                                        },
928                                    }],
929                                },
930                            },
931                        },
932                        SourceMapEntry {
933                            source_location: InstructionIndex(1),
934                            target_location: CalibrationExpansion {
935                                calibration_used: CalibrationSource::MeasureCalibration(
936                                    MeasureCalibrationIdentifier {
937                                        name: None,
938                                        qubit: crate::instruction::Qubit::Fixed(0),
939                                        target: Some("addr".to_string()),
940                                    },
941                                ),
942                                range: InstructionIndex(2)..InstructionIndex(3),
943                                expansions: SourceMap::default(),
944                            },
945                        },
946                        SourceMapEntry {
947                            source_location: InstructionIndex(2),
948                            target_location: CalibrationExpansion {
949                                calibration_used: CalibrationSource::Calibration(
950                                    CalibrationIdentifier {
951                                        modifiers: vec![],
952                                        name: "Y".to_string(),
953                                        parameters: vec![],
954                                        qubits: vec![crate::instruction::Qubit::Fixed(0)],
955                                    },
956                                ),
957                                range: InstructionIndex(3)..InstructionIndex(5),
958                                expansions: SourceMap {
959                                    entries: vec![SourceMapEntry {
960                                        source_location: InstructionIndex(1),
961                                        target_location: CalibrationExpansion {
962                                            calibration_used: CalibrationSource::Calibration(
963                                                CalibrationIdentifier {
964                                                    modifiers: vec![],
965                                                    name: "Z".to_string(),
966                                                    parameters: vec![],
967                                                    qubits: vec![crate::instruction::Qubit::Fixed(
968                                                        0,
969                                                    )],
970                                                },
971                                            ),
972                                            range: InstructionIndex(1)..InstructionIndex(2),
973                                            expansions: SourceMap::default(),
974                                        },
975                                    }],
976                                },
977                            },
978                        },
979                    ],
980                },
981            },
982        };
983
984        pretty_assertions::assert_eq!(expansion, Some(expected));
985    }
986
987    #[test]
988    fn test_eq() {
989        let input = "DEFCAL X 0:
990    PULSE 0 \"xy\" gaussian(duration: 1, fwhm: 2, t0: 3)
991X 0";
992        let a = Program::from_str(input);
993        let b = Program::from_str(input);
994        assert_eq!(a, b);
995    }
996
997    #[test]
998    fn test_ne() {
999        let input_a = "DEFCAL X 0:
1000    PULSE 0 \"xy\" gaussian(duration: 1, fwhm: 2, t0: 3)
1001X 0";
1002        let input_b = "DEFCAL X 1:
1003    PULSE 1 \"xy\" gaussian(duration: 1, fwhm: 2, t0: 3)
1004X 1";
1005        let a = Program::from_str(input_a);
1006        let b = Program::from_str(input_b);
1007        assert_ne!(a, b);
1008    }
1009}