diff --git a/src/instruction.rs b/src/instruction.rs index c770eabf..d2c56b8d 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -16,6 +16,7 @@ use serde::{Deserialize, Serialize}; use std::{collections::HashMap, fmt}; use crate::expression::Expression; +use crate::program::frame::FrameMatchCondition; #[cfg(test)] use proptest_derive::Arbitrary; @@ -941,6 +942,90 @@ impl Instruction { _ => {} } } + + pub(crate) fn get_frame_match_condition( + &self, + include_blocked: bool, + ) -> Option { + match self { + Instruction::Pulse(Pulse { + blocking, frame, .. + }) + | Instruction::Capture(Capture { + blocking, frame, .. + }) + | Instruction::RawCapture(RawCapture { + blocking, frame, .. + }) => Some(if *blocking && include_blocked { + FrameMatchCondition::AnyOfQubits(&frame.qubits) + } else { + FrameMatchCondition::Specific(frame) + }), + Instruction::Delay(Delay { + frame_names, + qubits, + .. + }) => Some(if frame_names.is_empty() { + FrameMatchCondition::ExactQubits(qubits) + } else { + FrameMatchCondition::And(vec![ + FrameMatchCondition::ExactQubits(qubits), + FrameMatchCondition::AnyOfNames(frame_names), + ]) + }), + Instruction::Fence(Fence { qubits }) => Some(if qubits.is_empty() { + FrameMatchCondition::All + } else { + FrameMatchCondition::AnyOfQubits(qubits) + }), + Instruction::SetFrequency(SetFrequency { frame, .. }) + | Instruction::SetPhase(SetPhase { frame, .. }) + | Instruction::SetScale(SetScale { frame, .. }) + | Instruction::ShiftFrequency(ShiftFrequency { frame, .. }) + | Instruction::ShiftPhase(ShiftPhase { frame, .. }) => { + Some(FrameMatchCondition::Specific(frame)) + } + Instruction::SwapPhases(SwapPhases { frame_1, frame_2 }) => { + Some(FrameMatchCondition::And(vec![ + FrameMatchCondition::Specific(frame_1), + FrameMatchCondition::Specific(frame_2), + ])) + } + Instruction::Gate(_) + | Instruction::CircuitDefinition(_) + | Instruction::GateDefinition(_) + | Instruction::Declaration(_) + | Instruction::Measurement(_) + | Instruction::Reset(_) + | Instruction::CalibrationDefinition(_) + | Instruction::FrameDefinition(_) + | Instruction::MeasureCalibrationDefinition(_) + | Instruction::Pragma(_) + | Instruction::WaveformDefinition(_) + | Instruction::Arithmetic(_) + | Instruction::Halt + | Instruction::Label(_) + | Instruction::Move(_) + | Instruction::Exchange(_) + | Instruction::Load(_) + | Instruction::Store(_) + | Instruction::Jump(_) + | Instruction::JumpWhen(_) + | Instruction::JumpUnless(_) => None, + } + } + + #[cfg(test)] + /// Parse a single instruction from an input string. Returns an error if the input fails to parse, + /// or if there is input left over after parsing. + pub(crate) fn parse(input: &str) -> Result { + use crate::parser::{instruction::parse_instruction, lex}; + + let lexed = lex(input)?; + let (_, instruction) = + nom::combinator::all_consuming(parse_instruction)(&lexed).map_err(|e| e.to_string())?; + Ok(instruction) + } } #[cfg(test)] diff --git a/src/parser/mod.rs b/src/parser/mod.rs index d45268a0..e576f743 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -27,7 +27,7 @@ mod macros; mod common; mod error; mod expression; -mod instruction; +pub(crate) mod instruction; mod lexer; type ParserInput<'a> = &'a [Token]; diff --git a/src/program/frame.rs b/src/program/frame.rs index fa0ee052..53bfb8ef 100644 --- a/src/program/frame.rs +++ b/src/program/frame.rs @@ -34,18 +34,47 @@ impl FrameSet { self.frames.keys().collect() } - /// Return all contained FrameIdentifiers which include **exactly** these qubits (in any order) - /// and - if names are provided - match one of the names. - pub fn get_matching_keys(&self, qubits: &[Qubit], names: &[String]) -> Vec<&FrameIdentifier> { - let qubit_set: HashSet<&Qubit> = qubits.iter().collect(); - self.frames - .iter() - .filter(|(identifier, _)| { - (names.is_empty() || names.contains(&identifier.name)) - && qubit_set == identifier.qubits.iter().collect() - }) - .map(|(id, _)| id) - .collect::>() + /// Return all frames in the set which match all of these conditions. If a frame _would_ match, but is + /// not present in this [FrameSet], then it is not returned (notably, the [FrameMatchCondition::Specific] + /// match condition). + pub(crate) fn get_matching_keys<'s, 'a>( + &'s self, + condition: FrameMatchCondition<'a>, + ) -> HashSet<&'s FrameIdentifier> { + let keys = self.frames.keys(); + + match condition { + FrameMatchCondition::All => keys.collect(), + FrameMatchCondition::AnyOfNames(names) => { + keys.filter(|&f| names.contains(&f.name)).collect() + } + FrameMatchCondition::AnyOfQubits(qubits) => { + let any_of_set: HashSet<_> = qubits.iter().collect(); + + keys.filter(|&f| f.qubits.iter().any(|q| any_of_set.contains(q))) + .collect() + } + FrameMatchCondition::ExactQubits(qubits) => { + let exact_set: HashSet<_> = qubits.iter().collect(); + + keys.filter(|&f| f.qubits.iter().collect::>() == exact_set) + .collect() + } + FrameMatchCondition::Specific(frame) => { + // This unusual pattern (fetch key & value by key, discard value) allows us to return + // a reference to `self` rather than `condition`, keeping lifetimes simpler. + if let Some((frame, _)) = self.frames.get_key_value(frame) { + vec![frame].into_iter().collect() + } else { + HashSet::new() + } + } + FrameMatchCondition::And(conditions) => conditions + .into_iter() + .map(|c| self.get_matching_keys(c)) + .reduce(|acc, el| acc.into_iter().filter(|&v| el.contains(v)).collect()) + .unwrap_or_default(), + } } /// Retrieve the attributes of a frame by its identifier. @@ -86,3 +115,23 @@ impl FrameSet { .collect() } } + +pub(crate) enum FrameMatchCondition<'a> { + /// Match all frames in the set + All, + + /// Match all frames which share any one of these names + AnyOfNames(&'a [String]), + + /// Match all frames which contain any of these qubits + AnyOfQubits(&'a [Qubit]), + + /// Match all frames which contain exactly these qubits + ExactQubits(&'a [Qubit]), + + /// Return these specific frames, if present in the set + Specific(&'a FrameIdentifier), + + /// Return all frames which match all of these conditions + And(Vec>), +} diff --git a/src/program/graph.rs b/src/program/graph.rs index 042f718e..2734be68 100644 --- a/src/program/graph.rs +++ b/src/program/graph.rs @@ -233,19 +233,26 @@ impl InstructionBlock { Ok(()) } InstructionRole::RFControl => { - let frames = match program.get_frames_for_instruction(instruction, true) { - Some(frames) => frames, - None => vec![], - }; - - // Mark a dependency on the last instruction which executed in the context of each target frame - for frame in frames { + let used_frames = program + .get_frames_for_instruction(instruction, false) + .unwrap_or_default(); + let blocked_frames = program + .get_frames_for_instruction(instruction, true) + .unwrap_or_default(); + + // Take a dependency on any previous instructions to _block_ a frame which this instruction _uses_. + for frame in used_frames { let previous_node_id = last_instruction_by_frame - .entry(frame.clone()) - .or_insert(ScheduledGraphNode::BlockStart); + .get(frame) + .unwrap_or(&ScheduledGraphNode::BlockStart); add_dependency!(graph, *previous_node_id => node, ExecutionDependency::ReferenceFrame); + } + + // We mark all "blocked" frames as such for later instructions to take a dependency on + for frame in blocked_frames { last_instruction_by_frame.insert(frame.clone(), node); } + Ok(()) } InstructionRole::ControlFlow => Err(ScheduleError { diff --git a/src/program/graphviz_dot.rs b/src/program/graphviz_dot.rs index 97cd99aa..1aecb7c1 100644 --- a/src/program/graphviz_dot.rs +++ b/src/program/graphviz_dot.rs @@ -233,6 +233,8 @@ DEFFRAME 0 \"ro_rx\": INITIAL-FREQUENCY: 1e6 DEFFRAME 0 \"ro_tx\": INITIAL-FREQUENCY: 1e6 +DEFFRAME 0 1 \"cz\": + INITIAL-FREQUENCY: 1e6 "; let program = @@ -308,8 +310,19 @@ NONBLOCKING PULSE 0 \"rf\" test(duration: 1e6) NONBLOCKING PULSE 1 \"rf\" test(duration: 1e6) " ); + build_dot_format_snapshot_test_case!(fence_all, "FENCE"); + build_dot_format_snapshot_test_case!( + fence_wrapper, + " +FENCE +NONBLOCKING PULSE 0 1 \"cz\" test(duration: 1e-6) +NONBLOCKING PULSE 1 \"rf\" test(duration: 1e-6) +FENCE 1 +" + ); + build_dot_format_snapshot_test_case!( jump, "DECLARE ro BIT diff --git a/src/program/mod.rs b/src/program/mod.rs index c04f8a73..e18644cf 100644 --- a/src/program/mod.rs +++ b/src/program/mod.rs @@ -16,9 +16,7 @@ use std::collections::BTreeMap; use std::str::FromStr; use crate::instruction::{ - Capture, Declaration, Delay, Fence, FrameDefinition, FrameIdentifier, Instruction, Pulse, - RawCapture, SetFrequency, SetPhase, SetScale, ShiftFrequency, ShiftPhase, SwapPhases, Waveform, - WaveformDefinition, + Declaration, FrameDefinition, FrameIdentifier, Instruction, Waveform, WaveformDefinition, }; use crate::parser::{lex, parse_instructions}; @@ -28,7 +26,7 @@ pub use self::memory::MemoryRegion; mod calibration; mod error; -mod frame; +pub(crate) mod frame; pub mod graph; mod memory; @@ -125,78 +123,22 @@ impl Program { /// /// Return `None` if the instruction does not execute in the context of a frame - such /// as classical instructions. + /// + /// See the [Quil-T spec](https://github.com/quil-lang/quil/blob/master/rfcs/analog/proposal.md) + /// for more information. pub fn get_frames_for_instruction<'a>( &'a self, instruction: &'a Instruction, include_blocked: bool, ) -> Option> { - match &instruction { - Instruction::Pulse(Pulse { - blocking, frame, .. - }) => { - if *blocking && include_blocked { - Some(self.frames.get_keys()) - } else { - Some(vec![frame]) - } - } - Instruction::Delay(Delay { - frame_names, - qubits, - .. - }) => { - let frame_ids = self.frames.get_matching_keys(qubits, frame_names); - Some(frame_ids) - } - Instruction::Fence(Fence { qubits }) => { - if qubits.is_empty() { - Some(self.frames.get_keys()) - } else { - Some(self.frames.get_matching_keys(qubits, &[])) - } - } - Instruction::Capture(Capture { - blocking, frame, .. + instruction + .get_frame_match_condition(include_blocked) + .map(|condition| { + self.frames + .get_matching_keys(condition) + .into_iter() + .collect() }) - | Instruction::RawCapture(RawCapture { - blocking, frame, .. - }) => { - if *blocking && include_blocked { - Some(self.frames.get_keys()) - } else { - Some(vec![frame]) - } - } - Instruction::SetFrequency(SetFrequency { frame, .. }) - | Instruction::SetPhase(SetPhase { frame, .. }) - | Instruction::SetScale(SetScale { frame, .. }) - | Instruction::ShiftFrequency(ShiftFrequency { frame, .. }) - | Instruction::ShiftPhase(ShiftPhase { frame, .. }) => Some(vec![frame]), - Instruction::SwapPhases(SwapPhases { frame_1, frame_2 }) => { - Some(vec![frame_1, frame_2]) - } - Instruction::Gate(_) - | Instruction::CircuitDefinition(_) - | Instruction::GateDefinition(_) - | Instruction::Declaration(_) - | Instruction::Measurement(_) - | Instruction::Reset(_) - | Instruction::CalibrationDefinition(_) - | Instruction::FrameDefinition(_) - | Instruction::MeasureCalibrationDefinition(_) - | Instruction::Pragma(_) - | Instruction::WaveformDefinition(_) - | Instruction::Arithmetic(_) - | Instruction::Halt - | Instruction::Label(_) - | Instruction::Move(_) - | Instruction::Exchange(_) - | Instruction::Load(_) - | Instruction::Store(_) - | Instruction::Jump(_) - | Instruction::JumpWhen(_) - | Instruction::JumpUnless(_) => None, - } } pub fn to_instructions(&self, include_headers: bool) -> Vec { @@ -254,7 +196,9 @@ impl FromStr for Program { #[cfg(test)] mod tests { - use std::str::FromStr; + use std::{collections::HashSet, str::FromStr}; + + use crate::instruction::Instruction; use super::Program; @@ -368,4 +312,132 @@ DECLARE ec BIT // program after being re-parsed and serialized. assert!(program1.lines().eq(program2.lines())); } + + #[test] + fn frame_blocking() { + let input = "DEFFRAME 0 \"a\": +\tHARDWARE-OBJECT: \"hardware\" + +DEFFRAME 0 \"b\": +\tHARDWARE-OBJECT: \"hardware\" + +DEFFRAME 1 \"c\": +\tHARDWARE-OBJECT: \"hardware\" + +DEFFRAME 0 1 \"2q\": +\tHARDWARE-OBJECT: \"hardware\" +"; + + let program = Program::from_str(input).unwrap(); + + for (instruction_string, expected_used_frames, expected_blocked_frames) in vec![ + // Blocking pulses use only the specified frame but block frames intersecting the frame's qubits + ( + r#"PULSE 0 "a" custom_waveform"#, + vec![r#"0 "a""#], + vec![r#"0 "a""#, r#"0 "b""#, r#"0 1 "2q""#], + ), + ( + r#"PULSE 1 "c" custom_waveform"#, + vec![r#"1 "c""#], + vec![r#"1 "c""#, r#"0 1 "2q""#], + ), + // Pulses on non-declared frames and unused qubits do not use or block any frames in the program + (r#"PULSE 2 "a" custom_waveform"#, vec![], vec![]), + // Captures work identically to Pulses + ( + r#"CAPTURE 0 "a" custom_waveform ro[0]"#, + vec![r#"0 "a""#], + vec![r#"0 "a""#, r#"0 "b""#, r#"0 1 "2q""#], + ), + ( + r#"CAPTURE 1 "c" custom_waveform ro[0]"#, + vec![r#"1 "c""#], + vec![r#"1 "c""#, r#"0 1 "2q""#], + ), + (r#"CAPTURE 2 "a" custom_waveform ro[0]"#, vec![], vec![]), + // Raw Captures work identically to Pulses + ( + r#"RAW-CAPTURE 0 "a" 1e-6 ro[0]"#, + vec![r#"0 "a""#], + vec![r#"0 "a""#, r#"0 "b""#, r#"0 1 "2q""#], + ), + ( + r#"RAW-CAPTURE 1 "c" 1e-6 ro[0]"#, + vec![r#"1 "c""#], + vec![r#"1 "c""#, r#"0 1 "2q""#], + ), + (r#"RAW-CAPTURE 2 "a" 1e-6 ro[0]"#, vec![], vec![]), + // A non-blocking pulse blocks only its precise frame, not other frames on the same qubits + ( + r#"NONBLOCKING PULSE 0 "a" custom_waveform"#, + vec![r#"0 "a""#], + vec![r#"0 "a""#], + ), + ( + r#"NONBLOCKING PULSE 1 "c" custom_waveform"#, + vec![r#"1 "c""#], + vec![r#"1 "c""#], + ), + ( + r#"NONBLOCKING PULSE 0 1 "2q" custom_waveform"#, + vec![r#"0 1 "2q""#], + vec![r#"0 1 "2q""#], + ), + // A Fence with qubits specified uses and blocks all frames intersecting that qubit + ( + r#"FENCE 1"#, + vec![r#"1 "c""#, r#"0 1 "2q""#], + vec![r#"1 "c""#, r#"0 1 "2q""#], + ), + // Fence-all uses and blocks all frames declared in the program + ( + r#"FENCE"#, + vec![r#"0 "a""#, r#"0 "b""#, r#"1 "c""#, r#"0 1 "2q""#], + vec![r#"0 "a""#, r#"0 "b""#, r#"1 "c""#, r#"0 1 "2q""#], + ), + // Delay uses and blocks frames on exactly the given qubits and with any of the given names + ( + r#"DELAY 0 1.0"#, + vec![r#"0 "a""#, r#"0 "b""#], + vec![r#"0 "a""#, r#"0 "b""#], + ), + (r#"DELAY 1 1.0"#, vec![r#"1 "c""#], vec![r#"1 "c""#]), + (r#"DELAY 1 "c" 1.0"#, vec![r#"1 "c""#], vec![r#"1 "c""#]), + (r#"DELAY 0 1 1.0"#, vec![r#"0 1 "2q""#], vec![r#"0 1 "2q""#]), + ] { + let instruction = Instruction::parse(instruction_string).unwrap(); + let used_frames: HashSet = program + .get_frames_for_instruction(&instruction, false) + .unwrap_or_default() + .into_iter() + .map(|f| f.to_string()) + .collect(); + let expected_used_frames: HashSet = expected_used_frames + .into_iter() + .map(|el| el.to_owned()) + .collect(); + assert_eq!( + used_frames, expected_used_frames, + "Instruction {} *used* frames `{:?}` but we expected `{:?}`", + instruction, used_frames, expected_used_frames + ); + + let blocked_frames: HashSet = program + .get_frames_for_instruction(&instruction, true) + .unwrap() + .into_iter() + .map(|f| f.to_string()) + .collect(); + let expected_blocked_frames: HashSet = expected_blocked_frames + .into_iter() + .map(|el| el.to_owned()) + .collect(); + assert_eq!( + blocked_frames, expected_blocked_frames, + "Instruction {} *blocked* frames `{:?}` but we expected `{:?}`", + instruction, blocked_frames, expected_blocked_frames + ); + } + } } diff --git a/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__different_frames_blocking.snap b/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__different_frames_blocking.snap index 56de0ae4..49e23e32 100644 --- a/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__different_frames_blocking.snap +++ b/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__different_frames_blocking.snap @@ -10,11 +10,13 @@ digraph { node [style="filled"]; "block_0_start" [shape=circle, label="start"]; "block_0_start" -> "block_0_0" [label="frame"]; + "block_0_start" -> "block_0_1" [label="frame"]; + "block_0_start" -> "block_0_2" [label="frame"]; "block_0_start" -> "block_0_end" [label="ordering"]; "block_0_0" [shape=rectangle, label="[0] PULSE 0 \"rf\" test(duration: 1000000.0)"]; - "block_0_0" -> "block_0_1" [label="frame"]; + "block_0_0" -> "block_0_end" [label="frame"]; "block_0_1" [shape=rectangle, label="[1] PULSE 1 \"rf\" test(duration: 1000000.0)"]; - "block_0_1" -> "block_0_2" [label="frame"]; + "block_0_1" -> "block_0_end" [label="frame"]; "block_0_2" [shape=rectangle, label="[2] PULSE 2 \"rf\" test(duration: 1000000.0)"]; "block_0_2" -> "block_0_end" [label="frame"]; "block_0_end" [shape=circle, label="end"]; diff --git a/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__fence_wrapper.snap b/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__fence_wrapper.snap new file mode 100644 index 00000000..a7d4f4be --- /dev/null +++ b/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__fence_wrapper.snap @@ -0,0 +1,27 @@ +--- +source: src/program/graphviz_dot.rs +expression: dot_format +--- +digraph { + entry -> "block_0_start"; + entry [label="Entry Point"]; + subgraph cluster_0 { + label="block_0"; + node [style="filled"]; + "block_0_start" [shape=circle, label="start"]; + "block_0_start" -> "block_0_0" [label="frame"]; + "block_0_start" -> "block_0_end" [label="ordering"]; + "block_0_0" [shape=rectangle, label="[0] FENCE"]; + "block_0_0" -> "block_0_1" [label="frame"]; + "block_0_0" -> "block_0_2" [label="frame"]; + "block_0_0" -> "block_0_end" [label="frame"]; + "block_0_1" [shape=rectangle, label="[1] NONBLOCKING PULSE 0 1 \"cz\" test(duration: 1e-6)"]; + "block_0_1" -> "block_0_3" [label="frame"]; + "block_0_2" [shape=rectangle, label="[2] NONBLOCKING PULSE 1 \"rf\" test(duration: 1e-6)"]; + "block_0_2" -> "block_0_3" [label="frame"]; + "block_0_3" [shape=rectangle, label="[3] FENCE 1"]; + "block_0_3" -> "block_0_end" [label="frame"]; + "block_0_end" [shape=circle, label="end"]; + } +} + diff --git a/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__parametric_pulses_using_capture_results.snap b/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__parametric_pulses_using_capture_results.snap index 5330e3de..79060814 100644 --- a/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__parametric_pulses_using_capture_results.snap +++ b/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__parametric_pulses_using_capture_results.snap @@ -10,23 +10,21 @@ digraph { node [style="filled"]; "block_0_start" [shape=circle, label="start"]; "block_0_start" -> "block_0_0" [label="frame"]; + "block_0_start" -> "block_0_2" [label="frame"]; "block_0_start" -> "block_0_end" [label="ordering"]; "block_0_0" [shape=rectangle, label="[0] CAPTURE 0 \"ro_rx\" test(a: param[0]) ro[0]"]; "block_0_0" -> "block_0_1" [label="await capture frame"]; - "block_0_0" -> "block_0_2" [label="frame"]; "block_0_0" -> "block_0_3" [label="frame"]; "block_0_0" -> "block_0_end" [label="await read"]; "block_0_1" [shape=rectangle, label="[1] NONBLOCKING PULSE 0 \"rf\" test(a: ro[0])"]; - "block_0_1" -> "block_0_3" [label="await read -frame"]; + "block_0_1" -> "block_0_3" [label="await read"]; "block_0_2" [shape=rectangle, label="[2] NONBLOCKING PULSE 1 \"rf\" test(a: ro[0])"]; - "block_0_2" -> "block_0_3" [label="await read -frame"]; + "block_0_2" -> "block_0_3" [label="await read"]; + "block_0_2" -> "block_0_5" [label="frame"]; "block_0_3" [shape=rectangle, label="[3] CAPTURE 0 \"ro_rx\" test(a: param[0]) ro[0]"]; "block_0_3" -> "block_0_4" [label="await capture frame"]; - "block_0_3" -> "block_0_5" [label="frame"]; "block_0_3" -> "block_0_end" [label="await read frame"]; "block_0_4" [shape=rectangle, label="[4] NONBLOCKING PULSE 0 \"rf\" test(a: ro[0])"];