#![expect(clippy::unsafe_derive_deserialize)]
use enum_as_inner::EnumAsInner;
use num::complex::Complex64;
use quil_rs::program::SyntaxError;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use std::convert::TryFrom;
use std::num::TryFromIntError;
use std::str::FromStr;
use std::time::Duration;
use itertools::Itertools;
use ndarray::prelude::*;
use crate::{
qpu::{QpuResultData, ReadoutValues},
qvm::QvmResultData,
RegisterData,
};
#[cfg(feature = "stubs")]
use pyo3_stub_gen::derive::gen_stub_pyclass;
#[derive(Debug, Clone, PartialEq, EnumAsInner, Deserialize, Serialize)]
#[cfg_attr(feature = "python", derive(pyo3::FromPyObject, pyo3::IntoPyObject))]
pub enum ResultData {
Qvm(QvmResultData),
Qpu(QpuResultData),
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[cfg_attr(not(feature = "python"), optipy::strip_pyo3)]
#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
#[cfg_attr(feature = "python", pyo3::pyclass(module = "qcs_sdk", eq))]
pub struct ExecutionData {
#[pyo3(get)]
pub result_data: ResultData,
#[pyo3(get)]
pub duration: Option<Duration>,
}
#[derive(Clone, Debug, EnumAsInner, PartialEq, Serialize, Deserialize)]
pub enum RegisterMatrix {
Integer(Array2<i64>),
Real(Array2<f64>),
Complex(Array2<Complex64>),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[repr(transparent)]
#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
#[cfg_attr(feature = "python", pyo3::pyclass(module = "qcs_sdk", mapping))]
pub struct RegisterMap(pub HashMap<String, RegisterMatrix>);
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
pub enum RegisterMatrixConversionError {
#[error("The data for register {register} does fit into a rectangular matrix")]
InvalidShape { register: String },
#[error("The mapping of {memory_reference} to {alias} had no readout values")]
UnmappedAlias {
memory_reference: String,
alias: String,
},
#[error("Missing readout values for {register}[{index}]")]
MissingRow { register: String, index: usize },
#[error("{0}")]
MemoryReferenceParseError(MemoryReferenceParseError),
}
impl ResultData {
pub fn to_register_map(&self) -> Result<RegisterMap, RegisterMatrixConversionError> {
match self {
ResultData::Qvm(data) => RegisterMap::from_qvm_result_data(data),
ResultData::Qpu(data) => RegisterMap::from_qpu_result_data(data),
}
}
}
impl RegisterMap {
#[must_use]
pub fn get_register_matrix(&self, register_name: &str) -> Option<&RegisterMatrix> {
self.0.get(register_name)
}
#[must_use]
pub fn from_hashmap(map: HashMap<String, RegisterMatrix>) -> Self {
Self(map)
}
pub(crate) fn from_qvm_result_data(
result_data: &QvmResultData,
) -> Result<Self, RegisterMatrixConversionError> {
#[cfg(feature = "tracing")]
tracing::trace!("converting QVM result data to RegisterMap");
Ok(Self(
result_data
.memory
.iter()
.map(|(name, register)| {
let register_matrix = match register {
RegisterData::I8(data) => Array::from_shape_vec(
(data.len(), data.first().map_or(0, Vec::len)),
data.iter().flatten().copied().map(i64::from).collect(),
)
.map(RegisterMatrix::Integer),
RegisterData::I16(data) => Array::from_shape_vec(
(data.len(), data.first().map_or(0, Vec::len)),
data.iter().flatten().copied().map(i64::from).collect(),
)
.map(RegisterMatrix::Integer),
RegisterData::F64(data) => Array::from_shape_vec(
(data.len(), data.first().map_or(0, Vec::len)),
data.iter().flatten().copied().collect(),
)
.map(RegisterMatrix::Real),
RegisterData::Complex32(data) => Array::from_shape_vec(
(data.len(), data.first().map_or(0, Vec::len)),
data.iter()
.flatten()
.copied()
.map(|c| Complex64::new(c.re.into(), c.im.into()))
.collect(),
)
.map(RegisterMatrix::Complex),
}
.map_err(|_| {
RegisterMatrixConversionError::InvalidShape {
register: name.clone(),
}
})?;
Ok((name.clone(), register_matrix))
})
.collect::<Result<HashMap<String, RegisterMatrix>, RegisterMatrixConversionError>>(
)?,
))
}
pub(crate) fn from_qpu_result_data(
qpu_result_data: &QpuResultData,
) -> Result<Self, RegisterMatrixConversionError> {
#[cfg(feature = "tracing")]
tracing::trace!("converting QPU result data to RegisterMap");
let register_map = qpu_result_data
.mappings
.iter()
.map(|(memory_reference, alias)| {
Ok((
parse_readout_register(memory_reference).map_err(|e| {
RegisterMatrixConversionError::MemoryReferenceParseError(e)
})?,
qpu_result_data.readout_values.get(alias).ok_or_else(||
RegisterMatrixConversionError::UnmappedAlias {
memory_reference: memory_reference.clone(),
alias: alias.clone(),
},
)?,
))
})
.collect::<Result<
BTreeMap<MemoryReference, &ReadoutValues>,
RegisterMatrixConversionError,
>>()?;
let mut reference_windows = register_map.keys().tuple_windows().peekable();
if let Some((reference_a, _)) = reference_windows.peek() {
if reference_a.index != 0 {
return Err(RegisterMatrixConversionError::MissingRow {
register: reference_a.name.clone(),
index: 0,
});
}
}
for (reference_a, reference_b) in register_map.keys().tuple_windows() {
if reference_a.name == reference_b.name {
if reference_a.index + 1 != reference_b.index {
return Err(RegisterMatrixConversionError::MissingRow {
register: reference_a.name.clone(),
index: reference_a.index + 1,
});
}
} else if reference_b.index != 0 {
return Err(RegisterMatrixConversionError::MissingRow {
register: reference_b.name.clone(),
index: 0,
});
}
}
Ok(Self(
register_map.into_iter().try_rfold(
HashMap::with_capacity(qpu_result_data.readout_values.len()),
|mut register_map, (reference, values)| {
let matrix =
register_map
.entry(reference.name.clone())
.or_insert(match values {
ReadoutValues::Integer(v) => RegisterMatrix::Integer(
Array2::zeros((v.len(), reference.index + 1)),
),
ReadoutValues::Complex(v) => RegisterMatrix::Complex(
Array2::zeros((v.len(), reference.index + 1)),
),
ReadoutValues::Real(v) => RegisterMatrix::Real(Array2::zeros((
v.len(),
reference.index + 1,
))),
});
match (matrix, values) {
(RegisterMatrix::Integer(m), ReadoutValues::Integer(v))
if m.nrows() == v.len() =>
{
m.column_mut(reference.index)
.assign(&Array::from_vec(v.clone()));
}
(RegisterMatrix::Real(m), ReadoutValues::Real(v))
if m.nrows() == v.len() =>
{
m.column_mut(reference.index)
.assign(&Array::from_vec(v.clone()));
}
(RegisterMatrix::Complex(m), ReadoutValues::Complex(v))
if m.nrows() == v.len() =>
{
m.column_mut(reference.index)
.assign(&Array::from_vec(v.clone()));
}
_ => {
return Err(RegisterMatrixConversionError::InvalidShape {
register: reference.name,
})
}
}
Ok(register_map)
},
)?,
))
}
}
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord)]
struct MemoryReference {
name: String,
index: usize,
}
#[derive(Debug, thiserror::Error)]
pub enum MemoryReferenceParseError {
#[error("{0}")]
InvalidFormat(#[from] SyntaxError<quil_rs::instruction::MemoryReference>),
#[error("Could not convert index from u64 to a usize: {0}")]
OversizedIndex(#[from] TryFromIntError),
}
fn parse_readout_register(
register_name: &str,
) -> Result<MemoryReference, MemoryReferenceParseError> {
let reference = quil_rs::instruction::MemoryReference::from_str(register_name)?;
Ok(MemoryReference {
name: reference.name,
index: usize::try_from(reference.index)?,
})
}
#[cfg(test)]
mod describe_register_map {
use maplit::hashmap;
use ndarray::prelude::*;
use crate::qpu::result_data::MemoryValues;
use crate::qpu::QpuResultData;
use crate::qvm::QvmResultData;
use super::{RegisterData, RegisterMap};
use qcs_api_client_grpc::models::controller::readout_values::Values;
use qcs_api_client_grpc::models::controller::{
self, BinaryDataValue, DataValue as ControllerMemoryValue, IntegerDataValue,
IntegerReadoutValues, ReadoutValues, RealDataValue,
};
fn dummy_readout_values(v: Vec<i32>) -> ReadoutValues {
ReadoutValues {
values: Some(Values::IntegerValues(IntegerReadoutValues { values: v })),
}
}
fn dummy_memory_values(
binary: Vec<u8>,
int: Vec<i64>,
real: Vec<f64>,
) -> (
ControllerMemoryValue,
ControllerMemoryValue,
ControllerMemoryValue,
) {
(
ControllerMemoryValue {
value: Some(controller::data_value::Value::Binary(BinaryDataValue {
data: binary,
})),
},
ControllerMemoryValue {
value: Some(controller::data_value::Value::Integer(IntegerDataValue {
data: int,
})),
},
ControllerMemoryValue {
value: Some(controller::data_value::Value::Real(RealDataValue {
data: real,
})),
},
)
}
#[test]
fn it_converts_rectangular_qpu_result_data_to_register_map() {
let readout_mappings = hashmap! {
String::from("ro[1]") => String::from("qB"),
String::from("ro[2]") => String::from("qC"),
String::from("ro[0]") => String::from("qA"),
String::from("bar[0]") => String::from("qE"),
String::from("bar[1]") => String::from("qD")
};
let readout_values = hashmap! {
String::from("qA") => dummy_readout_values(vec![1, 2]),
String::from("qB") => dummy_readout_values(vec![3, 4]),
String::from("qC") => dummy_readout_values(vec![5, 6]),
String::from("qD") => dummy_readout_values(vec![0, 1]),
String::from("qE") => dummy_readout_values(vec![2, 3]),
};
let (binary_values, integer_values, real_values) =
dummy_memory_values(vec![0, 1, 2], vec![3, 4, 5], vec![6.0, 7.0, 8.0]);
let memory_values = hashmap! {
String::from("binary") => binary_values,
String::from("int") => integer_values,
String::from("real") => real_values,
};
let qpu_result_data = QpuResultData::from_controller_mappings_and_values(
&readout_mappings,
&readout_values,
&memory_values,
);
let register_map = RegisterMap::from_qpu_result_data(&qpu_result_data)
.expect("Should be able to create RegisterMap from rectangular QPU readout");
let ro = register_map
.get_register_matrix("ro")
.expect("RegisterMap should have ro")
.as_integer()
.expect("Should be a register of integer values");
let expected_ro = arr2(&[[1, 3, 5], [2, 4, 6]]);
assert_eq!(ro, expected_ro);
let bar = register_map
.get_register_matrix("bar")
.expect("RegisterMap should have bar")
.as_integer()
.expect("Shout be a register of integer values");
let expected_bar = arr2(&[[2, 0], [3, 1]]);
assert_eq!(bar, expected_bar);
let expected_memory_values = hashmap! {
String::from("binary") => MemoryValues::Binary(vec![0, 1, 2]),
String::from("int") => MemoryValues::Integer(vec![3, 4, 5]),
String::from("real") => MemoryValues::Real(vec![6.0, 7.0, 8.0]),
};
assert_eq!(qpu_result_data.memory_values, expected_memory_values);
}
#[test]
fn it_fails_to_convert_missing_readout_indices_to_register_map() {
let readout_mappings = hashmap! {
String::from("ro[1]") => String::from("qA"),
String::from("ro[2]") => String::from("qB"),
String::from("ro[0]") => String::from("qC"),
String::from("bar[3]") => String::from("qD"),
String::from("bar[5]") => String::from("qE"),
};
let readout_values = hashmap! {
String::from("qA") => dummy_readout_values(vec![11]),
String::from("qB") => dummy_readout_values(vec![22]),
String::from("qD") => dummy_readout_values(vec![33]),
String::from("qE") => dummy_readout_values(vec![44]),
};
let qpu_result_data = QpuResultData::from_controller_mappings_and_values(
&readout_mappings,
&readout_values,
&hashmap! {},
);
RegisterMap::from_qpu_result_data(&qpu_result_data)
.expect_err("Should not be able to create RegisterMap from QPU readout with missing indices for a register");
}
#[test]
fn it_fails_to_convert_jagged_qpu_result_data_to_register_map() {
let readout_mappings = hashmap! {
String::from("ro[1]") => String::from("qB"),
String::from("ro[2]") => String::from("qC"),
String::from("ro[0]") => String::from("qA"),
};
let readout_values = hashmap! {
String::from("qA") => dummy_readout_values(vec![1, 2]),
String::from("qB") => dummy_readout_values(vec![2]),
String::from("qC") => dummy_readout_values(vec![3]),
};
let (binary_values, integer_values, real_values) =
dummy_memory_values(vec![0, 1, 2], vec![3, 4, 5], vec![6.0, 7.0, 8.0]);
let memory_values = hashmap! {
String::from("binary") => binary_values,
String::from("int") => integer_values,
String::from("real") => real_values,
};
let qpu_result_data = QpuResultData::from_controller_mappings_and_values(
&readout_mappings,
&readout_values,
&memory_values,
);
RegisterMap::from_qpu_result_data(&qpu_result_data)
.expect_err("Should not be able to create RegisterMap from QPU readout with jagged data for a register");
}
#[test]
fn it_converts_from_qvm_result_data() {
let qvm_result_data = QvmResultData::from_memory_map(hashmap! {
String::from("ro") => RegisterData::I8(vec![vec![1, 0, 1]]),
});
let register_map = RegisterMap::from_qvm_result_data(&qvm_result_data)
.expect("Should be able to create RegisterMap from QvmResultData");
let ro = register_map
.get_register_matrix("ro")
.expect("RegisterMap should have ro")
.as_integer()
.expect("Should be a register of integers");
let expected = arr2(&[[1, 0, 1]]);
assert_eq!(ro, expected);
}
}