Skip to content

Commit 1645bb7

Browse files
feat!: TranslationOptions builder-like struct with Python bindings (#308)
* feat(python): support TranslationOptions * feat!: TranslationOptions owned by these crates rather than imported BREAKING CHANGE: change to signature of `qpu::translate` to be generic over translation options --------- Co-authored-by: Jake Selig <[email protected]>
1 parent 468941b commit 1645bb7

File tree

6 files changed

+99
-49
lines changed

6 files changed

+99
-49
lines changed

crates/lib/src/qpu/translation.rs

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ use std::{collections::HashMap, time::Duration};
66
use qcs_api_client_grpc::{
77
models::controller::EncryptedControllerJob,
88
services::translation::{
9-
translate_quil_to_encrypted_controller_job_request::NumShots,
10-
TranslateQuilToEncryptedControllerJobRequest, TranslationOptions,
9+
self, translate_quil_to_encrypted_controller_job_request::NumShots,
10+
translation_options::TranslationBackend, TranslateQuilToEncryptedControllerJobRequest,
11+
TranslationOptions as ApiTranslationOptions,
1112
},
1213
};
1314
use qcs_api_client_openapi::{
@@ -33,25 +34,30 @@ pub struct EncryptedTranslationResult {
3334
}
3435

3536
/// Translate a program, returning an encrypted and translated program.
36-
pub async fn translate(
37+
pub async fn translate<TO>(
3738
quantum_processor_id: &str,
3839
quil_program: &str,
3940
num_shots: u32,
4041
client: &Qcs,
41-
translation_options: Option<TranslationOptions>,
42-
) -> Result<EncryptedTranslationResult, GrpcClientError> {
42+
translation_options: Option<TO>,
43+
) -> Result<EncryptedTranslationResult, GrpcClientError>
44+
where
45+
TO: Into<ApiTranslationOptions>,
46+
{
4347
#[cfg(feature = "tracing")]
4448
tracing::debug!(
4549
%num_shots,
4650
"translating program for {}",
4751
quantum_processor_id,
4852
);
4953

54+
let options = translation_options.map(Into::into);
55+
5056
let request = TranslateQuilToEncryptedControllerJobRequest {
5157
quantum_processor_id: quantum_processor_id.to_owned(),
5258
num_shots: Some(NumShots::NumShotsValue(num_shots)),
5359
quil_program: quil_program.to_owned(),
54-
options: translation_options,
60+
options,
5561
};
5662

5763
let response = client
@@ -104,3 +110,33 @@ pub async fn get_quilt_calibrations(
104110
})
105111
.await?
106112
}
113+
114+
/// Options available for Quil program translation.
115+
///
116+
/// This wraps [`ApiTranslationOptions`] in order to improve the user experience,
117+
/// because the structs auto-generated by `prost` can be clumsy to use directly.
118+
#[allow(clippy::module_name_repetitions)]
119+
#[derive(Clone, Debug, Default)]
120+
pub struct TranslationOptions {
121+
inner: ApiTranslationOptions,
122+
}
123+
124+
impl TranslationOptions {
125+
/// Use the first-generation translation backend available on QCS since 2018.
126+
pub fn use_backend_v1(&mut self) {
127+
self.inner.translation_backend =
128+
Some(TranslationBackend::V1(translation::BackendV1Options {}));
129+
}
130+
131+
/// Use the second-generation translation backend available on QCS since 2023
132+
pub fn use_backend_v2(&mut self) {
133+
self.inner.translation_backend =
134+
Some(TranslationBackend::V2(translation::BackendV2Options {}));
135+
}
136+
}
137+
138+
impl From<TranslationOptions> for ApiTranslationOptions {
139+
fn from(options: TranslationOptions) -> Self {
140+
options.inner
141+
}
142+
}

crates/python/qcs_sdk/qpu/translation.pyi

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ def translate(
8888
num_shots: int,
8989
quantum_processor_id: str,
9090
client: Optional[QCSClient] = None,
91+
translation_options: Optional[TranslationOptions] = None,
9192
) -> TranslationResult:
9293
"""
9394
Translates a native Quil program into an executable program.
@@ -109,6 +110,7 @@ async def translate_async(
109110
num_shots: int,
110111
quantum_processor_id: str,
111112
client: Optional[QCSClient] = None,
113+
translation_options: Optional[TranslationOptions] = None,
112114
) -> TranslationResult:
113115
"""
114116
Translates a native Quil program into an executable program.
@@ -125,3 +127,19 @@ async def translate_async(
125127
:raises TranslationError: If the `native_quil` program could not be translated.
126128
"""
127129
...
130+
131+
@final
132+
class TranslationOptions:
133+
"""
134+
Options for translating via the QCS API.
135+
"""
136+
137+
def use_backend_v1(self) -> None:
138+
"""
139+
Use the v1 backend for translation, available on QCS since 2018.
140+
"""
141+
142+
def use_backend_v2(self) -> None:
143+
"""
144+
Use the v2 backend for translation, available on QCS since 2023.
145+
"""

crates/python/src/executable.rs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,18 @@ use std::{num::NonZeroU16, sync::Arc};
22

33
use pyo3::{pyclass, FromPyObject};
44
use qcs::{Error, Executable, ExecutionData, JobHandle, Service};
5-
use qcs_api_client_grpc::services::translation::TranslationOptions;
65
use rigetti_pyo3::{
76
impl_as_mut_for_wrapper, py_wrap_error, py_wrap_simple_enum, py_wrap_type,
87
pyo3::{exceptions::PyRuntimeError, pymethods, types::PyDict, Py, PyAny, PyResult, Python},
9-
wrap_error, PyTryFrom, PyWrapper, ToPython, ToPythonError,
8+
wrap_error, PyWrapper, ToPython, ToPythonError,
109
};
1110
use tokio::sync::Mutex;
1211

1312
use crate::{
1413
compiler::quilc::PyCompilerOpts,
1514
execution_data::PyExecutionData,
16-
grpc::models::translation::PyTranslationOptions,
1715
py_sync::{py_async, py_sync},
16+
qpu::translation::PyTranslationOptions,
1817
};
1918

2019
wrap_error!(RustExecutionError(Error));
@@ -151,8 +150,7 @@ impl PyExecutable {
151150
endpoint_id: Option<String>,
152151
translation_options: Option<PyTranslationOptions>,
153152
) -> PyResult<PyExecutionData> {
154-
let translation_options =
155-
Option::<TranslationOptions>::py_try_from(py, &translation_options)?;
153+
let translation_options = translation_options.map(|opts| opts.as_inner().clone().into());
156154
match endpoint_id {
157155
Some(endpoint_id) => py_sync!(
158156
py,
@@ -184,8 +182,7 @@ impl PyExecutable {
184182
endpoint_id: Option<String>,
185183
translation_options: Option<PyTranslationOptions>,
186184
) -> PyResult<&PyAny> {
187-
let translation_options =
188-
Option::<TranslationOptions>::py_try_from(py, &translation_options)?;
185+
let translation_options = translation_options.map(|opts| opts.as_inner().clone().into());
189186
match endpoint_id {
190187
Some(endpoint_id) => py_async!(
191188
py,
@@ -217,8 +214,7 @@ impl PyExecutable {
217214
endpoint_id: Option<String>,
218215
translation_options: Option<PyTranslationOptions>,
219216
) -> PyResult<PyJobHandle> {
220-
let translation_options =
221-
Option::<TranslationOptions>::py_try_from(py, &translation_options)?;
217+
let translation_options = translation_options.map(|opts| opts.as_inner().clone().into());
222218
match endpoint_id {
223219
Some(endpoint_id) => py_sync!(
224220
py,
@@ -250,8 +246,7 @@ impl PyExecutable {
250246
endpoint_id: Option<String>,
251247
translation_options: Option<PyTranslationOptions>,
252248
) -> PyResult<&PyAny> {
253-
let translation_options =
254-
Option::<TranslationOptions>::py_try_from(py, &translation_options)?;
249+
let translation_options = translation_options.map(|opts| opts.as_inner().clone().into());
255250
match endpoint_id {
256251
Some(endpoint_id) => {
257252
py_async!(

crates/python/src/grpc/models/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
pub mod controller;
2-
pub mod translation;

crates/python/src/grpc/models/translation.rs

Lines changed: 0 additions & 28 deletions
This file was deleted.

crates/python/src/qpu/translation.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
//! Translating programs.
22
use std::{collections::HashMap, time::Duration};
33

4-
use pyo3::{exceptions::PyRuntimeError, pyclass, pyfunction, types::PyString, Py, PyResult};
4+
use pyo3::{
5+
exceptions::PyRuntimeError, pyclass, pyfunction, pymethods, types::PyString, Py, PyResult,
6+
};
57
use qcs::client::GrpcClientError;
8+
use qcs::qpu::translation::TranslationOptions;
69
use qcs_api_client_openapi::models::GetQuiltCalibrationsResponse;
710
use rigetti_pyo3::{
8-
create_init_submodule, py_wrap_data_struct, py_wrap_error, wrap_error, PyWrapper, ToPythonError,
11+
create_init_submodule, py_wrap_data_struct, py_wrap_error, wrap_error, ToPythonError,
912
};
1013

11-
use crate::{grpc::models::translation::PyTranslationOptions, py_sync::py_function_sync_async};
14+
use crate::py_sync::py_function_sync_async;
1215

1316
use crate::client::PyQcsClient;
1417

1518
create_init_submodule! {
1619
classes: [
1720
PyQuiltCalibrations,
21+
PyTranslationOptions,
1822
PyTranslationResult
1923
],
2024
errors: [
@@ -83,6 +87,32 @@ py_wrap_error!(
8387
PyRuntimeError
8488
);
8589

90+
#[derive(Clone, Default)]
91+
#[pyclass(name = "TranslationOptions")]
92+
pub struct PyTranslationOptions(TranslationOptions);
93+
94+
impl PyTranslationOptions {
95+
pub fn as_inner(&self) -> &TranslationOptions {
96+
&self.0
97+
}
98+
}
99+
100+
#[pymethods]
101+
impl PyTranslationOptions {
102+
#[new]
103+
fn __new__() -> PyResult<Self> {
104+
Ok(Self(Default::default()))
105+
}
106+
107+
fn use_backend_v1(&mut self) {
108+
self.0.use_backend_v1()
109+
}
110+
111+
fn use_backend_v2(&mut self) {
112+
self.0.use_backend_v2()
113+
}
114+
}
115+
86116
/// The result of a call to [`translate`] which provides information about the
87117
/// translated program.
88118
#[pyclass]

0 commit comments

Comments
 (0)