Skip to main content

quil/
lib.rs

1// This error is triggered by PyO3 v0.20.3; it's fixed in 0.21, so this attribute can be removed
2// when we update PyO3.
3#![allow(non_local_definitions)]
4
5use pyo3::prelude::*;
6use rigetti_pyo3::create_init_submodule;
7
8pub mod expression;
9pub mod instruction;
10pub mod program;
11pub mod units;
12pub mod validation;
13pub mod waveforms;
14
15create_init_submodule! {
16    submodules: [
17        "expression": expression::init_submodule,
18        "instructions": instruction::init_submodule,
19        "program": program::init_submodule,
20        "validation": validation::init_submodule,
21        "waveforms": waveforms::init_submodule
22    ],
23}
24
25#[pymodule]
26fn quil(py: Python<'_>, m: &PyModule) -> PyResult<()> {
27    init_submodule("quil", py, m)?;
28    Ok(())
29}
30
31pub fn init_quil_submodule(name: &str, py: Python<'_>, m: &PyModule) -> PyResult<()> {
32    init_submodule(name, py, m)?;
33    Ok(())
34}
35
36/// Implement `to_quil` and `to_quil_or_debug` methods for wrapper types whose inner type
37/// implements [`Quil`](quil_rs::quil::Quil).
38#[macro_export]
39macro_rules! impl_to_quil {
40    ($name: ident) => {
41        #[pyo3::pymethods]
42        impl $name {
43            pub fn to_quil(&self) -> pyo3::PyResult<String> {
44                quil_rs::quil::Quil::to_quil(rigetti_pyo3::PyWrapper::as_inner(self))
45                    .map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))
46            }
47
48            pub fn to_quil_or_debug(&self) -> String {
49                quil_rs::quil::Quil::to_quil_or_debug(rigetti_pyo3::PyWrapper::as_inner(self))
50            }
51        }
52    };
53}
54
55/// Implements pickling for an instruction by implementing __getstate__ and __reduce__.
56///
57/// The program is serialized using [`Quil`](quil_rs::quil::Quil), which means pickling can fail if
58/// `to_quil()` raises an error (e.g. because the instruction contains placeholders).
59///
60/// To correctly implement __reduce__, an additional `_from_state` method is added to the class.
61///
62/// See [Python's __reduce__ documentation](https://docs.python.org/3/library/pickle.html#object.__reduce__)
63#[macro_export]
64macro_rules! impl_pickle_for_instruction {
65    ($name: ident) => {
66        #[pyo3::pymethods]
67        impl $name {
68            // This will raise an error if the instruction contains any unresolved
69            // placeholders. This is because they can't be converted to valid quil,
70            // nor can they be serialized and deserialized in a consistent
71            // way.
72            pub fn __getstate__(
73                &self,
74                py: pyo3::Python<'_>,
75            ) -> pyo3::PyResult<pyo3::Py<pyo3::types::PyBytes>> {
76                use pyo3::IntoPy;
77                Ok(pyo3::types::PyBytes::new(py, self.to_quil()?.as_bytes()).into_py(py))
78            }
79
80            // __reduce__ must return a tuple containing the necessary components to successfully
81            // construct a class instance.
82            //
83            // In this case, we initialize we return the callable _from_state with the state
84            // generated by __getstate__ to reconstruct the instruction.
85            fn __reduce__<'py>(
86                &'py self,
87                py: pyo3::Python<'py>,
88            ) -> pyo3::PyResult<&'py pyo3::PyAny> {
89                use pyo3::IntoPy;
90                let callable = py.get_type::<Self>().getattr("_from_state")?;
91                let state = self.__getstate__(py)?;
92                let args = pyo3::types::PyTuple::new(py, &[state.into_py(py)]);
93                Ok(pyo3::types::PyTuple::new(py, &[callable, args]))
94            }
95
96            // __reduce__ must return a callable with any necessary arguments to initialize the
97            // class instance. This is often done with __new__, but because we define class
98            // specific parameters in __new__, and field access patterns differ between
99            // instruction types (e.g. enums versus data structs), we can't use __new__ in a
100            // generic enough way for this macro. As an alternative, we use the serialized state
101            // from __getstate__ to initialize a copy of the instance.
102            #[staticmethod]
103            pub fn _from_state(
104                py: pyo3::Python<'_>,
105                state: &pyo3::types::PyBytes,
106            ) -> pyo3::PyResult<Self> {
107                let input = std::str::from_utf8(state.as_bytes())?;
108                let instruction = $crate::instruction::PyInstruction::parse(input)?;
109                instruction.inner(py)?.extract(py)
110            }
111        }
112    };
113}
114
115#[macro_export]
116macro_rules! impl_eq {
117    ($name: ident) => {
118        #[pyo3::pymethods]
119        impl $name {
120            pub fn __richcmp__(
121                &self,
122                py: pyo3::Python<'_>,
123                other: &Self,
124                op: pyo3::pyclass::CompareOp,
125            ) -> pyo3::PyObject {
126                use pyo3::IntoPy;
127                match op {
128                    pyo3::pyclass::CompareOp::Eq => (self == other).into_py(py),
129                    pyo3::pyclass::CompareOp::Ne => (self != other).into_py(py),
130                    _ => py.NotImplemented(),
131                }
132            }
133        }
134    };
135}