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::{ExpansionResult, 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, ExpansionResult<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<
186                    InstructionIndex,
187                    ExpansionResult<CalibrationExpansion>,
188                >| {
189                    if let ExpansionResult::Rewritten(ref mut expansion) = entry.target_location {
190                        expansion.remove_target_index(InstructionIndex(target_within_expansion));
191                        !expansion.range.is_empty()
192                    } else {
193                        true
194                    }
195                },
196            );
197        }
198    }
199
200    pub fn calibration_used(&self) -> &CalibrationSource {
201        &self.calibration_used
202    }
203
204    pub fn range(&self) -> &Range<InstructionIndex> {
205        &self.range
206    }
207
208    pub fn expansions(
209        &self,
210    ) -> &SourceMap<InstructionIndex, ExpansionResult<CalibrationExpansion>> {
211        &self.expansions
212    }
213}
214
215impl SourceMapIndexable<InstructionIndex> for CalibrationExpansion {
216    fn contains(&self, other: &InstructionIndex) -> bool {
217        self.range.contains(other)
218    }
219}
220
221impl SourceMapIndexable<CalibrationSource> for CalibrationExpansion {
222    fn contains(&self, other: &CalibrationSource) -> bool {
223        self.calibration_used() == other
224    }
225}
226
227/// The source of a calibration, either a [`CalibrationIdentifier`] or a
228/// [`MeasureCalibrationIdentifier`].
229#[derive(Clone, Debug, PartialEq)]
230#[cfg_attr(feature = "stubs", gen_stub_pyclass_complex_enum)]
231#[cfg_attr(feature = "python", pyo3::pyclass(module = "quil.program", eq, frozen))]
232pub enum CalibrationSource {
233    /// Describes a `DEFCAL` instruction
234    Calibration(CalibrationIdentifier),
235
236    /// Describes a `DEFCAL MEASURE` instruction
237    MeasureCalibration(MeasureCalibrationIdentifier),
238}
239
240impl From<CalibrationIdentifier> for CalibrationSource {
241    fn from(value: CalibrationIdentifier) -> Self {
242        Self::Calibration(value)
243    }
244}
245
246impl From<MeasureCalibrationIdentifier> for CalibrationSource {
247    fn from(value: MeasureCalibrationIdentifier) -> Self {
248        Self::MeasureCalibration(value)
249    }
250}
251
252impl Calibrations {
253    /// Iterate over all [`CalibrationDefinition`]s in the set
254    pub fn iter_calibrations(
255        &self,
256    ) -> impl DoubleEndedIterator<Item = &CalibrationDefinition> + FusedIterator {
257        self.calibrations.iter()
258    }
259
260    /// Iterate over all [`MeasureCalibrationDefinition`]s calibrations in the set
261    pub fn iter_measure_calibrations(
262        &self,
263    ) -> impl DoubleEndedIterator<Item = &MeasureCalibrationDefinition> + FusedIterator {
264        self.measure_calibrations.iter()
265    }
266
267    /// Given an instruction, return the instructions to which it is expanded if there is a match.
268    /// Recursively calibrate instructions, returning an error if a calibration directly or indirectly
269    /// expands into itself.
270    ///
271    /// Return only the expanded instructions; for more information about the expansion process,
272    /// see [`Self::expand_with_detail`].
273    pub fn expand(
274        &self,
275        instruction: &Instruction,
276        previous_calibrations: &[Instruction],
277    ) -> Result<Option<Vec<Instruction>>, ProgramError> {
278        self.expand_inner(instruction, previous_calibrations, false)
279            .map(|expansion| expansion.map(|expansion| expansion.new_instructions))
280    }
281
282    /// Given an instruction, return the instructions to which it is expanded if there is a match.
283    /// Recursively calibrate instructions, returning an error if a calibration directly or indirectly
284    /// expands into itself.
285    ///
286    /// Also return information about the expansion.
287    pub fn expand_with_detail(
288        &self,
289        instruction: &Instruction,
290        previous_calibrations: &[Instruction],
291    ) -> Result<Option<CalibrationExpansionOutput>, ProgramError> {
292        self.expand_inner(instruction, previous_calibrations, true)
293    }
294
295    /// Expand an instruction, returning an error if a calibration directly or indirectly
296    /// expands into itself. Return `None` if there are no matching calibrations in `self`.
297    ///
298    /// # Arguments
299    ///
300    /// * `instruction` - The instruction to expand.
301    /// * `previous_calibrations` - The calibrations that were invoked to yield this current instruction.
302    /// * `build_source_map` - Whether to build a source map of the expansion.
303    fn expand_inner(
304        &self,
305        instruction: &Instruction,
306        previous_calibrations: &[Instruction],
307        build_source_map: bool,
308    ) -> Result<Option<CalibrationExpansionOutput>, ProgramError> {
309        if previous_calibrations.contains(instruction) {
310            return Err(ProgramError::RecursiveCalibration(instruction.clone()));
311        }
312        let expansion_result = match instruction {
313            Instruction::Gate(gate) => {
314                let matching_calibration = self.get_match_for_gate(gate);
315
316                match matching_calibration {
317                    Some(calibration) => {
318                        let mut qubit_expansions: HashMap<&String, Qubit> = HashMap::new();
319                        for (index, calibration_qubit) in
320                            calibration.identifier.qubits.iter().enumerate()
321                        {
322                            if let Qubit::Variable(identifier) = calibration_qubit {
323                                qubit_expansions.insert(identifier, gate.qubits[index].clone());
324                            }
325                        }
326
327                        // Variables used within the calibration's definition should be replaced with the actual expressions used by the gate.
328                        // That is, `DEFCAL RX(%theta): ...` should have `%theta` replaced by `pi` throughout if it's used to expand `RX(pi)`.
329                        let variable_expansions: HashMap<String, Expression> = calibration
330                            .identifier
331                            .parameters
332                            .iter()
333                            .zip(gate.parameters.iter())
334                            .filter_map(|(calibration_expression, gate_expression)| {
335                                if let Expression::Variable(variable_name) = calibration_expression
336                                {
337                                    Some((variable_name.clone(), gate_expression.clone()))
338                                } else {
339                                    None
340                                }
341                            })
342                            .collect();
343
344                        let mut instructions = calibration.instructions.clone();
345
346                        for instruction in instructions.iter_mut() {
347                            match instruction {
348                                Instruction::Gate(Gate { qubits, .. })
349                                | Instruction::Delay(Delay { qubits, .. })
350                                | Instruction::Capture(Capture {
351                                    frame: FrameIdentifier { qubits, .. },
352                                    ..
353                                })
354                                | Instruction::RawCapture(RawCapture {
355                                    frame: FrameIdentifier { qubits, .. },
356                                    ..
357                                })
358                                | Instruction::SetFrequency(SetFrequency {
359                                    frame: FrameIdentifier { qubits, .. },
360                                    ..
361                                })
362                                | Instruction::SetPhase(SetPhase {
363                                    frame: FrameIdentifier { qubits, .. },
364                                    ..
365                                })
366                                | Instruction::SetScale(SetScale {
367                                    frame: FrameIdentifier { qubits, .. },
368                                    ..
369                                })
370                                | Instruction::ShiftFrequency(ShiftFrequency {
371                                    frame: FrameIdentifier { qubits, .. },
372                                    ..
373                                })
374                                | Instruction::ShiftPhase(ShiftPhase {
375                                    frame: FrameIdentifier { qubits, .. },
376                                    ..
377                                })
378                                | Instruction::Pulse(Pulse {
379                                    frame: FrameIdentifier { qubits, .. },
380                                    ..
381                                })
382                                | Instruction::Fence(Fence { qubits }) => {
383                                    // Swap all qubits for their concrete implementations
384                                    for qubit in qubits {
385                                        match qubit {
386                                            Qubit::Variable(name) => {
387                                                if let Some(expansion) = qubit_expansions.get(name)
388                                                {
389                                                    *qubit = expansion.clone();
390                                                }
391                                            }
392                                            Qubit::Fixed(_) | Qubit::Placeholder(_) => {}
393                                        }
394                                    }
395                                }
396                                _ => {}
397                            }
398
399                            instruction.apply_to_expressions(|expr| {
400                                *expr = expr.substitute_variables(&variable_expansions);
401                            })
402                        }
403
404                        Some((
405                            instructions,
406                            CalibrationSource::Calibration(calibration.identifier.clone()),
407                        ))
408                    }
409                    None => None,
410                }
411            }
412            Instruction::Measurement(measurement) => {
413                let matching_calibration = self.get_match_for_measurement(measurement);
414
415                match matching_calibration {
416                    Some(calibration) => {
417                        let mut instructions = calibration.instructions.clone();
418                        for instruction in instructions.iter_mut() {
419                            match instruction {
420                                Instruction::Pragma(pragma) => {
421                                    if pragma.name == "LOAD-MEMORY"
422                                        && pragma.data == calibration.identifier.target
423                                    {
424                                        if let Some(target) = &measurement.target {
425                                            pragma.data = Some(target.to_quil_or_debug())
426                                        }
427                                    }
428                                }
429                                Instruction::Capture(capture) => {
430                                    if let Some(target) = &measurement.target {
431                                        capture.memory_reference = target.clone()
432                                    }
433                                }
434                                _ => {}
435                            }
436                        }
437                        Some((
438                            instructions,
439                            CalibrationSource::MeasureCalibration(calibration.identifier.clone()),
440                        ))
441                    }
442                    None => None,
443                }
444            }
445            _ => None,
446        };
447
448        // Add this instruction to the breadcrumb trail before recursion
449        let mut calibration_path = Vec::with_capacity(previous_calibrations.len() + 1);
450        calibration_path.push(instruction.clone());
451        calibration_path.extend_from_slice(previous_calibrations);
452
453        self.recursively_expand_inner(expansion_result, &calibration_path, build_source_map)
454    }
455
456    fn recursively_expand_inner(
457        &self,
458        expansion_result: Option<(Vec<Instruction>, CalibrationSource)>,
459        calibration_path: &[Instruction],
460        build_source_map: bool,
461    ) -> Result<Option<CalibrationExpansionOutput>, ProgramError> {
462        Ok(match expansion_result {
463            Some((instructions, matched_calibration)) => {
464                let mut recursively_expanded_instructions = CalibrationExpansionOutput {
465                    new_instructions: Vec::new(),
466                    detail: CalibrationExpansion {
467                        calibration_used: matched_calibration,
468                        range: InstructionIndex(0)..InstructionIndex(0),
469                        expansions: SourceMap::default(),
470                    },
471                };
472
473                for (expanded_index, instruction) in instructions.into_iter().enumerate() {
474                    let expanded_instructions =
475                        self.expand_inner(&instruction, calibration_path, build_source_map)?;
476                    match expanded_instructions {
477                        Some(mut output) => {
478                            if build_source_map {
479                                let range_start = InstructionIndex(
480                                    recursively_expanded_instructions.new_instructions.len(),
481                                );
482
483                                recursively_expanded_instructions
484                                    .new_instructions
485                                    .extend(output.new_instructions);
486
487                                let range_end = InstructionIndex(
488                                    recursively_expanded_instructions.new_instructions.len(),
489                                );
490                                output.detail.range = range_start..range_end;
491
492                                recursively_expanded_instructions
493                                    .detail
494                                    .expansions
495                                    .entries
496                                    .push(SourceMapEntry {
497                                        source_location: InstructionIndex(expanded_index),
498                                        target_location: ExpansionResult::Rewritten(output.detail),
499                                    });
500                            } else {
501                                recursively_expanded_instructions
502                                    .new_instructions
503                                    .extend(output.new_instructions);
504                            }
505                        }
506                        None => {
507                            if build_source_map {
508                                let target_index = InstructionIndex(
509                                    recursively_expanded_instructions.new_instructions.len(),
510                                );
511                                recursively_expanded_instructions
512                                    .detail
513                                    .expansions
514                                    .entries
515                                    .push(SourceMapEntry {
516                                        source_location: InstructionIndex(expanded_index),
517                                        target_location: ExpansionResult::Unmodified(target_index),
518                                    });
519                            }
520                            recursively_expanded_instructions
521                                .new_instructions
522                                .push(instruction);
523                        }
524                    };
525                }
526
527                if build_source_map {
528                    // While this appears to be duplicated information at this point, it's useful when multiple
529                    // source mappings are merged together.
530                    recursively_expanded_instructions.detail.range = InstructionIndex(0)
531                        ..InstructionIndex(
532                            recursively_expanded_instructions.new_instructions.len(),
533                        );
534                }
535
536                Some(recursively_expanded_instructions)
537            }
538            None => None,
539        })
540    }
541
542    /// Returns the last-specified [`MeasureCalibrationDefinition`] that matches the target
543    /// qubit (if any), or otherwise the last-specified one that specified no qubit.
544    ///
545    /// If multiple calibrations match the measurement, the precedence is as follows:
546    ///
547    ///   1. Match fixed qubit.
548    ///   2. Match variable qubit.
549    ///   3. Match no qubit.
550    ///
551    /// In the case of multiple calibrations with equal precedence, the last one wins.
552    pub fn get_match_for_measurement(
553        &self,
554        measurement: &Measurement,
555    ) -> Option<&MeasureCalibrationDefinition> {
556        /// Utility type: when collecting from an iterator, return only the first value it produces.
557        struct First<T>(Option<T>);
558
559        impl<T> Default for First<T> {
560            fn default() -> Self {
561                Self(None)
562            }
563        }
564
565        impl<A> Extend<A> for First<A> {
566            fn extend<T: IntoIterator<Item = A>>(&mut self, iter: T) {
567                if self.0.is_none() {
568                    self.0 = iter.into_iter().next()
569                }
570            }
571        }
572
573        let Measurement {
574            name,
575            qubit,
576            target,
577        } = measurement;
578
579        // Find the last matching measurement calibration, but prefer an exact qubit match to a
580        // wildcard qubit match.
581        let (First(exact), First(wildcard)) = self
582            .iter_measure_calibrations()
583            .rev()
584            .filter_map(|calibration| {
585                let identifier = &calibration.identifier;
586
587                if !(name == &identifier.name && target.is_some() == identifier.target.is_some()) {
588                    return None;
589                }
590
591                match &identifier.qubit {
592                    fixed @ Qubit::Fixed(_) if qubit == fixed => Some((calibration, true)),
593                    Qubit::Variable(_) => Some((calibration, false)),
594                    Qubit::Fixed(_) | Qubit::Placeholder(_) => None,
595                }
596            })
597            .partition_map(|(calibration, exact)| {
598                if exact {
599                    Either::Left(calibration)
600                } else {
601                    Either::Right(calibration)
602                }
603            });
604
605        exact.or(wildcard)
606    }
607
608    /// Return the final calibration which matches the gate per the QuilT specification:
609    ///
610    /// A calibration matches a gate if:
611    /// 1. It has the same name
612    /// 2. It has the same modifiers
613    /// 3. It has the same qubit count (any mix of fixed & variable)
614    /// 4. It has the same parameter count (both specified and unspecified)
615    /// 5. All fixed qubits in the calibration definition match those in the gate
616    /// 6. All specified parameters in the calibration definition match those in the gate
617    pub fn get_match_for_gate(&self, gate: &Gate) -> Option<&CalibrationDefinition> {
618        let mut matched_calibration: Option<MatchedCalibration> = None;
619
620        for calibration in self
621            .iter_calibrations()
622            .filter(|calibration| calibration.identifier.matches(gate))
623        {
624            matched_calibration = match matched_calibration {
625                None => Some(MatchedCalibration::new(calibration)),
626                Some(previous_match) => {
627                    let potential_match = MatchedCalibration::new(calibration);
628                    if potential_match.fixed_qubit_count >= previous_match.fixed_qubit_count {
629                        Some(potential_match)
630                    } else {
631                        Some(previous_match)
632                    }
633                }
634            }
635        }
636
637        matched_calibration.map(|m| m.calibration)
638    }
639
640    /// Return the Quil instructions which describe the contained calibrations, consuming the [`CalibrationSet`]
641    pub fn into_instructions(self) -> Vec<Instruction> {
642        self.calibrations
643            .into_iter()
644            .map(Instruction::CalibrationDefinition)
645            .chain(
646                self.measure_calibrations
647                    .into_iter()
648                    .map(Instruction::MeasureCalibrationDefinition),
649            )
650            .collect()
651    }
652}
653
654#[cfg(test)]
655mod tests {
656    use std::str::FromStr;
657
658    use crate::program::calibration::{CalibrationSource, MeasureCalibrationIdentifier};
659    use crate::program::source_map::{ExpansionResult, SourceMap, SourceMapEntry};
660    use crate::program::{InstructionIndex, Program};
661    use crate::quil::Quil;
662
663    use insta::assert_snapshot;
664    use rstest::rstest;
665
666    use super::{CalibrationExpansion, CalibrationExpansionOutput, CalibrationIdentifier};
667
668    #[rstest]
669    #[case(
670        "Calibration-Param-Precedence",
671        concat!(
672            "DEFCAL RX(%theta) %qubit:\n",
673            "    PULSE 1 \"xy\" gaussian(duration: 1, fwhm: 2, t0: 3)\n",
674            "DEFCAL RX(%theta) 0:\n",
675            "    PULSE 2 \"xy\" gaussian(duration: 1, fwhm: 2, t0: 3)\n",
676            "DEFCAL RX(pi/2) 0:\n",
677            "    PULSE 3 \"xy\" gaussian(duration: 1, fwhm: 2, t0: 3)\n",
678            "RX(pi/2) 1\n",
679            "RX(pi) 0\n",
680            "RX(pi/2) 0\n"
681        ),
682    )]
683    #[case(
684        "Calibration-Simple",
685        concat!(
686            "DEFCAL X 0:\n",
687            "    PULSE 0 \"xy\" gaussian(duration: 1, fwhm: 2, t0: 3)\n",
688            "X 0\n",
689        ),
690    )]
691    #[case(
692        "Calibration-Literal-Parameter",
693        concat!(
694            "DEFCAL RX(3.141592653589793) 0:\n",
695            "    NOP\n",
696            "RX(3.141592653589793) 0\n",
697        ),
698    )]
699    #[case(
700        "Calibration-Instruction-Match",
701        concat!(
702            "DEFCAL X 0:\n",
703            "    Y 0\n",
704            "DEFCAL Y 0:\n",
705            "    PULSE 0 \"xy\" gaussian(duration: 1, fwhm: 2, t0: 3)\n",
706            "X 0\n"
707        ),
708    )]
709    #[case(
710        "Measure-Calibration",
711        concat!(
712            "DEFCAL MEASURE 0 addr:\n",
713            "    PRAGMA INCORRECT_ORDERING\n",
714            "DEFCAL MEASURE 0 addr:\n",
715            "    PRAGMA CORRECT\n",
716            "DEFCAL MEASURE q addr:\n",
717            "    PRAGMA INCORRECT_PRECEDENCE\n",
718            "DEFCAL MEASURE 1 addr:\n",
719            "    PRAGMA INCORRECT_QUBIT\n",
720            "DEFCAL MEASURE q:\n",
721            "    PRAGMA INCORRECT_RECORD_VS_EFFECT\n",
722            "MEASURE 0 ro\n",
723        ),
724    )]
725    #[case(
726        "Calibration-Variable-Qubit",
727        concat!("DEFCAL I %q:\n", "    DELAY q 4e-8\n", "I 0\n",),
728    )]
729    #[case(
730        "Precedence-Fixed-Match",
731        concat!(
732            "DEFCAL MEASURE q:\n",
733            "    PRAGMA INCORRECT_RECORD_VS_EFFECT\n",
734            "DEFCAL MEASURE b addr:\n",
735            "    PRAGMA INCORRECT_PRECEDENCE\n",
736            "DEFCAL MEASURE 0 addr:\n",
737            "    PRAGMA INCORRECT_ORDER\n",
738            "DEFCAL MEASURE 0 addr:\n",
739            "    PRAGMA CORRECT\n",
740            "DEFCAL MEASURE q addr:\n",
741            "    PRAGMA INCORRECT_PRECEDENCE\n",
742            "DEFCAL MEASURE 1 addr:\n",
743            "    PRAGMA INCORRECT_QUBIT\n",
744            "MEASURE 0 ro\n",
745        )
746    )]
747    #[case(
748        "Precedence-Variable-Match",
749        concat!(
750            "DEFCAL MEASURE q:\n",
751            "    PRAGMA INCORRECT_RECORD_VS_EFFECT\n",
752            "DEFCAL MEASURE b addr:\n",
753            "    PRAGMA INCORRECT_ORDER\n",
754            "DEFCAL MEASURE q addr:\n",
755            "    PRAGMA CORRECT\n",
756            "MEASURE 0 ro\n",
757        )
758    )]
759    #[case(
760        "Precedence-No-Target-Match",
761        concat!(
762            "DEFCAL MEASURE b:\n",
763            "    PRAGMA INCORRECT_SAME_NAME_SHOULD_NOT_APPEAR_IN_OUTPUT\n",
764            "DEFCAL MEASURE b:\n",
765            "    PRAGMA INCORRECT_ORDER\n",
766            "DEFCAL MEASURE q:\n",
767            "    PRAGMA CORRECT\n",
768            "MEASURE 0\n",
769        )
770    )]
771    #[case(
772        "Precedence-Prefer-Exact-Qubit-Match",
773        concat!(
774            "DEFCAL MEASURE 0 addr:\n",
775            "    PRAGMA CORRECT\n",
776            "DEFCAL MEASURE q addr:\n",
777            "    PRAGMA INCORRECT_PRECEDENCE\n",
778            "MEASURE 0 ro\n",
779        )
780    )]
781    #[case(
782        "Precedence-Prefer-Exact-Qubit-Match-No-Target",
783        concat!(
784            "DEFCAL MEASURE 0:\n",
785            "    PRAGMA CORRECT\n",
786            "DEFCAL MEASURE q:\n",
787            "    PRAGMA INCORRECT_PRECEDENCE\n",
788            "MEASURE 0\n",
789        )
790    )]
791    #[case(
792        "Precedence-Require-No-Name-Match",
793        concat!(
794            "DEFCAL MEASURE q addr:\n",
795            "    PRAGMA CORRECT\n",
796            "DEFCAL MEASURE!wrong-name 0 addr:\n",
797            "    PRAGMA INCORRECT_NAME\n",
798            "DEFCAL MEASURE!midcircuit 0 addr:\n",
799            "    PRAGMA INCORRECT_NAME\n",
800            "MEASURE 0 ro\n",
801        )
802    )]
803    #[case(
804        "Precedence-Require-Name-Match",
805        concat!(
806            "DEFCAL MEASURE!midcircuit q addr:\n",
807            "    PRAGMA CORRECT\n",
808            "DEFCAL MEASURE!wrong-name 0 addr:\n",
809            "    PRAGMA INCORRECT_NAME\n",
810            "DEFCAL MEASURE 0 addr:\n",
811            "    PRAGMA INCORRECT_NAME\n",
812            "MEASURE!midcircuit 0 ro\n",
813        )
814    )]
815    #[case(
816        "ShiftPhase",
817        concat!(
818            "DEFCAL RZ(%theta) %q:\n",
819            "    SHIFT-PHASE %q \"rf\" -%theta\n",
820            "RZ(pi) 0\n",
821        )
822    )]
823    #[case(
824        "FenceVariableQubit",
825        concat!(
826            "DEFCAL FENCES q0 q1:\n",
827            "    FENCE q0\n",
828            "    FENCE q1\n",
829            "FENCES 0 1\n",
830        )
831    )]
832    fn test_expansion(#[case] description: &str, #[case] input: &str) {
833        let program = Program::from_str(input).unwrap();
834        let calibrated_program = program.expand_calibrations().unwrap();
835        insta::with_settings!({
836            snapshot_suffix => description,
837        }, {
838            assert_snapshot!(calibrated_program.to_quil_or_debug())
839        })
840    }
841
842    /// Assert that instruction expansion yields the expected [`SourceMap`] and resulting instructions.
843    #[test]
844    fn expand_with_detail_recursive() {
845        let input = r#"
846DEFCAL X 0:
847    Y 0
848    MEASURE 0 ro
849    Y 0
850
851DEFCAL Y 0:
852    NOP
853    Z 0
854
855DEFCAL Z 0:
856    WAIT
857
858DEFCAL MEASURE 0 addr:
859    HALT
860
861X 0
862"#;
863
864        let program = Program::from_str(input).unwrap();
865        let instruction = program.instructions.last().unwrap();
866        let expansion = program
867            .calibrations
868            .expand_with_detail(instruction, &[])
869            .unwrap();
870        let expected = CalibrationExpansionOutput {
871            new_instructions: vec![
872                crate::instruction::Instruction::Nop(),
873                crate::instruction::Instruction::Wait(),
874                crate::instruction::Instruction::Halt(),
875                crate::instruction::Instruction::Nop(),
876                crate::instruction::Instruction::Wait(),
877            ],
878            detail: CalibrationExpansion {
879                calibration_used: CalibrationSource::Calibration(CalibrationIdentifier {
880                    modifiers: vec![],
881                    name: "X".to_string(),
882                    parameters: vec![],
883                    qubits: vec![crate::instruction::Qubit::Fixed(0)],
884                }),
885                range: InstructionIndex(0)..InstructionIndex(5),
886                expansions: SourceMap {
887                    entries: vec![
888                        SourceMapEntry {
889                            source_location: InstructionIndex(0),
890                            target_location: ExpansionResult::Rewritten(CalibrationExpansion {
891                                calibration_used: CalibrationSource::Calibration(
892                                    CalibrationIdentifier {
893                                        modifiers: vec![],
894                                        name: "Y".to_string(),
895                                        parameters: vec![],
896                                        qubits: vec![crate::instruction::Qubit::Fixed(0)],
897                                    },
898                                ),
899                                range: InstructionIndex(0)..InstructionIndex(2),
900                                expansions: SourceMap {
901                                    entries: vec![
902                                        SourceMapEntry {
903                                            source_location: InstructionIndex(0),
904                                            target_location: ExpansionResult::Unmodified(
905                                                InstructionIndex(0),
906                                            ),
907                                        },
908                                        SourceMapEntry {
909                                            source_location: InstructionIndex(1),
910                                            target_location: ExpansionResult::Rewritten(
911                                                CalibrationExpansion {
912                                                    calibration_used:
913                                                        CalibrationSource::Calibration(
914                                                            CalibrationIdentifier {
915                                                                modifiers: vec![],
916                                                                name: "Z".to_string(),
917                                                                parameters: vec![],
918                                                                qubits: vec![
919                                                            crate::instruction::Qubit::Fixed(0),
920                                                        ],
921                                                            },
922                                                        ),
923                                                    range: InstructionIndex(1)..InstructionIndex(2),
924                                                    expansions: SourceMap {
925                                                        entries: vec![SourceMapEntry {
926                                                            source_location: InstructionIndex(0),
927                                                            target_location:
928                                                                ExpansionResult::Unmodified(
929                                                                    InstructionIndex(0),
930                                                                ),
931                                                        }],
932                                                    },
933                                                },
934                                            ),
935                                        },
936                                    ],
937                                },
938                            }),
939                        },
940                        SourceMapEntry {
941                            source_location: InstructionIndex(1),
942                            target_location: ExpansionResult::Rewritten(CalibrationExpansion {
943                                calibration_used: CalibrationSource::MeasureCalibration(
944                                    MeasureCalibrationIdentifier {
945                                        name: None,
946                                        qubit: crate::instruction::Qubit::Fixed(0),
947                                        target: Some("addr".to_string()),
948                                    },
949                                ),
950                                range: InstructionIndex(2)..InstructionIndex(3),
951                                expansions: SourceMap {
952                                    entries: vec![SourceMapEntry {
953                                        source_location: InstructionIndex(0),
954                                        target_location: ExpansionResult::Unmodified(
955                                            InstructionIndex(0),
956                                        ),
957                                    }],
958                                },
959                            }),
960                        },
961                        SourceMapEntry {
962                            source_location: InstructionIndex(2),
963                            target_location: ExpansionResult::Rewritten(CalibrationExpansion {
964                                calibration_used: CalibrationSource::Calibration(
965                                    CalibrationIdentifier {
966                                        modifiers: vec![],
967                                        name: "Y".to_string(),
968                                        parameters: vec![],
969                                        qubits: vec![crate::instruction::Qubit::Fixed(0)],
970                                    },
971                                ),
972                                range: InstructionIndex(3)..InstructionIndex(5),
973                                expansions: SourceMap {
974                                    entries: vec![
975                                        SourceMapEntry {
976                                            source_location: InstructionIndex(0),
977                                            target_location: ExpansionResult::Unmodified(
978                                                InstructionIndex(0),
979                                            ),
980                                        },
981                                        SourceMapEntry {
982                                            source_location: InstructionIndex(1),
983                                            target_location: ExpansionResult::Rewritten(
984                                                CalibrationExpansion {
985                                                    calibration_used:
986                                                        CalibrationSource::Calibration(
987                                                            CalibrationIdentifier {
988                                                                modifiers: vec![],
989                                                                name: "Z".to_string(),
990                                                                parameters: vec![],
991                                                                qubits: vec![
992                                                            crate::instruction::Qubit::Fixed(0),
993                                                        ],
994                                                            },
995                                                        ),
996                                                    range: InstructionIndex(1)..InstructionIndex(2),
997                                                    expansions: SourceMap {
998                                                        entries: vec![SourceMapEntry {
999                                                            source_location: InstructionIndex(0),
1000                                                            target_location:
1001                                                                ExpansionResult::Unmodified(
1002                                                                    InstructionIndex(0),
1003                                                                ),
1004                                                        }],
1005                                                    },
1006                                                },
1007                                            ),
1008                                        },
1009                                    ],
1010                                },
1011                            }),
1012                        },
1013                    ],
1014                },
1015            },
1016        };
1017
1018        pretty_assertions::assert_eq!(expansion, Some(expected));
1019    }
1020
1021    #[test]
1022    fn test_eq() {
1023        let input = "DEFCAL X 0:
1024    PULSE 0 \"xy\" gaussian(duration: 1, fwhm: 2, t0: 3)
1025X 0";
1026        let a = Program::from_str(input);
1027        let b = Program::from_str(input);
1028        assert_eq!(a, b);
1029    }
1030
1031    #[test]
1032    fn test_ne() {
1033        let input_a = "DEFCAL X 0:
1034    PULSE 0 \"xy\" gaussian(duration: 1, fwhm: 2, t0: 3)
1035X 0";
1036        let input_b = "DEFCAL X 1:
1037    PULSE 1 \"xy\" gaussian(duration: 1, fwhm: 2, t0: 3)
1038X 1";
1039        let a = Program::from_str(input_a);
1040        let b = Program::from_str(input_b);
1041        assert_ne!(a, b);
1042    }
1043}