use std::{collections::HashMap, time::Duration};
#[cfg(feature = "stubs")]
use pyo3_stub_gen::derive::gen_stub_pyclass;
use qcs_api_client_grpc::{
models::controller::EncryptedControllerJob,
services::translation::{
translate_quil_to_encrypted_controller_job_request::NumShots,
translation_options::{self, Riverlane, TranslationBackend},
BackendV1Options, BackendV2Options, GetQuantumProcessorQuilCalibrationProgramRequest,
TranslateQuilToEncryptedControllerJobRequest, TranslationOptions as ApiTranslationOptions,
},
};
use tokio::time::error::Elapsed;
#[cfg(feature = "tracing")]
use tracing::instrument;
use crate::client::{GrpcClientError, Qcs, DEFAULT_HTTP_API_TIMEOUT};
#[cfg(feature = "python")]
pub mod python;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Grpc(#[from] GrpcClientError),
#[error("Client configured timeout exceeded")]
ClientTimeout(#[from] Elapsed),
}
#[derive(Debug)]
pub struct EncryptedTranslationResult {
pub job: EncryptedControllerJob,
pub readout_map: HashMap<String, String>,
}
#[cfg_attr(
feature = "tracing",
instrument(skip(quil_program, client, translation_options))
)]
pub async fn translate<TO>(
quantum_processor_id: &str,
quil_program: &str,
num_shots: u32,
client: &Qcs,
translation_options: Option<TO>,
) -> Result<EncryptedTranslationResult, Error>
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,
};
let response = client
.get_translation_client()
.map_err(GrpcClientError::from)?
.translate_quil_to_encrypted_controller_job(request)
.await
.map_err(GrpcClientError::from)?
.into_inner();
Ok(EncryptedTranslationResult {
job: response
.job
.ok_or_else(|| GrpcClientError::ResponseEmpty("Encrypted Job".into()))?,
readout_map: response
.metadata
.ok_or_else(|| GrpcClientError::ResponseEmpty("Job Metadata".into()))?
.readout_mappings,
})
}
pub async fn get_quilt_calibrations(
quantum_processor_id: String,
client: &Qcs,
timeout: Option<Duration>,
) -> Result<String, Error> {
#[cfg(feature = "tracing")]
tracing::debug!("getting Quil-T calibrations for {}", quantum_processor_id);
let timeout = timeout.unwrap_or(DEFAULT_HTTP_API_TIMEOUT);
let mut translation_client = client
.get_translation_client()
.map_err(GrpcClientError::from)?;
tokio::time::timeout(timeout, async move {
Ok(translation_client
.get_quantum_processor_quil_calibration_program(
GetQuantumProcessorQuilCalibrationProgramRequest {
quantum_processor_id,
},
)
.await
.map_err(GrpcClientError::from)?
.into_inner()
.quil_calibration_program)
})
.await?
}
#[allow(clippy::module_name_repetitions)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, thiserror::Error)]
pub enum TranslationBackendMismatch {
#[error("tried to set an option for Translation V1 using a different backend")]
V1,
#[error("tried to set an option for Translation V2 using a different backend")]
V2,
}
#[allow(clippy::module_name_repetitions)]
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
#[cfg_attr(feature = "python", pyo3::pyclass(module = "qcs_sdk.qpu.translation"))]
pub struct TranslationOptions {
inner: ApiTranslationOptions,
}
impl TranslationOptions {
#[must_use]
pub fn backend(&self) -> Option<&TranslationBackend> {
self.inner.translation_backend.as_ref()
}
#[must_use]
pub fn backend_mut(&mut self) -> Option<&mut TranslationBackend> {
self.inner.translation_backend.as_mut()
}
pub fn with_backend_v1(&mut self) -> &mut BackendV1Options {
let backend = &mut self.inner.translation_backend;
if let Some(TranslationBackend::V1(options)) = backend {
return options;
}
*backend = Some(TranslationBackend::V1(BackendV1Options::default()));
let Some(TranslationBackend::V1(options)) = backend.as_mut() else {
unreachable!("backend was just set to V1")
};
options
}
pub fn with_backend_v2(&mut self) -> &mut BackendV2Options {
let backend = &mut self.inner.translation_backend;
if let Some(TranslationBackend::V2(options)) = backend {
return options;
}
*backend = Some(TranslationBackend::V2(BackendV2Options::default()));
let Some(TranslationBackend::V2(options)) = backend.as_mut() else {
unreachable!("backend was just set to V2")
};
options
}
#[allow(dead_code)]
fn ensure_backend_v1(&mut self) -> Result<&mut BackendV1Options, TranslationBackendMismatch> {
if matches!(self.backend(), None | Some(TranslationBackend::V1(_))) {
Ok(self.with_backend_v1())
} else {
Err(TranslationBackendMismatch::V1)
}
}
fn ensure_backend_v2(&mut self) -> Result<&mut BackendV2Options, TranslationBackendMismatch> {
if matches!(self.backend(), None | Some(TranslationBackend::V2(_))) {
Ok(self.with_backend_v2())
} else {
Err(TranslationBackendMismatch::V2)
}
}
pub fn v2_prepend_default_calibrations(
&mut self,
prepend: bool,
) -> Result<&mut Self, TranslationBackendMismatch> {
self.ensure_backend_v2()?.prepend_default_calibrations = Some(prepend);
Ok(self)
}
pub fn v2_passive_reset_delay_seconds(
&mut self,
delay: f64,
) -> Result<&mut Self, TranslationBackendMismatch> {
self.ensure_backend_v2()?.passive_reset_delay_seconds = Some(delay);
Ok(self)
}
pub fn v2_allow_unchecked_pointer_arithmetic(
&mut self,
allow: bool,
) -> Result<&mut Self, TranslationBackendMismatch> {
self.ensure_backend_v2()?.allow_unchecked_pointer_arithmetic = Some(allow);
Ok(self)
}
pub fn v2_allow_frame_redefinition(
&mut self,
allow: bool,
) -> Result<&mut Self, TranslationBackendMismatch> {
self.ensure_backend_v2()?.allow_frame_redefinition = Some(allow);
Ok(self)
}
pub fn q_ctrl(&mut self, q_ctrl: translation_options::QCtrl) -> &mut Self {
self.inner.q_ctrl = Some(q_ctrl);
self
}
pub fn riverlane(&mut self, riverlane: Riverlane) -> &mut Self {
self.inner.riverlane = Some(riverlane);
self
}
}
impl From<TranslationOptions> for ApiTranslationOptions {
fn from(options: TranslationOptions) -> Self {
options.inner
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn creating_new_options_does_not_fail() {
let mut options = TranslationOptions::default();
options.v2_allow_frame_redefinition(true).unwrap();
}
}