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