Skip to main content

quil/program/
mod.rs

1use std::{
2    collections::{HashMap, HashSet},
3    str::FromStr,
4};
5
6use indexmap::IndexMap;
7use numpy::{PyArray2, ToPyArray};
8use quil_rs::{
9    instruction::{Instruction, QubitPlaceholder, TargetPlaceholder, Waveform},
10    program::{
11        analysis::{ControlFlowGraph, ControlFlowGraphOwned},
12        Calibrations, FrameSet, MemoryRegion,
13    },
14    Program,
15};
16use rigetti_pyo3::{
17    create_init_submodule, impl_as_mut_for_wrapper, impl_from_str, impl_parse, impl_repr,
18    num_complex::Complex64,
19    py_wrap_error, py_wrap_type,
20    pyo3::{
21        exceptions::PyValueError,
22        prelude::*,
23        types::{PyBytes, PyFunction, PyList},
24        IntoPy,
25    },
26    wrap_error, PyTryFrom, PyWrapper, PyWrapperMut, ToPython, ToPythonError,
27};
28
29use crate::{
30    impl_eq, impl_to_quil,
31    instruction::{
32        PyDeclaration, PyGateDefinition, PyInstruction, PyMemoryReference, PyPragma, PyQubit,
33        PyTarget, PyWaveform,
34    },
35};
36
37use self::{
38    analysis::{PyBasicBlock, PyControlFlowGraph},
39    scheduling::{PyScheduleSeconds, PyScheduleSecondsItem, PyTimeSpanSeconds},
40    source_map::{
41        PyCalibrationExpansion, PyCalibrationExpansionSourceMap,
42        PyCalibrationExpansionSourceMapEntry, PyCalibrationSource, PyMaybeCalibrationExpansion,
43        PyProgramCalibrationExpansion, PyProgramCalibrationExpansionSourceMap,
44        PyProgramCalibrationExpansionSourceMapEntry,
45    },
46};
47pub use self::{calibration::PyCalibrationSet, frame::PyFrameSet, memory::PyMemoryRegion};
48
49mod analysis;
50mod calibration;
51mod frame;
52mod memory;
53mod scheduling;
54mod source_map;
55
56wrap_error!(ProgramError(quil_rs::program::ProgramError));
57py_wrap_error!(quil, ProgramError, PyProgramError, PyValueError);
58
59py_wrap_type! {
60    #[derive(Debug, PartialEq)]
61    // If unset, the module defaults to builtin, which can't be pickled
62    #[pyo3(module = "quil.program")]
63    PyProgram(Program) as "Program"
64}
65impl_as_mut_for_wrapper!(PyProgram);
66impl_repr!(PyProgram);
67impl_from_str!(PyProgram, ProgramError);
68impl_parse!(PyProgram);
69impl_to_quil!(PyProgram);
70impl_eq!(PyProgram);
71
72impl Default for PyProgram {
73    fn default() -> Self {
74        Self::new()
75    }
76}
77
78#[pymethods]
79impl PyProgram {
80    #[new]
81    pub fn new() -> Self {
82        Self(Program::default())
83    }
84
85    pub fn clone_without_body_instructions(&self) -> Self {
86        Self(self.as_inner().clone_without_body_instructions())
87    }
88
89    pub fn copy(&self) -> Self {
90        Self(self.as_inner().clone())
91    }
92
93    pub fn control_flow_graph(&self) -> PyControlFlowGraph {
94        ControlFlowGraphOwned::from(ControlFlowGraph::from(self.as_inner())).into()
95    }
96
97    pub fn expand_calibrations_with_source_map(&self) -> PyResult<PyProgramCalibrationExpansion> {
98        let expansion = self
99            .as_inner()
100            .expand_calibrations_with_source_map()
101            .map_err(ProgramError::from)
102            .map_err(ProgramError::to_py_err)?;
103        Ok(expansion.into())
104    }
105
106    #[getter]
107    pub fn body_instructions<'a>(&self, py: Python<'a>) -> PyResult<&'a PyList> {
108        Ok(PyList::new(
109            py,
110            self.as_inner()
111                .body_instructions()
112                .map(|i| i.to_python(py))
113                .collect::<PyResult<Vec<PyInstruction>>>()?,
114        ))
115    }
116
117    #[getter]
118    pub fn calibrations(&self, py: Python<'_>) -> PyResult<PyCalibrationSet> {
119        self.as_inner().calibrations.to_python(py)
120    }
121
122    #[setter]
123    pub fn set_calibrations(
124        &mut self,
125        py: Python<'_>,
126        calibrations: PyCalibrationSet,
127    ) -> PyResult<()> {
128        let program = self.as_inner_mut();
129        program.calibrations = Calibrations::py_try_from(py, &calibrations)?;
130        Ok(())
131    }
132
133    #[getter]
134    pub fn waveforms(&self, py: Python<'_>) -> PyResult<IndexMap<String, PyWaveform>> {
135        self.as_inner().waveforms.to_python(py)
136    }
137
138    #[setter]
139    pub fn set_waveforms(
140        &mut self,
141        py: Python<'_>,
142        waveforms: IndexMap<String, PyWaveform>,
143    ) -> PyResult<()> {
144        self.as_inner_mut().waveforms = IndexMap::<String, Waveform>::py_try_from(py, &waveforms)?;
145        Ok(())
146    }
147
148    #[getter]
149    pub fn frames(&self, py: Python<'_>) -> PyResult<PyFrameSet> {
150        self.as_inner().frames.to_python(py)
151    }
152
153    #[setter]
154    pub fn set_frames(&mut self, py: Python<'_>, frames: PyFrameSet) -> PyResult<()> {
155        self.as_inner_mut().frames = FrameSet::py_try_from(py, &frames)?;
156        Ok(())
157    }
158
159    #[getter]
160    pub fn memory_regions(&self, py: Python<'_>) -> PyResult<IndexMap<String, PyMemoryRegion>> {
161        self.as_inner()
162            .memory_regions
163            .iter()
164            .map(|(name, memory_region)| Ok((name.to_python(py)?, memory_region.to_python(py)?)))
165            .collect()
166    }
167
168    #[setter]
169    pub fn set_memory_regions(
170        &mut self,
171        py: Python<'_>,
172        memory_regions: IndexMap<String, PyMemoryRegion>,
173    ) -> PyResult<()> {
174        self.as_inner_mut().memory_regions =
175            IndexMap::<String, MemoryRegion>::py_try_from(py, &memory_regions)?;
176        Ok(())
177    }
178
179    #[getter]
180    // TODO: Should this filtering move to Program? Should we assume memory_regions will always make up all
181    // declarations and simplify this?
182    pub fn declarations(&self, py: Python<'_>) -> PyResult<HashMap<String, PyDeclaration>> {
183        self.as_inner()
184            .to_instructions()
185            .iter()
186            .filter_map(|inst| match inst {
187                Instruction::Declaration(declaration) => Some(declaration),
188                _ => None,
189            })
190            .map(|declaration| Ok((declaration.name.clone(), declaration.to_python(py)?)))
191            .collect()
192    }
193
194    #[getter]
195    pub fn gate_definitions(&self, py: Python<'_>) -> PyResult<IndexMap<String, PyGateDefinition>> {
196        self.as_inner()
197            .gate_definitions
198            .iter()
199            .map(|(name, gate_def)| Ok((name.to_python(py)?, gate_def.to_python(py)?)))
200            .collect()
201    }
202
203    #[setter]
204    pub fn set_gate_definitions(&mut self, definitions: IndexMap<String, PyGateDefinition>) {
205        self.as_inner_mut().gate_definitions = definitions
206            .into_iter()
207            .map(|(name, gate_def)| (name, gate_def.into_inner()))
208            .collect();
209    }
210
211    #[getter]
212    pub fn pragma_extern_map(&self) -> IndexMap<Option<String>, PyPragma> {
213        self.as_inner()
214            .extern_pragma_map
215            .clone()
216            .into_iter()
217            .map(|(k, v)| (k, v.into()))
218            .collect()
219    }
220
221    pub fn dagger(&self) -> PyResult<Self> {
222        self.as_inner()
223            .dagger()
224            .map(PyProgram::from)
225            .map_err(ProgramError::from)
226            .map_err(ProgramError::to_py_err)
227    }
228
229    pub fn expand_calibrations(&self) -> PyResult<Self> {
230        self.as_inner()
231            .expand_calibrations()
232            .map(PyProgram::from)
233            .map_err(ProgramError::from)
234            .map_err(ProgramError::to_py_err)
235    }
236
237    pub fn into_simplified(&self) -> PyResult<Self> {
238        self.as_inner()
239            .into_simplified()
240            .map(PyProgram::from)
241            .map_err(ProgramError::from)
242            .map_err(ProgramError::to_py_err)
243    }
244
245    pub fn get_used_qubits(&self, py: Python<'_>) -> PyResult<HashSet<PyQubit>> {
246        self.as_inner()
247            .get_used_qubits()
248            .iter()
249            .map(|q| q.to_python(py))
250            .collect()
251    }
252
253    pub fn add_instruction(&mut self, instruction: PyInstruction) {
254        self.as_inner_mut().add_instruction(instruction.into())
255    }
256
257    pub fn add_instructions(&mut self, instructions: Vec<PyInstruction>) {
258        self.as_inner_mut()
259            .add_instructions(instructions.into_iter().map(Into::into))
260    }
261
262    pub fn to_instructions(&self, py: Python<'_>) -> PyResult<Vec<PyInstruction>> {
263        self.as_inner()
264            .to_instructions()
265            .iter()
266            .map(|i| i.to_python(py))
267            .collect()
268    }
269
270    pub fn to_unitary(&self, py: Python<'_>, n_qubits: u64) -> PyResult<Py<PyArray2<Complex64>>> {
271        Ok(self
272            .as_inner()
273            .to_unitary(n_qubits)
274            .map_err(ProgramError::from)
275            .map_err(ProgramError::to_py_err)?
276            .to_pyarray(py)
277            .to_owned())
278    }
279
280    pub fn filter_instructions(&self, py: Python, predicate: Py<PyFunction>) -> PyResult<Self> {
281        let filtered = self.as_inner().filter_instructions(|inst| {
282            Python::with_gil(|py| {
283                predicate
284                    .call1(py, (inst.to_python(py).unwrap(),))
285                    .unwrap_or_else(|err| panic!("predicate function returned an error: {err}"))
286                    .extract(py)
287                    .unwrap_or_else(|err| panic!("predicate function must return a bool: {err}"))
288            })
289        });
290        filtered.to_python(py)
291    }
292
293    pub fn resolve_placeholders(&mut self) {
294        self.as_inner_mut().resolve_placeholders();
295    }
296
297    pub fn wrap_in_loop(
298        &self,
299        loop_count_reference: PyMemoryReference,
300        start_target: PyTarget,
301        end_target: PyTarget,
302        iterations: u32,
303    ) -> Self {
304        PyProgram(self.as_inner().wrap_in_loop(
305            loop_count_reference.into_inner(),
306            start_target.into_inner(),
307            end_target.into_inner(),
308            iterations,
309        ))
310    }
311
312    // Because we can't bubble up an error from inside the closures, they panic when the given
313    // Python functions return an error or an unexpected type. This is unusual, but in a Python
314    // program, this function will only raise because [`pyo3`] wraps Rust panics in a
315    // `PanicException`.
316    #[pyo3(signature = (*, target_resolver = None, qubit_resolver = None))]
317    pub fn resolve_placeholders_with_custom_resolvers(
318        &mut self,
319        target_resolver: Option<Py<PyFunction>>,
320        qubit_resolver: Option<Py<PyFunction>>,
321    ) {
322        #[allow(clippy::type_complexity)]
323        let rs_qubit_resolver: Box<dyn Fn(&QubitPlaceholder) -> Option<u64>> =
324            if let Some(resolver) = qubit_resolver {
325                Box::new(move |placeholder: &QubitPlaceholder| -> Option<u64> {
326                    Python::with_gil(|py| {
327                        let resolved_qubit = resolver
328                            .call1(
329                                py,
330                                (placeholder
331                                    .to_python(py)
332                                    .expect("QubitPlaceholder.to_python() should be infallible"),),
333                            )
334                            .unwrap_or_else(|err| {
335                                panic!("qubit_resolver returned an error: {err}")
336                            });
337
338                        resolved_qubit.extract(py).unwrap_or_else(|err| {
339                            panic!("qubit_resolver must return None or int: {err}")
340                        })
341                    })
342                })
343            } else {
344                self.as_inner().default_qubit_resolver()
345            };
346
347        #[allow(clippy::type_complexity)]
348        let rs_target_resolver: Box<dyn Fn(&TargetPlaceholder) -> Option<String>> =
349            if let Some(resolver) = target_resolver {
350                Box::new(move |placeholder: &TargetPlaceholder| -> Option<String> {
351                    Python::with_gil(|py| {
352                        let resolved_label = resolver
353                            .call1(
354                                py,
355                                (placeholder
356                                    .to_python(py)
357                                    .expect("TargetPlaceholder.to_python() should be infallibe"),),
358                            )
359                            .unwrap_or_else(|err| {
360                                panic!("label_resolver returned an error: {err}")
361                            });
362
363                        resolved_label.extract(py).unwrap_or_else(|err| {
364                            panic!("label_resolver must return None or str: {err}")
365                        })
366                    })
367                })
368            } else {
369                self.as_inner().default_target_resolver()
370            };
371
372        self.as_inner_mut()
373            .resolve_placeholders_with_custom_resolvers(rs_target_resolver, rs_qubit_resolver);
374    }
375
376    pub fn __add__(&self, py: Python<'_>, rhs: Self) -> PyResult<Self> {
377        let new = self.as_inner().clone() + rhs.as_inner().clone();
378        new.to_python(py)
379    }
380
381    pub fn __iadd__(&mut self, rhs: Self) {
382        *self.as_inner_mut() += rhs.as_inner().clone()
383    }
384
385    // This will raise an error if the program contains any unresolved
386    // placeholders. This is because they can't be converted to valid quil,
387    // nor can they be serialized and deserialized in a consistent
388    // way.
389    pub fn __getstate__(&self, py: Python<'_>) -> PyResult<Py<PyBytes>> {
390        Ok(PyBytes::new(py, self.to_quil()?.as_bytes()).into_py(py))
391    }
392
393    pub fn __setstate__(&mut self, py: Python<'_>, state: &PyBytes) -> PyResult<()> {
394        *self = Program::from_str(std::str::from_utf8(state.as_bytes())?)
395            .map_err(ProgramError::from)
396            .map_err(ProgramError::to_py_err)?
397            .to_python(py)?;
398        Ok(())
399    }
400}
401
402create_init_submodule! {
403    classes: [
404        PyFrameSet,
405        PyProgram,
406        PyCalibrationExpansion,
407        PyCalibrationExpansionSourceMap,
408        PyCalibrationExpansionSourceMapEntry,
409        PyCalibrationSource,
410        PyMaybeCalibrationExpansion,
411        PyProgramCalibrationExpansion,
412        PyProgramCalibrationExpansionSourceMap,
413        PyProgramCalibrationExpansionSourceMapEntry,
414        PyCalibrationSet,
415        PyMemoryRegion,
416        PyBasicBlock,
417        PyControlFlowGraph,
418        PyScheduleSeconds,
419        PyScheduleSecondsItem,
420        PyTimeSpanSeconds
421    ],
422}