Skip to content

Commit

Permalink
feat!: TranslationOptions builder-like struct with Python bindings (#308
Browse files Browse the repository at this point in the history
)

* 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]>
  • Loading branch information
kalzoo and jselig-rigetti authored Jun 6, 2023
1 parent 468941b commit 1645bb7
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 49 deletions.
48 changes: 42 additions & 6 deletions crates/lib/src/qpu/translation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -33,25 +34,30 @@ pub struct EncryptedTranslationResult {
}

/// Translate a program, returning an encrypted and translated program.
pub async fn translate(
pub async fn translate<TO>(
quantum_processor_id: &str,
quil_program: &str,
num_shots: u32,
client: &Qcs,
translation_options: Option<TranslationOptions>,
) -> Result<EncryptedTranslationResult, GrpcClientError> {
translation_options: Option<TO>,
) -> Result<EncryptedTranslationResult, GrpcClientError>
where
TO: Into<ApiTranslationOptions>,
{
#[cfg(feature = "tracing")]
tracing::debug!(
%num_shots,
"translating program for {}",
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
Expand Down Expand Up @@ -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<TranslationOptions> for ApiTranslationOptions {
fn from(options: TranslationOptions) -> Self {
options.inner
}
}
18 changes: 18 additions & 0 deletions crates/python/qcs_sdk/qpu/translation.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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.
"""
17 changes: 6 additions & 11 deletions crates/python/src/executable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -151,8 +150,7 @@ impl PyExecutable {
endpoint_id: Option<String>,
translation_options: Option<PyTranslationOptions>,
) -> PyResult<PyExecutionData> {
let translation_options =
Option::<TranslationOptions>::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,
Expand Down Expand Up @@ -184,8 +182,7 @@ impl PyExecutable {
endpoint_id: Option<String>,
translation_options: Option<PyTranslationOptions>,
) -> PyResult<&PyAny> {
let translation_options =
Option::<TranslationOptions>::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,
Expand Down Expand Up @@ -217,8 +214,7 @@ impl PyExecutable {
endpoint_id: Option<String>,
translation_options: Option<PyTranslationOptions>,
) -> PyResult<PyJobHandle> {
let translation_options =
Option::<TranslationOptions>::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,
Expand Down Expand Up @@ -250,8 +246,7 @@ impl PyExecutable {
endpoint_id: Option<String>,
translation_options: Option<PyTranslationOptions>,
) -> PyResult<&PyAny> {
let translation_options =
Option::<TranslationOptions>::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!(
Expand Down
1 change: 0 additions & 1 deletion crates/python/src/grpc/models/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
pub mod controller;
pub mod translation;
28 changes: 0 additions & 28 deletions crates/python/src/grpc/models/translation.rs

This file was deleted.

36 changes: 33 additions & 3 deletions crates/python/src/qpu/translation.rs
Original file line number Diff line number Diff line change
@@ -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: [
Expand Down Expand Up @@ -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<Self> {
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]
Expand Down

0 comments on commit 1645bb7

Please sign in to comment.