use std::collections::HashMap;
use crate::measurements::{
CheatedPauliZProductWrapper, CheatedWrapper, ClassicalRegisterWrapper, PauliZProductWrapper,
};
use crate::{QoqoError, QOQO_VERSION};
use pyo3::exceptions::{PyRuntimeError, PyTypeError, PyValueError};
use pyo3::prelude::*;
use pyo3::types::PyByteArray;
use roqoqo::measurements::Measure;
#[cfg(feature = "json_schema")]
use roqoqo::operations::SupportedVersion;
use roqoqo::QuantumProgram;
use roqoqo::ROQOQO_VERSION;
#[pyclass(from_py_object, name = "QuantumProgram", module = "qoqo")]
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct QuantumProgramWrapper {
pub internal: QuantumProgram,
}
impl QuantumProgramWrapper {
pub fn from_pyany(input: &Bound<PyAny>) -> PyResult<QuantumProgram> {
if let Ok(try_downcast) = input.extract::<QuantumProgramWrapper>() {
Ok(try_downcast.internal)
} else {
let get_bytes = input.call_method0("to_bincode").map_err(|_| {
PyTypeError::new_err("Python object cannot be converted to qoqo QuantumProgram: Cast to binary representation failed".to_string())
})?;
let bytes = get_bytes.extract::<Vec<u8>>().map_err(|_| {
PyTypeError::new_err("Python object cannot be converted to qoqo QuantumProgram: Cast to binary representation failed".to_string())
})?;
bincode::serde::decode_from_slice(&bytes[..], bincode::config::legacy()).map_err(|err| {
PyTypeError::new_err(format!(
"Python object cannot be converted to qoqo QuantumProgram: Deserialization failed: {err}"
))
}).map(|(deserialized, _)| deserialized)
}
}
}
#[pymethods]
impl QuantumProgramWrapper {
#[new]
pub fn new(measurement: &Bound<PyAny>, input_parameter_names: Vec<String>) -> PyResult<Self> {
if let Ok(try_downcast) = PauliZProductWrapper::from_pyany(measurement) {
return Ok(Self {
internal: QuantumProgram::PauliZProduct {
measurement: try_downcast,
input_parameter_names,
},
});
}
if let Ok(try_downcast) = CheatedPauliZProductWrapper::from_pyany(measurement) {
return Ok(Self {
internal: QuantumProgram::CheatedPauliZProduct {
measurement: try_downcast,
input_parameter_names,
},
});
}
if let Ok(try_downcast) = CheatedWrapper::from_pyany(measurement) {
return Ok(Self {
internal: QuantumProgram::Cheated {
measurement: try_downcast,
input_parameter_names,
},
});
}
if let Ok(try_downcast) = ClassicalRegisterWrapper::from_pyany(measurement) {
return Ok(Self {
internal: QuantumProgram::ClassicalRegister {
measurement: try_downcast,
input_parameter_names,
},
});
}
Err(PyTypeError::new_err(
"measurement is not of type Measurement. Are you using different versions of roqoqo?",
))
}
pub fn measurement<'py>(&'py self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
match self.internal.clone() {
QuantumProgram::PauliZProduct {
measurement,
input_parameter_names: _,
} => {
let pyref: Py<PauliZProductWrapper> = Py::new(
py,
PauliZProductWrapper {
internal: measurement.clone(),
},
)
.unwrap();
pyref
.into_pyobject(py)
.map(|bound| bound.as_any().to_owned())
}
QuantumProgram::CheatedPauliZProduct {
measurement,
input_parameter_names: _,
} => {
let pyref: Py<CheatedPauliZProductWrapper> = Py::new(
py,
CheatedPauliZProductWrapper {
internal: measurement.clone(),
},
)
.unwrap();
pyref
.into_pyobject(py)
.map(|bound| bound.as_any().to_owned())
}
QuantumProgram::Cheated {
measurement,
input_parameter_names: _,
} => {
let pyref: Py<CheatedWrapper> = Py::new(
py,
CheatedWrapper {
internal: measurement.clone(),
},
)
.unwrap();
pyref
.into_pyobject(py)
.map(|bound| bound.as_any().to_owned())
}
QuantumProgram::ClassicalRegister {
measurement,
input_parameter_names: _,
} => {
let pyref: Py<ClassicalRegisterWrapper> = Py::new(
py,
ClassicalRegisterWrapper {
internal: measurement.clone(),
},
)
.unwrap();
pyref
.into_pyobject(py)
.map(|bound| bound.as_any().to_owned())
}
_ => panic!("Unknown type of QuantumProgram"),
}
.map_err(|_| PyValueError::new_err("Couldn't convert the measurement to a pyobject."))
}
pub fn input_parameter_names(&self) -> Vec<String> {
match self.internal.clone() {
QuantumProgram::PauliZProduct {
measurement: _,
input_parameter_names,
} => input_parameter_names,
QuantumProgram::CheatedPauliZProduct {
measurement: _,
input_parameter_names,
} => input_parameter_names,
QuantumProgram::Cheated {
measurement: _,
input_parameter_names,
} => input_parameter_names,
QuantumProgram::ClassicalRegister {
measurement: _,
input_parameter_names,
} => input_parameter_names,
_ => panic!("Unknown type of QuantumProgram"),
}
}
#[pyo3(signature = (backend, parameters=None))]
pub fn run(&self, backend: &Bound<PyAny>, parameters: Option<Vec<f64>>) -> PyResult<Py<PyAny>> {
let parameters = parameters.unwrap_or_default();
match &self.internal{
QuantumProgram::PauliZProduct{measurement, input_parameter_names } => {
if parameters.len() != input_parameter_names.len() { return Err(PyValueError::new_err( format!("Wrong number of parameters {} parameters expected {} parameters given", input_parameter_names.len(), parameters.len())))};
let substituted_parameters: HashMap<String, f64> = input_parameter_names.iter().zip(parameters.iter()).map(|(key, value)| (key.clone(), *value)).collect();
let substituted_measurement = measurement.substitute_parameters(
substituted_parameters
).map_err(|err| PyRuntimeError::new_err(format!("Applying parameters failed {err:?}")))?;
backend.call_method1("run_measurement", (PauliZProductWrapper{internal: substituted_measurement}, )).map(|bound| bound.into())
}
QuantumProgram::CheatedPauliZProduct{measurement, input_parameter_names } => {
if parameters.len() != input_parameter_names.len() { return Err(PyValueError::new_err( format!("Wrong number of parameters {} parameters expected {} parameters given", input_parameter_names.len(), parameters.len())))};
let substituted_parameters: HashMap<String, f64> = input_parameter_names.iter().zip(parameters.iter()).map(|(key, value)| (key.clone(), *value)).collect();
let substituted_measurement = measurement.substitute_parameters(
substituted_parameters
).map_err(|err| PyRuntimeError::new_err(format!("Applying parameters failed {err:?}")))?;
backend.call_method1("run_measurement", (CheatedPauliZProductWrapper{internal: substituted_measurement}, )).map(|bound| bound.into())
}
QuantumProgram::Cheated{measurement, input_parameter_names } => {
if parameters.len() != input_parameter_names.len() { return Err(PyValueError::new_err( format!("Wrong number of parameters {} parameters expected {} parameters given", input_parameter_names.len(), parameters.len())))};
let substituted_parameters: HashMap<String, f64> = input_parameter_names.iter().zip(parameters.iter()).map(|(key, value)| (key.clone(), *value)).collect();
let substituted_measurement = measurement.substitute_parameters(
substituted_parameters
).map_err(|err| PyRuntimeError::new_err(format!("Applying parameters failed {err:?}")))?;
backend.call_method1("run_measurement", (CheatedWrapper{internal: substituted_measurement}, )).map(|bound| bound.into())
}
_ => Err(PyTypeError::new_err("A quantum programm returning classical registeres cannot be executed by `run` use `run_registers` instead".to_string()))
}
}
#[pyo3(signature = (backend, parameters=None))]
pub fn run_registers(
&self,
backend: &Bound<PyAny>,
parameters: Option<Vec<f64>>,
) -> PyResult<Py<PyAny>> {
let parameters = parameters.unwrap_or_default();
match &self.internal{
QuantumProgram::ClassicalRegister{measurement, input_parameter_names } => {
if parameters.len() != input_parameter_names.len() { return Err(PyValueError::new_err( format!("Wrong number of parameters {} parameters expected {} parameters given", input_parameter_names.len(), parameters.len())))};
let substituted_parameters: HashMap<String, f64> = input_parameter_names.iter().zip(parameters.iter()).map(|(key, value)| (key.clone(), *value)).collect();
let substituted_measurement = measurement.substitute_parameters(
substituted_parameters
).map_err(|err| PyRuntimeError::new_err(format!("Applying parameters failed {err:?}")))?;
backend.call_method1("run_measurement_registers", (ClassicalRegisterWrapper{internal: substituted_measurement}, )).map(|bound| bound.into())
},
_ => Err(PyTypeError::new_err("A quantum programm returning expectation values cannot be executed by `run_registers` use `run` instead".to_string()))
}
}
pub fn __copy__(&self) -> QuantumProgramWrapper {
self.clone()
}
pub fn __deepcopy__(&self, _memodict: &Bound<PyAny>) -> QuantumProgramWrapper {
self.clone()
}
fn _qoqo_versions(&self) -> (String, String) {
let mut rsplit = ROQOQO_VERSION.split('.').take(2);
let mut qsplit = QOQO_VERSION.split('.').take(2);
let rver = format!(
"{}.{}",
rsplit.next().expect("ROQOQO_VERSION badly formatted"),
rsplit.next().expect("ROQOQO_VERSION badly formatted")
);
let qver = format!(
"{}.{}",
qsplit.next().expect("QOQO_VERSION badly formatted"),
qsplit.next().expect("QOQO_VERSION badly formatted")
);
(rver, qver)
}
pub fn to_bincode(&self) -> PyResult<Py<PyByteArray>> {
let serialized =
bincode::serde::encode_to_vec(&self.internal, bincode::config::legacy())
.map_err(|_| PyValueError::new_err("Cannot serialize QuantumProgram to bytes"))?;
let b: Py<PyByteArray> = Python::attach(|py| -> Py<PyByteArray> {
PyByteArray::new(py, &serialized[..]).into()
});
Ok(b)
}
#[staticmethod]
pub fn from_bincode(input: &Bound<PyAny>) -> PyResult<Self> {
let bytes = input
.extract::<Vec<u8>>()
.map_err(|_| PyTypeError::new_err("Input cannot be converted to byte array"))?;
Ok(Self {
internal: bincode::serde::decode_from_slice(&bytes[..], bincode::config::legacy())
.map_err(|_| {
PyValueError::new_err("Input cannot be deserialized to QuantumProgram")
})?
.0,
})
}
fn to_json(&self) -> PyResult<String> {
let serialized = serde_json::to_string(&self.internal)
.map_err(|_| PyValueError::new_err("Cannot serialize QuantumProgram to json"))?;
Ok(serialized)
}
#[staticmethod]
fn from_json(input: &str) -> PyResult<Self> {
Ok(Self {
internal: serde_json::from_str(input).map_err(|_| {
PyValueError::new_err("Input cannot be deserialized to QuantumProgram")
})?,
})
}
#[cfg(feature = "json_schema")]
#[staticmethod]
pub fn json_schema() -> String {
let schema = schemars::schema_for!(QuantumProgram);
serde_json::to_string_pretty(&schema).expect("Unexpected failure to serialize schema")
}
#[cfg(feature = "json_schema")]
#[staticmethod]
pub fn current_version() -> String {
ROQOQO_VERSION.to_string()
}
#[cfg(feature = "json_schema")]
pub fn min_supported_version(&self) -> String {
let min_version: (u32, u32, u32) =
QuantumProgram::minimum_supported_roqoqo_version(&self.internal);
format!("{}.{}.{}", min_version.0, min_version.1, min_version.2)
}
fn __richcmp__(
&self,
other: &Bound<PyAny>,
op: pyo3::class::basic::CompareOp,
) -> PyResult<bool> {
let other = convert_into_quantum_program(other);
match op {
pyo3::class::basic::CompareOp::Eq => match other {
Ok(qp) => Ok(self.internal == qp),
_ => Ok(false),
},
pyo3::class::basic::CompareOp::Ne => match other {
Ok(qp) => Ok(self.internal != qp),
_ => Ok(true),
},
_ => Err(pyo3::exceptions::PyNotImplementedError::new_err(
"Other comparison not implemented",
)),
}
}
}
pub fn convert_into_quantum_program(input: &Bound<PyAny>) -> Result<QuantumProgram, QoqoError> {
if let Ok(try_downcast) = input.extract::<QuantumProgramWrapper>() {
return Ok(try_downcast.internal);
}
let get_bytes = input
.call_method0("to_bincode")
.map_err(|_| QoqoError::CannotExtractObject)?;
let bytes = get_bytes
.extract::<Vec<u8>>()
.map_err(|_| QoqoError::CannotExtractObject)?;
bincode::serde::decode_from_slice(&bytes[..], bincode::config::legacy())
.map_err(|_| QoqoError::CannotExtractObject)
.map(|(deserialized, _)| deserialized)
}