use std::{collections::HashMap, str::FromStr, time::Duration};
use num::Complex;
use qcs_api_client_grpc::{
models::controller::{readout_values, ControllerJobExecutionResult},
services::controller::{
get_controller_job_results_request::Target, GetControllerJobResultsRequest,
},
};
use qcs_api_client_openapi::apis::{quantum_processors_api, Error as OpenAPIError};
use quil_rs::expression::Expression;
use quil_rs::{program::ProgramError, Program};
use serde::Serialize;
use tokio::time::error::Elapsed;
use crate::qpu::{
self,
client::{GrpcClientError, Qcs},
quilc::{self, CompilerOpts, TargetDevice},
rewrite_arithmetic::{self, Substitutions},
runner,
translation::{self, EncryptedTranslationResult},
IsaError,
};
static DEFAULT_HTTP_API_TIMEOUT: Duration = Duration::from_secs(10);
pub fn compile(
quil: &str,
target: TargetDevice,
client: &Qcs,
options: CompilerOpts,
) -> Result<String, Box<dyn std::error::Error + Send + Sync + 'static>> {
quilc::compile_program(quil, target, client, options)
.map_err(Into::into)
.map(|p| p.to_string(true))
}
pub fn get_quilc_version(
client: &Qcs,
) -> Result<String, Box<dyn std::error::Error + Send + Sync + 'static>> {
quilc::get_version_info(client).map_err(Into::into)
}
#[derive(thiserror::Error, Debug)]
pub enum RewriteArithmeticError {
#[error("Could not parse program: {0}")]
Program(#[from] ProgramError<Program>),
#[error("Could not rewrite arithmetic: {0}")]
Rewrite(#[from] rewrite_arithmetic::Error),
}
#[derive(Clone, Debug, Serialize)]
pub struct RewriteArithmeticResult {
pub program: String,
pub recalculation_table: Vec<String>,
}
pub fn rewrite_arithmetic(
native_quil: Program,
) -> Result<RewriteArithmeticResult, rewrite_arithmetic::Error> {
let (program, subs) = qpu::rewrite_arithmetic::rewrite_arithmetic(native_quil)?;
let recalculation_table = subs.into_iter().map(|expr| expr.to_string()).collect();
Ok(RewriteArithmeticResult {
program: program.to_string(true),
recalculation_table,
})
}
#[derive(Debug, thiserror::Error)]
pub enum TranslationError {
#[error("Could not translate quil: {0}")]
Translate(#[from] GrpcClientError),
#[error("Could not serialize translation result: {0}")]
Serialize(#[from] serde_json::Error),
}
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize)]
pub struct TranslationResult {
pub program: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub ro_sources: Option<HashMap<String, String>>,
}
pub async fn translate(
native_quil: &str,
shots: u16,
quantum_processor_id: &str,
client: &Qcs,
) -> Result<TranslationResult, TranslationError> {
let EncryptedTranslationResult { job, readout_map } =
translation::translate(quantum_processor_id, native_quil, shots.into(), client).await?;
let program = serde_json::to_string(&job)?;
Ok(TranslationResult {
ro_sources: Some(readout_map),
program,
})
}
#[allow(clippy::implicit_hasher)]
pub async fn submit(
program: &str,
patch_values: HashMap<String, Vec<f64>>,
quantum_processor_id: &str,
client: &Qcs,
) -> Result<String, SubmitError> {
let patch_values = patch_values
.into_iter()
.map(|(k, v)| (k.into_boxed_str(), v))
.collect();
let job = serde_json::from_str(program)?;
let job_id = runner::submit(quantum_processor_id, job, &patch_values, client).await?;
Ok(job_id.0)
}
#[derive(Debug, thiserror::Error)]
pub enum SubmitError {
#[error("Failed to fetch ISA: {0}")]
IsaError(#[from] IsaError),
#[error("Failed a gRPC call: {0}")]
GrpcError(#[from] GrpcClientError),
#[error("Failed quilc compilation: {0}")]
QuilcError(#[from] quilc::Error),
#[error("Failed to deserialize job: {0}")]
DeserializeError(#[from] serde_json::Error),
}
#[allow(clippy::implicit_hasher)]
pub fn build_patch_values(
recalculation_table: &[String],
memory: &HashMap<Box<str>, Vec<f64>>,
) -> Result<HashMap<Box<str>, Vec<f64>>, String> {
let substitutions: Substitutions = recalculation_table
.iter()
.map(|expr| Expression::from_str(expr))
.collect::<Result<_, _>>()
.map_err(|e| format!("Unable to interpret recalculation table: {e:?}"))?;
rewrite_arithmetic::get_substitutions(&substitutions, memory)
}
#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(untagged)] pub enum Register {
F64(Vec<f64>),
I16(Vec<i16>),
I32(Vec<i32>),
Complex64(Vec<Complex<f32>>),
I8(Vec<i8>),
}
impl From<qpu::runner::Register> for Register {
fn from(register: qpu::runner::Register) -> Self {
match register {
runner::Register::F64(f) => Register::F64(f),
runner::Register::I16(i) => Register::I16(i),
runner::Register::Complex32(c) => {
Register::Complex64(c.iter().map(|c| Complex::<f32>::new(c.re, c.im)).collect())
}
runner::Register::I8(i) => Register::I8(i),
}
}
}
#[derive(Clone, Debug, Serialize)]
pub struct ExecutionResult {
shape: Vec<usize>,
data: Register,
dtype: String,
}
impl From<readout_values::Values> for ExecutionResult {
fn from(values: readout_values::Values) -> Self {
match values {
readout_values::Values::ComplexValues(c) => Self {
shape: vec![c.values.len(), 1],
dtype: "complex".into(),
data: Register::Complex64(
c.values
.iter()
.map(|c| {
Complex::<f32>::new(c.real.unwrap_or(0.0), c.imaginary.unwrap_or(0.0))
})
.collect(),
),
},
readout_values::Values::IntegerValues(i) => Self {
shape: vec![i.values.len(), 1],
dtype: "integer".into(),
data: Register::I32(i.values),
},
}
}
}
#[derive(Clone, Debug, Serialize)]
pub struct ExecutionResults {
buffers: HashMap<String, ExecutionResult>,
execution_duration_microseconds: Option<u64>,
}
impl From<ControllerJobExecutionResult> for ExecutionResults {
fn from(result: ControllerJobExecutionResult) -> Self {
let buffers = result
.readout_values
.into_iter()
.filter_map(|(key, value)| {
value
.values
.map(ExecutionResult::from)
.map(|result| (key, result))
})
.collect();
Self {
buffers,
execution_duration_microseconds: result.execution_duration_microseconds,
}
}
}
pub async fn retrieve_results(
job_id: &str,
quantum_processor_id: &str,
client: &Qcs,
) -> Result<ExecutionResults, GrpcClientError> {
let request = GetControllerJobResultsRequest {
job_execution_id: Some(job_id.into()),
target: Some(Target::QuantumProcessorId(quantum_processor_id.into())),
};
client
.get_controller_client(quantum_processor_id)
.await?
.get_controller_job_results(request)
.await?
.into_inner()
.result
.map(ExecutionResults::from)
.ok_or_else(|| GrpcClientError::ResponseEmpty("Controller Job Execution Results".into()))
}
#[derive(Debug, thiserror::Error)]
pub enum ListQuantumProcessorsError {
#[error("Failed to list processors via API: {0}")]
ApiError(#[from] OpenAPIError<quantum_processors_api::ListQuantumProcessorsError>),
#[error("API pagination did not finish before timeout: {0:?}")]
TimeoutError(#[from] Elapsed),
}
pub async fn list_quantum_processors(
client: &Qcs,
timeout: Option<Duration>,
) -> Result<Vec<String>, ListQuantumProcessorsError> {
let timeout = timeout.unwrap_or(DEFAULT_HTTP_API_TIMEOUT);
tokio::time::timeout(timeout, async move {
let mut quantum_processors = vec![];
let mut page_token = None;
loop {
let result = quantum_processors_api::list_quantum_processors(
&client.get_openapi_client(),
Some(100),
page_token.as_deref(),
)
.await?;
let mut data = result
.quantum_processors
.into_iter()
.map(|qpu| qpu.id)
.collect::<Vec<_>>();
quantum_processors.append(&mut data);
page_token = result.next_page_token;
if page_token.is_none() {
break;
}
}
Ok(quantum_processors)
})
.await?
}