quil_rs/program/frame.rs
1// Copyright 2021 Rigetti Computing
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{
16 borrow::Borrow,
17 collections::{HashMap, HashSet},
18};
19
20#[cfg(not(feature = "python"))]
21use optipy::strip_pyo3;
22#[cfg(feature = "stubs")]
23use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods};
24
25use crate::instruction::{FrameAttributes, FrameDefinition, FrameIdentifier, Instruction, Qubit};
26
27/// A collection of Quil frames (`DEFFRAME` instructions) with utility methods.
28#[derive(Clone, Debug, Default, PartialEq, Eq)]
29#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
30#[cfg_attr(feature = "python", pyo3::pyclass(module = "quil.program", eq))]
31pub struct FrameSet {
32 pub(crate) frames: HashMap<FrameIdentifier, FrameAttributes>,
33}
34
35impl FrameSet {
36 /// Retrieve the attributes of a frame by its identifier.
37 pub fn get(&self, identifier: &FrameIdentifier) -> Option<&FrameAttributes> {
38 self.frames.get(identifier)
39 }
40
41 /// Return a list of all frame IDs described by this FrameSet.
42 pub fn get_keys(&self) -> Vec<&FrameIdentifier> {
43 self.frames.keys().collect()
44 }
45
46 pub(crate) fn filter<'s>(&'s self, condition: FrameMatchConditions) -> MatchedFrames<'s> {
47 let used = condition
48 .used
49 .map_or_else(HashSet::new, |c| self.get_matching_keys_for_condition(c));
50
51 let blocked = condition.blocked.map_or_else(HashSet::new, |c| {
52 let mut blocked = self.get_matching_keys_for_condition(c);
53
54 if !used.is_empty() {
55 blocked.retain(|&f| !used.contains(&f));
56 }
57
58 blocked
59 });
60
61 MatchedFrames { used, blocked }
62 }
63
64 /// Return all frames in the set which match all of these conditions. If a frame _would_ match, but is
65 /// not present in this [FrameSet], then it is not returned (notably, the [FrameMatchCondition::Specific]
66 /// match condition).
67 pub(crate) fn get_matching_keys_for_condition<'s>(
68 &'s self,
69 condition: FrameMatchCondition,
70 ) -> HashSet<&'s FrameIdentifier> {
71 let keys = self.frames.keys();
72
73 match condition {
74 FrameMatchCondition::All => keys.collect(),
75 FrameMatchCondition::AnyOfNames(names) => {
76 keys.filter(|&f| names.contains(f.name.as_str())).collect()
77 }
78 FrameMatchCondition::AnyOfQubits(qubits) => keys
79 .filter(|&f| f.qubits.iter().any(|q| qubits.contains(&q)))
80 .collect(),
81 FrameMatchCondition::ExactQubits(qubits) => keys
82 .filter(|&f| f.qubits.iter().collect::<HashSet<_>>() == qubits)
83 .collect(),
84 FrameMatchCondition::Specific(frame) => {
85 // This unusual pattern (fetch key & value by key, discard value) allows us to return
86 // a reference to `self` rather than `condition`, keeping lifetimes simpler.
87 if let Some((frame, _)) = self.frames.get_key_value(frame) {
88 HashSet::from([frame])
89 } else {
90 HashSet::new()
91 }
92 }
93 FrameMatchCondition::And(conditions) => conditions
94 .into_iter()
95 .map(|c| self.get_matching_keys_for_condition(c))
96 .reduce(|acc, el| acc.into_iter().filter(|&v| el.contains(v)).collect())
97 .unwrap_or_default(),
98 FrameMatchCondition::Or(conditions) => conditions
99 .into_iter()
100 .flat_map(|c| self.get_matching_keys_for_condition(c))
101 .collect(),
102 }
103 }
104
105 /// Return a new [`FrameSet`] which describes only the given [`FrameIdentifier`]s.
106 pub fn intersection<T>(&self, identifiers: &HashSet<T>) -> Self
107 where
108 T: Borrow<FrameIdentifier> + Eq + std::hash::Hash,
109 {
110 let mut new_frameset = Self::new();
111
112 for (identifier, definition) in &self.frames {
113 if identifiers.contains(identifier) {
114 new_frameset.insert(identifier.clone(), definition.clone())
115 }
116 }
117
118 new_frameset
119 }
120
121 /// Iterate through the contained frames.
122 pub fn iter(&self) -> std::collections::hash_map::Iter<'_, FrameIdentifier, FrameAttributes> {
123 self.frames.iter()
124 }
125
126 /// Return the Quil instructions which describe the contained frames, consuming the [`FrameSet`].
127 pub fn into_instructions(self) -> Vec<Instruction> {
128 self.frames
129 .into_iter()
130 .map(|(identifier, attributes)| {
131 Instruction::FrameDefinition(FrameDefinition {
132 identifier,
133 attributes,
134 })
135 })
136 .collect()
137 }
138}
139
140#[cfg_attr(feature = "stubs", gen_stub_pymethods)]
141#[cfg_attr(feature = "python", pyo3::pymethods)]
142#[cfg_attr(not(feature = "python"), strip_pyo3)]
143impl FrameSet {
144 #[new]
145 pub fn new() -> Self {
146 Self::default()
147 }
148
149 /// Insert a new frame by ID, overwriting any existing one.
150 pub fn insert(&mut self, identifier: FrameIdentifier, attributes: FrameAttributes) {
151 self.frames.insert(identifier, attributes);
152 }
153
154 /// Merge another [FrameSet] with this one, overwriting any existing keys
155 pub fn merge(&mut self, other: FrameSet) {
156 self.frames.extend(other.frames);
157 }
158
159 /// Return the number of frames described within.
160 #[pyo3(name = "__len__")]
161 pub fn len(&self) -> usize {
162 self.frames.len()
163 }
164
165 /// Return true if this describes no frames.
166 pub fn is_empty(&self) -> bool {
167 self.frames.is_empty()
168 }
169
170 /// Return the Quil instructions which describe the contained frames.
171 pub fn to_instructions(&self) -> Vec<Instruction> {
172 self.frames
173 .iter()
174 .map(|(identifier, attributes)| {
175 Instruction::FrameDefinition(FrameDefinition {
176 identifier: identifier.clone(),
177 attributes: attributes.clone(),
178 })
179 })
180 .collect()
181 }
182}
183
184#[derive(Debug)]
185pub(crate) enum FrameMatchCondition<'a> {
186 /// Match all frames in the set
187 All,
188
189 /// Match all frames which share any one of these names
190 AnyOfNames(HashSet<&'a str>),
191
192 /// Match all frames which contain any of these qubits
193 AnyOfQubits(HashSet<&'a Qubit>),
194
195 /// Match all frames which contain exactly these qubits
196 ExactQubits(HashSet<&'a Qubit>),
197
198 /// Return this specific frame, if present in the set
199 Specific(&'a FrameIdentifier),
200
201 /// Return all frames which match all of these conditions
202 And(Vec<FrameMatchCondition<'a>>),
203
204 /// Return all frames which match any of these conditions
205 Or(Vec<FrameMatchCondition<'a>>),
206}
207
208/// A pair of conditions to match frames within a [`crate::Program`] (or another scope).
209///
210/// This allows for deferred evaluation of matching an instruction against available frames.
211pub(crate) struct FrameMatchConditions<'a> {
212 /// A condition to identify which frames within a [`crate::Program`] (or another scope)
213 /// are actively used by an [`Instruction`].
214 ///
215 /// If `None`, then this [`Instruction`] does not use any frames, regardless of which are available.
216 pub used: Option<FrameMatchCondition<'a>>,
217
218 /// A condition to identify which frames within a [`crate::Program`] (or another scope)
219 /// are blocked by an [`Instruction`]. A "blocked" frame is one which is not used by the
220 /// `Instruction` but is not available for use by other instructions while this one executes.
221 ///
222 /// **Note**: for efficiency in computation, this may match frames also matched by `used`.
223 /// In order to query which frames are _blocked but not used_, both conditions must first
224 /// be evaluated in the scope of the available frames.
225 pub blocked: Option<FrameMatchCondition<'a>>,
226}
227
228/// The product of evaluating [`FrameMatchConditions`] in the scope of available frames (such as
229/// within a [`crate::Program`]).
230///
231/// When performing this evaluation with functions from `quil-rs`, the fields will be appropriately
232/// disjoint as described in their documentation.
233#[derive(Clone, PartialEq, Eq, Debug, Default)]
234pub struct MatchedFrames<'a> {
235 /// Which concrete frames are blocked by the [`Instruction`] but not used by it.
236 ///
237 /// This set should be mutually exclusive with [`Self::used`].
238 pub blocked: HashSet<&'a FrameIdentifier>,
239
240 /// Which concrete frames are used by the [`Instruction`]
241 ///
242 /// This set should be mutually exclusive with [`Self::blocked`].
243 pub used: HashSet<&'a FrameIdentifier>,
244}
245
246impl MatchedFrames<'_> {
247 pub fn new() -> Self {
248 Self::default()
249 }
250}