1use std::{num::NonZeroU16, sync::Arc};
2
3use opentelemetry::trace::FutureExt;
4use pyo3::{pyclass, FromPyObject};
5use qcs::{Error, Executable, ExecutionData, JobHandle, Service};
6use rigetti_pyo3::{
7 impl_as_mut_for_wrapper, py_async, py_sync, py_wrap_error, py_wrap_simple_enum, py_wrap_type,
8 pyo3::{exceptions::PyRuntimeError, pymethods, types::PyDict, Py, PyAny, PyResult, Python},
9 wrap_error, PyWrapper, ToPython, ToPythonError,
10};
11use tokio::sync::Mutex;
12use tracing::instrument;
13
14use crate::{
15 compiler::quilc::{PyCompilerOpts, PyQuilcClient},
16 execution_data::PyExecutionData,
17 qpu::{api::PyExecutionOptions, translation::PyTranslationOptions},
18};
19
20wrap_error!(RustExecutionError(Error));
21py_wrap_error!(
22 executable,
23 RustExecutionError,
24 ExecutionError,
25 PyRuntimeError
26);
27
28py_wrap_type! {
32 PyExecutable(Arc<Mutex<Executable<'static, 'static>>>) as "Executable";
33}
34
35impl_as_mut_for_wrapper!(PyExecutable);
36
37#[pyclass]
38#[pyo3(name = "ExeParameter")]
39#[derive(FromPyObject)]
40pub struct PyParameter {
41 #[pyo3(get, set)]
42 pub name: String,
43 #[pyo3(get, set)]
44 pub index: usize,
45 #[pyo3(get, set)]
46 pub value: f64,
47}
48
49#[pymethods]
50impl PyParameter {
51 #[new]
52 pub fn new(name: String, index: usize, value: f64) -> Self {
53 Self { name, index, value }
54 }
55}
56
57macro_rules! py_executable_data {
60 ($self: ident, $method: ident $(, $arg: expr)* $(,)?) => {{
61 let arc = $self.as_inner().clone();
62 async move {
63 arc.lock()
64 .await
65 .$method($($arg),*)
66 .await
67 .map(ExecutionData::from)
68 .map(PyExecutionData::from)
69 .map_err(RustExecutionError::from)
70 .map_err(RustExecutionError::to_py_err)
71 }.with_current_context()
72 }};
73}
74
75macro_rules! py_job_handle {
78 ($self: ident, $method: ident $(, $arg: expr)* $(,)?) => {{
79 let arc = $self.as_inner().clone();
80 async move {
81 arc.lock()
82 .await
83 .$method($($arg),*)
84 .await
85 .map(JobHandle::from)
86 .map(PyJobHandle::from)
87 .map_err(RustExecutionError::from)
88 .map_err(RustExecutionError::to_py_err)
89 }
90 .with_current_context()
91 }};
92}
93
94#[pyo3_opentelemetry::pypropagate(exclude(new), on_context_extraction_failure = "ignore")]
95#[pymethods]
96impl PyExecutable {
97 #[new]
98 #[pyo3(signature = (
99 quil,
100 /,
101 registers = Vec::new(),
102 parameters = Vec::new(),
103 shots = None,
104 quilc_client = None,
105 compiler_options = None,
106 ))]
107 pub fn new(
108 quil: String,
109 registers: Vec<String>,
110 parameters: Vec<PyParameter>,
111 #[pyo3(from_py_with = "crate::from_py::optional_non_zero_u16")] shots: Option<NonZeroU16>,
112 quilc_client: Option<PyQuilcClient>,
113 compiler_options: Option<PyCompilerOpts>,
114 ) -> Self {
115 let quilc_client = quilc_client.map(|c| c.inner);
116 let mut exe = Executable::from_quil(quil).with_quilc_client(quilc_client);
117
118 for reg in registers {
119 exe = exe.read_from(reg);
120 }
121
122 for param in parameters {
123 exe.with_parameter(param.name, param.index, param.value);
124 }
125
126 if let Some(shots) = shots {
127 exe = exe.with_shots(shots);
128 }
129
130 if let Some(options) = compiler_options {
131 exe = exe.compiler_options(options.into_inner());
132 }
133
134 Self::from(Arc::new(Mutex::new(exe)))
135 }
136
137 #[instrument(skip_all)]
138 pub fn execute_on_qvm(
139 &self,
140 py: Python<'_>,
141 client: crate::qvm::PyQvmClient,
142 ) -> PyResult<PyExecutionData> {
143 py_sync!(py, py_executable_data!(self, execute_on_qvm, &client))
144 }
145
146 #[instrument(skip_all)]
147 pub fn execute_on_qvm_async<'py>(
148 &'py self,
149 py: Python<'py>,
150 client: crate::qvm::PyQvmClient,
151 ) -> PyResult<&PyAny> {
152 py_async!(py, py_executable_data!(self, execute_on_qvm, &client))
153 }
154
155 #[pyo3(signature = (quantum_processor_id, endpoint_id = None, translation_options = None, execution_options = None))]
156 pub fn execute_on_qpu(
157 &self,
158 py: Python<'_>,
159 quantum_processor_id: String,
160 endpoint_id: Option<String>,
161 translation_options: Option<PyTranslationOptions>,
162 execution_options: Option<PyExecutionOptions>,
163 ) -> PyResult<PyExecutionData> {
164 let translation_options = translation_options.map(|opts| opts.as_inner().clone());
165 match endpoint_id {
166 Some(endpoint_id) => py_sync!(
167 py,
168 py_executable_data!(
169 self,
170 execute_on_qpu_with_endpoint,
171 quantum_processor_id,
172 endpoint_id,
173 translation_options,
174 )
175 ),
176 None => py_sync!(
177 py,
178 py_executable_data!(
179 self,
180 execute_on_qpu,
181 quantum_processor_id,
182 translation_options,
183 execution_options.unwrap_or_default().as_inner(),
184 )
185 ),
186 }
187 }
188
189 #[pyo3(signature = (quantum_processor_id, endpoint_id = None, translation_options = None, execution_options = None))]
190 pub fn execute_on_qpu_async<'py>(
191 &'py self,
192 py: Python<'py>,
193 quantum_processor_id: String,
194 endpoint_id: Option<String>,
195 translation_options: Option<PyTranslationOptions>,
196 execution_options: Option<PyExecutionOptions>,
197 ) -> PyResult<&PyAny> {
198 let translation_options = translation_options.map(|opts| opts.as_inner().clone());
199 match endpoint_id {
200 Some(endpoint_id) => py_async!(
201 py,
202 py_executable_data!(
203 self,
204 execute_on_qpu_with_endpoint,
205 quantum_processor_id,
206 endpoint_id,
207 translation_options,
208 )
209 ),
210 None => py_async!(
211 py,
212 py_executable_data!(
213 self,
214 execute_on_qpu,
215 quantum_processor_id,
216 translation_options,
217 execution_options.unwrap_or_default().as_inner(),
218 )
219 ),
220 }
221 }
222
223 #[pyo3(signature = (quantum_processor_id, endpoint_id = None, translation_options = None, execution_options = None))]
224 pub fn submit_to_qpu(
225 &self,
226 py: Python<'_>,
227 quantum_processor_id: String,
228 endpoint_id: Option<String>,
229 translation_options: Option<PyTranslationOptions>,
230 execution_options: Option<PyExecutionOptions>,
231 ) -> PyResult<PyJobHandle> {
232 let translation_options = translation_options.map(|opts| opts.as_inner().clone());
233 match endpoint_id {
234 Some(endpoint_id) => py_sync!(
235 py,
236 py_job_handle!(
237 self,
238 submit_to_qpu_with_endpoint,
239 quantum_processor_id,
240 endpoint_id,
241 translation_options,
242 )
243 ),
244 None => py_sync!(
245 py,
246 py_job_handle!(
247 self,
248 submit_to_qpu,
249 quantum_processor_id,
250 translation_options,
251 execution_options.unwrap_or_default().as_inner(),
252 )
253 ),
254 }
255 }
256
257 #[pyo3(signature = (quantum_processor_id, endpoint_id = None, translation_options = None, execution_options = None))]
258 pub fn submit_to_qpu_async<'py>(
259 &'py self,
260 py: Python<'py>,
261 quantum_processor_id: String,
262 endpoint_id: Option<String>,
263 translation_options: Option<PyTranslationOptions>,
264 execution_options: Option<PyExecutionOptions>,
265 ) -> PyResult<&PyAny> {
266 let translation_options = translation_options.map(|opts| opts.as_inner().clone());
267 match endpoint_id {
268 Some(endpoint_id) => {
269 py_async!(
270 py,
271 py_job_handle!(
272 self,
273 submit_to_qpu_with_endpoint,
274 quantum_processor_id,
275 endpoint_id,
276 translation_options,
277 )
278 )
279 }
280 None => py_async!(
281 py,
282 py_job_handle!(
283 self,
284 submit_to_qpu,
285 quantum_processor_id,
286 translation_options,
287 execution_options.unwrap_or_default().as_inner(),
288 )
289 ),
290 }
291 }
292
293 pub fn retrieve_results(
294 &mut self,
295 py: Python<'_>,
296 job_handle: PyJobHandle,
297 ) -> PyResult<PyExecutionData> {
298 py_sync!(
299 py,
300 py_executable_data!(self, retrieve_results, job_handle.into())
301 )
302 }
303
304 pub fn retrieve_results_async<'py>(
305 &'py mut self,
306 py: Python<'py>,
307 job_handle: PyJobHandle,
308 ) -> PyResult<&PyAny> {
309 py_async!(
310 py,
311 py_executable_data!(self, retrieve_results, job_handle.into())
312 )
313 }
314}
315
316py_wrap_simple_enum! {
317 PyService(Service) as "Service" {
318 Quilc as Quilc,
319 Qvm as QVM,
320 Qcs as QCS,
321 Qpu as QPU
322 }
323}
324
325py_wrap_type! {
326 PyJobHandle(JobHandle<'static>) as "JobHandle";
327}
328
329#[pymethods]
330impl PyJobHandle {
331 #[getter]
332 pub fn job_id(&self) -> String {
333 self.as_inner().job_id().to_string()
334 }
335
336 #[getter]
337 pub fn readout_map(&self, py: Python) -> PyResult<Py<PyDict>> {
338 self.as_inner().readout_map().to_python(py)
339 }
340}