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 get_matching_keys_for_conditions<'s>(
47        &'s self,
48        condition: FrameMatchConditions,
49    ) -> MatchedFrames<'s> {
50        let used = condition
51            .used
52            .map_or_else(HashSet::new, |c| self.get_matching_keys_for_condition(c));
53
54        let blocked = condition.blocked.map_or_else(HashSet::new, |c| {
55            let mut blocked = self.get_matching_keys_for_condition(c);
56
57            if !used.is_empty() {
58                blocked.retain(|&f| !used.contains(&f));
59            }
60
61            blocked
62        });
63
64        MatchedFrames { used, blocked }
65    }
66
67    /// Return all frames in the set which match all of these conditions. If a frame _would_ match, but is
68    /// not present in this [FrameSet], then it is not returned (notably, the [FrameMatchCondition::Specific]
69    /// match condition).
70    pub(crate) fn get_matching_keys_for_condition<'s>(
71        &'s self,
72        condition: FrameMatchCondition,
73    ) -> HashSet<&'s FrameIdentifier> {
74        let keys = self.frames.keys();
75
76        match condition {
77            FrameMatchCondition::All => keys.collect(),
78            FrameMatchCondition::AnyOfNames(names) => {
79                keys.filter(|&f| names.contains(f.name.as_str())).collect()
80            }
81            FrameMatchCondition::AnyOfQubits(qubits) => keys
82                .filter(|&f| f.qubits.iter().any(|q| qubits.contains(&q)))
83                .collect(),
84            FrameMatchCondition::ExactQubits(qubits) => keys
85                .filter(|&f| f.qubits.iter().collect::<HashSet<_>>() == qubits)
86                .collect(),
87            FrameMatchCondition::Specific(frame) => {
88                // This unusual pattern (fetch key & value by key, discard value) allows us to return
89                // a reference to `self` rather than `condition`, keeping lifetimes simpler.
90                if let Some((frame, _)) = self.frames.get_key_value(frame) {
91                    HashSet::from([frame])
92                } else {
93                    HashSet::new()
94                }
95            }
96            FrameMatchCondition::And(conditions) => conditions
97                .into_iter()
98                .map(|c| self.get_matching_keys_for_condition(c))
99                .reduce(|acc, el| acc.into_iter().filter(|&v| el.contains(v)).collect())
100                .unwrap_or_default(),
101            FrameMatchCondition::Or(conditions) => conditions
102                .into_iter()
103                .flat_map(|c| self.get_matching_keys_for_condition(c))
104                .collect(),
105        }
106    }
107
108    /// Return a new [`FrameSet`] which describes only the given [`FrameIdentifier`]s.
109    pub fn intersection<T>(&self, identifiers: &HashSet<T>) -> Self
110    where
111        T: Borrow<FrameIdentifier> + Eq + std::hash::Hash,
112    {
113        let mut new_frameset = Self::new();
114
115        for (identifier, definition) in &self.frames {
116            if identifiers.contains(identifier) {
117                new_frameset.insert(identifier.clone(), definition.clone())
118            }
119        }
120
121        new_frameset
122    }
123
124    /// Iterate through the contained frames.
125    pub fn iter(&self) -> std::collections::hash_map::Iter<'_, FrameIdentifier, FrameAttributes> {
126        self.frames.iter()
127    }
128
129    /// Return the Quil instructions which describe the contained frames, consuming the [`FrameSet`].
130    pub fn into_instructions(self) -> Vec<Instruction> {
131        self.frames
132            .into_iter()
133            .map(|(identifier, attributes)| {
134                Instruction::FrameDefinition(FrameDefinition {
135                    identifier,
136                    attributes,
137                })
138            })
139            .collect()
140    }
141}
142
143#[cfg_attr(feature = "stubs", gen_stub_pymethods)]
144#[cfg_attr(feature = "python", pyo3::pymethods)]
145#[cfg_attr(not(feature = "python"), strip_pyo3)]
146impl FrameSet {
147    #[new]
148    pub fn new() -> Self {
149        Self::default()
150    }
151
152    /// Insert a new frame by ID, overwriting any existing one.
153    pub fn insert(&mut self, identifier: FrameIdentifier, attributes: FrameAttributes) {
154        self.frames.insert(identifier, attributes);
155    }
156
157    /// Merge another [FrameSet] with this one, overwriting any existing keys
158    pub fn merge(&mut self, other: FrameSet) {
159        self.frames.extend(other.frames);
160    }
161
162    /// Return the number of frames described within.
163    #[pyo3(name = "__len__")]
164    pub fn len(&self) -> usize {
165        self.frames.len()
166    }
167
168    /// Return true if this describes no frames.
169    pub fn is_empty(&self) -> bool {
170        self.frames.is_empty()
171    }
172
173    /// Return the Quil instructions which describe the contained frames.
174    pub fn to_instructions(&self) -> Vec<Instruction> {
175        self.frames
176            .iter()
177            .map(|(identifier, attributes)| {
178                Instruction::FrameDefinition(FrameDefinition {
179                    identifier: identifier.clone(),
180                    attributes: attributes.clone(),
181                })
182            })
183            .collect()
184    }
185}
186
187#[derive(Debug)]
188pub(crate) enum FrameMatchCondition<'a> {
189    /// Match all frames in the set
190    All,
191
192    /// Match all frames which share any one of these names
193    AnyOfNames(HashSet<&'a str>),
194
195    /// Match all frames which contain any of these qubits
196    AnyOfQubits(HashSet<&'a Qubit>),
197
198    /// Match all frames which contain exactly these qubits
199    ExactQubits(HashSet<&'a Qubit>),
200
201    /// Return this specific frame, if present in the set
202    Specific(&'a FrameIdentifier),
203
204    /// Return all frames which match all of these conditions
205    And(Vec<FrameMatchCondition<'a>>),
206
207    /// Return all frames which match any of these conditions
208    Or(Vec<FrameMatchCondition<'a>>),
209}
210
211/// A pair of conditions to match frames within a [`crate::Program`] (or another scope).
212///
213/// This allows for deferred evaluation of matching an instruction against available frames.
214pub(crate) struct FrameMatchConditions<'a> {
215    /// A condition to identify which frames within a [`crate::Program`] (or another scope)
216    /// are actively used by an [`Instruction`].
217    ///
218    /// If `None`, then this [`Instruction`] does not use any frames, regardless of which are available.
219    pub used: Option<FrameMatchCondition<'a>>,
220
221    /// A condition to identify which frames within a [`crate::Program`] (or another scope)
222    /// are blocked by an [`Instruction`]. A "blocked" frame is one which is not used by the
223    /// `Instruction` but is not available for use by other instructions while this one executes.
224    ///
225    /// **Note**: for efficiency in computation, this may match frames also matched by `used`.
226    /// In order to query which frames are _blocked but not used_, both conditions must first
227    /// be evaluated in the scope of the available frames.
228    pub blocked: Option<FrameMatchCondition<'a>>,
229}
230
231/// The product of evaluating [`FrameMatchConditions`] in the scope of available frames (such as
232/// within a [`crate::Program`]).
233///
234/// When performing this evaluation with functions from `quil-rs`, the fields will be appropriately
235/// disjoint as described in their documentation.
236#[derive(Clone, PartialEq, Eq, Debug, Default)]
237pub struct MatchedFrames<'a> {
238    /// Which concrete frames are blocked by the [`Instruction`] but not used by it.
239    ///
240    /// This set should be mutually exclusive with [`Self::used`].
241    pub blocked: HashSet<&'a FrameIdentifier>,
242
243    /// Which concrete frames are used by the [`Instruction`]
244    ///
245    /// This set should be mutually exclusive with [`Self::blocked`].
246    pub used: HashSet<&'a FrameIdentifier>,
247}
248
249impl MatchedFrames<'_> {
250    pub fn new() -> Self {
251        Self::default()
252    }
253}