diff --git a/crates/lib/src/qpu/translation.rs b/crates/lib/src/qpu/translation.rs index 6e30de029..d494a55e8 100644 --- a/crates/lib/src/qpu/translation.rs +++ b/crates/lib/src/qpu/translation.rs @@ -6,8 +6,9 @@ use std::{collections::HashMap, time::Duration}; use qcs_api_client_grpc::{ models::controller::EncryptedControllerJob, services::translation::{ - translate_quil_to_encrypted_controller_job_request::NumShots, - TranslateQuilToEncryptedControllerJobRequest, TranslationOptions, + self, translate_quil_to_encrypted_controller_job_request::NumShots, + translation_options::TranslationBackend, TranslateQuilToEncryptedControllerJobRequest, + TranslationOptions as ApiTranslationOptions, }, }; use qcs_api_client_openapi::{ @@ -33,13 +34,16 @@ pub struct EncryptedTranslationResult { } /// Translate a program, returning an encrypted and translated program. -pub async fn translate( +pub async fn translate( quantum_processor_id: &str, quil_program: &str, num_shots: u32, client: &Qcs, - translation_options: Option, -) -> Result { + translation_options: Option, +) -> Result +where + TO: Into, +{ #[cfg(feature = "tracing")] tracing::debug!( %num_shots, @@ -47,11 +51,13 @@ pub async fn translate( quantum_processor_id, ); + let options = translation_options.map(Into::into); + let request = TranslateQuilToEncryptedControllerJobRequest { quantum_processor_id: quantum_processor_id.to_owned(), num_shots: Some(NumShots::NumShotsValue(num_shots)), quil_program: quil_program.to_owned(), - options: translation_options, + options, }; let response = client @@ -104,3 +110,33 @@ pub async fn get_quilt_calibrations( }) .await? } + +/// Options available for Quil program translation. +/// +/// This wraps [`ApiTranslationOptions`] in order to improve the user experience, +/// because the structs auto-generated by `prost` can be clumsy to use directly. +#[allow(clippy::module_name_repetitions)] +#[derive(Clone, Debug, Default)] +pub struct TranslationOptions { + inner: ApiTranslationOptions, +} + +impl TranslationOptions { + /// Use the first-generation translation backend available on QCS since 2018. + pub fn use_backend_v1(&mut self) { + self.inner.translation_backend = + Some(TranslationBackend::V1(translation::BackendV1Options {})); + } + + /// Use the second-generation translation backend available on QCS since 2023 + pub fn use_backend_v2(&mut self) { + self.inner.translation_backend = + Some(TranslationBackend::V2(translation::BackendV2Options {})); + } +} + +impl From for ApiTranslationOptions { + fn from(options: TranslationOptions) -> Self { + options.inner + } +} diff --git a/crates/python/qcs_sdk/qpu/translation.pyi b/crates/python/qcs_sdk/qpu/translation.pyi index ea8fd13da..f3be090f7 100644 --- a/crates/python/qcs_sdk/qpu/translation.pyi +++ b/crates/python/qcs_sdk/qpu/translation.pyi @@ -88,6 +88,7 @@ def translate( num_shots: int, quantum_processor_id: str, client: Optional[QCSClient] = None, + translation_options: Optional[TranslationOptions] = None, ) -> TranslationResult: """ Translates a native Quil program into an executable program. @@ -109,6 +110,7 @@ async def translate_async( num_shots: int, quantum_processor_id: str, client: Optional[QCSClient] = None, + translation_options: Optional[TranslationOptions] = None, ) -> TranslationResult: """ Translates a native Quil program into an executable program. @@ -125,3 +127,19 @@ async def translate_async( :raises TranslationError: If the `native_quil` program could not be translated. """ ... + +@final +class TranslationOptions: + """ + Options for translating via the QCS API. + """ + + def use_backend_v1(self) -> None: + """ + Use the v1 backend for translation, available on QCS since 2018. + """ + + def use_backend_v2(self) -> None: + """ + Use the v2 backend for translation, available on QCS since 2023. + """ diff --git a/crates/python/src/executable.rs b/crates/python/src/executable.rs index 5610ff11f..20696e034 100644 --- a/crates/python/src/executable.rs +++ b/crates/python/src/executable.rs @@ -2,19 +2,18 @@ use std::{num::NonZeroU16, sync::Arc}; use pyo3::{pyclass, FromPyObject}; use qcs::{Error, Executable, ExecutionData, JobHandle, Service}; -use qcs_api_client_grpc::services::translation::TranslationOptions; use rigetti_pyo3::{ impl_as_mut_for_wrapper, py_wrap_error, py_wrap_simple_enum, py_wrap_type, pyo3::{exceptions::PyRuntimeError, pymethods, types::PyDict, Py, PyAny, PyResult, Python}, - wrap_error, PyTryFrom, PyWrapper, ToPython, ToPythonError, + wrap_error, PyWrapper, ToPython, ToPythonError, }; use tokio::sync::Mutex; use crate::{ compiler::quilc::PyCompilerOpts, execution_data::PyExecutionData, - grpc::models::translation::PyTranslationOptions, py_sync::{py_async, py_sync}, + qpu::translation::PyTranslationOptions, }; wrap_error!(RustExecutionError(Error)); @@ -151,8 +150,7 @@ impl PyExecutable { endpoint_id: Option, translation_options: Option, ) -> PyResult { - let translation_options = - Option::::py_try_from(py, &translation_options)?; + let translation_options = translation_options.map(|opts| opts.as_inner().clone().into()); match endpoint_id { Some(endpoint_id) => py_sync!( py, @@ -184,8 +182,7 @@ impl PyExecutable { endpoint_id: Option, translation_options: Option, ) -> PyResult<&PyAny> { - let translation_options = - Option::::py_try_from(py, &translation_options)?; + let translation_options = translation_options.map(|opts| opts.as_inner().clone().into()); match endpoint_id { Some(endpoint_id) => py_async!( py, @@ -217,8 +214,7 @@ impl PyExecutable { endpoint_id: Option, translation_options: Option, ) -> PyResult { - let translation_options = - Option::::py_try_from(py, &translation_options)?; + let translation_options = translation_options.map(|opts| opts.as_inner().clone().into()); match endpoint_id { Some(endpoint_id) => py_sync!( py, @@ -250,8 +246,7 @@ impl PyExecutable { endpoint_id: Option, translation_options: Option, ) -> PyResult<&PyAny> { - let translation_options = - Option::::py_try_from(py, &translation_options)?; + let translation_options = translation_options.map(|opts| opts.as_inner().clone().into()); match endpoint_id { Some(endpoint_id) => { py_async!( diff --git a/crates/python/src/grpc/models/mod.rs b/crates/python/src/grpc/models/mod.rs index 8eb70c293..cb9e0ac5c 100644 --- a/crates/python/src/grpc/models/mod.rs +++ b/crates/python/src/grpc/models/mod.rs @@ -1,2 +1 @@ pub mod controller; -pub mod translation; diff --git a/crates/python/src/grpc/models/translation.rs b/crates/python/src/grpc/models/translation.rs deleted file mode 100644 index 1c999272c..000000000 --- a/crates/python/src/grpc/models/translation.rs +++ /dev/null @@ -1,28 +0,0 @@ -use qcs_api_client_grpc::services::translation::{ - translation_options::TranslationBackend, BackendV1Options, BackendV2Options, TranslationOptions, -}; -use rigetti_pyo3::{py_wrap_data_struct, py_wrap_type, py_wrap_union_enum}; - -py_wrap_type! { - #[derive(Default)] - PyBackendV1Options(BackendV1Options) as "BackendV1Options"; -} - -py_wrap_type! { - #[derive(Default)] - PyBackendV2Options(BackendV2Options) as "BackendV2Options"; -} - -py_wrap_union_enum! { - PyTranslationBackend(TranslationBackend) as "TranslationBackend" { - v1: V1 => PyBackendV1Options, - v2: V2 => PyBackendV2Options - } -} - -py_wrap_data_struct! { - #[derive(Default)] - PyTranslationOptions(TranslationOptions) as "TranslationOptions" { - translation_backend: Option => Option - } -} diff --git a/crates/python/src/qpu/translation.rs b/crates/python/src/qpu/translation.rs index 3a6425034..4c13cc292 100644 --- a/crates/python/src/qpu/translation.rs +++ b/crates/python/src/qpu/translation.rs @@ -1,20 +1,24 @@ //! Translating programs. use std::{collections::HashMap, time::Duration}; -use pyo3::{exceptions::PyRuntimeError, pyclass, pyfunction, types::PyString, Py, PyResult}; +use pyo3::{ + exceptions::PyRuntimeError, pyclass, pyfunction, pymethods, types::PyString, Py, PyResult, +}; use qcs::client::GrpcClientError; +use qcs::qpu::translation::TranslationOptions; use qcs_api_client_openapi::models::GetQuiltCalibrationsResponse; use rigetti_pyo3::{ - create_init_submodule, py_wrap_data_struct, py_wrap_error, wrap_error, PyWrapper, ToPythonError, + create_init_submodule, py_wrap_data_struct, py_wrap_error, wrap_error, ToPythonError, }; -use crate::{grpc::models::translation::PyTranslationOptions, py_sync::py_function_sync_async}; +use crate::py_sync::py_function_sync_async; use crate::client::PyQcsClient; create_init_submodule! { classes: [ PyQuiltCalibrations, + PyTranslationOptions, PyTranslationResult ], errors: [ @@ -83,6 +87,32 @@ py_wrap_error!( PyRuntimeError ); +#[derive(Clone, Default)] +#[pyclass(name = "TranslationOptions")] +pub struct PyTranslationOptions(TranslationOptions); + +impl PyTranslationOptions { + pub fn as_inner(&self) -> &TranslationOptions { + &self.0 + } +} + +#[pymethods] +impl PyTranslationOptions { + #[new] + fn __new__() -> PyResult { + Ok(Self(Default::default())) + } + + fn use_backend_v1(&mut self) { + self.0.use_backend_v1() + } + + fn use_backend_v2(&mut self) { + self.0.use_backend_v2() + } +} + /// The result of a call to [`translate`] which provides information about the /// translated program. #[pyclass]