From 1a1e8a6c899f07f7a921dab15f4535032a39592d Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Fri, 3 May 2024 23:26:17 -0700 Subject: [PATCH 01/39] feat: add source mapping for calibration expansion --- Cargo.lock | 29 ++- quil-rs/Cargo.toml | 1 + quil-rs/src/instruction/classical.rs | 6 + quil-rs/src/program/calibration.rs | 321 +++++++++++++++++++++++++-- quil-rs/src/program/mod.rs | 141 ++++++++++-- quil-rs/src/program/source_map.rs | 72 ++++++ 6 files changed, 533 insertions(+), 37 deletions(-) create mode 100644 quil-rs/src/program/source_map.rs diff --git a/Cargo.lock b/Cargo.lock index 5995977c..daf45910 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -332,6 +332,12 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "dot-writer" version = "0.1.3" @@ -1009,6 +1015,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "proc-macro2" version = "0.4.30" @@ -1132,7 +1148,7 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quil-cli" -version = "0.2.1" +version = "0.3.1-rc.0" dependencies = [ "anyhow", "clap", @@ -1141,7 +1157,7 @@ dependencies = [ [[package]] name = "quil-py" -version = "0.9.1" +version = "0.10.1-rc.0" dependencies = [ "indexmap", "ndarray", @@ -1155,7 +1171,7 @@ dependencies = [ [[package]] name = "quil-rs" -version = "0.25.1" +version = "0.26.1-rc.0" dependencies = [ "approx", "clap", @@ -1171,6 +1187,7 @@ dependencies = [ "num-complex", "once_cell", "petgraph", + "pretty_assertions", "proptest", "proptest-derive", "rand", @@ -2035,3 +2052,9 @@ name = "windows_x86_64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/quil-rs/Cargo.toml b/quil-rs/Cargo.toml index a5e018d0..e13c410f 100644 --- a/quil-rs/Cargo.toml +++ b/quil-rs/Cargo.toml @@ -32,6 +32,7 @@ clap = { version = "4.3.19", features = ["derive", "string"] } criterion = { version = "0.5.1", features = ["html_reports"] } insta = "1.37.0" petgraph = "0.6.2" +pretty_assertions = "1.4.0" proptest = "1.0.0" proptest-derive = "0.3.0" rand = "0.8.5" diff --git a/quil-rs/src/instruction/classical.rs b/quil-rs/src/instruction/classical.rs index f1bd18b4..dbc980ce 100644 --- a/quil-rs/src/instruction/classical.rs +++ b/quil-rs/src/instruction/classical.rs @@ -68,6 +68,12 @@ impl Quil for ArithmeticOperand { } } +impl From for ArithmeticOperand { + fn from(memory_reference: MemoryReference) -> Self { + ArithmeticOperand::MemoryReference(memory_reference) + } +} + #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] pub enum ArithmeticOperator { Add, diff --git a/quil-rs/src/program/calibration.rs b/quil-rs/src/program/calibration.rs index 2e0bccbe..40bd8402 100644 --- a/quil-rs/src/program/calibration.rs +++ b/quil-rs/src/program/calibration.rs @@ -17,6 +17,7 @@ use std::collections::HashMap; use itertools::FoldWhile::{Continue, Done}; use itertools::Itertools; +use crate::instruction::GateModifier; use crate::quil::Quil; use crate::{ expression::Expression, @@ -27,6 +28,7 @@ use crate::{ }, }; +use super::source_map::{SourceMap, SourceMapEntry, SourceMapRange}; use super::{CalibrationSet, ProgramError}; /// A collection of Quil calibrations (`DEFCAL` instructions) with utility methods. @@ -57,6 +59,111 @@ impl<'a> MatchedCalibration<'a> { } } +#[derive(Clone, Debug, PartialEq)] +pub struct CalibrationExpansionOutput { + pub new_instructions: Vec, + pub detail: CalibrationExpansion, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct CalibrationExpansion { + pub calibration_used: CalibrationSource, + pub length: usize, + pub expansions: SourceMap, +} + +impl SourceMapRange for CalibrationExpansion { + type Value = usize; + + fn start(&self) -> &usize { + &0 + } + + fn contains(&self, value: &Self::Value) -> bool { + self.length > *value + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum MaybeCalibrationExpansion { + Expanded(CalibrationExpansion), + Unexpanded(usize), +} + +impl SourceMapRange for MaybeCalibrationExpansion { + type Value = usize; + + fn start(&self) -> &usize { + match self { + MaybeCalibrationExpansion::Expanded(expansion) => expansion.start(), + MaybeCalibrationExpansion::Unexpanded(index) => index, + } + } + + fn contains(&self, value: &Self::Value) -> bool { + match self { + MaybeCalibrationExpansion::Expanded(expansion) => expansion.contains(value), + MaybeCalibrationExpansion::Unexpanded(index) => index == value, + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum CalibrationSource { + Calibration(CalibrationIdentifier), + MeasureCalibration(MeasureCalibrationIdentifier), +} + +impl From for CalibrationSource { + fn from(value: CalibrationIdentifier) -> Self { + Self::Calibration(value) + } +} + +impl From for CalibrationSource { + fn from(value: MeasureCalibrationIdentifier) -> Self { + Self::MeasureCalibration(value) + } +} + +// For review consideration: making this a structural part of Calibration would +// (a) make some sense in principle and +// (b) allow CalibrationExpansion to contain a reference to it instead of an owned, cloned copy +#[derive(Clone, Debug, Default, PartialEq)] +pub struct CalibrationIdentifier { + pub modifiers: Vec, + pub name: String, + pub parameters: Vec, + pub qubits: Vec, +} + +impl From<&Calibration> for CalibrationIdentifier { + fn from(value: &Calibration) -> Self { + Self { + modifiers: value.modifiers.clone(), + name: value.name.clone(), + parameters: value.parameters.clone(), + qubits: value.qubits.clone(), + } + } +} + +// For review: how would we feel about making this a subfield of the `MeasureCalibrationDefinition` itself? +#[derive(Clone, Debug, Default, PartialEq)] +pub struct MeasureCalibrationIdentifier { + pub qubit: Option, + pub parameter: String, +} + +impl From<&MeasureCalibrationDefinition> for MeasureCalibrationIdentifier { + fn from(value: &MeasureCalibrationDefinition) -> Self { + Self { + qubit: value.qubit.clone(), + parameter: value.parameter.clone(), + } + } +} + impl Calibrations { /// Return a vector containing a reference to all [`Calibration`]s in the set. pub fn calibrations(&self) -> Vec<&Calibration> { @@ -79,18 +186,27 @@ impl Calibrations { self.measure_calibrations.iter() } + pub fn expand( + &self, + instruction: &Instruction, + previous_calibrations: &[Instruction], + ) -> Result>, ProgramError> { + self.expand_with_detail(instruction, previous_calibrations) + .map(|expansion| expansion.map(|expansion| expansion.new_instructions)) + } + /// Given an instruction, return the instructions to which it is expanded if there is a match. /// Recursively calibrate instructions, returning an error if a calibration directly or indirectly /// expands into itself. - pub fn expand( + pub fn expand_with_detail( &self, instruction: &Instruction, previous_calibrations: &[Instruction], - ) -> Result>, ProgramError> { + ) -> Result, ProgramError> { if previous_calibrations.contains(instruction) { return Err(ProgramError::RecursiveCalibration(instruction.clone())); } - let expanded_once_instructions = match instruction { + let calibration_result = match instruction { Instruction::Gate(gate) => { let matching_calibration = self.get_match_for_gate(gate); @@ -180,7 +296,10 @@ impl Calibrations { }) } - Some(instructions) + Some(( + instructions, + CalibrationSource::Calibration(calibration.into()), + )) } None => None, } @@ -210,7 +329,10 @@ impl Calibrations { _ => {} } } - Some(instructions) + Some(( + instructions, + CalibrationSource::MeasureCalibration(calibration.into()), + )) } None => None, } @@ -219,25 +341,50 @@ impl Calibrations { }; // Add this instruction to the breadcrumb trail before recursion - let mut downstream_previous_calibrations = - Vec::with_capacity(previous_calibrations.len() + 1); - downstream_previous_calibrations.push(instruction.clone()); - downstream_previous_calibrations.extend_from_slice(previous_calibrations); - - Ok(match expanded_once_instructions { - Some(instructions) => { - let mut recursively_expanded_instructions = vec![]; - - for instruction in instructions { + let mut calibration_path = Vec::with_capacity(previous_calibrations.len() + 1); + calibration_path.push(instruction.clone()); + calibration_path.extend_from_slice(previous_calibrations); + + Ok(match calibration_result { + Some((instructions, matched_calibration)) => { + let mut recursively_expanded_instructions = CalibrationExpansionOutput { + new_instructions: Vec::new(), + detail: CalibrationExpansion { + calibration_used: matched_calibration, + length: 0, + expansions: SourceMap::default(), + }, + }; + + for (expanded_index, instruction) in instructions.into_iter().enumerate() { let expanded_instructions = - self.expand(&instruction, &downstream_previous_calibrations)?; + self.expand_with_detail(&instruction, &calibration_path)?; match expanded_instructions { - Some(instructions) => { - recursively_expanded_instructions.extend(instructions) + Some(output) => { + recursively_expanded_instructions + .new_instructions + .extend(output.new_instructions); + recursively_expanded_instructions + .detail + .expansions + .entries + .push(SourceMapEntry { + source_location: expanded_index, + target_location: output.detail, + }); + } + None => { + recursively_expanded_instructions + .new_instructions + .push(instruction); } - None => recursively_expanded_instructions.push(instruction), }; } + + // While this appears to be duplicated information at this point, it's useful when multiple + // source mappings are merged together. + recursively_expanded_instructions.detail.length = + recursively_expanded_instructions.new_instructions.len(); Some(recursively_expanded_instructions) } None => None, @@ -441,12 +588,16 @@ impl Calibrations { mod tests { use std::str::FromStr; + use crate::program::calibration::{CalibrationSource, MeasureCalibrationIdentifier}; + use crate::program::source_map::{SourceMap, SourceMapEntry}; use crate::program::Program; use crate::quil::Quil; use insta::assert_snapshot; use rstest::rstest; + use super::{CalibrationExpansion, CalibrationExpansionOutput, CalibrationIdentifier}; + #[rstest] #[case( "Calibration-Param-Precedence", @@ -572,6 +723,138 @@ mod tests { }) } + #[test] + fn expand_with_detail_recursive() { + let input = r#" +DEFCAL X 0: + Y 0 + MEASURE 0 ro + Y 0 + +DEFCAL Y 0: + NOP + Z 0 + +DEFCAL Z 0: + WAIT + +DEFCAL MEASURE 0 addr: + HALT + +X 0 +"#; + + let program = Program::from_str(input).unwrap(); + let instruction = program.instructions.last().unwrap(); + let expansion = program + .calibrations + .expand_with_detail(instruction, &[]) + .unwrap(); + let expected = CalibrationExpansionOutput { + new_instructions: vec![ + crate::instruction::Instruction::Nop, + crate::instruction::Instruction::Wait, + crate::instruction::Instruction::Halt, + crate::instruction::Instruction::Nop, + crate::instruction::Instruction::Wait, + ], + detail: CalibrationExpansion { + calibration_used: CalibrationSource::Calibration(CalibrationIdentifier { + modifiers: vec![], + name: "X".to_string(), + parameters: vec![], + qubits: vec![crate::instruction::Qubit::Fixed(0)], + }), + length: 5, + expansions: SourceMap { + entries: vec![ + SourceMapEntry { + source_location: 0, + target_location: CalibrationExpansion { + calibration_used: CalibrationSource::Calibration( + CalibrationIdentifier { + modifiers: vec![], + name: "Y".to_string(), + parameters: vec![], + qubits: vec![crate::instruction::Qubit::Fixed(0)], + }, + ), + length: 2, + expansions: SourceMap { + entries: vec![SourceMapEntry { + source_location: 1, + target_location: CalibrationExpansion { + calibration_used: CalibrationSource::Calibration( + CalibrationIdentifier { + modifiers: vec![], + name: "Z".to_string(), + parameters: vec![], + qubits: vec![crate::instruction::Qubit::Fixed( + 0, + )], + }, + ), + length: 1, + expansions: SourceMap::default(), + }, + }], + }, + }, + }, + SourceMapEntry { + source_location: 1, + target_location: CalibrationExpansion { + calibration_used: CalibrationSource::MeasureCalibration( + MeasureCalibrationIdentifier { + qubit: Some(crate::instruction::Qubit::Fixed(0)), + parameter: "addr".to_string(), + }, + ), + length: 1, + expansions: SourceMap::default(), + }, + }, + SourceMapEntry { + source_location: 2, + target_location: CalibrationExpansion { + calibration_used: CalibrationSource::Calibration( + CalibrationIdentifier { + modifiers: vec![], + name: "Y".to_string(), + parameters: vec![], + qubits: vec![crate::instruction::Qubit::Fixed(0)], + }, + ), + length: 2, + expansions: SourceMap { + entries: vec![SourceMapEntry { + source_location: 1, + target_location: CalibrationExpansion { + calibration_used: CalibrationSource::Calibration( + CalibrationIdentifier { + modifiers: vec![], + name: "Z".to_string(), + parameters: vec![], + qubits: vec![crate::instruction::Qubit::Fixed( + 0, + )], + }, + ), + length: 1, + expansions: SourceMap::default(), + }, + }], + }, + }, + }, + ], + }, + }, + }; + + pretty_assertions::assert_eq!(expansion, Some(expected)); + } + #[test] fn test_eq() { let input = "DEFCAL X 0: diff --git a/quil-rs/src/program/mod.rs b/quil-rs/src/program/mod.rs index a7d4c5bb..9a007111 100644 --- a/quil-rs/src/program/mod.rs +++ b/quil-rs/src/program/mod.rs @@ -13,7 +13,7 @@ // limitations under the License. use std::collections::{HashMap, HashSet}; -use std::ops; +use std::ops::{self}; use std::str::FromStr; use indexmap::{IndexMap, IndexSet}; @@ -30,11 +30,13 @@ use crate::parser::{lex, parse_instructions, ParseError}; use crate::quil::Quil; pub use self::calibration::Calibrations; +use self::calibration::MaybeCalibrationExpansion; pub use self::calibration_set::CalibrationSet; pub use self::error::{disallow_leftover, map_parsed, recover, ParseProgramError, SyntaxError}; pub use self::frame::FrameSet; pub use self::frame::MatchedFrames; pub use self::memory::{MemoryAccesses, MemoryRegion}; +use self::source_map::{SourceMap, SourceMapEntry}; pub mod analysis; mod calibration; @@ -43,6 +45,7 @@ mod error; pub(crate) mod frame; mod memory; pub mod scheduling; +mod source_map; pub mod type_check; #[derive(Debug, thiserror::Error, PartialEq)] @@ -235,22 +238,29 @@ impl Program { } /// Expand any instructions in the program which have a matching calibration, leaving the others - /// unchanged. Recurses though each instruction while ensuring there is no cycle in the expansion - /// graph (i.e. no calibration expands directly or indirectly into itself) + /// unchanged. Returns the expanded copy of the program and a source mapping of the expansions made. + /// + /// See [`Program::expand_calibrations_with_source_map`] for a version that returns a source mapping. pub fn expand_calibrations(&self) -> Result { - let mut expanded_instructions: Vec = vec![]; + self._expand_calibrations(None) + } - for instruction in &self.instructions { - match self.calibrations.expand(instruction, &[])? { - Some(expanded) => { - expanded_instructions.extend(expanded); - } - None => { - expanded_instructions.push(instruction.clone()); - } - } - } + /// Expand any instructions in the program which have a matching calibration, leaving the others + /// unchanged. Returns the expanded copy of the program and a source mapping of the expansions made. + pub fn expand_calibrations_with_source_map(&self) -> Result { + let mut source_mapping = ProgramCalibrationExpansionSourceMapping::default(); + let new_program = self._expand_calibrations(Some(&mut source_mapping))?; + + Ok(ProgramCalibrationExpansion { + program: new_program, + source_mapping, + }) + } + fn _expand_calibrations( + &self, + mut source_mapping: Option<&mut ProgramCalibrationExpansionSourceMapping>, + ) -> Result { let mut new_program = Self { calibrations: self.calibrations.clone(), frames: self.frames.clone(), @@ -261,7 +271,37 @@ impl Program { used_qubits: HashSet::new(), }; - new_program.add_instructions(expanded_instructions); + for (index, instruction) in self.instructions.iter().enumerate() { + match self.calibrations.expand_with_detail(instruction, &[])? { + Some(mut expanded) => { + let previous_program_instruction_body_length = new_program.instructions.len(); + new_program.add_instructions(expanded.new_instructions); + if let Some(source_mapping) = source_mapping.as_mut() { + let length_considering_instructions_hoisted_to_header = + new_program.instructions.len() + - previous_program_instruction_body_length; + + expanded.detail.length = length_considering_instructions_hoisted_to_header; + + source_mapping.entries.push(SourceMapEntry { + source_location: index, + target_location: MaybeCalibrationExpansion::Expanded(expanded.detail), + }); + } + } + None => { + new_program.add_instruction(instruction.clone()); + if let Some(source_mapping) = source_mapping.as_mut() { + source_mapping.entries.push(SourceMapEntry { + source_location: index, + target_location: MaybeCalibrationExpansion::Unexpanded( + new_program.instructions.len() - 1, + ), + }); + } + } + } + } Ok(new_program) } @@ -693,6 +733,16 @@ impl ops::AddAssign for Program { } } +type InstructionIndex = usize; +pub type ProgramCalibrationExpansionSourceMapping = + SourceMap; + +#[derive(Clone, Debug, PartialEq)] +pub struct ProgramCalibrationExpansion { + pub program: Program, + pub source_mapping: ProgramCalibrationExpansionSourceMapping, +} + #[cfg(test)] mod tests { use super::Program; @@ -702,6 +752,10 @@ mod tests { Gate, Instruction, Jump, JumpUnless, JumpWhen, Label, Matrix, MemoryReference, Qubit, QubitPlaceholder, Target, TargetPlaceholder, }, + program::{ + calibration::{CalibrationExpansion, CalibrationIdentifier, MaybeCalibrationExpansion}, + source_map::{SourceMap, SourceMapEntry}, + }, quil::Quil, real, }; @@ -825,6 +879,63 @@ DECLARE ec BIT assert!(program1.lines().eq(program2.lines())); } + #[test] + fn expand_calibrations() { + let input = "DEFFRAME 0 \"a\": +\tHARDWARE-OBJECT: \"hardware\" + +DEFCAL I 0: + DECLARE ro BIT[1] + NOP + NOP + NOP + +I 0 +PULSE 0 \"a\" custom_waveform +"; + + let expected = "DECLARE ro BIT[1] +DEFFRAME 0 \"a\": +\tHARDWARE-OBJECT: \"hardware\" +DEFCAL I 0: +\tDECLARE ro BIT[1] +\tNOP +\tNOP +\tNOP +NOP +NOP +NOP +PULSE 0 \"a\" custom_waveform +"; + + let expected_source_map = SourceMap { + entries: vec![ + SourceMapEntry { + source_location: 0, + target_location: MaybeCalibrationExpansion::Expanded(CalibrationExpansion { + calibration_used: CalibrationIdentifier { + name: "I".to_string(), + qubits: vec![Qubit::Fixed(0)], + ..CalibrationIdentifier::default() + } + .into(), + length: 3, + expansions: SourceMap::default(), + }), + }, + SourceMapEntry { + source_location: 1, + target_location: MaybeCalibrationExpansion::Unexpanded(3), + }, + ], + }; + + let program = Program::from_str(input).unwrap(); + let expanded_program = program.expand_calibrations_with_source_map().unwrap(); + pretty_assertions::assert_eq!(expanded_program.program.to_quil().unwrap(), expected); + pretty_assertions::assert_eq!(expanded_program.source_mapping, expected_source_map); + } + #[test] fn frame_blocking() { let input = "DEFFRAME 0 \"a\": diff --git a/quil-rs/src/program/source_map.rs b/quil-rs/src/program/source_map.rs new file mode 100644 index 00000000..94577104 --- /dev/null +++ b/quil-rs/src/program/source_map.rs @@ -0,0 +1,72 @@ +use std::ops::Range; + +#[derive(Clone, Debug, PartialEq)] +pub struct SourceMap { + pub(crate) entries: Vec>, +} + +impl Default + for SourceMap +{ + fn default() -> Self { + Self { + entries: Vec::new(), + } + } +} + +impl SourceMap { + pub fn entries(&self) -> &[SourceMapEntry] { + &self.entries + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct SourceMapEntry { + pub(crate) source_location: SourceIndex, + pub(crate) target_location: TargetIndex, +} + +pub trait SourceMapRange { + type Value: PartialOrd; + + fn contains(&self, value: &Self::Value) -> bool; + + fn start(&self) -> &Self::Value; +} + +impl SourceMapRange for usize { + type Value = usize; + + fn contains(&self, value: &Self::Value) -> bool { + self == value + } + + fn start(&self) -> &Self::Value { + self + } +} + +impl SourceMapRange for Range { + type Value = usize; + + fn contains(&self, value: &Self::Value) -> bool { + self.contains(value) + } + + fn start(&self) -> &Self::Value { + &self.start + } +} + +impl + SourceMapEntry +{ + pub fn source_location(&self) -> &SourceIndex { + &self.source_location + } + + pub fn target_location(&self) -> &TargetIndex { + &self.target_location + } +} From 3a319f14c050b9ae654c651c70d754527880eecd Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Tue, 7 May 2024 13:11:46 -0700 Subject: [PATCH 02/39] chore: improve docs --- quil-rs/src/program/calibration.rs | 30 +++++++++++++++++++++- quil-rs/src/program/mod.rs | 5 +++- quil-rs/src/program/source_map.rs | 41 +++++++++++++++++++++--------- 3 files changed, 62 insertions(+), 14 deletions(-) diff --git a/quil-rs/src/program/calibration.rs b/quil-rs/src/program/calibration.rs index 40bd8402..70ef53bd 100644 --- a/quil-rs/src/program/calibration.rs +++ b/quil-rs/src/program/calibration.rs @@ -59,16 +59,26 @@ impl<'a> MatchedCalibration<'a> { } } +/// The product of expanding an instruction using a calibration #[derive(Clone, Debug, PartialEq)] pub struct CalibrationExpansionOutput { + /// The new instructions resulting from the expansion pub new_instructions: Vec, + + /// Details about the expansion process pub detail: CalibrationExpansion, } +/// Details about the expansion of a calibration #[derive(Clone, Debug, PartialEq)] pub struct CalibrationExpansion { + /// The calibration used to expand the instruction pub calibration_used: CalibrationSource, + + /// The number of instructions yielded by the expansion pub length: usize, + + /// A map of source locations to the expansions they produced pub expansions: SourceMap, } @@ -84,9 +94,13 @@ impl SourceMapRange for CalibrationExpansion { } } +/// The result of an attempt to expand an instruction within a [`Program`] #[derive(Clone, Debug, PartialEq)] pub enum MaybeCalibrationExpansion { + /// The instruction was expanded into others Expanded(CalibrationExpansion), + + /// The instruction was not expanded, but was simply copied over into the target program at the given instruction index Unexpanded(usize), } @@ -108,9 +122,13 @@ impl SourceMapRange for MaybeCalibrationExpansion { } } +/// A source of a calibration, either a [`Calibration`] or a [`MeasureCalibrationDefinition`] #[derive(Clone, Debug, PartialEq)] pub enum CalibrationSource { + /// Describes a `DEFCAL` instruction Calibration(CalibrationIdentifier), + + /// Describes a `DEFCAL MEASURE` instruction MeasureCalibration(MeasureCalibrationIdentifier), } @@ -131,9 +149,16 @@ impl From for CalibrationSource { // (b) allow CalibrationExpansion to contain a reference to it instead of an owned, cloned copy #[derive(Clone, Debug, Default, PartialEq)] pub struct CalibrationIdentifier { + /// The modifiers applied to the gate pub modifiers: Vec, + + /// The name of the gate pub name: String, + + /// The parameters of the gate - these are the variables in the calibration definition pub parameters: Vec, + + /// The qubits on which the gate is applied pub qubits: Vec, } @@ -151,7 +176,10 @@ impl From<&Calibration> for CalibrationIdentifier { // For review: how would we feel about making this a subfield of the `MeasureCalibrationDefinition` itself? #[derive(Clone, Debug, Default, PartialEq)] pub struct MeasureCalibrationIdentifier { + /// The qubit which is the target of measurement, if any pub qubit: Option, + + /// The memory region name to which the measurement result is written pub parameter: String, } @@ -684,7 +712,6 @@ mod tests { " PRAGMA CORRECT\n", "MEASURE 0 ro\n", ) - )] #[case( "Precedence-No-Qubit-Match", @@ -723,6 +750,7 @@ mod tests { }) } + /// Assert that instruction expansion yields the expected [`SourceMap`] and resulting instructions. #[test] fn expand_with_detail_recursive() { let input = r#" diff --git a/quil-rs/src/program/mod.rs b/quil-rs/src/program/mod.rs index 9a007111..2bc40fff 100644 --- a/quil-rs/src/program/mod.rs +++ b/quil-rs/src/program/mod.rs @@ -238,7 +238,7 @@ impl Program { } /// Expand any instructions in the program which have a matching calibration, leaving the others - /// unchanged. Returns the expanded copy of the program and a source mapping of the expansions made. + /// unchanged. Returns the expanded copy of the program. /// /// See [`Program::expand_calibrations_with_source_map`] for a version that returns a source mapping. pub fn expand_calibrations(&self) -> Result { @@ -257,6 +257,9 @@ impl Program { }) } + /// Expand calibrations, writing expansions to a [`SourceMap`] if provided. + /// + /// Source map may be omitted for faster performance. fn _expand_calibrations( &self, mut source_mapping: Option<&mut ProgramCalibrationExpansionSourceMapping>, diff --git a/quil-rs/src/program/source_map.rs b/quil-rs/src/program/source_map.rs index 94577104..d65a4818 100644 --- a/quil-rs/src/program/source_map.rs +++ b/quil-rs/src/program/source_map.rs @@ -1,5 +1,15 @@ use std::ops::Range; +/// A SourceMap provides information necessary to understand which parts of a target +/// were derived from which parts of a source artifact, in such a way that they can be +/// mapped in either direction. +/// +/// The behavior of such mappings depends on the implementations of the generic `Index` types, +/// but this may be a many-to-many mapping, where one element of the source is mapped (contributed) +/// to zero or many elements of the target, and vice versa. +/// +/// This is also intended to be mergeable in a chain, such that the combined result of a series +/// of transformations can be expressed within a single source mapping. #[derive(Clone, Debug, PartialEq)] pub struct SourceMap { pub(crate) entries: Vec>, @@ -27,11 +37,30 @@ pub struct SourceMapEntry + SourceMapEntry +{ + pub fn source_location(&self) -> &SourceIndex { + &self.source_location + } + + pub fn target_location(&self) -> &TargetIndex { + &self.target_location + } +} + +/// A `SourceMapRange` is a section of the source or target artifact that can be mapped to. +/// +/// This may be a single line, a range of lines, or a more complex data structure. pub trait SourceMapRange { type Value: PartialOrd; + /// Whether the given value is contained (even partially) within this range. + /// + /// This is used to determine overlap between source and target ranges. fn contains(&self, value: &Self::Value) -> bool; + /// The starting value of this range. fn start(&self) -> &Self::Value; } @@ -58,15 +87,3 @@ impl SourceMapRange for Range { &self.start } } - -impl - SourceMapEntry -{ - pub fn source_location(&self) -> &SourceIndex { - &self.source_location - } - - pub fn target_location(&self) -> &TargetIndex { - &self.target_location - } -} From e13abf75fa0563365cf8535977682a61033b1ea3 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Tue, 7 May 2024 13:49:07 -0700 Subject: [PATCH 03/39] fix bug in target range mapping in calibration expansion --- quil-rs/src/program/calibration.rs | 36 +++++++++++++++++++----------- quil-rs/src/program/mod.rs | 26 ++++++++++++++++----- quil-rs/src/program/source_map.rs | 3 +++ 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/quil-rs/src/program/calibration.rs b/quil-rs/src/program/calibration.rs index 70ef53bd..48f181f2 100644 --- a/quil-rs/src/program/calibration.rs +++ b/quil-rs/src/program/calibration.rs @@ -13,6 +13,7 @@ // limitations under the License. use std::collections::HashMap; +use std::ops::Range; use itertools::FoldWhile::{Continue, Done}; use itertools::Itertools; @@ -75,8 +76,8 @@ pub struct CalibrationExpansion { /// The calibration used to expand the instruction pub calibration_used: CalibrationSource, - /// The number of instructions yielded by the expansion - pub length: usize, + /// The target instruction indices produced by the expansion + pub range: Range, /// A map of source locations to the expansions they produced pub expansions: SourceMap, @@ -90,7 +91,7 @@ impl SourceMapRange for CalibrationExpansion { } fn contains(&self, value: &Self::Value) -> bool { - self.length > *value + self.range.contains(value) } } @@ -379,7 +380,7 @@ impl Calibrations { new_instructions: Vec::new(), detail: CalibrationExpansion { calibration_used: matched_calibration, - length: 0, + range: 0..0, expansions: SourceMap::default(), }, }; @@ -388,10 +389,19 @@ impl Calibrations { let expanded_instructions = self.expand_with_detail(&instruction, &calibration_path)?; match expanded_instructions { - Some(output) => { + Some(mut output) => { + let range_start = + recursively_expanded_instructions.new_instructions.len(); + recursively_expanded_instructions .new_instructions .extend(output.new_instructions); + + let range_end = + recursively_expanded_instructions.new_instructions.len(); + let target_range = range_start..range_end; + output.detail.range = target_range; + recursively_expanded_instructions .detail .expansions @@ -411,8 +421,8 @@ impl Calibrations { // While this appears to be duplicated information at this point, it's useful when multiple // source mappings are merged together. - recursively_expanded_instructions.detail.length = - recursively_expanded_instructions.new_instructions.len(); + recursively_expanded_instructions.detail.range = + 0..recursively_expanded_instructions.new_instructions.len(); Some(recursively_expanded_instructions) } None => None, @@ -793,7 +803,7 @@ X 0 parameters: vec![], qubits: vec![crate::instruction::Qubit::Fixed(0)], }), - length: 5, + range: 0..5, expansions: SourceMap { entries: vec![ SourceMapEntry { @@ -807,7 +817,7 @@ X 0 qubits: vec![crate::instruction::Qubit::Fixed(0)], }, ), - length: 2, + range: 0..2, expansions: SourceMap { entries: vec![SourceMapEntry { source_location: 1, @@ -822,7 +832,7 @@ X 0 )], }, ), - length: 1, + range: 1..2, expansions: SourceMap::default(), }, }], @@ -838,7 +848,7 @@ X 0 parameter: "addr".to_string(), }, ), - length: 1, + range: 2..3, expansions: SourceMap::default(), }, }, @@ -853,7 +863,7 @@ X 0 qubits: vec![crate::instruction::Qubit::Fixed(0)], }, ), - length: 2, + range: 3..5, expansions: SourceMap { entries: vec![SourceMapEntry { source_location: 1, @@ -868,7 +878,7 @@ X 0 )], }, ), - length: 1, + range: 1..2, expansions: SourceMap::default(), }, }], diff --git a/quil-rs/src/program/mod.rs b/quil-rs/src/program/mod.rs index 2bc40fff..8d09e461 100644 --- a/quil-rs/src/program/mod.rs +++ b/quil-rs/src/program/mod.rs @@ -280,11 +280,8 @@ impl Program { let previous_program_instruction_body_length = new_program.instructions.len(); new_program.add_instructions(expanded.new_instructions); if let Some(source_mapping) = source_mapping.as_mut() { - let length_considering_instructions_hoisted_to_header = - new_program.instructions.len() - - previous_program_instruction_body_length; - - expanded.detail.length = length_considering_instructions_hoisted_to_header; + expanded.detail.range = previous_program_instruction_body_length + ..new_program.instructions.len(); source_mapping.entries.push(SourceMapEntry { source_location: index, @@ -895,6 +892,7 @@ DEFCAL I 0: I 0 PULSE 0 \"a\" custom_waveform +I 0 "; let expected = "DECLARE ro BIT[1] @@ -909,6 +907,9 @@ NOP NOP NOP PULSE 0 \"a\" custom_waveform +NOP +NOP +NOP "; let expected_source_map = SourceMap { @@ -922,7 +923,7 @@ PULSE 0 \"a\" custom_waveform ..CalibrationIdentifier::default() } .into(), - length: 3, + range: 0..3, expansions: SourceMap::default(), }), }, @@ -930,6 +931,19 @@ PULSE 0 \"a\" custom_waveform source_location: 1, target_location: MaybeCalibrationExpansion::Unexpanded(3), }, + SourceMapEntry { + source_location: 2, + target_location: MaybeCalibrationExpansion::Expanded(CalibrationExpansion { + calibration_used: CalibrationIdentifier { + name: "I".to_string(), + qubits: vec![Qubit::Fixed(0)], + ..CalibrationIdentifier::default() + } + .into(), + range: 4..7, + expansions: SourceMap::default(), + }), + }, ], }; diff --git a/quil-rs/src/program/source_map.rs b/quil-rs/src/program/source_map.rs index d65a4818..97d6a03e 100644 --- a/quil-rs/src/program/source_map.rs +++ b/quil-rs/src/program/source_map.rs @@ -33,7 +33,10 @@ impl SourceMap { + /// The locator within the source artifact pub(crate) source_location: SourceIndex, + + /// The locator within the target artifact pub(crate) target_location: TargetIndex, } From 917ba2330c6407e93b1831a10f4baa4ce05726da Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Tue, 7 May 2024 14:08:07 -0700 Subject: [PATCH 04/39] WIP: header-aware program calibration expansion --- quil-rs/src/program/mod.rs | 58 +++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/quil-rs/src/program/mod.rs b/quil-rs/src/program/mod.rs index 8d09e461..56c13874 100644 --- a/quil-rs/src/program/mod.rs +++ b/quil-rs/src/program/mod.rs @@ -30,7 +30,9 @@ use crate::parser::{lex, parse_instructions, ParseError}; use crate::quil::Quil; pub use self::calibration::Calibrations; -use self::calibration::MaybeCalibrationExpansion; +use self::calibration::{ + CalibrationExpansion, CalibrationExpansionOutput, MaybeCalibrationExpansion, +}; pub use self::calibration_set::CalibrationSet; pub use self::error::{disallow_leftover, map_parsed, recover, ParseProgramError, SyntaxError}; pub use self::frame::FrameSet; @@ -276,18 +278,12 @@ impl Program { for (index, instruction) in self.instructions.iter().enumerate() { match self.calibrations.expand_with_detail(instruction, &[])? { - Some(mut expanded) => { - let previous_program_instruction_body_length = new_program.instructions.len(); - new_program.add_instructions(expanded.new_instructions); - if let Some(source_mapping) = source_mapping.as_mut() { - expanded.detail.range = previous_program_instruction_body_length - ..new_program.instructions.len(); - - source_mapping.entries.push(SourceMapEntry { - source_location: index, - target_location: MaybeCalibrationExpansion::Expanded(expanded.detail), - }); - } + Some(expanded) => { + new_program._append_calibration_expansion_output( + expanded, + index, + &mut source_mapping, + ); } None => { new_program.add_instruction(instruction.clone()); @@ -306,6 +302,27 @@ impl Program { Ok(new_program) } + fn _append_calibration_expansion_output( + &mut self, + mut expansion_output: CalibrationExpansionOutput, + source_index: usize, + source_mapping: &mut Option<&mut ProgramCalibrationExpansionSourceMapping>, + ) { + let previous_program_instruction_body_length = self.instructions.len(); + self.add_instructions(expansion_output.new_instructions); + if let Some(source_mapping) = source_mapping.as_mut() { + expansion_output.detail.range = + previous_program_instruction_body_length..self.instructions.len(); + + if expansion_output.detail.range.len() > 0 { + source_mapping.entries.push(SourceMapEntry { + source_location: source_index, + target_location: MaybeCalibrationExpansion::Expanded(expansion_output.detail), + }); + } + } + } + /// Build a program from a list of instructions pub fn from_instructions(instructions: Vec) -> Self { let mut program = Self::default(); @@ -879,15 +896,21 @@ DECLARE ec BIT assert!(program1.lines().eq(program2.lines())); } + /// Assert that a program's instructions are correctly expanded using its calibrations, + /// emitting the expected [`SourceMap`] for the expansion. #[test] fn expand_calibrations() { - let input = "DEFFRAME 0 \"a\": + let input = "DECLARE ro BIT[1] +DEFFRAME 0 \"a\": \tHARDWARE-OBJECT: \"hardware\" DEFCAL I 0: - DECLARE ro BIT[1] + DECLAREMEM NOP NOP + +DEFCAL DECLAREMEM: + DECLARE mem BIT[1] NOP I 0 @@ -896,12 +919,15 @@ I 0 "; let expected = "DECLARE ro BIT[1] +DECLARE mem BIT[1] DEFFRAME 0 \"a\": \tHARDWARE-OBJECT: \"hardware\" DEFCAL I 0: -\tDECLARE ro BIT[1] +\tDECLAREMEM \tNOP \tNOP +DEFCAL DECLAREMEM: +\tDECLARE mem BIT[1] \tNOP NOP NOP From bf246b3cce0e9ea5da107638461688d7a756b641 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Wed, 8 May 2024 18:07:56 -0700 Subject: [PATCH 05/39] fix bug in program calibration expansion into header instructions --- quil-rs/src/program/calibration.rs | 34 ++++++++++++++ quil-rs/src/program/mod.rs | 72 ++++++++++++++++++++++++++---- 2 files changed, 98 insertions(+), 8 deletions(-) diff --git a/quil-rs/src/program/calibration.rs b/quil-rs/src/program/calibration.rs index 48f181f2..aeb54cc5 100644 --- a/quil-rs/src/program/calibration.rs +++ b/quil-rs/src/program/calibration.rs @@ -83,6 +83,40 @@ pub struct CalibrationExpansion { pub expansions: SourceMap, } +impl CalibrationExpansion { + /// Remove the given target index from all entries, recursively. + /// + /// This is to be used when the named index is removed from the target program + /// in the process of calibration expansion. + pub(crate) fn remove_target_index(&mut self, target_index: usize) { + eprintln!("Removing target index {} from\n{self:#?}", target_index); + + if self.range.start >= target_index { + self.range.start = self.range.start.saturating_sub(1); + + if self.range.end > target_index { + self.range.end = self.range.end.saturating_sub(1); + } + } + + let target_index_offset = self.range.start; + eprintln!("Offset: {}", target_index_offset); + + self.expansions.entries.retain_mut( + |entry: &mut SourceMapEntry| { + if let Some(target_with_offset) = target_index.checked_sub(target_index_offset) { + eprintln!("Target with offset: {}", target_with_offset); + entry + .target_location + .remove_target_index(target_with_offset); + } + + entry.target_location.range.len() > 0 + }, + ); + } +} + impl SourceMapRange for CalibrationExpansion { type Value = usize; diff --git a/quil-rs/src/program/mod.rs b/quil-rs/src/program/mod.rs index 56c13874..d4df0719 100644 --- a/quil-rs/src/program/mod.rs +++ b/quil-rs/src/program/mod.rs @@ -30,9 +30,7 @@ use crate::parser::{lex, parse_instructions, ParseError}; use crate::quil::Quil; pub use self::calibration::Calibrations; -use self::calibration::{ - CalibrationExpansion, CalibrationExpansionOutput, MaybeCalibrationExpansion, -}; +use self::calibration::{CalibrationExpansionOutput, MaybeCalibrationExpansion}; pub use self::calibration_set::CalibrationSet; pub use self::error::{disallow_leftover, map_parsed, recover, ParseProgramError, SyntaxError}; pub use self::frame::FrameSet; @@ -302,15 +300,36 @@ impl Program { Ok(new_program) } + /// Append the result of a calibration expansion to this program, being aware of which expanded instructions + /// land in the program body (and thus merit inclusion within a target range) and which do not. + /// + /// For example, `DECLARE` instructions are hoisted to a specialized data structure and thus do not appear in + /// the program body. Thus, they should not be counted in the `target_index` range within a [`SourceMapEntry`]. fn _append_calibration_expansion_output( &mut self, mut expansion_output: CalibrationExpansionOutput, source_index: usize, source_mapping: &mut Option<&mut ProgramCalibrationExpansionSourceMapping>, ) { - let previous_program_instruction_body_length = self.instructions.len(); - self.add_instructions(expansion_output.new_instructions); if let Some(source_mapping) = source_mapping.as_mut() { + let previous_program_instruction_body_length = self.instructions.len(); + + for instruction in expansion_output.new_instructions { + let start_length = self.instructions.len(); + self.add_instruction(instruction.clone()); + let end_length = self.instructions.len(); + + // If the instruction was not added to the program body, remove its target index from the source map + // so that the map stays correct. + if start_length == end_length { + let relative_target_index = + start_length - previous_program_instruction_body_length; + expansion_output + .detail + .remove_target_index(relative_target_index); + } + } + expansion_output.detail.range = previous_program_instruction_body_length..self.instructions.len(); @@ -320,6 +339,8 @@ impl Program { target_location: MaybeCalibrationExpansion::Expanded(expansion_output.detail), }); } + } else { + self.add_instructions(expansion_output.new_instructions); } } @@ -770,7 +791,10 @@ mod tests { QubitPlaceholder, Target, TargetPlaceholder, }, program::{ - calibration::{CalibrationExpansion, CalibrationIdentifier, MaybeCalibrationExpansion}, + calibration::{ + CalibrationExpansion, CalibrationIdentifier, CalibrationSource, + MaybeCalibrationExpansion, + }, source_map::{SourceMap, SourceMapEntry}, }, quil::Quil, @@ -950,7 +974,23 @@ NOP } .into(), range: 0..3, - expansions: SourceMap::default(), + expansions: SourceMap { + entries: vec![SourceMapEntry { + source_location: 0, + target_location: CalibrationExpansion { + calibration_used: CalibrationSource::Calibration( + CalibrationIdentifier { + modifiers: vec![], + name: "DECLAREMEM".to_string(), + parameters: vec![], + qubits: vec![], + }, + ), + range: 0..1, + expansions: SourceMap { entries: vec![] }, + }, + }], + }, }), }, SourceMapEntry { @@ -967,7 +1007,23 @@ NOP } .into(), range: 4..7, - expansions: SourceMap::default(), + expansions: SourceMap { + entries: vec![SourceMapEntry { + source_location: 0, + target_location: CalibrationExpansion { + calibration_used: CalibrationSource::Calibration( + CalibrationIdentifier { + modifiers: vec![], + name: "DECLAREMEM".to_string(), + parameters: vec![], + qubits: vec![], + }, + ), + range: 0..1, + expansions: SourceMap { entries: vec![] }, + }, + }], + }, }), }, ], From 277105d140002bf7f9299f79f1208c2d253d7626 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Wed, 8 May 2024 18:10:24 -0700 Subject: [PATCH 06/39] remove unnecessary SourceMapRange trait --- quil-rs/src/program/calibration.rs | 32 +---------------- quil-rs/src/program/source_map.rs | 55 +++--------------------------- 2 files changed, 6 insertions(+), 81 deletions(-) diff --git a/quil-rs/src/program/calibration.rs b/quil-rs/src/program/calibration.rs index aeb54cc5..46cacc98 100644 --- a/quil-rs/src/program/calibration.rs +++ b/quil-rs/src/program/calibration.rs @@ -29,7 +29,7 @@ use crate::{ }, }; -use super::source_map::{SourceMap, SourceMapEntry, SourceMapRange}; +use super::source_map::{SourceMap, SourceMapEntry}; use super::{CalibrationSet, ProgramError}; /// A collection of Quil calibrations (`DEFCAL` instructions) with utility methods. @@ -117,18 +117,6 @@ impl CalibrationExpansion { } } -impl SourceMapRange for CalibrationExpansion { - type Value = usize; - - fn start(&self) -> &usize { - &0 - } - - fn contains(&self, value: &Self::Value) -> bool { - self.range.contains(value) - } -} - /// The result of an attempt to expand an instruction within a [`Program`] #[derive(Clone, Debug, PartialEq)] pub enum MaybeCalibrationExpansion { @@ -139,24 +127,6 @@ pub enum MaybeCalibrationExpansion { Unexpanded(usize), } -impl SourceMapRange for MaybeCalibrationExpansion { - type Value = usize; - - fn start(&self) -> &usize { - match self { - MaybeCalibrationExpansion::Expanded(expansion) => expansion.start(), - MaybeCalibrationExpansion::Unexpanded(index) => index, - } - } - - fn contains(&self, value: &Self::Value) -> bool { - match self { - MaybeCalibrationExpansion::Expanded(expansion) => expansion.contains(value), - MaybeCalibrationExpansion::Unexpanded(index) => index == value, - } - } -} - /// A source of a calibration, either a [`Calibration`] or a [`MeasureCalibrationDefinition`] #[derive(Clone, Debug, PartialEq)] pub enum CalibrationSource { diff --git a/quil-rs/src/program/source_map.rs b/quil-rs/src/program/source_map.rs index 97d6a03e..5b86203f 100644 --- a/quil-rs/src/program/source_map.rs +++ b/quil-rs/src/program/source_map.rs @@ -1,5 +1,3 @@ -use std::ops::Range; - /// A SourceMap provides information necessary to understand which parts of a target /// were derived from which parts of a source artifact, in such a way that they can be /// mapped in either direction. @@ -11,13 +9,11 @@ use std::ops::Range; /// This is also intended to be mergeable in a chain, such that the combined result of a series /// of transformations can be expressed within a single source mapping. #[derive(Clone, Debug, PartialEq)] -pub struct SourceMap { +pub struct SourceMap { pub(crate) entries: Vec>, } -impl Default - for SourceMap -{ +impl Default for SourceMap { fn default() -> Self { Self { entries: Vec::new(), @@ -25,14 +21,14 @@ impl Default } } -impl SourceMap { +impl SourceMap { pub fn entries(&self) -> &[SourceMapEntry] { &self.entries } } #[derive(Clone, Debug, PartialEq)] -pub struct SourceMapEntry { +pub struct SourceMapEntry { /// The locator within the source artifact pub(crate) source_location: SourceIndex, @@ -40,9 +36,7 @@ pub struct SourceMapEntry - SourceMapEntry -{ +impl SourceMapEntry { pub fn source_location(&self) -> &SourceIndex { &self.source_location } @@ -51,42 +45,3 @@ impl &self.target_location } } - -/// A `SourceMapRange` is a section of the source or target artifact that can be mapped to. -/// -/// This may be a single line, a range of lines, or a more complex data structure. -pub trait SourceMapRange { - type Value: PartialOrd; - - /// Whether the given value is contained (even partially) within this range. - /// - /// This is used to determine overlap between source and target ranges. - fn contains(&self, value: &Self::Value) -> bool; - - /// The starting value of this range. - fn start(&self) -> &Self::Value; -} - -impl SourceMapRange for usize { - type Value = usize; - - fn contains(&self, value: &Self::Value) -> bool { - self == value - } - - fn start(&self) -> &Self::Value { - self - } -} - -impl SourceMapRange for Range { - type Value = usize; - - fn contains(&self, value: &Self::Value) -> bool { - self.contains(value) - } - - fn start(&self) -> &Self::Value { - &self.start - } -} From b35f8fe362cebe1951e23d9dafb3cab250b9cde4 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Wed, 8 May 2024 19:04:41 -0700 Subject: [PATCH 07/39] add source map python bindings --- quil-py/src/program/mod.rs | 11 +++ quil-py/src/program/source_map.rs | 134 +++++++++++++++++++++++++++++ quil-rs/src/program/calibration.rs | 18 +++- quil-rs/src/program/mod.rs | 32 ++++--- 4 files changed, 182 insertions(+), 13 deletions(-) create mode 100644 quil-py/src/program/source_map.rs diff --git a/quil-py/src/program/mod.rs b/quil-py/src/program/mod.rs index 9df4ab7d..3e42919d 100644 --- a/quil-py/src/program/mod.rs +++ b/quil-py/src/program/mod.rs @@ -37,6 +37,7 @@ use crate::{ use self::{ analysis::{PyBasicBlock, PyControlFlowGraph}, scheduling::{PyScheduleSeconds, PyScheduleSecondsItem, PyTimeSpanSeconds}, + source_map::PyProgramCalibrationExpansion, }; pub use self::{calibration::PyCalibrationSet, frame::PyFrameSet, memory::PyMemoryRegion}; @@ -45,6 +46,7 @@ mod calibration; mod frame; mod memory; mod scheduling; +mod source_map; wrap_error!(ProgramError(quil_rs::program::ProgramError)); py_wrap_error!(quil, ProgramError, PyProgramError, PyValueError); @@ -87,6 +89,15 @@ impl PyProgram { ControlFlowGraphOwned::from(ControlFlowGraph::from(self.as_inner())).into() } + pub fn expand_calibrations_with_source_map(&self) -> PyResult { + let expansion = self + .as_inner() + .expand_calibrations_with_source_map() + .map_err(ProgramError::from) + .map_err(ProgramError::to_py_err)?; + Ok(expansion.into()) + } + #[getter] pub fn body_instructions<'a>(&self, py: Python<'a>) -> PyResult<&'a PyList> { Ok(PyList::new( diff --git a/quil-py/src/program/source_map.rs b/quil-py/src/program/source_map.rs new file mode 100644 index 00000000..6d457954 --- /dev/null +++ b/quil-py/src/program/source_map.rs @@ -0,0 +1,134 @@ +use pyo3::{types::PyInt, Py}; +use quil_rs::program::{ + CalibrationExpansion, CalibrationSource, MaybeCalibrationExpansion, + ProgramCalibrationExpansion, ProgramCalibrationExpansionSourceMap, SourceMap, SourceMapEntry, +}; +use rigetti_pyo3::{impl_repr, py_wrap_type, py_wrap_union_enum, pyo3::pymethods, PyWrapper}; + +use super::PyProgram; + +py_wrap_type! { + #[derive(Debug, PartialEq)] + PyProgramCalibrationExpansion(ProgramCalibrationExpansion) as "ProgramCalibrationExpansion" +} + +impl_repr!(PyProgramCalibrationExpansion); + +#[pymethods] +impl PyProgramCalibrationExpansion { + pub fn program(&self) -> PyProgram { + self.as_inner().program().into() + } + + pub fn source_mapping(&self) -> PyProgramCalibrationExpansionSourceMap { + self.as_inner().source_mapping().clone().into() + } +} + +py_wrap_type! { + #[derive(Debug, PartialEq)] + PyProgramCalibrationExpansionSourceMap(ProgramCalibrationExpansionSourceMap) as "ProgramCalibrationExpansionSourceMap" +} + +impl_repr!(PyProgramCalibrationExpansionSourceMap); + +#[pymethods] +impl PyProgramCalibrationExpansionSourceMap { + pub fn entries(&self) -> Vec { + self.as_inner() + .entries() + .iter() + .map(|entry| entry.into()) + .collect() + } +} + +py_wrap_union_enum! { + #[derive(Debug, PartialEq)] + PyMaybeCalibrationExpansion(MaybeCalibrationExpansion) as "MaybeCalibrationExpansion" { + expanded: Expanded => PyCalibrationExpansion, + unexpanded: Unexpanded => usize => Py + } +} + +// impl_repr!(PyMaybeCalibrationExpansion); + +type CalibrationExpansionSourceMap = SourceMap; +type CalibrationExpansionSourceMapEntry = SourceMapEntry; +type ProgramCalibrationExpansionSourceMapEntry = SourceMapEntry; + +py_wrap_type! { + #[derive(Debug, PartialEq)] + PyProgramCalibrationExpansionSourceMapEntry(ProgramCalibrationExpansionSourceMapEntry) as "ProgramCalibrationExpansionSourceMapEntry" +} + +impl_repr!(PyProgramCalibrationExpansionSourceMapEntry); + +py_wrap_type! { + #[derive(Debug, PartialEq)] + PyCalibrationExpansion(CalibrationExpansion) as "CalibrationExpansion" +} + +impl_repr!(PyCalibrationExpansion); + +#[pymethods] +impl PyCalibrationExpansion { + pub fn calibration_used(&self) -> PyCalibrationSource { + self.as_inner().calibration_used().into() + } + + // Reviewer: is there a better return type? + pub fn range(&self) -> (usize, usize) { + (self.as_inner().range().start, self.as_inner().range().end) + } + + pub fn expansions(&self) -> PyCalibrationExpansionSourceMap { + self.as_inner().expansions().into() + } +} + +py_wrap_type! { + #[derive(Debug, PartialEq)] + PyCalibrationExpansionSourceMap(CalibrationExpansionSourceMap) as "CalibrationExpansionSourceMap" +} + +impl_repr!(PyCalibrationExpansionSourceMap); + +#[pymethods] +impl PyCalibrationExpansionSourceMap { + pub fn entries(&self) -> Vec { + self.as_inner() + .entries() + .iter() + .map(|entry| entry.into()) + .collect() + } +} + +py_wrap_type! { + #[derive(Debug, PartialEq)] + PyCalibrationExpansionSourceMapEntry(CalibrationExpansionSourceMapEntry) as "CalibrationExpansionSourceMapEntry" +} + +impl_repr!(PyCalibrationExpansionSourceMapEntry); + +#[pymethods] +impl PyCalibrationExpansionSourceMapEntry { + pub fn source_location(&self) -> usize { + self.as_inner().source_location().clone().into() + } + + pub fn target_location(&self) -> PyCalibrationExpansion { + self.as_inner().target_location().clone().into() + } +} + +py_wrap_union_enum! { + #[derive(Debug, PartialEq)] + PyCalibrationSource(CalibrationSource) as "CalibrationSource" { + calibration: Calibration => PyCalibrationIdentifier, + measurement_calibration: MeasurementCalibration => PyMeasurementCalibrationIdentifier + } +} + +impl_repr!(PyCalibrationSource); diff --git a/quil-rs/src/program/calibration.rs b/quil-rs/src/program/calibration.rs index 46cacc98..6490f5f8 100644 --- a/quil-rs/src/program/calibration.rs +++ b/quil-rs/src/program/calibration.rs @@ -74,13 +74,13 @@ pub struct CalibrationExpansionOutput { #[derive(Clone, Debug, PartialEq)] pub struct CalibrationExpansion { /// The calibration used to expand the instruction - pub calibration_used: CalibrationSource, + pub(crate) calibration_used: CalibrationSource, /// The target instruction indices produced by the expansion - pub range: Range, + pub(crate) range: Range, /// A map of source locations to the expansions they produced - pub expansions: SourceMap, + pub(crate) expansions: SourceMap, } impl CalibrationExpansion { @@ -115,6 +115,18 @@ impl CalibrationExpansion { }, ); } + + pub fn calibration_used(&self) -> &CalibrationSource { + &self.calibration_used + } + + pub fn range(&self) -> &Range { + &self.range + } + + pub fn expansions(&self) -> &SourceMap { + &self.expansions + } } /// The result of an attempt to expand an instruction within a [`Program`] diff --git a/quil-rs/src/program/mod.rs b/quil-rs/src/program/mod.rs index d4df0719..570b9ca8 100644 --- a/quil-rs/src/program/mod.rs +++ b/quil-rs/src/program/mod.rs @@ -30,13 +30,15 @@ use crate::parser::{lex, parse_instructions, ParseError}; use crate::quil::Quil; pub use self::calibration::Calibrations; -use self::calibration::{CalibrationExpansionOutput, MaybeCalibrationExpansion}; +pub use self::calibration::{ + CalibrationExpansion, CalibrationExpansionOutput, CalibrationSource, MaybeCalibrationExpansion, +}; pub use self::calibration_set::CalibrationSet; pub use self::error::{disallow_leftover, map_parsed, recover, ParseProgramError, SyntaxError}; pub use self::frame::FrameSet; pub use self::frame::MatchedFrames; pub use self::memory::{MemoryAccesses, MemoryRegion}; -use self::source_map::{SourceMap, SourceMapEntry}; +pub use self::source_map::{SourceMap, SourceMapEntry}; pub mod analysis; mod calibration; @@ -248,12 +250,12 @@ impl Program { /// Expand any instructions in the program which have a matching calibration, leaving the others /// unchanged. Returns the expanded copy of the program and a source mapping of the expansions made. pub fn expand_calibrations_with_source_map(&self) -> Result { - let mut source_mapping = ProgramCalibrationExpansionSourceMapping::default(); + let mut source_mapping = ProgramCalibrationExpansionSourceMap::default(); let new_program = self._expand_calibrations(Some(&mut source_mapping))?; Ok(ProgramCalibrationExpansion { program: new_program, - source_mapping, + source_map: source_mapping, }) } @@ -262,7 +264,7 @@ impl Program { /// Source map may be omitted for faster performance. fn _expand_calibrations( &self, - mut source_mapping: Option<&mut ProgramCalibrationExpansionSourceMapping>, + mut source_mapping: Option<&mut ProgramCalibrationExpansionSourceMap>, ) -> Result { let mut new_program = Self { calibrations: self.calibrations.clone(), @@ -309,7 +311,7 @@ impl Program { &mut self, mut expansion_output: CalibrationExpansionOutput, source_index: usize, - source_mapping: &mut Option<&mut ProgramCalibrationExpansionSourceMapping>, + source_mapping: &mut Option<&mut ProgramCalibrationExpansionSourceMap>, ) { if let Some(source_mapping) = source_mapping.as_mut() { let previous_program_instruction_body_length = self.instructions.len(); @@ -772,13 +774,23 @@ impl ops::AddAssign for Program { } type InstructionIndex = usize; -pub type ProgramCalibrationExpansionSourceMapping = +pub type ProgramCalibrationExpansionSourceMap = SourceMap; #[derive(Clone, Debug, PartialEq)] pub struct ProgramCalibrationExpansion { - pub program: Program, - pub source_mapping: ProgramCalibrationExpansionSourceMapping, + program: Program, + source_map: ProgramCalibrationExpansionSourceMap, +} + +impl ProgramCalibrationExpansion { + pub fn program(&self) -> &Program { + &self.program + } + + pub fn source_mapping(&self) -> &ProgramCalibrationExpansionSourceMap { + &self.source_map + } } #[cfg(test)] @@ -1032,7 +1044,7 @@ NOP let program = Program::from_str(input).unwrap(); let expanded_program = program.expand_calibrations_with_source_map().unwrap(); pretty_assertions::assert_eq!(expanded_program.program.to_quil().unwrap(), expected); - pretty_assertions::assert_eq!(expanded_program.source_mapping, expected_source_map); + pretty_assertions::assert_eq!(expanded_program.source_map, expected_source_map); } #[test] From 1bec3303a71a97c0202add21d8d0b87661fd35b4 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Wed, 8 May 2024 19:32:45 -0700 Subject: [PATCH 08/39] feat!: refactor identifiers out of Calibration and MeasureCalibrationDefinition --- quil-py/src/instruction/calibration.rs | 89 +++++++++++--- quil-py/src/instruction/mod.rs | 5 +- quil-py/src/program/source_map.rs | 4 +- quil-rs/src/instruction/calibration.rs | 161 +++++++++++++++++++------ quil-rs/src/instruction/mod.rs | 12 +- quil-rs/src/parser/command.rs | 29 +++-- quil-rs/src/parser/instruction.rs | 22 ++-- quil-rs/src/program/calibration.rs | 112 ++++++----------- quil-rs/src/program/memory.rs | 1 + quil-rs/src/program/mod.rs | 9 +- 10 files changed, 283 insertions(+), 161 deletions(-) diff --git a/quil-py/src/instruction/calibration.rs b/quil-py/src/instruction/calibration.rs index 146211c0..07b7a417 100644 --- a/quil-py/src/instruction/calibration.rs +++ b/quil-py/src/instruction/calibration.rs @@ -1,6 +1,9 @@ use quil_rs::{ expression::Expression, - instruction::{Calibration, GateModifier, Instruction, MeasureCalibrationDefinition, Qubit}, + instruction::{ + Calibration, CalibrationIdentifier, GateModifier, Instruction, + MeasureCalibrationDefinition, MeasureCalibrationIdentifier, Qubit, + }, }; use rigetti_pyo3::{ @@ -20,11 +23,8 @@ py_wrap_data_struct! { #[derive(Debug, PartialEq)] #[pyo3(subclass)] PyCalibration(Calibration) as "Calibration" { - instructions: Vec => Vec, - modifiers: Vec => Vec, - name: String => Py, - parameters: Vec => Vec, - qubits: Vec => Vec + identifier: CalibrationIdentifier => PyCalibrationIdentifier, + instructions: Vec => Vec } } impl_repr!(PyCalibration); @@ -34,22 +34,54 @@ impl_eq!(PyCalibration); #[pymethods] impl PyCalibration { + #[new] + pub fn new( + py: Python<'_>, + identifier: PyCalibrationIdentifier, + instructions: Vec, + ) -> PyResult { + Ok(Self( + Calibration::new( + CalibrationIdentifier::py_try_from(py, &identifier)?, + Vec::::py_try_from(py, &instructions)?, + ) + .map_err(RustIdentifierValidationError::from) + .map_err(RustIdentifierValidationError::to_py_err)?, + )) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq)] + #[pyo3(subclass)] + PyCalibrationIdentifier(CalibrationIdentifier) as "CalibrationIdentifier" { + modifiers: Vec => Vec, + name: String => Py, + parameters: Vec => Vec, + qubits: Vec => Vec + } +} +impl_repr!(PyCalibrationIdentifier); +impl_to_quil!(PyCalibrationIdentifier); +impl_copy_for_instruction!(PyCalibrationIdentifier); +impl_eq!(PyCalibrationIdentifier); + +#[pymethods] +impl PyCalibrationIdentifier { #[new] pub fn new( py: Python<'_>, name: &str, parameters: Vec, qubits: Vec, - instructions: Vec, modifiers: Vec, ) -> PyResult { Ok(Self( - Calibration::new( - name, + CalibrationIdentifier::new( + name.to_string(), + Vec::::py_try_from(py, &modifiers)?, Vec::::py_try_from(py, ¶meters)?, Vec::::py_try_from(py, &qubits)?, - Vec::::py_try_from(py, &instructions)?, - Vec::::py_try_from(py, &modifiers)?, ) .map_err(RustIdentifierValidationError::from) .map_err(RustIdentifierValidationError::to_py_err)?, @@ -61,8 +93,7 @@ py_wrap_data_struct! { #[derive(Debug, PartialEq)] #[pyo3(subclass)] PyMeasureCalibrationDefinition(MeasureCalibrationDefinition) as "MeasureCalibrationDefinition" { - qubit: Option => Option, - parameter: String => Py, + identifier: MeasureCalibrationIdentifier => PyMeasureCalibrationIdentifier, instructions: Vec => Vec } } @@ -73,6 +104,35 @@ impl_eq!(PyMeasureCalibrationDefinition); #[pymethods] impl PyMeasureCalibrationDefinition { + #[new] + #[pyo3(signature = (identifier, instructions))] + pub fn new( + py: Python<'_>, + identifier: PyMeasureCalibrationIdentifier, + instructions: Vec, + ) -> PyResult { + Ok(Self(MeasureCalibrationDefinition::new( + MeasureCalibrationIdentifier::py_try_from(py, &identifier)?, + Vec::::py_try_from(py, &instructions)?, + ))) + } +} + +py_wrap_data_struct! { + #[derive(Debug, PartialEq)] + #[pyo3(subclass)] + PyMeasureCalibrationIdentifier(MeasureCalibrationIdentifier) as "PyMeasureCalibrationIdentifier" { + qubit: Option => Option, + parameter: String => Py + } +} +impl_repr!(PyMeasureCalibrationIdentifier); +impl_to_quil!(PyMeasureCalibrationIdentifier); +impl_copy_for_instruction!(PyMeasureCalibrationIdentifier); +impl_eq!(PyMeasureCalibrationIdentifier); + +#[pymethods] +impl PyMeasureCalibrationIdentifier { #[new] #[pyo3(signature = (qubit, parameter, instructions))] pub fn new( @@ -81,10 +141,9 @@ impl PyMeasureCalibrationDefinition { parameter: String, instructions: Vec, ) -> PyResult { - Ok(Self(MeasureCalibrationDefinition::new( + Ok(Self(MeasureCalibrationIdentifier::new( Option::::py_try_from(py, &qubit)?, parameter, - Vec::::py_try_from(py, &instructions)?, ))) } } diff --git a/quil-py/src/instruction/mod.rs b/quil-py/src/instruction/mod.rs index bb2c4ba7..99c939cf 100644 --- a/quil-py/src/instruction/mod.rs +++ b/quil-py/src/instruction/mod.rs @@ -8,7 +8,10 @@ use rigetti_pyo3::{ use crate::{impl_eq, impl_to_quil}; pub use self::{ - calibration::{PyCalibration, PyMeasureCalibrationDefinition}, + calibration::{ + PyCalibration, PyCalibrationIdentifier, PyMeasureCalibrationDefinition, + PyMeasureCalibrationIdentifier, + }, circuit::PyCircuitDefinition, classical::{ PyArithmetic, PyArithmeticOperand, PyArithmeticOperator, PyBinaryLogic, PyBinaryOperand, diff --git a/quil-py/src/program/source_map.rs b/quil-py/src/program/source_map.rs index 6d457954..c62c5ca7 100644 --- a/quil-py/src/program/source_map.rs +++ b/quil-py/src/program/source_map.rs @@ -5,6 +5,8 @@ use quil_rs::program::{ }; use rigetti_pyo3::{impl_repr, py_wrap_type, py_wrap_union_enum, pyo3::pymethods, PyWrapper}; +use crate::instruction::{PyCalibrationIdentifier, PyMeasureCalibrationIdentifier}; + use super::PyProgram; py_wrap_type! { @@ -127,7 +129,7 @@ py_wrap_union_enum! { #[derive(Debug, PartialEq)] PyCalibrationSource(CalibrationSource) as "CalibrationSource" { calibration: Calibration => PyCalibrationIdentifier, - measurement_calibration: MeasurementCalibration => PyMeasurementCalibrationIdentifier + measurement_calibration: MeasureCalibration => PyMeasureCalibrationIdentifier } } diff --git a/quil-rs/src/instruction/calibration.rs b/quil-rs/src/instruction/calibration.rs index 88b5f3e0..f1ce4667 100644 --- a/quil-rs/src/instruction/calibration.rs +++ b/quil-rs/src/instruction/calibration.rs @@ -20,46 +20,42 @@ pub trait CalibrationSignature { #[derive(Clone, Debug, Default, PartialEq)] pub struct Calibration { + pub identifier: CalibrationIdentifier, pub instructions: Vec, - pub modifiers: Vec, - pub name: String, - pub parameters: Vec, - pub qubits: Vec, } impl Calibration { /// Builds a new calibration definition. - /// - /// # Errors - /// - /// Returns an error if the given name isn't a valid Quil identifier. pub fn new( - name: &str, - parameters: Vec, - qubits: Vec, + identifier: CalibrationIdentifier, instructions: Vec, - modifiers: Vec, ) -> Result { - validate_identifier(name)?; Ok(Self { + identifier, instructions, - modifiers, - name: name.to_string(), - parameters, - qubits, }) } } +impl CalibrationSignature for Calibration { + type Signature<'a> = (&'a str, &'a [Expression], &'a [Qubit]); + + fn signature(&self) -> Self::Signature<'_> { + self.identifier.signature() + } + + fn has_signature(&self, signature: &Self::Signature<'_>) -> bool { + self.identifier.has_signature(signature) + } +} + impl Quil for Calibration { fn write( &self, f: &mut impl std::fmt::Write, fall_back_to_debug: bool, ) -> crate::quil::ToQuilResult<()> { - write!(f, "DEFCAL {}", self.name)?; - write_expression_parameter_string(f, fall_back_to_debug, &self.parameters)?; - write_qubit_parameters(f, fall_back_to_debug, &self.qubits)?; + self.identifier.write(f, fall_back_to_debug)?; write!(f, ":")?; for instruction in &self.instructions { write!(f, "\n\t")?; @@ -69,7 +65,45 @@ impl Quil for Calibration { } } -impl CalibrationSignature for Calibration { +/// Unique identifier for a calibration definition within a program +#[derive(Clone, Debug, Default, PartialEq)] +pub struct CalibrationIdentifier { + /// The modifiers applied to the gate + pub modifiers: Vec, + + /// The name of the gate + pub name: String, + + /// The parameters of the gate - these are the variables in the calibration definition + pub parameters: Vec, + + /// The qubits on which the gate is applied + pub qubits: Vec, +} + +impl CalibrationIdentifier { + /// Builds a new calibration identifier. + /// + /// # Errors + /// + /// Returns an error if the given name isn't a valid Quil identifier. + pub fn new( + name: String, + modifiers: Vec, + parameters: Vec, + qubits: Vec, + ) -> Result { + validate_identifier(name.as_str())?; + Ok(Self { + modifiers, + name, + parameters, + qubits, + }) + } +} + +impl CalibrationSignature for CalibrationIdentifier { type Signature<'a> = (&'a str, &'a [Expression], &'a [Qubit]); fn signature(&self) -> Self::Signature<'_> { @@ -86,18 +120,29 @@ impl CalibrationSignature for Calibration { } } +impl Quil for CalibrationIdentifier { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + write!(f, "DEFCAL {}", self.name)?; + write_expression_parameter_string(f, fall_back_to_debug, &self.parameters)?; + write_qubit_parameters(f, fall_back_to_debug, &self.qubits)?; + Ok(()) + } +} + #[derive(Clone, Debug, PartialEq)] pub struct MeasureCalibrationDefinition { - pub qubit: Option, - pub parameter: String, + pub identifier: MeasureCalibrationIdentifier, pub instructions: Vec, } impl MeasureCalibrationDefinition { - pub fn new(qubit: Option, parameter: String, instructions: Vec) -> Self { + pub fn new(identifier: MeasureCalibrationIdentifier, instructions: Vec) -> Self { Self { - qubit, - parameter, + identifier, instructions, } } @@ -106,6 +151,49 @@ impl MeasureCalibrationDefinition { impl CalibrationSignature for MeasureCalibrationDefinition { type Signature<'a> = (Option<&'a Qubit>, &'a str); + fn signature(&self) -> Self::Signature<'_> { + self.identifier.signature() + } + + fn has_signature(&self, signature: &Self::Signature<'_>) -> bool { + self.identifier.has_signature(signature) + } +} + +impl Quil for MeasureCalibrationDefinition { + fn write( + &self, + f: &mut impl std::fmt::Write, + fall_back_to_debug: bool, + ) -> crate::quil::ToQuilResult<()> { + self.identifier.write(f, fall_back_to_debug)?; + writeln!(f, ":")?; + + write_instruction_block(f, fall_back_to_debug, &self.instructions)?; + writeln!(f)?; + Ok(()) + } +} + +// For review: how would we feel about making this a subfield of the `MeasureCalibrationDefinition` itself? +#[derive(Clone, Debug, Default, PartialEq)] +pub struct MeasureCalibrationIdentifier { + /// The qubit which is the target of measurement, if any + pub qubit: Option, + + /// The memory region name to which the measurement result is written + pub parameter: String, +} + +impl MeasureCalibrationIdentifier { + pub fn new(qubit: Option, parameter: String) -> Self { + Self { qubit, parameter } + } +} + +impl CalibrationSignature for MeasureCalibrationIdentifier { + type Signature<'a> = (Option<&'a Qubit>, &'a str); + fn signature(&self) -> Self::Signature<'_> { (self.qubit.as_ref(), self.parameter.as_str()) } @@ -116,7 +204,7 @@ impl CalibrationSignature for MeasureCalibrationDefinition { } } -impl Quil for MeasureCalibrationDefinition { +impl Quil for MeasureCalibrationIdentifier { fn write( &self, f: &mut impl std::fmt::Write, @@ -127,11 +215,7 @@ impl Quil for MeasureCalibrationDefinition { write!(f, " ")?; qubit.write(f, fall_back_to_debug)?; } - - writeln!(f, " {}:", self.parameter,)?; - - write_instruction_block(f, fall_back_to_debug, &self.instructions)?; - writeln!(f)?; + writeln!(f, " {}", self.parameter,)?; Ok(()) } } @@ -140,6 +224,7 @@ impl Quil for MeasureCalibrationDefinition { mod test_measure_calibration_definition { use super::MeasureCalibrationDefinition; use crate::expression::Expression; + use crate::instruction::calibration::MeasureCalibrationIdentifier; use crate::instruction::{Gate, Instruction, Qubit}; use crate::quil::Quil; use insta::assert_snapshot; @@ -149,8 +234,10 @@ mod test_measure_calibration_definition { #[case( "With Fixed Qubit", MeasureCalibrationDefinition { - qubit: Some(Qubit::Fixed(0)), - parameter: "theta".to_string(), + identifier: MeasureCalibrationIdentifier { + qubit: Some(Qubit::Fixed(0)), + parameter: "theta".to_string(), + }, instructions: vec![Instruction::Gate(Gate { name: "X".to_string(), parameters: vec![Expression::Variable("theta".to_string())], @@ -162,8 +249,10 @@ mod test_measure_calibration_definition { #[case( "With Variable Qubit", MeasureCalibrationDefinition { - qubit: Some(Qubit::Variable("q".to_string())), - parameter: "theta".to_string(), + identifier: MeasureCalibrationIdentifier { + qubit: Some(Qubit::Variable("q".to_string())), + parameter: "theta".to_string(), + }, instructions: vec![Instruction::Gate(Gate { name: "X".to_string(), parameters: vec![Expression::Variable("theta".to_string())], diff --git a/quil-rs/src/instruction/mod.rs b/quil-rs/src/instruction/mod.rs index 851b8c6e..192f948a 100644 --- a/quil-rs/src/instruction/mod.rs +++ b/quil-rs/src/instruction/mod.rs @@ -40,7 +40,10 @@ mod reset; mod timing; mod waveform; -pub use self::calibration::{Calibration, CalibrationSignature, MeasureCalibrationDefinition}; +pub use self::calibration::{ + Calibration, CalibrationIdentifier, CalibrationSignature, MeasureCalibrationDefinition, + MeasureCalibrationIdentifier, +}; pub use self::circuit::CircuitDefinition; pub use self::classical::{ Arithmetic, ArithmeticOperand, ArithmeticOperator, BinaryLogic, BinaryOperand, BinaryOperands, @@ -402,7 +405,10 @@ impl Instruction { /// ``` pub fn apply_to_expressions(&mut self, mut closure: impl FnMut(&mut Expression)) { match self { - Instruction::CalibrationDefinition(Calibration { parameters, .. }) + Instruction::CalibrationDefinition(Calibration { + identifier: CalibrationIdentifier { parameters, .. }, + .. + }) | Instruction::Gate(Gate { parameters, .. }) => { parameters.iter_mut().for_each(closure); } @@ -586,6 +592,7 @@ impl Instruction { match self { Instruction::Gate(gate) => gate.qubits.iter_mut().collect(), Instruction::CalibrationDefinition(calibration) => calibration + .identifier .qubits .iter_mut() .chain( @@ -596,6 +603,7 @@ impl Instruction { ) .collect(), Instruction::MeasureCalibrationDefinition(measurement) => measurement + .identifier .qubit .iter_mut() .chain( diff --git a/quil-rs/src/parser/command.rs b/quil-rs/src/parser/command.rs index 42a89918..0dfc678f 100644 --- a/quil-rs/src/parser/command.rs +++ b/quil-rs/src/parser/command.rs @@ -6,12 +6,13 @@ use nom::sequence::{delimited, pair, preceded, tuple}; use crate::expression::Expression; use crate::instruction::{ Arithmetic, ArithmeticOperand, ArithmeticOperator, BinaryLogic, BinaryOperator, Calibration, - Capture, CircuitDefinition, Comparison, ComparisonOperator, Convert, Declaration, Delay, - Exchange, Fence, FrameDefinition, GateDefinition, GateSpecification, GateType, Include, - Instruction, Jump, JumpUnless, JumpWhen, Label, Load, MeasureCalibrationDefinition, - Measurement, Move, PauliSum, Pragma, PragmaArgument, Pulse, Qubit, RawCapture, Reset, - SetFrequency, SetPhase, SetScale, ShiftFrequency, ShiftPhase, Store, SwapPhases, Target, - UnaryLogic, UnaryOperator, ValidationError, Waveform, WaveformDefinition, + CalibrationIdentifier, Capture, CircuitDefinition, Comparison, ComparisonOperator, Convert, + Declaration, Delay, Exchange, Fence, FrameDefinition, GateDefinition, GateSpecification, + GateType, Include, Instruction, Jump, JumpUnless, JumpWhen, Label, Load, + MeasureCalibrationDefinition, MeasureCalibrationIdentifier, Measurement, Move, PauliSum, + Pragma, PragmaArgument, Pulse, Qubit, RawCapture, Reset, SetFrequency, SetPhase, SetScale, + ShiftFrequency, ShiftPhase, Store, SwapPhases, Target, UnaryLogic, UnaryOperator, + ValidationError, Waveform, WaveformDefinition, }; use crate::parser::instruction::parse_block; @@ -184,11 +185,13 @@ pub(crate) fn parse_defcal_gate<'a>( Ok(( input, Instruction::CalibrationDefinition(Calibration { - name, - parameters, - qubits, + identifier: CalibrationIdentifier { + name, + parameters, + qubits, + modifiers, + }, instructions, - modifiers, }), )) } @@ -207,8 +210,10 @@ pub(crate) fn parse_defcal_measure<'a>( Ok(( input, Instruction::MeasureCalibrationDefinition(MeasureCalibrationDefinition { - qubit, - parameter: destination, + identifier: MeasureCalibrationIdentifier { + qubit, + parameter: destination, + }, instructions, }), )) diff --git a/quil-rs/src/parser/instruction.rs b/quil-rs/src/parser/instruction.rs index bfc42e05..21d534a6 100644 --- a/quil-rs/src/parser/instruction.rs +++ b/quil-rs/src/parser/instruction.rs @@ -159,12 +159,12 @@ mod tests { }; use crate::instruction::{ Arithmetic, ArithmeticOperand, ArithmeticOperator, AttributeValue, BinaryLogic, - BinaryOperand, BinaryOperator, Calibration, Capture, Comparison, ComparisonOperand, - ComparisonOperator, Convert, FrameDefinition, FrameIdentifier, Gate, GateDefinition, - GateSpecification, Include, Instruction, Jump, JumpWhen, Label, MemoryReference, Move, - Pulse, Qubit, RawCapture, Reset, SetFrequency, SetPhase, SetScale, ShiftFrequency, - ShiftPhase, SwapPhases, Target, UnaryLogic, UnaryOperator, Waveform, WaveformDefinition, - WaveformInvocation, WaveformParameters, + BinaryOperand, BinaryOperator, Calibration, CalibrationIdentifier, Capture, Comparison, + ComparisonOperand, ComparisonOperator, Convert, FrameDefinition, FrameIdentifier, Gate, + GateDefinition, GateSpecification, Include, Instruction, Jump, JumpWhen, Label, + MemoryReference, Move, Pulse, Qubit, RawCapture, Reset, SetFrequency, SetPhase, SetScale, + ShiftFrequency, ShiftPhase, SwapPhases, Target, UnaryLogic, UnaryOperator, Waveform, + WaveformDefinition, WaveformInvocation, WaveformParameters, }; use crate::parser::common::tests::KITCHEN_SINK_QUIL; use crate::parser::lexer::lex; @@ -572,10 +572,12 @@ mod tests { parse_instructions, "DEFCAL RX(%theta) %qubit:\n\tPULSE 1 \"xy\" custom_waveform(a: 1)", vec![Instruction::CalibrationDefinition(Calibration { - name: "RX".to_owned(), - parameters: vec![Expression::Variable("theta".to_owned())], - qubits: vec![Qubit::Variable("qubit".to_owned())], - modifiers: vec![], + identifier: CalibrationIdentifier { + name: "RX".to_owned(), + parameters: vec![Expression::Variable("theta".to_owned())], + qubits: vec![Qubit::Variable("qubit".to_owned())], + modifiers: vec![], + }, instructions: vec![Instruction::Pulse(Pulse { blocking: true, frame: FrameIdentifier { diff --git a/quil-rs/src/program/calibration.rs b/quil-rs/src/program/calibration.rs index 6490f5f8..0b2fcbb4 100644 --- a/quil-rs/src/program/calibration.rs +++ b/quil-rs/src/program/calibration.rs @@ -18,7 +18,7 @@ use std::ops::Range; use itertools::FoldWhile::{Continue, Done}; use itertools::Itertools; -use crate::instruction::GateModifier; +use crate::instruction::{CalibrationIdentifier, MeasureCalibrationIdentifier}; use crate::quil::Quil; use crate::{ expression::Expression, @@ -49,6 +49,7 @@ impl<'a> MatchedCalibration<'a> { Self { calibration, fixed_qubit_count: calibration + .identifier .qubits .iter() .filter(|q| match q { @@ -161,54 +162,6 @@ impl From for CalibrationSource { } } -// For review consideration: making this a structural part of Calibration would -// (a) make some sense in principle and -// (b) allow CalibrationExpansion to contain a reference to it instead of an owned, cloned copy -#[derive(Clone, Debug, Default, PartialEq)] -pub struct CalibrationIdentifier { - /// The modifiers applied to the gate - pub modifiers: Vec, - - /// The name of the gate - pub name: String, - - /// The parameters of the gate - these are the variables in the calibration definition - pub parameters: Vec, - - /// The qubits on which the gate is applied - pub qubits: Vec, -} - -impl From<&Calibration> for CalibrationIdentifier { - fn from(value: &Calibration) -> Self { - Self { - modifiers: value.modifiers.clone(), - name: value.name.clone(), - parameters: value.parameters.clone(), - qubits: value.qubits.clone(), - } - } -} - -// For review: how would we feel about making this a subfield of the `MeasureCalibrationDefinition` itself? -#[derive(Clone, Debug, Default, PartialEq)] -pub struct MeasureCalibrationIdentifier { - /// The qubit which is the target of measurement, if any - pub qubit: Option, - - /// The memory region name to which the measurement result is written - pub parameter: String, -} - -impl From<&MeasureCalibrationDefinition> for MeasureCalibrationIdentifier { - fn from(value: &MeasureCalibrationDefinition) -> Self { - Self { - qubit: value.qubit.clone(), - parameter: value.parameter.clone(), - } - } -} - impl Calibrations { /// Return a vector containing a reference to all [`Calibration`]s in the set. pub fn calibrations(&self) -> Vec<&Calibration> { @@ -258,7 +211,9 @@ impl Calibrations { match matching_calibration { Some(calibration) => { let mut qubit_expansions: HashMap<&String, Qubit> = HashMap::new(); - for (index, calibration_qubit) in calibration.qubits.iter().enumerate() { + for (index, calibration_qubit) in + calibration.identifier.qubits.iter().enumerate() + { if let Qubit::Variable(identifier) = calibration_qubit { qubit_expansions.insert(identifier, gate.qubits[index].clone()); } @@ -267,6 +222,7 @@ impl Calibrations { // Variables used within the calibration's definition should be replaced with the actual expressions used by the gate. // That is, `DEFCAL RX(%theta): ...` should have `%theta` replaced by `pi` throughout if it's used to expand `RX(pi)`. let variable_expansions: HashMap = calibration + .identifier .parameters .iter() .zip(gate.parameters.iter()) @@ -343,7 +299,7 @@ impl Calibrations { Some(( instructions, - CalibrationSource::Calibration(calibration.into()), + CalibrationSource::Calibration(calibration.identifier.clone()), )) } None => None, @@ -359,7 +315,8 @@ impl Calibrations { match instruction { Instruction::Pragma(pragma) => { if pragma.name == "LOAD-MEMORY" - && pragma.data.as_ref() == Some(&calibration.parameter) + && pragma.data.as_ref() + == Some(&calibration.identifier.parameter) { if let Some(target) = &measurement.target { pragma.data = Some(target.to_quil_or_debug()) @@ -376,7 +333,7 @@ impl Calibrations { } Some(( instructions, - CalibrationSource::MeasureCalibration(calibration.into()), + CalibrationSource::MeasureCalibration(calibration.identifier.clone()), )) } None => None, @@ -465,12 +422,12 @@ impl Calibrations { .into_iter() .rev() .fold_while(None, |best_match, calibration| { - if let Some(qubit) = &calibration.qubit { + if let Some(qubit) = &calibration.identifier.qubit { match qubit { Qubit::Fixed(_) if qubit == &measurement.qubit => Done(Some(calibration)), Qubit::Variable(_) if best_match.is_none() - || best_match.is_some_and(|c| c.qubit.is_none()) => + || best_match.is_some_and(|c| c.identifier.qubit.is_none()) => { Continue(Some(calibration)) } @@ -499,22 +456,23 @@ impl Calibrations { for calibration in self.iter_calibrations() { // Filter out non-matching calibrations: check rules 1-4 - if calibration.name != gate.name - || calibration.modifiers != gate.modifiers - || calibration.parameters.len() != gate.parameters.len() - || calibration.qubits.len() != gate.qubits.len() + if calibration.identifier.name != gate.name + || calibration.identifier.modifiers != gate.modifiers + || calibration.identifier.parameters.len() != gate.parameters.len() + || calibration.identifier.qubits.len() != gate.qubits.len() { continue; } let fixed_qubits_match = calibration + .identifier .qubits .iter() .enumerate() .all(|(calibration_index, _)| { match ( - &calibration.qubits[calibration_index], + &calibration.identifier.qubits[calibration_index], &gate.qubits[calibration_index], ) { // Placeholders never match @@ -534,24 +492,22 @@ impl Calibrations { continue; } - let fixed_parameters_match = - calibration - .parameters - .iter() - .enumerate() - .all(|(calibration_index, _)| { - let calibration_parameters = calibration.parameters[calibration_index] - .clone() - .into_simplified(); - let gate_parameters = - gate.parameters[calibration_index].clone().into_simplified(); - match (calibration_parameters, gate_parameters) { - // If the calibration is variable, it matches any fixed qubit - (Expression::Variable(_), _) => true, - // If the calibration is fixed, but the gate's qubit is variable, it's not a match - (calib, gate) => calib == gate, - } - }); + let fixed_parameters_match = calibration.identifier.parameters.iter().enumerate().all( + |(calibration_index, _)| { + let calibration_parameters = calibration.identifier.parameters + [calibration_index] + .clone() + .into_simplified(); + let gate_parameters = + gate.parameters[calibration_index].clone().into_simplified(); + match (calibration_parameters, gate_parameters) { + // If the calibration is variable, it matches any fixed qubit + (Expression::Variable(_), _) => true, + // If the calibration is fixed, but the gate's qubit is variable, it's not a match + (calib, gate) => calib == gate, + } + }, + ); if !fixed_parameters_match { continue; } diff --git a/quil-rs/src/program/memory.rs b/quil-rs/src/program/memory.rs index 1caec4c2..038b4c5f 100644 --- a/quil-rs/src/program/memory.rs +++ b/quil-rs/src/program/memory.rs @@ -156,6 +156,7 @@ impl Instruction { }, Instruction::CalibrationDefinition(definition) => { let references: Vec<&MemoryReference> = definition + .identifier .parameters .iter() .flat_map(|expr| expr.get_memory_references()) diff --git a/quil-rs/src/program/mod.rs b/quil-rs/src/program/mod.rs index 570b9ca8..21fcb2b1 100644 --- a/quil-rs/src/program/mod.rs +++ b/quil-rs/src/program/mod.rs @@ -799,14 +799,11 @@ mod tests { use crate::{ imag, instruction::{ - Gate, Instruction, Jump, JumpUnless, JumpWhen, Label, Matrix, MemoryReference, Qubit, - QubitPlaceholder, Target, TargetPlaceholder, + CalibrationIdentifier, Gate, Instruction, Jump, JumpUnless, JumpWhen, Label, Matrix, + MemoryReference, Qubit, QubitPlaceholder, Target, TargetPlaceholder, }, program::{ - calibration::{ - CalibrationExpansion, CalibrationIdentifier, CalibrationSource, - MaybeCalibrationExpansion, - }, + calibration::{CalibrationExpansion, CalibrationSource, MaybeCalibrationExpansion}, source_map::{SourceMap, SourceMapEntry}, }, quil::Quil, From ec904155205a26ba8f1dfc3084d1d4c3cd0ea1ee Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Wed, 8 May 2024 19:59:59 -0700 Subject: [PATCH 09/39] add and fix py stubs for CalibrationIdentifier and MeasureCalibrationIdentifier --- quil-py/quil/instructions/__init__.pyi | 96 ++++++++++++++++++++++++-- quil-py/src/instruction/calibration.rs | 55 ++++++++++++--- quil-py/src/instruction/mod.rs | 2 + 3 files changed, 138 insertions(+), 15 deletions(-) diff --git a/quil-py/quil/instructions/__init__.pyi b/quil-py/quil/instructions/__init__.pyi index 6997ca8b..8d813dd8 100644 --- a/quil-py/quil/instructions/__init__.pyi +++ b/quil-py/quil/instructions/__init__.pyi @@ -826,11 +826,8 @@ class UnaryLogic: class Calibration: def __new__( cls, - name: str, - parameters: Sequence[Expression], - qubits: Sequence[Qubit], + identifier: CalibrationIdentifier, instructions: Sequence[Instruction], - modifiers: Sequence[GateModifier], ) -> Self: ... @property def name(self) -> str: ... @@ -845,6 +842,10 @@ class Calibration: @qubits.setter def qubits(self, qubits: Sequence[Qubit]) -> None: ... @property + def identifier(self) -> CalibrationIdentifier: ... + @identifier.setter + def identifier(self, identifier:CalibrationIdentifier) -> None: ... + @property def instructions(self) -> List[Instruction]: ... @instructions.setter def instructions(self, instructions: Sequence[Instruction]) -> None: ... @@ -873,11 +874,55 @@ class Calibration: def __copy__(self) -> Self: """Returns a shallow copy of the class.""" +class CalibrationIdentifier: + def __new__( + cls, + name: str, + parameters: Sequence[Expression], + qubits: Sequence[Qubit], + modifiers: Sequence[GateModifier], + ) -> Self: ... + @property + def name(self) -> str: ... + @name.setter + def name(self, name: str) -> None: ... + @property + def parameters(self) -> List[Expression]: ... + @parameters.setter + def parameters(self, parameters: Sequence[Expression]) -> None: ... + @property + def qubits(self) -> List[Qubit]: ... + @qubits.setter + def qubits(self, qubits: Sequence[Qubit]) -> None: ... + @property + def modifiers(self) -> List[GateModifier]: ... + @modifiers.setter + def modifiers(self, modifiers: Sequence[GateModifier]) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + class MeasureCalibrationDefinition: def __new__( cls, - qubit: Optional[Qubit], - parameter: str, + identifier: MeasureCalibrationIdentifier, instructions: Sequence[Instruction], ) -> Self: ... @property @@ -889,6 +934,10 @@ class MeasureCalibrationDefinition: @parameter.setter def parameter(self, parameter: str) -> None: ... @property + def identifier(self) -> MeasureCalibrationIdentifier: ... + @identifier.setter + def identifier(self, identifier: MeasureCalibrationIdentifier) -> None: ... + @property def instructions(self) -> List[Instruction]: ... @instructions.setter def instructions(self, instructions: Sequence[Instruction]) -> None: ... @@ -913,6 +962,41 @@ class MeasureCalibrationDefinition: def __copy__(self) -> Self: """Returns a shallow copy of the class.""" +class MeasureCalibrationIdentifier: + def __new__( + cls, + qubit: Optional[Qubit], + parameter: str, + ) -> Self: ... + @property + def qubit(self) -> Optional[Qubit]: ... + @qubit.setter + def qubit(self, qubit: Optional[Qubit]) -> None: ... + @property + def parameter(self) -> str: ... + @parameter.setter + def parameter(self, parameter: str) -> None: ... + def to_quil(self) -> str: + """Attempt to convert the instruction to a valid Quil string. + + Raises an exception if the instruction can't be converted to valid Quil. + """ + ... + def to_quil_or_debug(self) -> str: + """Convert the instruction to a Quil string. + + If any part of the instruction can't be converted to valid Quil, it will be printed in a human-readable debug format. + """ + def __deepcopy__(self, _: Dict) -> Self: + """Creates and returns a deep copy of the class. + + If the instruction contains any ``QubitPlaceholder`` or ``TargetPlaceholder``, then they will be replaced with + new placeholders so resolving them in the copy will not resolve them in the original. + Should be used by passing an instance of the class to ``copy.deepcopy`` + """ + def __copy__(self) -> Self: + """Returns a shallow copy of the class.""" + class CircuitDefinition: def __new__( cls, diff --git a/quil-py/src/instruction/calibration.rs b/quil-py/src/instruction/calibration.rs index 07b7a417..1e38dfa2 100644 --- a/quil-py/src/instruction/calibration.rs +++ b/quil-py/src/instruction/calibration.rs @@ -9,7 +9,7 @@ use quil_rs::{ use rigetti_pyo3::{ impl_repr, py_wrap_data_struct, pyo3::{pymethods, types::PyString, Py, PyResult, Python}, - PyTryFrom, ToPythonError, + PyTryFrom, PyWrapper, ToPythonError, }; use crate::{ @@ -49,6 +49,40 @@ impl PyCalibration { .map_err(RustIdentifierValidationError::to_py_err)?, )) } + + pub fn name(&self) -> &str { + &self.as_inner().identifier.name + } + + pub fn parameters(&self) -> Vec { + self.as_inner() + .identifier + .parameters + .clone() + .into_iter() + .map(Into::into) + .collect() + } + + pub fn qubits(&self) -> Vec { + self.as_inner() + .identifier + .qubits + .clone() + .into_iter() + .map(Into::into) + .collect() + } + + pub fn modifiers(&self) -> Vec { + self.as_inner() + .identifier + .modifiers + .clone() + .into_iter() + .map(Into::into) + .collect() + } } py_wrap_data_struct! { @@ -116,12 +150,20 @@ impl PyMeasureCalibrationDefinition { Vec::::py_try_from(py, &instructions)?, ))) } + + pub fn qubit(&self) -> Option { + self.as_inner().identifier.qubit.clone().map(Into::into) + } + + pub fn parameter(&self) -> String { + self.as_inner().identifier.parameter.clone() + } } py_wrap_data_struct! { #[derive(Debug, PartialEq)] #[pyo3(subclass)] - PyMeasureCalibrationIdentifier(MeasureCalibrationIdentifier) as "PyMeasureCalibrationIdentifier" { + PyMeasureCalibrationIdentifier(MeasureCalibrationIdentifier) as "MeasureCalibrationIdentifier" { qubit: Option => Option, parameter: String => Py } @@ -134,13 +176,8 @@ impl_eq!(PyMeasureCalibrationIdentifier); #[pymethods] impl PyMeasureCalibrationIdentifier { #[new] - #[pyo3(signature = (qubit, parameter, instructions))] - pub fn new( - py: Python<'_>, - qubit: Option, - parameter: String, - instructions: Vec, - ) -> PyResult { + #[pyo3(signature = (qubit, parameter))] + pub fn new(py: Python<'_>, qubit: Option, parameter: String) -> PyResult { Ok(Self(MeasureCalibrationIdentifier::new( Option::::py_try_from(py, &qubit)?, parameter, diff --git a/quil-py/src/instruction/mod.rs b/quil-py/src/instruction/mod.rs index 99c939cf..9901b03d 100644 --- a/quil-py/src/instruction/mod.rs +++ b/quil-py/src/instruction/mod.rs @@ -151,8 +151,10 @@ create_init_submodule! { PyUnaryLogic, PyUnaryOperator, PyCalibration, + PyCalibrationIdentifier, PyCircuitDefinition, PyMeasureCalibrationDefinition, + PyMeasureCalibrationIdentifier, PyDeclaration, PyLoad, PyOffset, From 2ecf658384b66776dc26f57aa5740aa6bef787f8 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Wed, 8 May 2024 20:01:58 -0700 Subject: [PATCH 10/39] reorder bindings file in lex order --- quil-py/src/program/source_map.rs | 106 +++++++++++++++--------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/quil-py/src/program/source_map.rs b/quil-py/src/program/source_map.rs index c62c5ca7..86a12a80 100644 --- a/quil-py/src/program/source_map.rs +++ b/quil-py/src/program/source_map.rs @@ -9,63 +9,10 @@ use crate::instruction::{PyCalibrationIdentifier, PyMeasureCalibrationIdentifier use super::PyProgram; -py_wrap_type! { - #[derive(Debug, PartialEq)] - PyProgramCalibrationExpansion(ProgramCalibrationExpansion) as "ProgramCalibrationExpansion" -} - -impl_repr!(PyProgramCalibrationExpansion); - -#[pymethods] -impl PyProgramCalibrationExpansion { - pub fn program(&self) -> PyProgram { - self.as_inner().program().into() - } - - pub fn source_mapping(&self) -> PyProgramCalibrationExpansionSourceMap { - self.as_inner().source_mapping().clone().into() - } -} - -py_wrap_type! { - #[derive(Debug, PartialEq)] - PyProgramCalibrationExpansionSourceMap(ProgramCalibrationExpansionSourceMap) as "ProgramCalibrationExpansionSourceMap" -} - -impl_repr!(PyProgramCalibrationExpansionSourceMap); - -#[pymethods] -impl PyProgramCalibrationExpansionSourceMap { - pub fn entries(&self) -> Vec { - self.as_inner() - .entries() - .iter() - .map(|entry| entry.into()) - .collect() - } -} - -py_wrap_union_enum! { - #[derive(Debug, PartialEq)] - PyMaybeCalibrationExpansion(MaybeCalibrationExpansion) as "MaybeCalibrationExpansion" { - expanded: Expanded => PyCalibrationExpansion, - unexpanded: Unexpanded => usize => Py - } -} - -// impl_repr!(PyMaybeCalibrationExpansion); - type CalibrationExpansionSourceMap = SourceMap; type CalibrationExpansionSourceMapEntry = SourceMapEntry; type ProgramCalibrationExpansionSourceMapEntry = SourceMapEntry; -py_wrap_type! { - #[derive(Debug, PartialEq)] - PyProgramCalibrationExpansionSourceMapEntry(ProgramCalibrationExpansionSourceMapEntry) as "ProgramCalibrationExpansionSourceMapEntry" -} - -impl_repr!(PyProgramCalibrationExpansionSourceMapEntry); - py_wrap_type! { #[derive(Debug, PartialEq)] PyCalibrationExpansion(CalibrationExpansion) as "CalibrationExpansion" @@ -134,3 +81,56 @@ py_wrap_union_enum! { } impl_repr!(PyCalibrationSource); + +py_wrap_union_enum! { + #[derive(Debug, PartialEq)] + PyMaybeCalibrationExpansion(MaybeCalibrationExpansion) as "MaybeCalibrationExpansion" { + expanded: Expanded => PyCalibrationExpansion, + unexpanded: Unexpanded => usize => Py + } +} + +impl_repr!(PyMaybeCalibrationExpansion); + +py_wrap_type! { + #[derive(Debug, PartialEq)] + PyProgramCalibrationExpansion(ProgramCalibrationExpansion) as "ProgramCalibrationExpansion" +} + +impl_repr!(PyProgramCalibrationExpansion); + +#[pymethods] +impl PyProgramCalibrationExpansion { + pub fn program(&self) -> PyProgram { + self.as_inner().program().into() + } + + pub fn source_mapping(&self) -> PyProgramCalibrationExpansionSourceMap { + self.as_inner().source_mapping().clone().into() + } +} + +py_wrap_type! { + #[derive(Debug, PartialEq)] + PyProgramCalibrationExpansionSourceMap(ProgramCalibrationExpansionSourceMap) as "ProgramCalibrationExpansionSourceMap" +} + +impl_repr!(PyProgramCalibrationExpansionSourceMap); + +py_wrap_type! { + #[derive(Debug, PartialEq)] + PyProgramCalibrationExpansionSourceMapEntry(ProgramCalibrationExpansionSourceMapEntry) as "ProgramCalibrationExpansionSourceMapEntry" +} + +impl_repr!(PyProgramCalibrationExpansionSourceMapEntry); + +#[pymethods] +impl PyProgramCalibrationExpansionSourceMap { + pub fn entries(&self) -> Vec { + self.as_inner() + .entries() + .iter() + .map(|entry| entry.into()) + .collect() + } +} From 8446b6113da13635f10bdba18897b1d1e19d3eb5 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Wed, 8 May 2024 20:20:09 -0700 Subject: [PATCH 11/39] add source-map py stubs --- quil-py/quil/program/__init__.pyi | 64 ++++++++++++++++++++++++++++++- quil-py/src/program/mod.rs | 27 ++++++++++++- quil-py/src/program/source_map.rs | 31 ++++++++++----- quil-rs/src/program/mod.rs | 2 +- 4 files changed, 110 insertions(+), 14 deletions(-) diff --git a/quil-py/quil/program/__init__.pyi b/quil-py/quil/program/__init__.pyi index 5478a841..f61ee746 100644 --- a/quil-py/quil/program/__init__.pyi +++ b/quil-py/quil/program/__init__.pyi @@ -1,4 +1,4 @@ -from typing import Callable, Dict, FrozenSet, List, Optional, Sequence, Set, final +from typing import Callable, Dict, FrozenSet, List, Optional, Sequence, Set, Tuple, Union, final import numpy as np from numpy.typing import NDArray @@ -7,12 +7,14 @@ from typing_extensions import Self from quil.instructions import ( AttributeValue, Calibration, + CalibrationIdentifier, Declaration, FrameIdentifier, Gate, GateDefinition, Instruction, MeasureCalibrationDefinition, + MeasureCalibrationIdentifier, Measurement, MemoryReference, Qubit, @@ -258,6 +260,37 @@ class BasicBlock: If this is ``None``, the implicit behavior is to "continue" to the subsequent block. """ +@final +class CalibrationExpansion: + def calibration_used(self) -> CalibrationSource: ... + def range(self) -> Tuple[int, int]: ... + def expansions(self) -> CalibrationExpansionSourceMap: ... + +@final +class CalibrationExpansionSourceMap: + def entries(self) -> List[CalibrationExpansionSourceMapEntry]: ... + +@final +class CalibrationExpansionSourceMapEntry: + def source_location(self) -> int: ... + def target_location(self) -> CalibrationExpansion: ... + +@final +class CalibrationSource: + def as_calibration(self) -> CalibrationIdentifier: ... + def as_measure_calibration(self) -> MeasureCalibrationIdentifier: ... + def is_calibration(self) -> bool: ... + def is_measure_calibration(self) -> bool: ... + def to_calibration(self) -> CalibrationIdentifier: ... + def to_measure_calibration(self) -> MeasureCalibrationIdentifier: ... + @staticmethod + def from_calibration(inner: CalibrationIdentifier): ... + @staticmethod + def from_measure_calibration(inner: MeasureCalibrationIdentifier): ... + def inner(self) -> Union[CalibrationIdentifier, MeasureCalibrationIdentifier]: ... + + + @final class CalibrationSet: @staticmethod @@ -318,6 +351,20 @@ class CalibrationSet: """Return the Quil instructions which describe the contained calibrations.""" ... +@final +class MaybeCalibrationExpansion: + def as_expanded(self) -> CalibrationExpansion: ... + def as_unexpanded(self) -> int: ... + @staticmethod + def from_expanded(inner: CalibrationExpansion): ... + @staticmethod + def from_unexpanded(inner: int): ... + def inner(self) -> Union[CalibrationExpansion, int]: ... + def is_expanded(self) -> bool: ... + def is_unexpanded(self) -> bool: ... + def to_expanded(self) -> CalibrationExpansion: ... + def to_unexpanded(self) -> int: ... + class ScheduleSecondsItem: """A single item within a fixed schedule, representing a single instruction within a basic block.""" @@ -412,3 +459,18 @@ class MemoryRegion: def sharing(self) -> Optional[Sharing]: ... @sharing.setter def sharing(self, sharing: Optional[Sharing]): ... + + +@final +class ProgramCalibrationExpansion: + def program(self) -> Program: ... + def source_map(self) -> ProgramCalibrationExpansionSourceMap: ... + +@final +class ProgramCalibrationExpansionSourceMap: + def entries(self) -> List[ProgramCalibrationExpansionSourceMapEntry]: ... + +@final +class ProgramCalibrationExpansionSourceMapEntry: + def source_location(self) -> int: ... + def target_location(self) -> MaybeCalibrationExpansion: ... \ No newline at end of file diff --git a/quil-py/src/program/mod.rs b/quil-py/src/program/mod.rs index 3e42919d..d9c744c7 100644 --- a/quil-py/src/program/mod.rs +++ b/quil-py/src/program/mod.rs @@ -37,7 +37,12 @@ use crate::{ use self::{ analysis::{PyBasicBlock, PyControlFlowGraph}, scheduling::{PyScheduleSeconds, PyScheduleSecondsItem, PyTimeSpanSeconds}, - source_map::PyProgramCalibrationExpansion, + source_map::{ + PyCalibrationExpansion, PyCalibrationExpansionSourceMap, + PyCalibrationExpansionSourceMapEntry, PyCalibrationSource, PyMaybeCalibrationExpansion, + PyProgramCalibrationExpansion, PyProgramCalibrationExpansionSourceMap, + PyProgramCalibrationExpansionSourceMapEntry, + }, }; pub use self::{calibration::PyCalibrationSet, frame::PyFrameSet, memory::PyMemoryRegion}; @@ -385,5 +390,23 @@ impl PyProgram { } create_init_submodule! { - classes: [ PyFrameSet, PyProgram, PyCalibrationSet, PyMemoryRegion, PyBasicBlock, PyControlFlowGraph, PyScheduleSeconds, PyScheduleSecondsItem, PyTimeSpanSeconds ], + classes: [ + PyFrameSet, + PyProgram, + PyCalibrationExpansion, + PyCalibrationExpansionSourceMap, + PyCalibrationExpansionSourceMapEntry, + PyCalibrationSource, + PyMaybeCalibrationExpansion, + PyProgramCalibrationExpansion, + PyProgramCalibrationExpansionSourceMap, + PyProgramCalibrationExpansionSourceMapEntry, + PyCalibrationSet, + PyMemoryRegion, + PyBasicBlock, + PyControlFlowGraph, + PyScheduleSeconds, + PyScheduleSecondsItem, + PyTimeSpanSeconds + ], } diff --git a/quil-py/src/program/source_map.rs b/quil-py/src/program/source_map.rs index 86a12a80..2bb20add 100644 --- a/quil-py/src/program/source_map.rs +++ b/quil-py/src/program/source_map.rs @@ -76,7 +76,7 @@ py_wrap_union_enum! { #[derive(Debug, PartialEq)] PyCalibrationSource(CalibrationSource) as "CalibrationSource" { calibration: Calibration => PyCalibrationIdentifier, - measurement_calibration: MeasureCalibration => PyMeasureCalibrationIdentifier + measure_calibration: MeasureCalibration => PyMeasureCalibrationIdentifier } } @@ -105,8 +105,8 @@ impl PyProgramCalibrationExpansion { self.as_inner().program().into() } - pub fn source_mapping(&self) -> PyProgramCalibrationExpansionSourceMap { - self.as_inner().source_mapping().clone().into() + pub fn source_map(&self) -> PyProgramCalibrationExpansionSourceMap { + self.as_inner().source_map().clone().into() } } @@ -117,13 +117,6 @@ py_wrap_type! { impl_repr!(PyProgramCalibrationExpansionSourceMap); -py_wrap_type! { - #[derive(Debug, PartialEq)] - PyProgramCalibrationExpansionSourceMapEntry(ProgramCalibrationExpansionSourceMapEntry) as "ProgramCalibrationExpansionSourceMapEntry" -} - -impl_repr!(PyProgramCalibrationExpansionSourceMapEntry); - #[pymethods] impl PyProgramCalibrationExpansionSourceMap { pub fn entries(&self) -> Vec { @@ -134,3 +127,21 @@ impl PyProgramCalibrationExpansionSourceMap { .collect() } } + +py_wrap_type! { + #[derive(Debug, PartialEq)] + PyProgramCalibrationExpansionSourceMapEntry(ProgramCalibrationExpansionSourceMapEntry) as "ProgramCalibrationExpansionSourceMapEntry" +} + +impl_repr!(PyProgramCalibrationExpansionSourceMapEntry); + +#[pymethods] +impl PyProgramCalibrationExpansionSourceMapEntry { + pub fn source_location(&self) -> usize { + self.as_inner().source_location().clone().into() + } + + pub fn target_location(&self) -> PyMaybeCalibrationExpansion { + self.as_inner().target_location().clone().into() + } +} diff --git a/quil-rs/src/program/mod.rs b/quil-rs/src/program/mod.rs index 21fcb2b1..aaff701c 100644 --- a/quil-rs/src/program/mod.rs +++ b/quil-rs/src/program/mod.rs @@ -788,7 +788,7 @@ impl ProgramCalibrationExpansion { &self.program } - pub fn source_mapping(&self) -> &ProgramCalibrationExpansionSourceMap { + pub fn source_map(&self) -> &ProgramCalibrationExpansionSourceMap { &self.source_map } } From feaa61cafece50ab9ac843fbdf718b31ce58d73f Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Wed, 8 May 2024 20:22:57 -0700 Subject: [PATCH 12/39] Add py stub for Program.expand_calibrations_with_source_map --- quil-py/quil/program/__init__.pyi | 6 ++++++ quil-rs/src/program/mod.rs | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/quil-py/quil/program/__init__.pyi b/quil-py/quil/program/__init__.pyi index f61ee746..59075226 100644 --- a/quil-py/quil/program/__init__.pyi +++ b/quil-py/quil/program/__init__.pyi @@ -67,6 +67,12 @@ class Program: expands directly or indirectly into itself) """ ... + def expand_calibrations_with_source_map(self) -> ProgramCalibrationExpansion: + """Expand any instructions in the program which have a matching calibration, leaving the others unchanged. + + Return the expanded copy of the program and a source mapping describing the expansions made. + """ + ... def into_simplified(self) -> "Program": """Simplify this program into a new `Program` which contains only instructions and definitions which are executed; effectively, perform dead code removal. diff --git a/quil-rs/src/program/mod.rs b/quil-rs/src/program/mod.rs index aaff701c..249e73b3 100644 --- a/quil-rs/src/program/mod.rs +++ b/quil-rs/src/program/mod.rs @@ -240,15 +240,15 @@ impl Program { } /// Expand any instructions in the program which have a matching calibration, leaving the others - /// unchanged. Returns the expanded copy of the program. + /// unchanged. Return the expanded copy of the program. /// /// See [`Program::expand_calibrations_with_source_map`] for a version that returns a source mapping. pub fn expand_calibrations(&self) -> Result { self._expand_calibrations(None) } - /// Expand any instructions in the program which have a matching calibration, leaving the others - /// unchanged. Returns the expanded copy of the program and a source mapping of the expansions made. + // / Expand any instructions in the program which have a matching calibration, leaving the others + // / unchanged. Return the expanded copy of the program and a source mapping of the expansions made. pub fn expand_calibrations_with_source_map(&self) -> Result { let mut source_mapping = ProgramCalibrationExpansionSourceMap::default(); let new_program = self._expand_calibrations(Some(&mut source_mapping))?; From 00119a601cf626807fdc4d153dcac43073740826 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Wed, 8 May 2024 20:59:16 -0700 Subject: [PATCH 13/39] chore: docs --- quil-py/quil/program/__init__.pyi | 60 ++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/quil-py/quil/program/__init__.pyi b/quil-py/quil/program/__init__.pyi index 59075226..e0644b40 100644 --- a/quil-py/quil/program/__init__.pyi +++ b/quil-py/quil/program/__init__.pyi @@ -269,8 +269,11 @@ class BasicBlock: @final class CalibrationExpansion: def calibration_used(self) -> CalibrationSource: ... + """The calibration which was used to expand the instruction.""" def range(self) -> Tuple[int, int]: ... + """The range of instructions in the expanded list which were generated by this expansion.""" def expansions(self) -> CalibrationExpansionSourceMap: ... + """The source map describing the nested expansions made.""" @final class CalibrationExpansionSourceMap: @@ -278,11 +281,51 @@ class CalibrationExpansionSourceMap: @final class CalibrationExpansionSourceMapEntry: + """ + A description of the expansion of one instruction into other instructions. + + If present, the instruction located at `source_location` was expanded using calibrations + into the instructions located at `target_location`. + + Note that both `source_location` and `target_location` are relative to the scope of expansion. + In the case of a nested expansion, both describe the location relative only to that + level of expansion and *not* the original program. + + Consider the following example: + + ``` + DEFCAL A: + NOP + B + HALT + + + DEFCAL B: + NOP + WAIT + + NOP + NOP + NOP + A + ``` + + In this program, `A` will expand into `NOP`, `B`, and `HALT`. Then, `B` will expand into `NOP` and `WAIT`. + Each level of this expansion will have its own `CalibrationExpansionSourceMap` describing the expansion. + In the map of `B` to `NOP` and `WAIT`, the `source_location` will be `1` because `B` is the second instruction + in `DEFCAL A`, even though `A` is the 4th instruction (index = 3) in the original program. + """ def source_location(self) -> int: ... + """The instruction index within the source program's body instructions.""" def target_location(self) -> CalibrationExpansion: ... + """The location of the expanded instruction within the target program's body instructions.""" @final class CalibrationSource: + """ + The source of a calibration expansion, which can be either a calibration (`DEFCAL`) + or a measure calibration (`DEFCAL MEASURE`). + """ def as_calibration(self) -> CalibrationIdentifier: ... def as_measure_calibration(self) -> MeasureCalibrationIdentifier: ... def is_calibration(self) -> bool: ... @@ -359,6 +402,13 @@ class CalibrationSet: @final class MaybeCalibrationExpansion: + """ + The result of having expanded a certain instruction within a program. Has two variants: + + - `expanded`: The instruction was expanded into other instructions, described by a `CalibrationExpansion`. + - `int`: The instruction was not expanded and is described by an integer, the index of the instruction + within the resulting program's body instructions. + """ def as_expanded(self) -> CalibrationExpansion: ... def as_unexpanded(self) -> int: ... @staticmethod @@ -470,7 +520,9 @@ class MemoryRegion: @final class ProgramCalibrationExpansion: def program(self) -> Program: ... + """The program containing the expanded instructions""" def source_map(self) -> ProgramCalibrationExpansionSourceMap: ... + """The source mapping describing the expansions made""" @final class ProgramCalibrationExpansionSourceMap: @@ -478,5 +530,11 @@ class ProgramCalibrationExpansionSourceMap: @final class ProgramCalibrationExpansionSourceMapEntry: + """ + A description of the possible expansion of one instruction into other instructions + within the scope of a program's calibrations. + """ def source_location(self) -> int: ... - def target_location(self) -> MaybeCalibrationExpansion: ... \ No newline at end of file + """The instruction index within the source program's body instructions.""" + def target_location(self) -> MaybeCalibrationExpansion: ... + """The location of the possibly-expanded instruction within the target program's body instructions.""" \ No newline at end of file From 1965440f1888bf679120ddc5612ae512d313943f Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Wed, 8 May 2024 20:59:31 -0700 Subject: [PATCH 14/39] chore: fmt --- quil-py/quil/instructions/__init__.pyi | 2 +- quil-py/quil/program/__init__.pyi | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/quil-py/quil/instructions/__init__.pyi b/quil-py/quil/instructions/__init__.pyi index 8d813dd8..225fd520 100644 --- a/quil-py/quil/instructions/__init__.pyi +++ b/quil-py/quil/instructions/__init__.pyi @@ -844,7 +844,7 @@ class Calibration: @property def identifier(self) -> CalibrationIdentifier: ... @identifier.setter - def identifier(self, identifier:CalibrationIdentifier) -> None: ... + def identifier(self, identifier: CalibrationIdentifier) -> None: ... @property def instructions(self) -> List[Instruction]: ... @instructions.setter diff --git a/quil-py/quil/program/__init__.pyi b/quil-py/quil/program/__init__.pyi index e0644b40..ae0d14f9 100644 --- a/quil-py/quil/program/__init__.pyi +++ b/quil-py/quil/program/__init__.pyi @@ -68,7 +68,7 @@ class Program: """ ... def expand_calibrations_with_source_map(self) -> ProgramCalibrationExpansion: - """Expand any instructions in the program which have a matching calibration, leaving the others unchanged. + """Expand any instructions in the program which have a matching calibration, leaving the others unchanged. Return the expanded copy of the program and a source mapping describing the expansions made. """ @@ -338,8 +338,6 @@ class CalibrationSource: def from_measure_calibration(inner: MeasureCalibrationIdentifier): ... def inner(self) -> Union[CalibrationIdentifier, MeasureCalibrationIdentifier]: ... - - @final class CalibrationSet: @staticmethod @@ -516,7 +514,6 @@ class MemoryRegion: @sharing.setter def sharing(self, sharing: Optional[Sharing]): ... - @final class ProgramCalibrationExpansion: def program(self) -> Program: ... @@ -537,4 +534,4 @@ class ProgramCalibrationExpansionSourceMapEntry: def source_location(self) -> int: ... """The instruction index within the source program's body instructions.""" def target_location(self) -> MaybeCalibrationExpansion: ... - """The location of the possibly-expanded instruction within the target program's body instructions.""" \ No newline at end of file + """The location of the possibly-expanded instruction within the target program's body instructions.""" From 8906a7648e0f17ffacc968bcc58edacb02d7bf1d Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Wed, 8 May 2024 21:18:02 -0700 Subject: [PATCH 15/39] refactor instruction calibration expansion so that source maps are only built when returned to the user --- quil-rs/src/instruction/calibration.rs | 2 +- quil-rs/src/program/calibration.rs | 102 ++++++++++++++++++------- 2 files changed, 75 insertions(+), 29 deletions(-) diff --git a/quil-rs/src/instruction/calibration.rs b/quil-rs/src/instruction/calibration.rs index f1ce4667..b1530855 100644 --- a/quil-rs/src/instruction/calibration.rs +++ b/quil-rs/src/instruction/calibration.rs @@ -215,7 +215,7 @@ impl Quil for MeasureCalibrationIdentifier { write!(f, " ")?; qubit.write(f, fall_back_to_debug)?; } - writeln!(f, " {}", self.parameter,)?; + write!(f, " {}", self.parameter,)?; Ok(()) } } diff --git a/quil-rs/src/program/calibration.rs b/quil-rs/src/program/calibration.rs index 0b2fcbb4..94f64b71 100644 --- a/quil-rs/src/program/calibration.rs +++ b/quil-rs/src/program/calibration.rs @@ -71,6 +71,17 @@ pub struct CalibrationExpansionOutput { pub detail: CalibrationExpansion, } +/// The product of expanding an instruction using a calibration, possibly including +/// `CalibrationExpansion` source map information. +#[derive(Clone, Debug, PartialEq)] +struct ExpandOutput { + /// The new instructions resulting from the expansion + pub new_instructions: Vec, + + /// Details about the expansion process + pub detail: Option, +} + /// Details about the expansion of a calibration #[derive(Clone, Debug, PartialEq)] pub struct CalibrationExpansion { @@ -189,7 +200,7 @@ impl Calibrations { instruction: &Instruction, previous_calibrations: &[Instruction], ) -> Result>, ProgramError> { - self.expand_with_detail(instruction, previous_calibrations) + self._expand(instruction, previous_calibrations, false) .map(|expansion| expansion.map(|expansion| expansion.new_instructions)) } @@ -200,11 +211,28 @@ impl Calibrations { &self, instruction: &Instruction, previous_calibrations: &[Instruction], + ) -> Result, ProgramError> { + self._expand(instruction, previous_calibrations, true) + } + + /// Expand an instruction, returning an error if a calibration directly or indirectly + /// expands into itself. Return `None` if there are no matching calibrations in `self`. + /// + /// # Arguments + /// + /// * `instruction` - The instruction to expand. + /// * `previous_calibrations` - The calibrations that were invoked to yield this current instruction. + /// * `build_source_map` - Whether to build a source map of the expansion. + fn _expand( + &self, + instruction: &Instruction, + previous_calibrations: &[Instruction], + build_source_map: bool, ) -> Result, ProgramError> { if previous_calibrations.contains(instruction) { return Err(ProgramError::RecursiveCalibration(instruction.clone())); } - let calibration_result = match instruction { + let expansion_result = match instruction { Instruction::Gate(gate) => { let matching_calibration = self.get_match_for_gate(gate); @@ -347,7 +375,16 @@ impl Calibrations { calibration_path.push(instruction.clone()); calibration_path.extend_from_slice(previous_calibrations); - Ok(match calibration_result { + self._recursively_expand(expansion_result, &calibration_path, build_source_map) + } + + fn _recursively_expand( + &self, + expansion_result: Option<(Vec, CalibrationSource)>, + calibration_path: &[Instruction], + build_source_map: bool, + ) -> Result, ProgramError> { + Ok(match expansion_result { Some((instructions, matched_calibration)) => { let mut recursively_expanded_instructions = CalibrationExpansionOutput { new_instructions: Vec::new(), @@ -360,29 +397,35 @@ impl Calibrations { for (expanded_index, instruction) in instructions.into_iter().enumerate() { let expanded_instructions = - self.expand_with_detail(&instruction, &calibration_path)?; + self._expand(&instruction, &calibration_path, build_source_map)?; match expanded_instructions { Some(mut output) => { - let range_start = - recursively_expanded_instructions.new_instructions.len(); - - recursively_expanded_instructions - .new_instructions - .extend(output.new_instructions); - - let range_end = - recursively_expanded_instructions.new_instructions.len(); - let target_range = range_start..range_end; - output.detail.range = target_range; - - recursively_expanded_instructions - .detail - .expansions - .entries - .push(SourceMapEntry { - source_location: expanded_index, - target_location: output.detail, - }); + if build_source_map { + let range_start = + recursively_expanded_instructions.new_instructions.len(); + + recursively_expanded_instructions + .new_instructions + .extend(output.new_instructions); + + let range_end = + recursively_expanded_instructions.new_instructions.len(); + let target_range = range_start..range_end; + output.detail.range = target_range; + + recursively_expanded_instructions + .detail + .expansions + .entries + .push(SourceMapEntry { + source_location: expanded_index, + target_location: output.detail, + }); + } else { + recursively_expanded_instructions + .new_instructions + .extend(output.new_instructions); + } } None => { recursively_expanded_instructions @@ -392,10 +435,13 @@ impl Calibrations { }; } - // While this appears to be duplicated information at this point, it's useful when multiple - // source mappings are merged together. - recursively_expanded_instructions.detail.range = - 0..recursively_expanded_instructions.new_instructions.len(); + if build_source_map { + // While this appears to be duplicated information at this point, it's useful when multiple + // source mappings are merged together. + recursively_expanded_instructions.detail.range = + 0..recursively_expanded_instructions.new_instructions.len(); + } + Some(recursively_expanded_instructions) } None => None, From 645e1a2c2e03a9c6f4232eabf6a732a63474c70d Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Wed, 8 May 2024 21:23:50 -0700 Subject: [PATCH 16/39] update snapshot --- ...il_rs__program__tests__to_instructions.snap | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/quil-rs/src/program/snapshots/quil_rs__program__tests__to_instructions.snap b/quil-rs/src/program/snapshots/quil_rs__program__tests__to_instructions.snap index b0428176..1e6f2f6e 100644 --- a/quil-rs/src/program/snapshots/quil_rs__program__tests__to_instructions.snap +++ b/quil-rs/src/program/snapshots/quil_rs__program__tests__to_instructions.snap @@ -54,6 +54,16 @@ expression: program.to_instructions() ), CalibrationDefinition( Calibration { + identifier: CalibrationIdentifier { + modifiers: [], + name: "I", + parameters: [], + qubits: [ + Fixed( + 1, + ), + ], + }, instructions: [ Delay( Delay { @@ -72,14 +82,6 @@ expression: program.to_instructions() }, ), ], - modifiers: [], - name: "I", - parameters: [], - qubits: [ - Fixed( - 1, - ), - ], }, ), GateDefinition( From cafe117b46743fe0e6ed18d5b6230f61e5e053e6 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Fri, 10 May 2024 11:14:49 -0700 Subject: [PATCH 17/39] chore: lint --- quil-py/src/program/source_map.rs | 6 +++--- quil-rs/src/program/calibration.rs | 4 ++-- quil-rs/src/program/mod.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/quil-py/src/program/source_map.rs b/quil-py/src/program/source_map.rs index 2bb20add..991d428b 100644 --- a/quil-py/src/program/source_map.rs +++ b/quil-py/src/program/source_map.rs @@ -64,11 +64,11 @@ impl_repr!(PyCalibrationExpansionSourceMapEntry); #[pymethods] impl PyCalibrationExpansionSourceMapEntry { pub fn source_location(&self) -> usize { - self.as_inner().source_location().clone().into() + *self.as_inner().source_location() } pub fn target_location(&self) -> PyCalibrationExpansion { - self.as_inner().target_location().clone().into() + self.as_inner().target_location().into() } } @@ -138,7 +138,7 @@ impl_repr!(PyProgramCalibrationExpansionSourceMapEntry); #[pymethods] impl PyProgramCalibrationExpansionSourceMapEntry { pub fn source_location(&self) -> usize { - self.as_inner().source_location().clone().into() + *self.as_inner().source_location() } pub fn target_location(&self) -> PyMaybeCalibrationExpansion { diff --git a/quil-rs/src/program/calibration.rs b/quil-rs/src/program/calibration.rs index 94f64b71..64967c68 100644 --- a/quil-rs/src/program/calibration.rs +++ b/quil-rs/src/program/calibration.rs @@ -123,7 +123,7 @@ impl CalibrationExpansion { .remove_target_index(target_with_offset); } - entry.target_location.range.len() > 0 + !entry.target_location.range.is_empty() }, ); } @@ -397,7 +397,7 @@ impl Calibrations { for (expanded_index, instruction) in instructions.into_iter().enumerate() { let expanded_instructions = - self._expand(&instruction, &calibration_path, build_source_map)?; + self._expand(&instruction, calibration_path, build_source_map)?; match expanded_instructions { Some(mut output) => { if build_source_map { diff --git a/quil-rs/src/program/mod.rs b/quil-rs/src/program/mod.rs index 249e73b3..8c735bda 100644 --- a/quil-rs/src/program/mod.rs +++ b/quil-rs/src/program/mod.rs @@ -335,7 +335,7 @@ impl Program { expansion_output.detail.range = previous_program_instruction_body_length..self.instructions.len(); - if expansion_output.detail.range.len() > 0 { + if !expansion_output.detail.range.is_empty() { source_mapping.entries.push(SourceMapEntry { source_location: source_index, target_location: MaybeCalibrationExpansion::Expanded(expansion_output.detail), From 3216ef3e9955d8c593a5682535a7f09cf92896e3 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Mon, 1 Jul 2024 00:55:52 -0700 Subject: [PATCH 18/39] chore: cleanup --- quil-py/src/program/source_map.rs | 29 ++++++++-- quil-rs/src/instruction/calibration.rs | 56 +++++++++++++++++++- quil-rs/src/program/calibration.rs | 73 ++------------------------ quil-rs/src/waveform/templates.rs | 6 +-- 4 files changed, 87 insertions(+), 77 deletions(-) diff --git a/quil-py/src/program/source_map.rs b/quil-py/src/program/source_map.rs index 991d428b..171620d2 100644 --- a/quil-py/src/program/source_map.rs +++ b/quil-py/src/program/source_map.rs @@ -1,11 +1,19 @@ -use pyo3::{types::PyInt, Py}; +use std::ops::Range; + +use pyo3::{ + types::{PyInt, PyTuple}, + Py, PyResult, Python, +}; use quil_rs::program::{ CalibrationExpansion, CalibrationSource, MaybeCalibrationExpansion, ProgramCalibrationExpansion, ProgramCalibrationExpansionSourceMap, SourceMap, SourceMapEntry, }; use rigetti_pyo3::{impl_repr, py_wrap_type, py_wrap_union_enum, pyo3::pymethods, PyWrapper}; -use crate::instruction::{PyCalibrationIdentifier, PyMeasureCalibrationIdentifier}; +use crate::{ + impl_eq, + instruction::{PyCalibrationIdentifier, PyMeasureCalibrationIdentifier}, +}; use super::PyProgram; @@ -19,6 +27,7 @@ py_wrap_type! { } impl_repr!(PyCalibrationExpansion); +impl_eq!(PyCalibrationExpansion); #[pymethods] impl PyCalibrationExpansion { @@ -27,8 +36,13 @@ impl PyCalibrationExpansion { } // Reviewer: is there a better return type? - pub fn range(&self) -> (usize, usize) { - (self.as_inner().range().start, self.as_inner().range().end) + pub fn range(&self) -> PyResult<(usize, usize)> { + Python::with_gil(|py| { + let range = py.import("builtins")?.get_item("range")?; + let Range { start, end } = self.as_inner().range(); + let args = PyTuple::new(py, [start, end]); + range.call1(args)?.extract() + }) } pub fn expansions(&self) -> PyCalibrationExpansionSourceMap { @@ -42,6 +56,7 @@ py_wrap_type! { } impl_repr!(PyCalibrationExpansionSourceMap); +impl_eq!(PyCalibrationExpansionSourceMap); #[pymethods] impl PyCalibrationExpansionSourceMap { @@ -60,6 +75,7 @@ py_wrap_type! { } impl_repr!(PyCalibrationExpansionSourceMapEntry); +impl_eq!(PyCalibrationExpansionSourceMapEntry); #[pymethods] impl PyCalibrationExpansionSourceMapEntry { @@ -81,6 +97,7 @@ py_wrap_union_enum! { } impl_repr!(PyCalibrationSource); +impl_eq!(PyCalibrationSource); py_wrap_union_enum! { #[derive(Debug, PartialEq)] @@ -91,6 +108,7 @@ py_wrap_union_enum! { } impl_repr!(PyMaybeCalibrationExpansion); +impl_eq!(PyMaybeCalibrationExpansion); py_wrap_type! { #[derive(Debug, PartialEq)] @@ -98,6 +116,7 @@ py_wrap_type! { } impl_repr!(PyProgramCalibrationExpansion); +impl_eq!(PyProgramCalibrationExpansion); #[pymethods] impl PyProgramCalibrationExpansion { @@ -116,6 +135,7 @@ py_wrap_type! { } impl_repr!(PyProgramCalibrationExpansionSourceMap); +impl_eq!(PyProgramCalibrationExpansionSourceMap); #[pymethods] impl PyProgramCalibrationExpansionSourceMap { @@ -134,6 +154,7 @@ py_wrap_type! { } impl_repr!(PyProgramCalibrationExpansionSourceMapEntry); +impl_eq!(PyProgramCalibrationExpansionSourceMapEntry); #[pymethods] impl PyProgramCalibrationExpansionSourceMapEntry { diff --git a/quil-rs/src/instruction/calibration.rs b/quil-rs/src/instruction/calibration.rs index b1530855..475edbc7 100644 --- a/quil-rs/src/instruction/calibration.rs +++ b/quil-rs/src/instruction/calibration.rs @@ -7,7 +7,7 @@ use crate::{ validation::identifier::{validate_identifier, IdentifierValidationError}, }; -use super::write_qubit_parameters; +use super::{write_qubit_parameters, Gate}; pub trait CalibrationSignature { type Signature<'a> @@ -101,6 +101,60 @@ impl CalibrationIdentifier { qubits, }) } + + pub fn matches(&self, gate: &Gate) -> bool { + // Filter out non-matching calibrations: check rules 1-4 + if self.name != gate.name + || self.modifiers != gate.modifiers + || self.parameters.len() != gate.parameters.len() + || self.qubits.len() != gate.qubits.len() + { + return false; + } + + let fixed_qubits_match = self + .qubits + .iter() + .enumerate() + .all(|(calibration_index, _)| { + match ( + &self.qubits[calibration_index], + &gate.qubits[calibration_index], + ) { + // Placeholders never match + (Qubit::Placeholder(_), _) | (_, Qubit::Placeholder(_)) => false, + // If they're both fixed, test if they're fixed to the same qubit + (Qubit::Fixed(calibration_fixed_qubit), Qubit::Fixed(gate_fixed_qubit)) => { + calibration_fixed_qubit == gate_fixed_qubit + } + // If the calibration is variable, it matches any fixed qubit + (Qubit::Variable(_), _) => true, + // If the calibration is fixed, but the gate's qubit is variable, it's not a match + (Qubit::Fixed(_), _) => false, + } + }); + if !fixed_qubits_match { + return false; + } + + let fixed_parameters_match = + self.parameters + .iter() + .enumerate() + .all(|(calibration_index, _)| { + let calibration_parameters = + self.parameters[calibration_index].clone().into_simplified(); + let gate_parameters = + gate.parameters[calibration_index].clone().into_simplified(); + match (calibration_parameters, gate_parameters) { + // If the calibration is variable, it matches any fixed qubit + (Expression::Variable(_), _) => true, + // If the calibration is fixed, but the gate's qubit is variable, it's not a match + (calib, gate) => calib == gate, + } + }); + !fixed_parameters_match + } } impl CalibrationSignature for CalibrationIdentifier { diff --git a/quil-rs/src/program/calibration.rs b/quil-rs/src/program/calibration.rs index 64967c68..85ef0751 100644 --- a/quil-rs/src/program/calibration.rs +++ b/quil-rs/src/program/calibration.rs @@ -71,17 +71,6 @@ pub struct CalibrationExpansionOutput { pub detail: CalibrationExpansion, } -/// The product of expanding an instruction using a calibration, possibly including -/// `CalibrationExpansion` source map information. -#[derive(Clone, Debug, PartialEq)] -struct ExpandOutput { - /// The new instructions resulting from the expansion - pub new_instructions: Vec, - - /// Details about the expansion process - pub detail: Option, -} - /// Details about the expansion of a calibration #[derive(Clone, Debug, PartialEq)] pub struct CalibrationExpansion { @@ -500,64 +489,10 @@ impl Calibrations { pub fn get_match_for_gate(&self, gate: &Gate) -> Option<&Calibration> { let mut matched_calibration: Option = None; - for calibration in self.iter_calibrations() { - // Filter out non-matching calibrations: check rules 1-4 - if calibration.identifier.name != gate.name - || calibration.identifier.modifiers != gate.modifiers - || calibration.identifier.parameters.len() != gate.parameters.len() - || calibration.identifier.qubits.len() != gate.qubits.len() - { - continue; - } - - let fixed_qubits_match = - calibration - .identifier - .qubits - .iter() - .enumerate() - .all(|(calibration_index, _)| { - match ( - &calibration.identifier.qubits[calibration_index], - &gate.qubits[calibration_index], - ) { - // Placeholders never match - (Qubit::Placeholder(_), _) | (_, Qubit::Placeholder(_)) => false, - // If they're both fixed, test if they're fixed to the same qubit - ( - Qubit::Fixed(calibration_fixed_qubit), - Qubit::Fixed(gate_fixed_qubit), - ) => calibration_fixed_qubit == gate_fixed_qubit, - // If the calibration is variable, it matches any fixed qubit - (Qubit::Variable(_), _) => true, - // If the calibration is fixed, but the gate's qubit is variable, it's not a match - (Qubit::Fixed(_), _) => false, - } - }); - if !fixed_qubits_match { - continue; - } - - let fixed_parameters_match = calibration.identifier.parameters.iter().enumerate().all( - |(calibration_index, _)| { - let calibration_parameters = calibration.identifier.parameters - [calibration_index] - .clone() - .into_simplified(); - let gate_parameters = - gate.parameters[calibration_index].clone().into_simplified(); - match (calibration_parameters, gate_parameters) { - // If the calibration is variable, it matches any fixed qubit - (Expression::Variable(_), _) => true, - // If the calibration is fixed, but the gate's qubit is variable, it's not a match - (calib, gate) => calib == gate, - } - }, - ); - if !fixed_parameters_match { - continue; - } - + for calibration in self + .iter_calibrations() + .filter(|calibration| calibration.identifier.matches(gate)) + { matched_calibration = match matched_calibration { None => Some(MatchedCalibration::new(calibration)), Some(previous_match) => { diff --git a/quil-rs/src/waveform/templates.rs b/quil-rs/src/waveform/templates.rs index 0cc3b5a1..b13c5805 100644 --- a/quil-rs/src/waveform/templates.rs +++ b/quil-rs/src/waveform/templates.rs @@ -30,7 +30,7 @@ pub fn apply_phase_and_detuning( /// To handle accumulated floating point errors in sweeps above typical floating point imprecision /// we make epsilon 10x larger than floating point epsilon. fn ceiling_with_epsilon(value: f64) -> f64 { - let truncated = value - (value * 10.0 * std::f64::EPSILON); + let truncated = value - (value * 10.0 * f64::EPSILON); truncated.ceil() } @@ -297,8 +297,8 @@ mod tests { #[rstest::rstest] #[case(0.0, 0.0)] - #[case(0.0-std::f64::EPSILON, 0.0)] - #[case(0.0+std::f64::EPSILON, 1.0)] + #[case(0.0-f64::EPSILON, 0.0)] + #[case(0.0+f64::EPSILON, 1.0)] // Based on a past edge case #[case(8.800_000_000_000_001e-8 * 1.0e9, 88.0)] fn ceiling_with_epsilon(#[case] value: f64, #[case] expected: f64) { From ac63749501772daed077bf06e7dbd0dce7bfe241 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Mon, 22 Jul 2024 07:11:24 -0700 Subject: [PATCH 19/39] add query (lookup) operations for SourceMap --- quil-py/quil/program/__init__.pyi | 60 ++++++++++++++++++---- quil-py/src/program/source_map.rs | 82 ++++++++++++++++++++++++++++++ quil-rs/src/program/calibration.rs | 32 +++++++++++- quil-rs/src/program/source_map.rs | 54 +++++++++++++++++++- 4 files changed, 216 insertions(+), 12 deletions(-) diff --git a/quil-py/quil/program/__init__.pyi b/quil-py/quil/program/__init__.pyi index ae0d14f9..c248c9b8 100644 --- a/quil-py/quil/program/__init__.pyi +++ b/quil-py/quil/program/__init__.pyi @@ -278,11 +278,30 @@ class CalibrationExpansion: @final class CalibrationExpansionSourceMap: def entries(self) -> List[CalibrationExpansionSourceMapEntry]: ... + def list_sources_for_target_index(self, target_index: int) -> List[int]: + """Return the locations in the source which were expanded to generate that instruction. + + This is `O(n)` where `n` is the number of first-level calibration expansions performed. + """ + ... + + def list_sources_for_calibration_used(self, calibration_used: CalibrationSource) -> List[int]: + """Return the locations in the source program which were expanded using a calibration. + + This is `O(n)` where `n` is the number of first-level calibration expansions performed. + """ + ... + + def list_targets_for_source_index(self, source_index: int) -> List[CalibrationExpansion]: + """Given a source index, return information about its expansion. + + This is `O(n)` where `n` is the number of source instructions. + """ + ... @final class CalibrationExpansionSourceMapEntry: - """ - A description of the expansion of one instruction into other instructions. + """A description of the expansion of one instruction into other instructions. If present, the instruction located at `source_location` was expanded using calibrations into the instructions located at `target_location`. @@ -322,9 +341,9 @@ class CalibrationExpansionSourceMapEntry: @final class CalibrationSource: - """ - The source of a calibration expansion, which can be either a calibration (`DEFCAL`) - or a measure calibration (`DEFCAL MEASURE`). + """The source of a calibration expansion. + + Can be either a calibration (`DEFCAL`) or a measure calibration (`DEFCAL MEASURE`). """ def as_calibration(self) -> CalibrationIdentifier: ... def as_measure_calibration(self) -> MeasureCalibrationIdentifier: ... @@ -400,8 +419,9 @@ class CalibrationSet: @final class MaybeCalibrationExpansion: - """ - The result of having expanded a certain instruction within a program. Has two variants: + """The result of having expanded a certain instruction within a program. + + Has two variants: - `expanded`: The instruction was expanded into other instructions, described by a `CalibrationExpansion`. - `int`: The instruction was not expanded and is described by an integer, the index of the instruction @@ -524,12 +544,32 @@ class ProgramCalibrationExpansion: @final class ProgramCalibrationExpansionSourceMap: def entries(self) -> List[ProgramCalibrationExpansionSourceMapEntry]: ... + def list_sources_for_target_index(self, target_index: int) -> List[int]: + """Return the locations in the source which were expanded to generate that instruction. + + This is `O(n)` where `n` is the number of first-level calibration expansions performed. + """ + ... + + def list_sources_for_calibration_used(self, calibration_used: CalibrationSource) -> List[int]: + """Return the locations in the source program which were expanded using a calibration. + + This is `O(n)` where `n` is the number of first-level calibration expansions performed. + """ + ... + + def list_targets_for_source_index(self, source_index: int) -> List[CalibrationExpansion]: + """Given a source index, return information about its expansion. + + This is `O(n)` where `n` is the number of source instructions. + """ + ... @final class ProgramCalibrationExpansionSourceMapEntry: - """ - A description of the possible expansion of one instruction into other instructions - within the scope of a program's calibrations. + """A description of the possible expansion of one instruction into other instructions. + + Valid within the scope of a program's calibrations. """ def source_location(self) -> int: ... """The instruction index within the source program's body instructions.""" diff --git a/quil-py/src/program/source_map.rs b/quil-py/src/program/source_map.rs index 171620d2..56d8ba51 100644 --- a/quil-py/src/program/source_map.rs +++ b/quil-py/src/program/source_map.rs @@ -67,6 +67,47 @@ impl PyCalibrationExpansionSourceMap { .map(|entry| entry.into()) .collect() } + + /// Given an instruction index within the resulting expansion, return the locations in the source + /// which were expanded to generate that instruction. + /// + /// This is `O(n)` where `n` is the number of first-level calibration expansions performed. + pub fn list_sources_for_target_index(&self, target_index: usize) -> Vec { + self.as_inner() + .list_sources(&target_index) + .into_iter() + .copied() + .collect() + } + + /// Given a particular calibration (`DEFCAL` or `DEFCAL MEASURE`), return the locations in the source + /// program which were expanded using that calibration. + /// + /// This is `O(n)` where `n` is the number of first-level calibration expansions performed. + pub fn list_sources_for_calibration_used( + &self, + calibration_used: PyCalibrationSource, + ) -> Vec { + self.as_inner() + .list_sources(calibration_used.as_inner()) + .into_iter() + .copied() + .collect() + } + + /// Given a source index, return information about its expansion. + /// + /// This is `O(n)` where `n` is the number of source instructions. + pub fn list_targets_for_source_index( + &self, + source_index: usize, + ) -> Vec { + self.as_inner() + .list_targets(&source_index) + .into_iter() + .map(|expansion| expansion.into()) + .collect() + } } py_wrap_type! { @@ -146,6 +187,47 @@ impl PyProgramCalibrationExpansionSourceMap { .map(|entry| entry.into()) .collect() } + + /// Given an instruction index within the resulting expansion, return the locations in the source + /// which were expanded to generate that instruction. + /// + /// This is `O(n)` where `n` is the number of first-level calibration expansions performed. + pub fn list_sources_for_target_index(&self, target_index: usize) -> Vec { + self.as_inner() + .list_sources(&target_index) + .into_iter() + .copied() + .collect() + } + + /// Given a particular calibration (`DEFCAL` or `DEFCAL MEASURE`), return the locations in the source + /// program which were expanded using that calibration. + /// + /// This is `O(n)` where `n` is the number of first-level calibration expansions performed. + pub fn list_sources_for_calibration_used( + &self, + calibration_used: PyCalibrationSource, + ) -> Vec { + self.as_inner() + .list_sources(calibration_used.as_inner()) + .into_iter() + .copied() + .collect() + } + + /// Given a source index, return information about its expansion. + /// + /// This is `O(n)` where `n` is the number of source instructions. + pub fn list_targets_for_source_index( + &self, + source_index: usize, + ) -> Vec { + self.as_inner() + .list_targets(&source_index) + .into_iter() + .map(|expansion| expansion.into()) + .collect() + } } py_wrap_type! { diff --git a/quil-rs/src/program/calibration.rs b/quil-rs/src/program/calibration.rs index 85ef0751..2203f605 100644 --- a/quil-rs/src/program/calibration.rs +++ b/quil-rs/src/program/calibration.rs @@ -29,7 +29,7 @@ use crate::{ }, }; -use super::source_map::{SourceMap, SourceMapEntry}; +use super::source_map::{SourceMap, SourceMapEntry, SourceMapIndexable}; use super::{CalibrationSet, ProgramError}; /// A collection of Quil calibrations (`DEFCAL` instructions) with utility methods. @@ -130,6 +130,18 @@ impl CalibrationExpansion { } } +impl SourceMapIndexable for CalibrationExpansion { + fn intersects(&self, other: &usize) -> bool { + self.range.contains(other) + } +} + +impl SourceMapIndexable for CalibrationExpansion { + fn intersects(&self, other: &CalibrationSource) -> bool { + self.calibration_used() == other + } +} + /// The result of an attempt to expand an instruction within a [`Program`] #[derive(Clone, Debug, PartialEq)] pub enum MaybeCalibrationExpansion { @@ -140,6 +152,24 @@ pub enum MaybeCalibrationExpansion { Unexpanded(usize), } +impl SourceMapIndexable for MaybeCalibrationExpansion { + fn intersects(&self, other: &usize) -> bool { + match self { + MaybeCalibrationExpansion::Expanded(expansion) => expansion.intersects(other), + MaybeCalibrationExpansion::Unexpanded(index) => index == other, + } + } +} + +impl SourceMapIndexable for MaybeCalibrationExpansion { + fn intersects(&self, other: &CalibrationSource) -> bool { + match self { + MaybeCalibrationExpansion::Expanded(expansion) => expansion.intersects(other), + MaybeCalibrationExpansion::Unexpanded(_) => false, + } + } +} + /// A source of a calibration, either a [`Calibration`] or a [`MeasureCalibrationDefinition`] #[derive(Clone, Debug, PartialEq)] pub enum CalibrationSource { diff --git a/quil-rs/src/program/source_map.rs b/quil-rs/src/program/source_map.rs index 5b86203f..b3f24be5 100644 --- a/quil-rs/src/program/source_map.rs +++ b/quil-rs/src/program/source_map.rs @@ -3,7 +3,7 @@ /// mapped in either direction. /// /// The behavior of such mappings depends on the implementations of the generic `Index` types, -/// but this may be a many-to-many mapping, where one element of the source is mapped (contributed) +/// but this may be a many-to-many mapping, where one element of the source is mapped (contributes) /// to zero or many elements of the target, and vice versa. /// /// This is also intended to be mergeable in a chain, such that the combined result of a series @@ -13,6 +13,46 @@ pub struct SourceMap { pub(crate) entries: Vec>, } +impl SourceMap { + /// Return all source ranges in the source map which were used to generate the target range. + /// + /// This is `O(n)` where `n` is the number of target entries in the map. + pub fn list_sources(&self, target_index: &QueryIndex) -> Vec<&SourceIndex> + where + TargetIndex: SourceMapIndexable, + { + self.entries + .iter() + .filter_map(|entry| { + if entry.target_location().intersects(target_index) { + Some(entry.source_location()) + } else { + None + } + }) + .collect() + } + + /// Return all target ranges in the source map which were used to generate the source range. + /// + /// This is `O(n)` where `n` is the number of source entries in the map. + pub fn list_targets(&self, source_index: &QueryIndex) -> Vec<&TargetIndex> + where + SourceIndex: SourceMapIndexable, + { + self.entries + .iter() + .filter_map(|entry| { + if entry.source_location().intersects(source_index) { + Some(entry.target_location()) + } else { + None + } + }) + .collect() + } +} + impl Default for SourceMap { fn default() -> Self { Self { @@ -45,3 +85,15 @@ impl SourceMapEntry { &self.target_location } } + +/// A trait for types which can be used as lookup indices in a `SourceMap.` +pub trait SourceMapIndexable { + /// Return `true` if a source or target index intersects `other`. + fn intersects(&self, other: &Index) -> bool; +} + +impl SourceMapIndexable for usize { + fn intersects(&self, other: &usize) -> bool { + self == other + } +} \ No newline at end of file From 4b61fc8999457e0f953cb8acd15e22af8e3c8460 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Mon, 22 Jul 2024 08:32:16 -0700 Subject: [PATCH 20/39] fix bug in calibration expansion --- quil-rs/src/instruction/calibration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quil-rs/src/instruction/calibration.rs b/quil-rs/src/instruction/calibration.rs index 475edbc7..0b83aa8f 100644 --- a/quil-rs/src/instruction/calibration.rs +++ b/quil-rs/src/instruction/calibration.rs @@ -153,7 +153,7 @@ impl CalibrationIdentifier { (calib, gate) => calib == gate, } }); - !fixed_parameters_match + fixed_parameters_match } } From 07ae1d3a8171c5fa8650f9b827d9d85aaa3d82a9 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Mon, 22 Jul 2024 08:32:28 -0700 Subject: [PATCH 21/39] chore: lint --- quil-rs/src/program/source_map.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/quil-rs/src/program/source_map.rs b/quil-rs/src/program/source_map.rs index b3f24be5..d0c765ef 100644 --- a/quil-rs/src/program/source_map.rs +++ b/quil-rs/src/program/source_map.rs @@ -15,7 +15,7 @@ pub struct SourceMap { impl SourceMap { /// Return all source ranges in the source map which were used to generate the target range. - /// + /// /// This is `O(n)` where `n` is the number of target entries in the map. pub fn list_sources(&self, target_index: &QueryIndex) -> Vec<&SourceIndex> where @@ -34,7 +34,7 @@ impl SourceMap { } /// Return all target ranges in the source map which were used to generate the source range. - /// + /// /// This is `O(n)` where `n` is the number of source entries in the map. pub fn list_targets(&self, source_index: &QueryIndex) -> Vec<&TargetIndex> where @@ -96,4 +96,4 @@ impl SourceMapIndexable for usize { fn intersects(&self, other: &usize) -> bool { self == other } -} \ No newline at end of file +} From 78df471b03ddc2298a6975344ae562edce6d0533 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Mon, 22 Jul 2024 08:33:42 -0700 Subject: [PATCH 22/39] fix range return type --- quil-py/quil/program/__init__.pyi | 6 +++--- quil-py/src/program/source_map.rs | 17 +++++++---------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/quil-py/quil/program/__init__.pyi b/quil-py/quil/program/__init__.pyi index c248c9b8..78a08dc6 100644 --- a/quil-py/quil/program/__init__.pyi +++ b/quil-py/quil/program/__init__.pyi @@ -1,4 +1,4 @@ -from typing import Callable, Dict, FrozenSet, List, Optional, Sequence, Set, Tuple, Union, final +from typing import Callable, Dict, FrozenSet, List, Optional, Sequence, Set, Union, final import numpy as np from numpy.typing import NDArray @@ -270,7 +270,7 @@ class BasicBlock: class CalibrationExpansion: def calibration_used(self) -> CalibrationSource: ... """The calibration which was used to expand the instruction.""" - def range(self) -> Tuple[int, int]: ... + def range(self) -> range: ... """The range of instructions in the expanded list which were generated by this expansion.""" def expansions(self) -> CalibrationExpansionSourceMap: ... """The source map describing the nested expansions made.""" @@ -558,7 +558,7 @@ class ProgramCalibrationExpansionSourceMap: """ ... - def list_targets_for_source_index(self, source_index: int) -> List[CalibrationExpansion]: + def list_targets_for_source_index(self, source_index: int) -> List[MaybeCalibrationExpansion]: """Given a source index, return information about its expansion. This is `O(n)` where `n` is the number of source instructions. diff --git a/quil-py/src/program/source_map.rs b/quil-py/src/program/source_map.rs index 56d8ba51..7871fd09 100644 --- a/quil-py/src/program/source_map.rs +++ b/quil-py/src/program/source_map.rs @@ -1,8 +1,8 @@ use std::ops::Range; use pyo3::{ - types::{PyInt, PyTuple}, - Py, PyResult, Python, + types::{PyInt, PyModule, PyTuple}, + Py, PyAny, PyResult, Python, }; use quil_rs::program::{ CalibrationExpansion, CalibrationSource, MaybeCalibrationExpansion, @@ -35,14 +35,11 @@ impl PyCalibrationExpansion { self.as_inner().calibration_used().into() } - // Reviewer: is there a better return type? - pub fn range(&self) -> PyResult<(usize, usize)> { - Python::with_gil(|py| { - let range = py.import("builtins")?.get_item("range")?; - let Range { start, end } = self.as_inner().range(); - let args = PyTuple::new(py, [start, end]); - range.call1(args)?.extract() - }) + pub fn range<'py>(&self, py: Python<'py>) -> PyResult<&'py PyAny> { + let range = PyModule::import(py, "builtins")?.getattr("range")?; + let Range { start, end } = self.as_inner().range(); + let tuple = PyTuple::new(py, [start, end]); + range.call1(tuple)?.extract() } pub fn expansions(&self) -> PyCalibrationExpansionSourceMap { From 25bd576108a83f65437bde8f01925ec9c488e0fd Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Mon, 22 Jul 2024 08:33:51 -0700 Subject: [PATCH 23/39] add test --- quil-py/test/program/test_program.py | 45 ++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/quil-py/test/program/test_program.py b/quil-py/test/program/test_program.py index 86f81179..d8ee0751 100644 --- a/quil-py/test/program/test_program.py +++ b/quil-py/test/program/test_program.py @@ -186,3 +186,48 @@ def test_filter_instructions(snapshot: SnapshotAssertion): program = Program.parse(input) program_without_quil_t = program.filter_instructions(lambda instruction: not instruction.is_quil_t()) assert program_without_quil_t.to_quil() == snapshot + + +def test_calibration_expansion(): + program = Program.parse( + """DEFCAL X 0: + Y 0 + +DEFCAL Y 0: + Z 0 + +X 0 +Y 0 +""" + ) + expansion = program.expand_calibrations_with_source_map() + source_map = expansion.source_map() + + assert ( + expansion.program().to_quil() + == Program.parse("""DEFCAL X 0: + Y 0 + +DEFCAL Y 0: + Z 0 + +Z 0 +Z 0 +""").to_quil() + ) + + # The X at index 0 should have been replaced with a Z at index 0 + targets = source_map.list_targets_for_source_index(0) + assert len(targets) == 1 + expanded = targets[0].as_expanded() + assert expanded.range() == range(0, 1) + + # The Y at index 1 should have been replaced with a Z at index 1 + targets = source_map.list_targets_for_source_index(1) + assert len(targets) == 1 + expanded = targets[0].as_expanded() + assert expanded.range() == range(1, 2) + + # There is no source index 2 and so there should be no mapping + assert source_map.list_targets_for_source_index(2) == [] + assert source_map.list_sources_for_target_index(0) == [0] From f64aa8a2d7441623bd8cdcd9f0f0f19325ba6258 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Mon, 22 Jul 2024 08:47:39 -0700 Subject: [PATCH 24/39] chore: cleanup --- quil-rs/src/program/mod.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/quil-rs/src/program/mod.rs b/quil-rs/src/program/mod.rs index 8c735bda..683e9433 100644 --- a/quil-rs/src/program/mod.rs +++ b/quil-rs/src/program/mod.rs @@ -242,16 +242,18 @@ impl Program { /// Expand any instructions in the program which have a matching calibration, leaving the others /// unchanged. Return the expanded copy of the program. /// + /// Return an error if any instruction expands into itself. + /// /// See [`Program::expand_calibrations_with_source_map`] for a version that returns a source mapping. pub fn expand_calibrations(&self) -> Result { - self._expand_calibrations(None) + self.expand_calibrations_inner(None) } // / Expand any instructions in the program which have a matching calibration, leaving the others // / unchanged. Return the expanded copy of the program and a source mapping of the expansions made. pub fn expand_calibrations_with_source_map(&self) -> Result { let mut source_mapping = ProgramCalibrationExpansionSourceMap::default(); - let new_program = self._expand_calibrations(Some(&mut source_mapping))?; + let new_program = self.expand_calibrations_inner(Some(&mut source_mapping))?; Ok(ProgramCalibrationExpansion { program: new_program, @@ -261,8 +263,10 @@ impl Program { /// Expand calibrations, writing expansions to a [`SourceMap`] if provided. /// + /// Return an error if any instruction expands into itself. + /// /// Source map may be omitted for faster performance. - fn _expand_calibrations( + fn expand_calibrations_inner( &self, mut source_mapping: Option<&mut ProgramCalibrationExpansionSourceMap>, ) -> Result { @@ -279,7 +283,7 @@ impl Program { for (index, instruction) in self.instructions.iter().enumerate() { match self.calibrations.expand_with_detail(instruction, &[])? { Some(expanded) => { - new_program._append_calibration_expansion_output( + new_program.append_calibration_expansion_output_inner( expanded, index, &mut source_mapping, @@ -307,7 +311,7 @@ impl Program { /// /// For example, `DECLARE` instructions are hoisted to a specialized data structure and thus do not appear in /// the program body. Thus, they should not be counted in the `target_index` range within a [`SourceMapEntry`]. - fn _append_calibration_expansion_output( + fn append_calibration_expansion_output_inner( &mut self, mut expansion_output: CalibrationExpansionOutput, source_index: usize, @@ -933,9 +937,9 @@ DECLARE ec BIT /// emitting the expected [`SourceMap`] for the expansion. #[test] fn expand_calibrations() { - let input = "DECLARE ro BIT[1] -DEFFRAME 0 \"a\": -\tHARDWARE-OBJECT: \"hardware\" + let input = r#"DECLARE ro BIT[1] +DEFFRAME 0 "a": + HARDWARE-OBJECT: "hardware" DEFCAL I 0: DECLAREMEM @@ -947,9 +951,9 @@ DEFCAL DECLAREMEM: NOP I 0 -PULSE 0 \"a\" custom_waveform +PULSE 0 "a" custom_waveform I 0 -"; +"#; let expected = "DECLARE ro BIT[1] DECLARE mem BIT[1] From d3abce63aa6c5ad5023b401d3eed488861332b14 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Mon, 22 Jul 2024 08:54:25 -0700 Subject: [PATCH 25/39] chore: fix docs --- quil-py/quil/program/__init__.pyi | 6 +++--- quil-py/src/program/source_map.rs | 6 +++--- quil-rs/src/program/source_map.rs | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/quil-py/quil/program/__init__.pyi b/quil-py/quil/program/__init__.pyi index 78a08dc6..343f07bc 100644 --- a/quil-py/quil/program/__init__.pyi +++ b/quil-py/quil/program/__init__.pyi @@ -295,7 +295,7 @@ class CalibrationExpansionSourceMap: def list_targets_for_source_index(self, source_index: int) -> List[CalibrationExpansion]: """Given a source index, return information about its expansion. - This is `O(n)` where `n` is the number of source instructions. + This is `O(n)` where `n` is the number of first-level calibration expansions performed. """ ... @@ -547,14 +547,14 @@ class ProgramCalibrationExpansionSourceMap: def list_sources_for_target_index(self, target_index: int) -> List[int]: """Return the locations in the source which were expanded to generate that instruction. - This is `O(n)` where `n` is the number of first-level calibration expansions performed. + This is `O(n)` where `n` is the number of source instructions. """ ... def list_sources_for_calibration_used(self, calibration_used: CalibrationSource) -> List[int]: """Return the locations in the source program which were expanded using a calibration. - This is `O(n)` where `n` is the number of first-level calibration expansions performed. + This is `O(n)` where `n` is the number of source instructions. """ ... diff --git a/quil-py/src/program/source_map.rs b/quil-py/src/program/source_map.rs index 7871fd09..e2e3ef8c 100644 --- a/quil-py/src/program/source_map.rs +++ b/quil-py/src/program/source_map.rs @@ -94,7 +94,7 @@ impl PyCalibrationExpansionSourceMap { /// Given a source index, return information about its expansion. /// - /// This is `O(n)` where `n` is the number of source instructions. + /// This is `O(n)` where `n` is the number of first-level calibration expansions performed. pub fn list_targets_for_source_index( &self, source_index: usize, @@ -188,7 +188,7 @@ impl PyProgramCalibrationExpansionSourceMap { /// Given an instruction index within the resulting expansion, return the locations in the source /// which were expanded to generate that instruction. /// - /// This is `O(n)` where `n` is the number of first-level calibration expansions performed. + /// This is `O(n)` where `n` is the number of source instructions. pub fn list_sources_for_target_index(&self, target_index: usize) -> Vec { self.as_inner() .list_sources(&target_index) @@ -200,7 +200,7 @@ impl PyProgramCalibrationExpansionSourceMap { /// Given a particular calibration (`DEFCAL` or `DEFCAL MEASURE`), return the locations in the source /// program which were expanded using that calibration. /// - /// This is `O(n)` where `n` is the number of first-level calibration expansions performed. + /// This is `O(n)` where `n` is the number of source instructions. pub fn list_sources_for_calibration_used( &self, calibration_used: PyCalibrationSource, diff --git a/quil-rs/src/program/source_map.rs b/quil-rs/src/program/source_map.rs index d0c765ef..9dc9a725 100644 --- a/quil-rs/src/program/source_map.rs +++ b/quil-rs/src/program/source_map.rs @@ -16,7 +16,7 @@ pub struct SourceMap { impl SourceMap { /// Return all source ranges in the source map which were used to generate the target range. /// - /// This is `O(n)` where `n` is the number of target entries in the map. + /// This is `O(n)` where `n` is the number of entries in the map. pub fn list_sources(&self, target_index: &QueryIndex) -> Vec<&SourceIndex> where TargetIndex: SourceMapIndexable, @@ -35,7 +35,7 @@ impl SourceMap { /// Return all target ranges in the source map which were used to generate the source range. /// - /// This is `O(n)` where `n` is the number of source entries in the map. + /// This is `O(n)` where `n` is the number of entries in the map. pub fn list_targets(&self, source_index: &QueryIndex) -> Vec<&TargetIndex> where SourceIndex: SourceMapIndexable, From c97dcca21394352ad9c23920b49c89faf0bc9e72 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Mon, 22 Jul 2024 08:55:23 -0700 Subject: [PATCH 26/39] chore: fix test --- quil-py/test/program/test_program.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/quil-py/test/program/test_program.py b/quil-py/test/program/test_program.py index d8ee0751..2f8f2afb 100644 --- a/quil-py/test/program/test_program.py +++ b/quil-py/test/program/test_program.py @@ -221,13 +221,14 @@ def test_calibration_expansion(): assert len(targets) == 1 expanded = targets[0].as_expanded() assert expanded.range() == range(0, 1) + assert source_map.list_sources_for_target_index(0) == [0] # The Y at index 1 should have been replaced with a Z at index 1 targets = source_map.list_targets_for_source_index(1) assert len(targets) == 1 expanded = targets[0].as_expanded() assert expanded.range() == range(1, 2) + assert source_map.list_sources_for_target_index(1) == [1] # There is no source index 2 and so there should be no mapping assert source_map.list_targets_for_source_index(2) == [] - assert source_map.list_sources_for_target_index(0) == [0] From 8ffb15ae3b0b2d21760f3a51aff39752265a44e9 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Mon, 22 Jul 2024 09:35:06 -0700 Subject: [PATCH 27/39] chore: cleanup --- quil-rs/src/instruction/calibration.rs | 2 +- quil-rs/src/program/calibration.rs | 36 ++++++++++++++------------ quil-rs/src/program/mod.rs | 4 +-- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/quil-rs/src/instruction/calibration.rs b/quil-rs/src/instruction/calibration.rs index 0b83aa8f..69cc871a 100644 --- a/quil-rs/src/instruction/calibration.rs +++ b/quil-rs/src/instruction/calibration.rs @@ -229,7 +229,7 @@ impl Quil for MeasureCalibrationDefinition { } } -// For review: how would we feel about making this a subfield of the `MeasureCalibrationDefinition` itself? +/// A unique identifier for a measurement calibration definition within a program #[derive(Clone, Debug, Default, PartialEq)] pub struct MeasureCalibrationIdentifier { /// The qubit which is the target of measurement, if any diff --git a/quil-rs/src/program/calibration.rs b/quil-rs/src/program/calibration.rs index 2203f605..9d111375 100644 --- a/quil-rs/src/program/calibration.rs +++ b/quil-rs/src/program/calibration.rs @@ -90,23 +90,20 @@ impl CalibrationExpansion { /// This is to be used when the named index is removed from the target program /// in the process of calibration expansion. pub(crate) fn remove_target_index(&mut self, target_index: usize) { - eprintln!("Removing target index {} from\n{self:#?}", target_index); - + // Adjust the start of the range if the target index is within the range if self.range.start >= target_index { self.range.start = self.range.start.saturating_sub(1); + } - if self.range.end > target_index { - self.range.end = self.range.end.saturating_sub(1); - } + // Adjust the end of the range if the target index is within the range + if self.range.end > target_index { + self.range.end = self.range.end.saturating_sub(1); } let target_index_offset = self.range.start; - eprintln!("Offset: {}", target_index_offset); - self.expansions.entries.retain_mut( |entry: &mut SourceMapEntry| { if let Some(target_with_offset) = target_index.checked_sub(target_index_offset) { - eprintln!("Target with offset: {}", target_with_offset); entry .target_location .remove_target_index(target_with_offset); @@ -214,24 +211,32 @@ impl Calibrations { self.measure_calibrations.iter() } + /// Given an instruction, return the instructions to which it is expanded if there is a match. + /// Recursively calibrate instructions, returning an error if a calibration directly or indirectly + /// expands into itself. + /// + /// Return only the expanded instructions; for more information about the expansion process, + /// see [`Self::expand_with_detail`]. pub fn expand( &self, instruction: &Instruction, previous_calibrations: &[Instruction], ) -> Result>, ProgramError> { - self._expand(instruction, previous_calibrations, false) + self.expand_inner(instruction, previous_calibrations, false) .map(|expansion| expansion.map(|expansion| expansion.new_instructions)) } /// Given an instruction, return the instructions to which it is expanded if there is a match. /// Recursively calibrate instructions, returning an error if a calibration directly or indirectly /// expands into itself. + /// + /// Also return information about the expansion. pub fn expand_with_detail( &self, instruction: &Instruction, previous_calibrations: &[Instruction], ) -> Result, ProgramError> { - self._expand(instruction, previous_calibrations, true) + self.expand_inner(instruction, previous_calibrations, true) } /// Expand an instruction, returning an error if a calibration directly or indirectly @@ -242,7 +247,7 @@ impl Calibrations { /// * `instruction` - The instruction to expand. /// * `previous_calibrations` - The calibrations that were invoked to yield this current instruction. /// * `build_source_map` - Whether to build a source map of the expansion. - fn _expand( + fn expand_inner( &self, instruction: &Instruction, previous_calibrations: &[Instruction], @@ -394,10 +399,10 @@ impl Calibrations { calibration_path.push(instruction.clone()); calibration_path.extend_from_slice(previous_calibrations); - self._recursively_expand(expansion_result, &calibration_path, build_source_map) + self.recursively_expand_inner(expansion_result, &calibration_path, build_source_map) } - fn _recursively_expand( + fn recursively_expand_inner( &self, expansion_result: Option<(Vec, CalibrationSource)>, calibration_path: &[Instruction], @@ -416,7 +421,7 @@ impl Calibrations { for (expanded_index, instruction) in instructions.into_iter().enumerate() { let expanded_instructions = - self._expand(&instruction, calibration_path, build_source_map)?; + self.expand_inner(&instruction, calibration_path, build_source_map)?; match expanded_instructions { Some(mut output) => { if build_source_map { @@ -429,8 +434,7 @@ impl Calibrations { let range_end = recursively_expanded_instructions.new_instructions.len(); - let target_range = range_start..range_end; - output.detail.range = target_range; + output.detail.range = range_start..range_end; recursively_expanded_instructions .detail diff --git a/quil-rs/src/program/mod.rs b/quil-rs/src/program/mod.rs index 683e9433..0f4376e3 100644 --- a/quil-rs/src/program/mod.rs +++ b/quil-rs/src/program/mod.rs @@ -249,8 +249,8 @@ impl Program { self.expand_calibrations_inner(None) } - // / Expand any instructions in the program which have a matching calibration, leaving the others - // / unchanged. Return the expanded copy of the program and a source mapping of the expansions made. + /// Expand any instructions in the program which have a matching calibration, leaving the others + /// unchanged. Return the expanded copy of the program and a source mapping of the expansions made. pub fn expand_calibrations_with_source_map(&self) -> Result { let mut source_mapping = ProgramCalibrationExpansionSourceMap::default(); let new_program = self.expand_calibrations_inner(Some(&mut source_mapping))?; From 2ca8eea898b8b8ec95f681dc1be1d5fcf55652d0 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Mon, 22 Jul 2024 10:14:09 -0700 Subject: [PATCH 28/39] chore: cleanup --- quil-rs/src/program/calibration.rs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/quil-rs/src/program/calibration.rs b/quil-rs/src/program/calibration.rs index 9d111375..d9482370 100644 --- a/quil-rs/src/program/calibration.rs +++ b/quil-rs/src/program/calibration.rs @@ -87,8 +87,8 @@ pub struct CalibrationExpansion { impl CalibrationExpansion { /// Remove the given target index from all entries, recursively. /// - /// This is to be used when the named index is removed from the target program - /// in the process of calibration expansion. + /// This is to be used when the given index is removed from the target program + /// in the process of calibration expansion (for example, a `DECLARE`). pub(crate) fn remove_target_index(&mut self, target_index: usize) { // Adjust the start of the range if the target index is within the range if self.range.start >= target_index { @@ -100,18 +100,20 @@ impl CalibrationExpansion { self.range.end = self.range.end.saturating_sub(1); } - let target_index_offset = self.range.start; - self.expansions.entries.retain_mut( - |entry: &mut SourceMapEntry| { - if let Some(target_with_offset) = target_index.checked_sub(target_index_offset) { + // Then walk through all entries expanded for this calibration and remove the + // index as well. This is needed when a recursively-expanded instruction contains + // an instruction which is excised from the overall calibration. + if let Some(target_within_expansion) = target_index.checked_sub(self.range.start) { + self.expansions.entries.retain_mut( + |entry: &mut SourceMapEntry| { entry .target_location - .remove_target_index(target_with_offset); - } + .remove_target_index(target_within_expansion); - !entry.target_location.range.is_empty() - }, - ); + !entry.target_location.range.is_empty() + }, + ); + } } pub fn calibration_used(&self) -> &CalibrationSource { From 8f91186a82cd156861559f980480df3f5eb3bdb5 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Mon, 29 Jul 2024 10:57:27 -0700 Subject: [PATCH 29/39] chore: fix comment --- quil-rs/src/program/calibration.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quil-rs/src/program/calibration.rs b/quil-rs/src/program/calibration.rs index d9482370..8f5d4d12 100644 --- a/quil-rs/src/program/calibration.rs +++ b/quil-rs/src/program/calibration.rs @@ -90,12 +90,12 @@ impl CalibrationExpansion { /// This is to be used when the given index is removed from the target program /// in the process of calibration expansion (for example, a `DECLARE`). pub(crate) fn remove_target_index(&mut self, target_index: usize) { - // Adjust the start of the range if the target index is within the range + // Adjust the start of the range if the target index is before the range if self.range.start >= target_index { self.range.start = self.range.start.saturating_sub(1); } - // Adjust the end of the range if the target index is within the range + // Adjust the end of the range if the target index is before the end of the range if self.range.end > target_index { self.range.end = self.range.end.saturating_sub(1); } From 56476ca321a3574c62c00cfd34cbda22c15021d5 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Mon, 29 Jul 2024 15:06:31 -0700 Subject: [PATCH 30/39] fix test --- quil-py/test/instructions/test_copy.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/quil-py/test/instructions/test_copy.py b/quil-py/test/instructions/test_copy.py index e43ed78b..6f6db819 100644 --- a/quil-py/test/instructions/test_copy.py +++ b/quil-py/test/instructions/test_copy.py @@ -3,6 +3,7 @@ from quil.expression import Expression from quil.instructions import ( Calibration, + CalibrationIdentifier, Delay, FrameIdentifier, Instruction, @@ -38,11 +39,13 @@ def test_instruction_with_duplicate_placeholders(): placeholder = Qubit.from_placeholder(QubitPlaceholder()) calibration = Calibration( - "MYCAL", - [], - [placeholder], + CalibrationIdentifier( + "MYCAL", + [], + [placeholder], + [] + ), [Instruction.from_delay(Delay(Expression.from_number(complex(0.5)), [], [placeholder]))], - [], ) calibration_copy = copy(calibration) @@ -51,4 +54,4 @@ def test_instruction_with_duplicate_placeholders(): calibration_deepcopy = deepcopy(calibration) assert calibration_deepcopy != calibration - assert calibration_deepcopy.qubits == calibration_deepcopy.instructions[0].to_delay().qubits + assert calibration_deepcopy.identifier.qubits == calibration_deepcopy.instructions[0].to_delay().qubits From 5d9f7591d8e95beb6609bdb23309fb34d6ba6f84 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Mon, 29 Jul 2024 15:06:47 -0700 Subject: [PATCH 31/39] make test more readable --- quil-py/test/program/test_program.py | 46 ++++++++++++++++++---------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/quil-py/test/program/test_program.py b/quil-py/test/program/test_program.py index 2f8f2afb..65445149 100644 --- a/quil-py/test/program/test_program.py +++ b/quil-py/test/program/test_program.py @@ -189,31 +189,43 @@ def test_filter_instructions(snapshot: SnapshotAssertion): def test_calibration_expansion(): - program = Program.parse( - """DEFCAL X 0: - Y 0 + """ + Assert that program calibration expansion happens as expected and that the source map is correct. + """ + import inspect -DEFCAL Y 0: - Z 0 + program_text = inspect.cleandoc( + """ + DEFCAL X 0: + Y 0 -X 0 -Y 0 -""" + DEFCAL Y 0: + Z 0 + + X 0 + Y 0 + """ ) + program = Program.parse(program_text) expansion = program.expand_calibrations_with_source_map() source_map = expansion.source_map() - assert ( - expansion.program().to_quil() - == Program.parse("""DEFCAL X 0: - Y 0 + expected_program_text = inspect.cleandoc( + """ + DEFCAL X 0: + Y 0 -DEFCAL Y 0: - Z 0 + DEFCAL Y 0: + Z 0 -Z 0 -Z 0 -""").to_quil() + Z 0 + Z 0 + """ + ) + + assert ( + expansion.program().to_quil() + == Program.parse(expected_program_text).to_quil() ) # The X at index 0 should have been replaced with a Z at index 0 From 59daee1e500ee63d8aa5489c3d120d6783dd6787 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Mon, 29 Jul 2024 15:07:15 -0700 Subject: [PATCH 32/39] chore: fix readme --- quil-py/README-py.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quil-py/README-py.md b/quil-py/README-py.md index b51f462a..18340fba 100644 --- a/quil-py/README-py.md +++ b/quil-py/README-py.md @@ -4,7 +4,7 @@ The `quil` package provides tools for constructing, manipulating, parsing, and printing [Quil](https://github.com/quil-lang/quil) programs. Internally, it is powered by [quil-rs](https://github.com/rigetti/quil-rs). -This package is still in early development and breaking changes should be expected between minor versions. +This package is still in development and breaking changes should be expected between minor versions. # Documentation From 7a5972cda438ab86e05d24b44c9eec1a2ea95677 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Mon, 29 Jul 2024 15:07:46 -0700 Subject: [PATCH 33/39] chore: lint --- quil-py/test/instructions/test_copy.py | 7 +------ quil-py/test/program/test_program.py | 5 +---- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/quil-py/test/instructions/test_copy.py b/quil-py/test/instructions/test_copy.py index 6f6db819..f36e87f3 100644 --- a/quil-py/test/instructions/test_copy.py +++ b/quil-py/test/instructions/test_copy.py @@ -39,12 +39,7 @@ def test_instruction_with_duplicate_placeholders(): placeholder = Qubit.from_placeholder(QubitPlaceholder()) calibration = Calibration( - CalibrationIdentifier( - "MYCAL", - [], - [placeholder], - [] - ), + CalibrationIdentifier("MYCAL", [], [placeholder], []), [Instruction.from_delay(Delay(Expression.from_number(complex(0.5)), [], [placeholder]))], ) diff --git a/quil-py/test/program/test_program.py b/quil-py/test/program/test_program.py index 65445149..41f458f9 100644 --- a/quil-py/test/program/test_program.py +++ b/quil-py/test/program/test_program.py @@ -223,10 +223,7 @@ def test_calibration_expansion(): """ ) - assert ( - expansion.program().to_quil() - == Program.parse(expected_program_text).to_quil() - ) + assert expansion.program().to_quil() == Program.parse(expected_program_text).to_quil() # The X at index 0 should have been replaced with a Z at index 0 targets = source_map.list_targets_for_source_index(0) From 5d31908ca31f86e87c0baf1e45e57b21b2c0e822 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Mon, 29 Jul 2024 15:08:00 -0700 Subject: [PATCH 34/39] add useful example to Python docs --- quil-py/quil/program/__init__.pyi | 67 +++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/quil-py/quil/program/__init__.pyi b/quil-py/quil/program/__init__.pyi index 343f07bc..15e478f4 100644 --- a/quil-py/quil/program/__init__.pyi +++ b/quil-py/quil/program/__init__.pyi @@ -1,3 +1,70 @@ +""" +The `quil.program` module contains classes for constructing and representing a Quil program. + +# Examples + +## Source Mapping for Calibration Expansion + +```py + import inspect + + program_text = inspect.cleandoc( + \"\"\" + DEFCAL X 0: + Y 0 + + DEFCAL Y 0: + Z 0 + + X 0 # This instruction is index 0 + Y 0 # This instruction is index 1 + \"\"\" + ) + + # First, we parse the program and expand its calibrations + program = Program.parse(program_text) + expansion = program.expand_calibrations_with_source_map() + source_map = expansion.source_map() + + # This is what we expect the expanded program to be. X and Y have been replaced by Z + expected_program_text = inspect.cleandoc( + \"\"\" + DEFCAL X 0: + Y 0 + + DEFCAL Y 0: + Z 0 + + Z 0 # This instruction is index 0 + Z 0 # This instruction is index 1 + \"\"\" + ) + + # In order to discover _which_ calibration led to each Z in the resulting program, we + # can interrogate the expansion source mapping: + + # The X at index 0 should have been replaced with a Z at index 0: + + # ...first, we list the calibration expansion targets for that first instruction... + targets = source_map.list_targets_for_source_index(0) + + # ...then we extract the expanded instruction. + # If the instruction had _not_ been expanded (i.e. there was no matching calibration), then `as_expanded()` would return `None`. + expanded = targets[0].as_expanded() + + # ...This line shows how that `X 0` was expanded into instruction index 0 (only) within the expanded program. + # The end of the range is exclusive. + assert expanded.range() == range(0, 1) + + # We can also map instructions in reverse: given an instruction index in the expanded program, we can find the source index. + # This is useful for understanding the provenance of instructions in the expanded program. + sources = source_map.list_sources_for_target_index(1) + + # ... in this case, the instruction was expanded from the source program at index 1. + assert sources == [1] +``` +""" + from typing import Callable, Dict, FrozenSet, List, Optional, Sequence, Set, Union, final import numpy as np From de94d05d944715205eb1a9838fea74353a01b12f Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Mon, 29 Jul 2024 15:27:36 -0700 Subject: [PATCH 35/39] fix doc example --- quil-py/quil/program/__init__.pyi | 85 ++++++++++++++++--------------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/quil-py/quil/program/__init__.pyi b/quil-py/quil/program/__init__.pyi index 15e478f4..f67a4f15 100644 --- a/quil-py/quil/program/__init__.pyi +++ b/quil-py/quil/program/__init__.pyi @@ -6,62 +6,63 @@ The `quil.program` module contains classes for constructing and representing a Q ## Source Mapping for Calibration Expansion ```py - import inspect +import inspect +from quil.program import Program - program_text = inspect.cleandoc( - \"\"\" - DEFCAL X 0: - Y 0 +program_text = inspect.cleandoc( + \"\"\" + DEFCAL X 0: + Y 0 - DEFCAL Y 0: - Z 0 + DEFCAL Y 0: + Z 0 - X 0 # This instruction is index 0 - Y 0 # This instruction is index 1 - \"\"\" - ) + X 0 # This instruction is index 0 + Y 0 # This instruction is index 1 + \"\"\" +) - # First, we parse the program and expand its calibrations - program = Program.parse(program_text) - expansion = program.expand_calibrations_with_source_map() - source_map = expansion.source_map() +# First, we parse the program and expand its calibrations +program = Program.parse(program_text) +expansion = program.expand_calibrations_with_source_map() +source_map = expansion.source_map() - # This is what we expect the expanded program to be. X and Y have been replaced by Z - expected_program_text = inspect.cleandoc( - \"\"\" - DEFCAL X 0: - Y 0 +# This is what we expect the expanded program to be. X and Y have been replaced by Z +expected_program_text = inspect.cleandoc( + \"\"\" + DEFCAL X 0: + Y 0 - DEFCAL Y 0: - Z 0 + DEFCAL Y 0: + Z 0 - Z 0 # This instruction is index 0 - Z 0 # This instruction is index 1 - \"\"\" - ) + Z 0 # This instruction is index 0 + Z 0 # This instruction is index 1 + \"\"\" +) - # In order to discover _which_ calibration led to each Z in the resulting program, we - # can interrogate the expansion source mapping: +# In order to discover _which_ calibration led to each Z in the resulting program, we +# can interrogate the expansion source mapping: - # The X at index 0 should have been replaced with a Z at index 0: +# The X at index 0 should have been replaced with a Z at index 0: - # ...first, we list the calibration expansion targets for that first instruction... - targets = source_map.list_targets_for_source_index(0) +# ...first, we list the calibration expansion targets for that first instruction... +targets = source_map.list_targets_for_source_index(0) - # ...then we extract the expanded instruction. - # If the instruction had _not_ been expanded (i.e. there was no matching calibration), then `as_expanded()` would return `None`. - expanded = targets[0].as_expanded() +# ...then we extract the expanded instruction. +# If the instruction had _not_ been expanded (i.e. there was no matching calibration), then `as_expanded()` would return `None`. +expanded = targets[0].as_expanded() - # ...This line shows how that `X 0` was expanded into instruction index 0 (only) within the expanded program. - # The end of the range is exclusive. - assert expanded.range() == range(0, 1) +# ...This line shows how that `X 0` was expanded into instruction index 0 (only) within the expanded program. +# The end of the range is exclusive. +assert expanded.range() == range(0, 1) - # We can also map instructions in reverse: given an instruction index in the expanded program, we can find the source index. - # This is useful for understanding the provenance of instructions in the expanded program. - sources = source_map.list_sources_for_target_index(1) +# We can also map instructions in reverse: given an instruction index in the expanded program, we can find the source index. +# This is useful for understanding the provenance of instructions in the expanded program. +sources = source_map.list_sources_for_target_index(1) - # ... in this case, the instruction was expanded from the source program at index 1. - assert sources == [1] +# ... in this case, the instruction was expanded from the source program at index 1. +assert sources == [1] ``` """ From 5c05993f9b6c7b7200bd8df4e1f2b196058603f1 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Mon, 29 Jul 2024 15:28:47 -0700 Subject: [PATCH 36/39] fix doc example --- quil-py/quil/program/__init__.pyi | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/quil-py/quil/program/__init__.pyi b/quil-py/quil/program/__init__.pyi index f67a4f15..95be3e20 100644 --- a/quil-py/quil/program/__init__.pyi +++ b/quil-py/quil/program/__init__.pyi @@ -27,7 +27,7 @@ program = Program.parse(program_text) expansion = program.expand_calibrations_with_source_map() source_map = expansion.source_map() -# This is what we expect the expanded program to be. X and Y have been replaced by Z +# This is what we expect the expanded program to be. X and Y have each been replaced by Z. expected_program_text = inspect.cleandoc( \"\"\" DEFCAL X 0: @@ -40,20 +40,22 @@ expected_program_text = inspect.cleandoc( Z 0 # This instruction is index 1 \"\"\" ) +assert expansion.program().to_quil() == Program.parse(expected_program_text).to_quil() -# In order to discover _which_ calibration led to each Z in the resulting program, we -# can interrogate the expansion source mapping: +# In order to discover _which_ calibration led to the first Z in the resulting program, we +# can interrogate the expansion source mapping. +# +# For instance, the X at index 0 should have been replaced with a Z at index 0. +# Here's how we can confirm that: -# The X at index 0 should have been replaced with a Z at index 0: - -# ...first, we list the calibration expansion targets for that first instruction... +# First, we list the calibration expansion targets for that first instruction... targets = source_map.list_targets_for_source_index(0) # ...then we extract the expanded instruction. # If the instruction had _not_ been expanded (i.e. there was no matching calibration), then `as_expanded()` would return `None`. expanded = targets[0].as_expanded() -# ...This line shows how that `X 0` was expanded into instruction index 0 (only) within the expanded program. +# This line shows how that `X 0` was expanded into instruction index 0 (only) within the expanded program. # The end of the range is exclusive. assert expanded.range() == range(0, 1) @@ -61,7 +63,7 @@ assert expanded.range() == range(0, 1) # This is useful for understanding the provenance of instructions in the expanded program. sources = source_map.list_sources_for_target_index(1) -# ... in this case, the instruction was expanded from the source program at index 1. +# In this case, the instruction was expanded from the source program at index 1. assert sources == [1] ``` """ From a298437ea83d56c71a30f4bc7b6d29424f4bf639 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Mon, 29 Jul 2024 15:48:03 -0700 Subject: [PATCH 37/39] chore: lint --- quil-rs/src/parser/command.rs | 2 +- quil-rs/src/program/memory.rs | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/quil-rs/src/parser/command.rs b/quil-rs/src/parser/command.rs index cf661ec5..0ff152ff 100644 --- a/quil-rs/src/parser/command.rs +++ b/quil-rs/src/parser/command.rs @@ -5,7 +5,7 @@ use nom::sequence::{delimited, pair, preceded, tuple}; use crate::expression::Expression; use crate::instruction::{ - Arithmetic, ArithmeticOperand, ArithmeticOperator, BinaryLogic, BinaryOperator, Calibration, + Arithmetic, ArithmeticOperator, BinaryLogic, BinaryOperator, Calibration, CalibrationIdentifier, Capture, CircuitDefinition, Comparison, ComparisonOperator, Convert, Declaration, Delay, Exchange, Fence, FrameDefinition, GateDefinition, GateSpecification, GateType, Include, Instruction, Jump, JumpUnless, JumpWhen, Label, Load, diff --git a/quil-rs/src/program/memory.rs b/quil-rs/src/program/memory.rs index f3b186a6..fa02a58a 100644 --- a/quil-rs/src/program/memory.rs +++ b/quil-rs/src/program/memory.rs @@ -37,12 +37,6 @@ impl MemoryRegion { impl Eq for MemoryRegion {} -#[derive(Clone, Debug)] -pub struct MemoryAccess { - pub regions: HashSet, - pub access_type: MemoryAccessType, -} - #[derive(Clone, Debug, Default)] pub struct MemoryAccesses { pub captures: HashSet, From 022900a385a782efe96f3a26fe97d42e0841c942 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Fri, 11 Oct 2024 17:34:06 -0700 Subject: [PATCH 38/39] make InstructionIndex a newtype struct for source mapping --- quil-py/src/program/source_map.rs | 144 ++++++++++++++++++++++++----- quil-rs/src/program/calibration.rs | 76 +++++++-------- quil-rs/src/program/mod.rs | 43 +++++---- quil-rs/src/program/source_map.rs | 6 +- 4 files changed, 193 insertions(+), 76 deletions(-) diff --git a/quil-py/src/program/source_map.rs b/quil-py/src/program/source_map.rs index e2e3ef8c..cdedc8b2 100644 --- a/quil-py/src/program/source_map.rs +++ b/quil-py/src/program/source_map.rs @@ -1,14 +1,18 @@ use std::ops::Range; use pyo3::{ - types::{PyInt, PyModule, PyTuple}, - Py, PyAny, PyResult, Python, + conversion, exceptions, + types::{PyModule, PyTuple}, + IntoPy, Py, PyAny, PyResult, Python, }; use quil_rs::program::{ - CalibrationExpansion, CalibrationSource, MaybeCalibrationExpansion, + CalibrationExpansion, CalibrationSource, InstructionIndex, MaybeCalibrationExpansion, ProgramCalibrationExpansion, ProgramCalibrationExpansionSourceMap, SourceMap, SourceMapEntry, }; -use rigetti_pyo3::{impl_repr, py_wrap_type, py_wrap_union_enum, pyo3::pymethods, PyWrapper}; +use rigetti_pyo3::{ + impl_as_mut_for_wrapper, impl_repr, py_wrap_type, py_wrap_union_enum, pyo3::pymethods, + PyTryFrom, PyWrapper, ToPython, +}; use crate::{ impl_eq, @@ -17,9 +21,10 @@ use crate::{ use super::PyProgram; -type CalibrationExpansionSourceMap = SourceMap; -type CalibrationExpansionSourceMapEntry = SourceMapEntry; -type ProgramCalibrationExpansionSourceMapEntry = SourceMapEntry; +type CalibrationExpansionSourceMap = SourceMap; +type CalibrationExpansionSourceMapEntry = SourceMapEntry; +type ProgramCalibrationExpansionSourceMapEntry = + SourceMapEntry; py_wrap_type! { #[derive(Debug, PartialEq)] @@ -38,7 +43,7 @@ impl PyCalibrationExpansion { pub fn range<'py>(&self, py: Python<'py>) -> PyResult<&'py PyAny> { let range = PyModule::import(py, "builtins")?.getattr("range")?; let Range { start, end } = self.as_inner().range(); - let tuple = PyTuple::new(py, [start, end]); + let tuple = PyTuple::new(py, [start.0, end.0]); range.call1(tuple)?.extract() } @@ -71,9 +76,9 @@ impl PyCalibrationExpansionSourceMap { /// This is `O(n)` where `n` is the number of first-level calibration expansions performed. pub fn list_sources_for_target_index(&self, target_index: usize) -> Vec { self.as_inner() - .list_sources(&target_index) + .list_sources(&InstructionIndex(target_index)) .into_iter() - .copied() + .map(|index| index.0) .collect() } @@ -88,7 +93,7 @@ impl PyCalibrationExpansionSourceMap { self.as_inner() .list_sources(calibration_used.as_inner()) .into_iter() - .copied() + .map(|index| index.0) .collect() } @@ -100,7 +105,7 @@ impl PyCalibrationExpansionSourceMap { source_index: usize, ) -> Vec { self.as_inner() - .list_targets(&source_index) + .list_targets(&InstructionIndex(source_index)) .into_iter() .map(|expansion| expansion.into()) .collect() @@ -118,7 +123,7 @@ impl_eq!(PyCalibrationExpansionSourceMapEntry); #[pymethods] impl PyCalibrationExpansionSourceMapEntry { pub fn source_location(&self) -> usize { - *self.as_inner().source_location() + self.as_inner().source_location().0 } pub fn target_location(&self) -> PyCalibrationExpansion { @@ -137,11 +142,106 @@ py_wrap_union_enum! { impl_repr!(PyCalibrationSource); impl_eq!(PyCalibrationSource); -py_wrap_union_enum! { +// Note: this type is manually implemented below because there is no `Into` conversion from `InstructionIndex` to `usize` +// This manual implementation follows the same API as this invocation otherwise would: +// ``` +// py_wrap_union_enum! { +// #[derive(Debug, PartialEq)] +// PyMaybeCalibrationExpansion(MaybeCalibrationExpansion) as "MaybeCalibrationExpansion" { +// expanded: Expanded => PyCalibrationExpansion, +// unexpanded: Unexpanded => InstructionIndex => usize +// } +// } +// ``` +py_wrap_type! { #[derive(Debug, PartialEq)] - PyMaybeCalibrationExpansion(MaybeCalibrationExpansion) as "MaybeCalibrationExpansion" { - expanded: Expanded => PyCalibrationExpansion, - unexpanded: Unexpanded => usize => Py + PyMaybeCalibrationExpansion(MaybeCalibrationExpansion) as "MaybeCalibrationExpansion" +} + +impl_as_mut_for_wrapper!(PyMaybeCalibrationExpansion); + +#[pymethods] +impl PyMaybeCalibrationExpansion { + #[new] + pub fn new(py: Python, input: &PyAny) -> PyResult { + if let Ok(inner) = <_ as PyTryFrom>::py_try_from(py, input) { + let inner = &inner; + if let Ok(item) = PyTryFrom::py_try_from(py, inner) { + return Ok(Self::from(MaybeCalibrationExpansion::Expanded(item))); + } + } + + if let Ok(inner) = <_ as PyTryFrom>::py_try_from(py, input) { + if let Ok(item) = PyTryFrom::::py_try_from(py, &inner) { + return Ok(Self::from(MaybeCalibrationExpansion::Unexpanded( + InstructionIndex(item), + ))); + } + } + + Err(exceptions::PyValueError::new_err(format!( + "could not create {} from {}", + stringify!($name), + input.repr()? + ))) + } + + #[allow(unreachable_code, unreachable_patterns)] + pub fn inner(&self, py: Python) -> PyResult> { + match &self.0 { + MaybeCalibrationExpansion::Expanded(inner) => Ok( + conversion::IntoPy::>::into_py(ToPython::to_python(&inner, py)?, py), + ), + MaybeCalibrationExpansion::Unexpanded(inner) => Ok(inner.0.into_py(py)), + _ => { + use exceptions::PyRuntimeError; + Err(PyRuntimeError::new_err( + "Enum variant has no inner data or is unimplemented", + )) + } + } + } + + pub fn as_expanded(&self) -> Option { + match &self.0 { + MaybeCalibrationExpansion::Expanded(inner) => { + Some(PyCalibrationExpansion(inner.clone())) + } + _ => None, + } + } + + pub fn as_unexpanded(&self) -> Option { + match &self.0 { + MaybeCalibrationExpansion::Unexpanded(inner) => Some(inner.0), + _ => None, + } + } + + pub fn is_expanded(&self) -> bool { + matches!(self.0, MaybeCalibrationExpansion::Expanded(_)) + } + + pub fn is_unexpanded(&self) -> bool { + matches!(self.0, MaybeCalibrationExpansion::Unexpanded(_)) + } + + pub fn to_expanded(&self) -> PyResult { + match &self.0 { + MaybeCalibrationExpansion::Expanded(inner) => Ok(PyCalibrationExpansion(inner.clone())), + _ => Err(pyo3::exceptions::PyValueError::new_err( + "expected self to be an Expanded variant", + )), + } + } + + pub fn to_unexpanded(&self) -> PyResult { + match &self.0 { + MaybeCalibrationExpansion::Unexpanded(inner) => Ok(inner.0), + _ => Err(pyo3::exceptions::PyValueError::new_err( + "expected self to be an Unexpanded variant", + )), + } } } @@ -191,9 +291,9 @@ impl PyProgramCalibrationExpansionSourceMap { /// This is `O(n)` where `n` is the number of source instructions. pub fn list_sources_for_target_index(&self, target_index: usize) -> Vec { self.as_inner() - .list_sources(&target_index) + .list_sources(&InstructionIndex(target_index)) .into_iter() - .copied() + .map(|index| index.0) .collect() } @@ -208,7 +308,7 @@ impl PyProgramCalibrationExpansionSourceMap { self.as_inner() .list_sources(calibration_used.as_inner()) .into_iter() - .copied() + .map(|index| index.0) .collect() } @@ -220,7 +320,7 @@ impl PyProgramCalibrationExpansionSourceMap { source_index: usize, ) -> Vec { self.as_inner() - .list_targets(&source_index) + .list_targets(&InstructionIndex(source_index)) .into_iter() .map(|expansion| expansion.into()) .collect() @@ -238,7 +338,7 @@ impl_eq!(PyProgramCalibrationExpansionSourceMapEntry); #[pymethods] impl PyProgramCalibrationExpansionSourceMapEntry { pub fn source_location(&self) -> usize { - *self.as_inner().source_location() + self.as_inner().source_location().0 } pub fn target_location(&self) -> PyMaybeCalibrationExpansion { diff --git a/quil-rs/src/program/calibration.rs b/quil-rs/src/program/calibration.rs index 8f5d4d12..57f48c9a 100644 --- a/quil-rs/src/program/calibration.rs +++ b/quil-rs/src/program/calibration.rs @@ -30,7 +30,7 @@ use crate::{ }; use super::source_map::{SourceMap, SourceMapEntry, SourceMapIndexable}; -use super::{CalibrationSet, ProgramError}; +use super::{CalibrationSet, InstructionIndex, ProgramError}; /// A collection of Quil calibrations (`DEFCAL` instructions) with utility methods. #[derive(Clone, Debug, Default, PartialEq)] @@ -78,10 +78,10 @@ pub struct CalibrationExpansion { pub(crate) calibration_used: CalibrationSource, /// The target instruction indices produced by the expansion - pub(crate) range: Range, + pub(crate) range: Range, /// A map of source locations to the expansions they produced - pub(crate) expansions: SourceMap, + pub(crate) expansions: SourceMap, } impl CalibrationExpansion { @@ -89,26 +89,26 @@ impl CalibrationExpansion { /// /// This is to be used when the given index is removed from the target program /// in the process of calibration expansion (for example, a `DECLARE`). - pub(crate) fn remove_target_index(&mut self, target_index: usize) { + pub(crate) fn remove_target_index(&mut self, target_index: InstructionIndex) { // Adjust the start of the range if the target index is before the range if self.range.start >= target_index { - self.range.start = self.range.start.saturating_sub(1); + self.range.start = self.range.start.map(|v| v.saturating_sub(1)); } // Adjust the end of the range if the target index is before the end of the range if self.range.end > target_index { - self.range.end = self.range.end.saturating_sub(1); + self.range.end = self.range.end.map(|v| v.saturating_sub(1)); } // Then walk through all entries expanded for this calibration and remove the // index as well. This is needed when a recursively-expanded instruction contains // an instruction which is excised from the overall calibration. - if let Some(target_within_expansion) = target_index.checked_sub(self.range.start) { + if let Some(target_within_expansion) = target_index.0.checked_sub(self.range.start.0) { self.expansions.entries.retain_mut( - |entry: &mut SourceMapEntry| { + |entry: &mut SourceMapEntry| { entry .target_location - .remove_target_index(target_within_expansion); + .remove_target_index(InstructionIndex(target_within_expansion)); !entry.target_location.range.is_empty() }, @@ -120,17 +120,17 @@ impl CalibrationExpansion { &self.calibration_used } - pub fn range(&self) -> &Range { + pub fn range(&self) -> &Range { &self.range } - pub fn expansions(&self) -> &SourceMap { + pub fn expansions(&self) -> &SourceMap { &self.expansions } } -impl SourceMapIndexable for CalibrationExpansion { - fn intersects(&self, other: &usize) -> bool { +impl SourceMapIndexable for CalibrationExpansion { + fn intersects(&self, other: &InstructionIndex) -> bool { self.range.contains(other) } } @@ -148,11 +148,11 @@ pub enum MaybeCalibrationExpansion { Expanded(CalibrationExpansion), /// The instruction was not expanded, but was simply copied over into the target program at the given instruction index - Unexpanded(usize), + Unexpanded(InstructionIndex), } -impl SourceMapIndexable for MaybeCalibrationExpansion { - fn intersects(&self, other: &usize) -> bool { +impl SourceMapIndexable for MaybeCalibrationExpansion { + fn intersects(&self, other: &InstructionIndex) -> bool { match self { MaybeCalibrationExpansion::Expanded(expansion) => expansion.intersects(other), MaybeCalibrationExpansion::Unexpanded(index) => index == other, @@ -416,7 +416,7 @@ impl Calibrations { new_instructions: Vec::new(), detail: CalibrationExpansion { calibration_used: matched_calibration, - range: 0..0, + range: InstructionIndex(0)..InstructionIndex(0), expansions: SourceMap::default(), }, }; @@ -427,15 +427,17 @@ impl Calibrations { match expanded_instructions { Some(mut output) => { if build_source_map { - let range_start = - recursively_expanded_instructions.new_instructions.len(); + let range_start = InstructionIndex( + recursively_expanded_instructions.new_instructions.len(), + ); recursively_expanded_instructions .new_instructions .extend(output.new_instructions); - let range_end = - recursively_expanded_instructions.new_instructions.len(); + let range_end = InstructionIndex( + recursively_expanded_instructions.new_instructions.len(), + ); output.detail.range = range_start..range_end; recursively_expanded_instructions @@ -443,7 +445,7 @@ impl Calibrations { .expansions .entries .push(SourceMapEntry { - source_location: expanded_index, + source_location: InstructionIndex(expanded_index), target_location: output.detail, }); } else { @@ -463,8 +465,10 @@ impl Calibrations { if build_source_map { // While this appears to be duplicated information at this point, it's useful when multiple // source mappings are merged together. - recursively_expanded_instructions.detail.range = - 0..recursively_expanded_instructions.new_instructions.len(); + recursively_expanded_instructions.detail.range = InstructionIndex(0) + ..InstructionIndex( + recursively_expanded_instructions.new_instructions.len(), + ); } Some(recursively_expanded_instructions) @@ -617,7 +621,7 @@ mod tests { use crate::program::calibration::{CalibrationSource, MeasureCalibrationIdentifier}; use crate::program::source_map::{SourceMap, SourceMapEntry}; - use crate::program::Program; + use crate::program::{InstructionIndex, Program}; use crate::quil::Quil; use insta::assert_snapshot; @@ -792,11 +796,11 @@ X 0 parameters: vec![], qubits: vec![crate::instruction::Qubit::Fixed(0)], }), - range: 0..5, + range: InstructionIndex(0)..InstructionIndex(5), expansions: SourceMap { entries: vec![ SourceMapEntry { - source_location: 0, + source_location: InstructionIndex(0), target_location: CalibrationExpansion { calibration_used: CalibrationSource::Calibration( CalibrationIdentifier { @@ -806,10 +810,10 @@ X 0 qubits: vec![crate::instruction::Qubit::Fixed(0)], }, ), - range: 0..2, + range: InstructionIndex(0)..InstructionIndex(2), expansions: SourceMap { entries: vec![SourceMapEntry { - source_location: 1, + source_location: InstructionIndex(1), target_location: CalibrationExpansion { calibration_used: CalibrationSource::Calibration( CalibrationIdentifier { @@ -821,7 +825,7 @@ X 0 )], }, ), - range: 1..2, + range: InstructionIndex(1)..InstructionIndex(2), expansions: SourceMap::default(), }, }], @@ -829,7 +833,7 @@ X 0 }, }, SourceMapEntry { - source_location: 1, + source_location: InstructionIndex(1), target_location: CalibrationExpansion { calibration_used: CalibrationSource::MeasureCalibration( MeasureCalibrationIdentifier { @@ -837,12 +841,12 @@ X 0 parameter: "addr".to_string(), }, ), - range: 2..3, + range: InstructionIndex(2)..InstructionIndex(3), expansions: SourceMap::default(), }, }, SourceMapEntry { - source_location: 2, + source_location: InstructionIndex(2), target_location: CalibrationExpansion { calibration_used: CalibrationSource::Calibration( CalibrationIdentifier { @@ -852,10 +856,10 @@ X 0 qubits: vec![crate::instruction::Qubit::Fixed(0)], }, ), - range: 3..5, + range: InstructionIndex(3)..InstructionIndex(5), expansions: SourceMap { entries: vec![SourceMapEntry { - source_location: 1, + source_location: InstructionIndex(1), target_location: CalibrationExpansion { calibration_used: CalibrationSource::Calibration( CalibrationIdentifier { @@ -867,7 +871,7 @@ X 0 )], }, ), - range: 1..2, + range: InstructionIndex(1)..InstructionIndex(2), expansions: SourceMap::default(), }, }], diff --git a/quil-rs/src/program/mod.rs b/quil-rs/src/program/mod.rs index a58c33ec..2a8ad747 100644 --- a/quil-rs/src/program/mod.rs +++ b/quil-rs/src/program/mod.rs @@ -298,6 +298,8 @@ impl Program { }; for (index, instruction) in self.instructions.iter().enumerate() { + let index = InstructionIndex(index); + match self.calibrations.expand_with_detail(instruction, &[])? { Some(expanded) => { new_program.append_calibration_expansion_output_inner( @@ -312,7 +314,7 @@ impl Program { source_mapping.entries.push(SourceMapEntry { source_location: index, target_location: MaybeCalibrationExpansion::Unexpanded( - new_program.instructions.len() - 1, + InstructionIndex(new_program.instructions.len() - 1), ), }); } @@ -331,7 +333,7 @@ impl Program { fn append_calibration_expansion_output_inner( &mut self, mut expansion_output: CalibrationExpansionOutput, - source_index: usize, + source_index: InstructionIndex, source_mapping: &mut Option<&mut ProgramCalibrationExpansionSourceMap>, ) { if let Some(source_mapping) = source_mapping.as_mut() { @@ -346,7 +348,7 @@ impl Program { // so that the map stays correct. if start_length == end_length { let relative_target_index = - start_length - previous_program_instruction_body_length; + InstructionIndex(start_length - previous_program_instruction_body_length); expansion_output .detail .remove_target_index(relative_target_index); @@ -354,7 +356,8 @@ impl Program { } expansion_output.detail.range = - previous_program_instruction_body_length..self.instructions.len(); + InstructionIndex(previous_program_instruction_body_length) + ..InstructionIndex(self.instructions.len()); if !expansion_output.detail.range.is_empty() { source_mapping.entries.push(SourceMapEntry { @@ -835,7 +838,15 @@ impl ops::AddAssign for Program { } } -type InstructionIndex = usize; +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct InstructionIndex(pub usize); + +impl InstructionIndex { + fn map(self, f: impl FnOnce(usize) -> usize) -> Self { + Self(f(self.0)) + } +} + pub type ProgramCalibrationExpansionSourceMap = SourceMap; @@ -869,7 +880,7 @@ mod tests { program::{ calibration::{CalibrationExpansion, CalibrationSource, MaybeCalibrationExpansion}, source_map::{SourceMap, SourceMapEntry}, - MemoryAccesses, + InstructionIndex, MemoryAccesses, }, quil::{Quil, INDENT}, real, @@ -1039,7 +1050,7 @@ NOP let expected_source_map = SourceMap { entries: vec![ SourceMapEntry { - source_location: 0, + source_location: InstructionIndex(0), target_location: MaybeCalibrationExpansion::Expanded(CalibrationExpansion { calibration_used: CalibrationIdentifier { name: "I".to_string(), @@ -1047,10 +1058,10 @@ NOP ..CalibrationIdentifier::default() } .into(), - range: 0..3, + range: InstructionIndex(0)..InstructionIndex(3), expansions: SourceMap { entries: vec![SourceMapEntry { - source_location: 0, + source_location: InstructionIndex(0), target_location: CalibrationExpansion { calibration_used: CalibrationSource::Calibration( CalibrationIdentifier { @@ -1060,7 +1071,7 @@ NOP qubits: vec![], }, ), - range: 0..1, + range: InstructionIndex(0)..InstructionIndex(1), expansions: SourceMap { entries: vec![] }, }, }], @@ -1068,11 +1079,11 @@ NOP }), }, SourceMapEntry { - source_location: 1, - target_location: MaybeCalibrationExpansion::Unexpanded(3), + source_location: InstructionIndex(1), + target_location: MaybeCalibrationExpansion::Unexpanded(InstructionIndex(3)), }, SourceMapEntry { - source_location: 2, + source_location: InstructionIndex(2), target_location: MaybeCalibrationExpansion::Expanded(CalibrationExpansion { calibration_used: CalibrationIdentifier { name: "I".to_string(), @@ -1080,10 +1091,10 @@ NOP ..CalibrationIdentifier::default() } .into(), - range: 4..7, + range: InstructionIndex(4)..InstructionIndex(7), expansions: SourceMap { entries: vec![SourceMapEntry { - source_location: 0, + source_location: InstructionIndex(0), target_location: CalibrationExpansion { calibration_used: CalibrationSource::Calibration( CalibrationIdentifier { @@ -1093,7 +1104,7 @@ NOP qubits: vec![], }, ), - range: 0..1, + range: InstructionIndex(0)..InstructionIndex(1), expansions: SourceMap { entries: vec![] }, }, }], diff --git a/quil-rs/src/program/source_map.rs b/quil-rs/src/program/source_map.rs index 9dc9a725..eb3aa336 100644 --- a/quil-rs/src/program/source_map.rs +++ b/quil-rs/src/program/source_map.rs @@ -1,3 +1,5 @@ +use super::InstructionIndex; + /// A SourceMap provides information necessary to understand which parts of a target /// were derived from which parts of a source artifact, in such a way that they can be /// mapped in either direction. @@ -92,8 +94,8 @@ pub trait SourceMapIndexable { fn intersects(&self, other: &Index) -> bool; } -impl SourceMapIndexable for usize { - fn intersects(&self, other: &usize) -> bool { +impl SourceMapIndexable for InstructionIndex { + fn intersects(&self, other: &InstructionIndex) -> bool { self == other } } From f72ae40eceb139f52bbad12dcd1a1d4cc75d03ad Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Fri, 11 Oct 2024 17:41:52 -0700 Subject: [PATCH 39/39] add missing from_ methods to new python bindings --- quil-py/src/program/source_map.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/quil-py/src/program/source_map.rs b/quil-py/src/program/source_map.rs index cdedc8b2..14167226 100644 --- a/quil-py/src/program/source_map.rs +++ b/quil-py/src/program/source_map.rs @@ -218,6 +218,18 @@ impl PyMaybeCalibrationExpansion { } } + #[staticmethod] + pub fn from_expanded(inner: PyCalibrationExpansion) -> Self { + Self(MaybeCalibrationExpansion::Expanded(inner.into_inner())) + } + + #[staticmethod] + pub fn from_unexpanded(inner: usize) -> Self { + Self(MaybeCalibrationExpansion::Unexpanded(InstructionIndex( + inner, + ))) + } + pub fn is_expanded(&self) -> bool { matches!(self.0, MaybeCalibrationExpansion::Expanded(_)) }