From 0181c1b8e85ec0836aac73a97d9475c31dd8fc77 Mon Sep 17 00:00:00 2001 From: Marquess Valdez Date: Tue, 28 Nov 2023 10:53:57 -0800 Subject: [PATCH] feat: Add memory_values to QPUResultData (#393) * feat: Add memory_values to QPUResultData * implement python half * clippy * make memory accessible on py execution result * add missing import * dont wrap memory_values in Option * update type hints * clippy * update py tests * fix test * update optional type on executionresult --- crates/lib/src/execution_data.rs | 78 +++++++++++++++++-- crates/lib/src/qpu/execution.rs | 1 + crates/lib/src/qpu/result_data.rs | 50 +++++++++++- crates/python/qcs_sdk/qpu/__init__.pyi | 58 +++++++++++++- crates/python/qcs_sdk/qpu/api.pyi | 9 ++- crates/python/src/execution_data.rs | 2 +- crates/python/src/qpu/api.rs | 73 ++++++++++++++--- crates/python/src/qpu/mod.rs | 5 +- crates/python/src/qpu/result_data.rs | 59 ++++++++++++-- .../execution_data/test_execution_data.py | 11 ++- crates/python/tests/qpu/test_qpu.py | 8 +- 11 files changed, 318 insertions(+), 36 deletions(-) diff --git a/crates/lib/src/execution_data.rs b/crates/lib/src/execution_data.rs index aae083d17..ce7d1713f 100644 --- a/crates/lib/src/execution_data.rs +++ b/crates/lib/src/execution_data.rs @@ -352,12 +352,16 @@ 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::{IntegerReadoutValues, ReadoutValues}; + use qcs_api_client_grpc::models::controller::{ + self, BinaryDataValue, DataValue as ControllerMemoryValue, IntegerDataValue, + IntegerReadoutValues, ReadoutValues, RealDataValue, + }; fn dummy_readout_values(v: Vec) -> ReadoutValues { ReadoutValues { @@ -365,6 +369,34 @@ mod describe_register_map { } } + fn dummy_memory_values( + binary: Vec, + int: Vec, + real: Vec, + ) -> ( + 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! { @@ -383,8 +415,19 @@ mod describe_register_map { String::from("qE") => dummy_readout_values(vec![2, 3]), }; - let qpu_result_data = - QpuResultData::from_controller_mappings_and_values(&readout_mappings, &readout_values); + 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"); @@ -408,6 +451,13 @@ mod describe_register_map { 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] @@ -427,8 +477,11 @@ mod describe_register_map { String::from("qE") => dummy_readout_values(vec![44]), }; - let qpu_result_data = - QpuResultData::from_controller_mappings_and_values(&readout_mappings, &readout_values); + 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"); @@ -448,8 +501,19 @@ mod describe_register_map { String::from("qC") => dummy_readout_values(vec![3]), }; - let qpu_result_data = - QpuResultData::from_controller_mappings_and_values(&readout_mappings, &readout_values); + 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"); diff --git a/crates/lib/src/qpu/execution.rs b/crates/lib/src/qpu/execution.rs index 9ccd7db07..886dcbe22 100644 --- a/crates/lib/src/qpu/execution.rs +++ b/crates/lib/src/qpu/execution.rs @@ -283,6 +283,7 @@ impl<'a> Execution<'a> { result_data: ResultData::Qpu(QpuResultData::from_controller_mappings_and_values( job_handle.readout_map(), &response.readout_values, + &response.memory_values, )), duration: Some(Duration::from_micros( response.execution_duration_microseconds, diff --git a/crates/lib/src/qpu/result_data.rs b/crates/lib/src/qpu/result_data.rs index d6dd0b216..b1806af3c 100644 --- a/crates/lib/src/qpu/result_data.rs +++ b/crates/lib/src/qpu/result_data.rs @@ -6,7 +6,8 @@ use quil_rs::instruction::MemoryReference; use std::collections::HashMap; use qcs_api_client_grpc::models::controller::{ - readout_values as controller_readout_values, ReadoutValues as ControllerReadoutValues, + self, data_value as controller_memory_value, readout_values as controller_readout_values, + DataValue as ControllerMemoryValues, ReadoutValues as ControllerReadoutValues, }; /// A row of readout values from the QPU. Each row contains all the values emitted to a @@ -21,12 +22,25 @@ pub enum ReadoutValues { Complex(Vec), } +/// A row of data containing the contents of each memory region at the end of a job. +#[derive(Debug, Clone, EnumAsInner, PartialEq)] +pub enum MemoryValues { + /// Values that correspond to a memory region declared with the BIT or OCTET data type. + Binary(Vec), + /// Values that correspond to a memory region declared with the INTEGER data type. + Integer(Vec), + /// Values that correspond to a memory region declared with the REAL data type. + Real(Vec), +} + /// This struct encapsulates data returned from the QPU after executing a job. #[allow(clippy::module_name_repetitions)] #[derive(Debug, Clone, PartialEq)] pub struct QpuResultData { pub(crate) mappings: HashMap, pub(crate) readout_values: HashMap, + /// The final contents of each memory region, keyed on region name. + pub(crate) memory_values: HashMap, } impl QpuResultData { @@ -36,21 +50,24 @@ impl QpuResultData { pub fn from_mappings_and_values( mappings: HashMap, readout_values: HashMap, + memory_values: HashMap, ) -> Self { Self { mappings, readout_values, + memory_values, } } /// Creates a new [`QpuResultData`] using data returned from controller service. pub(crate) fn from_controller_mappings_and_values( mappings: &HashMap, - values: &HashMap, + readout_values: &HashMap, + memory_values: &HashMap, ) -> Self { Self { mappings: mappings.clone(), - readout_values: values + readout_values: readout_values .iter() .map(|(key, readout_values)| { ( @@ -74,6 +91,27 @@ impl QpuResultData { ) }) .collect(), + memory_values: memory_values + .iter() + .filter_map(|(key, memory_values)| { + memory_values.value.as_ref().map(|value| { + ( + key.clone(), + match value { + controller_memory_value::Value::Binary( + controller::BinaryDataValue { data: v }, + ) => MemoryValues::Binary(v.clone()), + controller_memory_value::Value::Integer( + controller::IntegerDataValue { data: v }, + ) => MemoryValues::Integer(v.clone()), + controller_memory_value::Value::Real( + controller::RealDataValue { data: v }, + ) => MemoryValues::Real(v.clone()), + }, + ) + }) + }) + .collect(), } } @@ -100,4 +138,10 @@ impl QpuResultData { pub fn readout_values(&self) -> &HashMap { &self.readout_values } + + /// Get mapping of a memory region (ie. "ro") to the final contents of that memory region. + #[must_use] + pub fn memory_values(&self) -> &HashMap { + &self.memory_values + } } diff --git a/crates/python/qcs_sdk/qpu/__init__.pyi b/crates/python/qcs_sdk/qpu/__init__.pyi index ae9de3f21..837e42bef 100644 --- a/crates/python/qcs_sdk/qpu/__init__.pyi +++ b/crates/python/qcs_sdk/qpu/__init__.pyi @@ -1,4 +1,4 @@ -from typing import Dict, List, Mapping, Sequence, Optional, Union, final, Any +from typing import Dict, List, Mapping, Sequence, Optional, Union, final from qcs_sdk.client import QCSClient @@ -51,6 +51,46 @@ class ReadoutValues: @staticmethod def from_complex(inner: Sequence[complex]) -> "ReadoutValues": ... +@final +class MemoryValues: + """ + A row of data containing the contents of a memory region at the end of a job. There + is a variant for each possible type the memory region could be. + + Variants: + - ``binary``: Corresponds to the Quil `BIT` and `OCTET` types. + - ``integer``: Corresponds to the Quil `INTEGER` types. + - ``real``: Corresponds to the Quil `REAL` type. + + Methods (each per variant): + - ``is_*``: if the underlying values are that type. + - ``as_*``: if the underlying values are that type, then those values, otherwise ``None``. + - ``to_*``: the underlying values as that type, raises ``ValueError`` if they are not. + - ``from_*``: wrap underlying values as this enum type. + """ + def __new__(cls, values: Union[List[int], List[float]]): + """Construct a new ReadoutValues from a list of values.""" + ... + def inner(self) -> Union[List[int], List[float]]: + """Return the inner list of readout values.""" + ... + def is_integer(self) -> bool: ... + def is_binary(self) -> bool: ... + def is_real(self) -> bool: ... + def as_integer(self) -> Optional[List[int]]: ... + def as_binary(self) -> Optional[List[int]]: ... + def as_real(self) -> Optional[List[float]]: ... + def to_integer(self) -> List[int]: ... + def to_binary(self) -> List[int]: ... + def to_real(self) -> List[float]: ... + @staticmethod + def from_integer(inner: Sequence[int]) -> "ReadoutValues": ... + @staticmethod + def from_binary(inner: Sequence[int]) -> "ReadoutValues": ... + @staticmethod + def from_real(inner: Sequence[float]) -> "ReadoutValues": ... + + @final class QPUResultData: """ @@ -62,7 +102,7 @@ class QPUResultData: across all shots. """ - def __new__(cls, mappings: Mapping[str, str], readout_values: Mapping[str, ReadoutValues]): ... + def __new__(cls, mappings: Mapping[str, str], readout_values: Mapping[str, ReadoutValues], memory_values: Mapping[str, MemoryValues]): ... @property def mappings(self) -> Dict[str, str]: """ @@ -75,13 +115,19 @@ class QPUResultData: Get the mappings of a readout values identifier (ie. "q0") to a set of ``ReadoutValues`` """ ... + @property + def memory_values(self) -> Dict[str, MemoryValues]: + """ + Get mapping of a memory region (ie. "ro") to the final contents of that memory region. + """ + ... def to_raw_readout_data( self, ) -> RawQPUReadoutData: """ Get a copy of this result data flattened into a ``RawQPUReadoutData``. This reduces the contained data down to primitive types, offering a simpler structure at the - cost of the type safety provided by ``ReadoutValues``. + cost of the type safety provided by ``ReadoutValues`` and ``MemoryValues``. """ ... @@ -105,6 +151,12 @@ class RawQPUReadoutData: Get the mappings of a readout values identifier (ie. "q0") to a list of those readout values """ ... + @property + def memory_values(self) -> Dict[str, Optional[Union[List[int], List[float]]]]: + """ + Get mapping of a memory region (ie. "ro") to the final contents of that memory region. + """ + ... class ListQuantumProcessorsError(RuntimeError): """A request to list available Quantum Processors failed.""" diff --git a/crates/python/qcs_sdk/qpu/api.pyi b/crates/python/qcs_sdk/qpu/api.pyi index 6794f0152..886f5a19b 100644 --- a/crates/python/qcs_sdk/qpu/api.pyi +++ b/crates/python/qcs_sdk/qpu/api.pyi @@ -1,7 +1,7 @@ -from enum import Enum -from typing import Callable, Dict, List, Sequence, Mapping, Optional, Union, final +from typing import Dict, List, Sequence, Mapping, Optional, Union, final from qcs_sdk.client import QCSClient +from qcs_sdk.qpu import MemoryValues class SubmissionError(RuntimeError): """There was a problem submitting the program to QCS for execution.""" @@ -77,6 +77,11 @@ class ExecutionResults: """ ... @property + def memory(self) -> Dict[str, MemoryValues]: + """ + The final state of memory for parameters that were read from and written to during the exectuion of the program. + """ + @property def execution_duration_microseconds(self) -> Optional[int]: """The time spent executing the program.""" ... diff --git a/crates/python/src/execution_data.rs b/crates/python/src/execution_data.rs index 15c5fd79b..3277ec411 100644 --- a/crates/python/src/execution_data.rs +++ b/crates/python/src/execution_data.rs @@ -21,7 +21,7 @@ py_wrap_union_enum! { qvm: Qvm => PyQvmResultData } } -impl_repr!(PyQpuResultData); +impl_repr!(PyResultData); wrap_error!(RustRegisterMatrixConversionError( qcs::RegisterMatrixConversionError diff --git a/crates/python/src/qpu/api.rs b/crates/python/src/qpu/api.rs index 7fe85954d..f98c407aa 100644 --- a/crates/python/src/qpu/api.rs +++ b/crates/python/src/qpu/api.rs @@ -9,10 +9,12 @@ use pyo3::{ pyclass::CompareOp, pyfunction, pymethods, types::{PyComplex, PyInt}, - IntoPy, Py, PyObject, PyResult, Python, + IntoPy, Py, PyObject, PyResult, Python, ToPyObject, }; use qcs::qpu::api::{ConnectionStrategy, ExecutionOptions, ExecutionOptionsBuilder}; -use qcs_api_client_grpc::models::controller::{readout_values, ControllerJobExecutionResult}; +use qcs_api_client_grpc::models::controller::{ + data_value, readout_values, ControllerJobExecutionResult, +}; use rigetti_pyo3::{ create_init_submodule, impl_as_mut_for_wrapper, impl_repr, num_complex, py_wrap_error, py_wrap_type, py_wrap_union_enum, wrap_error, PyWrapper, ToPythonError, @@ -22,6 +24,8 @@ use crate::py_sync::py_function_sync_async; use crate::client::PyQcsClient; +use super::result_data::PyMemoryValues; + create_init_submodule! { classes: [ PyRegister, @@ -187,6 +191,8 @@ pub struct ExecutionResults { /// QPU execution duration. #[pyo3(get)] pub execution_duration_microseconds: Option, + #[pyo3(get)] + pub memory: HashMap, } #[pymethods] @@ -194,27 +200,74 @@ impl ExecutionResults { #[new] fn new( buffers: HashMap, + memory: HashMap, execution_duration_microseconds: Option, ) -> Self { Self { buffers, execution_duration_microseconds, + memory, } } } -impl From for ExecutionResults { - fn from(value: ControllerJobExecutionResult) -> Self { - let buffers = value +impl ExecutionResults { + fn from_controller_job_execution_result( + py: Python<'_>, + result: ControllerJobExecutionResult, + ) -> PyResult { + let buffers = result .readout_values .into_iter() .filter_map(|(key, val)| val.values.map(|values| (key, values.into()))) .collect(); - Self { + let memory = result.memory_values.iter().try_fold( + HashMap::with_capacity(result.memory_values.len()), + |mut acc, (key, value)| -> PyResult> { + if let Some(value) = &value.value { + acc.insert( + key.clone(), + match value { + data_value::Value::Binary(value) => PyMemoryValues::from_binary( + py, + value + .data + .iter() + .map(|v| v.to_object(py).extract(py)) + .collect::>>()?, + )?, + data_value::Value::Integer(value) => PyMemoryValues::from_integer( + py, + value + .data + .iter() + .map(|v| v.to_object(py).extract(py)) + .collect::>>()?, + )?, + data_value::Value::Real(value) => PyMemoryValues::from_real( + py, + value + .data + .iter() + .map(|v| v.to_object(py).extract(py)) + .collect::>>()?, + )?, + }, + ) + } else { + None + }; + + Ok(acc) + }, + )?; + + Ok(Self { buffers, - execution_duration_microseconds: Some(value.execution_duration_microseconds), - } + execution_duration_microseconds: Some(result.execution_duration_microseconds), + memory, + }) } } @@ -234,7 +287,9 @@ py_function_sync_async! { .map_err(RustRetrieveResultsError::from) .map_err(RustRetrieveResultsError::to_py_err)?; - Ok(results.into()) + Python::with_gil(|py| { + ExecutionResults::from_controller_job_execution_result(py, results) + }) } } diff --git a/crates/python/src/qpu/mod.rs b/crates/python/src/qpu/mod.rs index 71433fe06..c088583de 100644 --- a/crates/python/src/qpu/mod.rs +++ b/crates/python/src/qpu/mod.rs @@ -14,11 +14,14 @@ pub mod translation; use crate::client::PyQcsClient; use crate::py_sync::py_function_sync_async; +use self::result_data::PyMemoryValues; + create_init_submodule! { classes: [ PyQpuResultData, RawQpuReadoutData, - PyReadoutValues + PyReadoutValues, + PyMemoryValues ], errors: [ ListQuantumProcessorsError diff --git a/crates/python/src/qpu/result_data.rs b/crates/python/src/qpu/result_data.rs index 6b6388655..24f5e8589 100644 --- a/crates/python/src/qpu/result_data.rs +++ b/crates/python/src/qpu/result_data.rs @@ -1,12 +1,15 @@ use std::collections::HashMap; use pyo3::{ - pyclass, pymethods, + exceptions::PyNotImplementedError, + pyclass, + pyclass::CompareOp, + pymethods, types::{PyComplex, PyFloat, PyInt, PyList}, IntoPy, Py, PyResult, Python, }; -use qcs::qpu::{QpuResultData, ReadoutValues}; -use rigetti_pyo3::{py_wrap_type, py_wrap_union_enum, PyTryFrom, PyWrapper, ToPython}; +use qcs::qpu::{result_data::MemoryValues, QpuResultData, ReadoutValues}; +use rigetti_pyo3::{impl_repr, py_wrap_type, py_wrap_union_enum, PyTryFrom, PyWrapper, ToPython}; py_wrap_union_enum! { PyReadoutValues(ReadoutValues) as "ReadoutValues" { @@ -16,9 +19,33 @@ py_wrap_union_enum! { } } +py_wrap_union_enum! { + #[derive(Debug, PartialEq)] + PyMemoryValues(MemoryValues) as "MemoryValues" { + binary: Binary => Vec>, + integer: Integer => Vec>, + real: Real => Vec> + } +} +impl_repr!(PyMemoryValues); + +#[pymethods] +impl PyMemoryValues { + fn __richcmp__(&self, other: &PyMemoryValues, op: CompareOp) -> PyResult { + match op { + CompareOp::Eq => Ok(self == other), + CompareOp::Ne => Ok(self != other), + _ => Err(PyNotImplementedError::new_err( + "MemoryValues only supports equality comparisons", + )), + } + } +} + py_wrap_type! { PyQpuResultData(QpuResultData) as "QPUResultData" } +impl_repr!(PyQpuResultData); #[pymethods] impl PyQpuResultData { @@ -27,10 +54,12 @@ impl PyQpuResultData { py: Python<'_>, mappings: HashMap, readout_values: HashMap, + memory_values: HashMap, ) -> PyResult { Ok(Self(QpuResultData::from_mappings_and_values( mappings, HashMap::::py_try_from(py, &readout_values)?, + HashMap::::py_try_from(py, &memory_values)?, ))) } @@ -44,6 +73,11 @@ impl PyQpuResultData { self.as_inner().readout_values().to_python(py) } + #[getter] + fn memory_values(&self, py: Python<'_>) -> PyResult> { + self.as_inner().memory_values().to_python(py) + } + pub(crate) fn to_raw_readout_data(&self, py: Python<'_>) -> RawQpuReadoutData { RawQpuReadoutData { mappings: self.as_inner().mappings().clone(), @@ -62,6 +96,21 @@ impl PyQpuResultData { ) }) .collect::>>(), + memory_values: self + .as_inner() + .memory_values() + .iter() + .map(|(register, memory_values)| { + ( + register.to_string(), + match memory_values { + MemoryValues::Binary(values) => PyList::new(py, values).into_py(py), + MemoryValues::Integer(values) => PyList::new(py, values).into_py(py), + MemoryValues::Real(values) => PyList::new(py, values).into_py(py), + }, + ) + }) + .collect::>>(), } } } @@ -72,11 +121,11 @@ impl PyQpuResultData { /// each register contains. #[derive(Debug)] #[pyclass(name = "RawQPUReadoutData")] +#[pyo3(get_all)] pub struct RawQpuReadoutData { - #[pyo3(get)] mappings: HashMap, - #[pyo3(get)] readout_values: HashMap>, + memory_values: HashMap>, } impl RawQpuReadoutData { diff --git a/crates/python/tests/execution_data/test_execution_data.py b/crates/python/tests/execution_data/test_execution_data.py index f0901bf95..5e8426717 100644 --- a/crates/python/tests/execution_data/test_execution_data.py +++ b/crates/python/tests/execution_data/test_execution_data.py @@ -1,4 +1,4 @@ -from qcs_sdk.qpu import ReadoutValues, QPUResultData +from qcs_sdk.qpu import ReadoutValues, MemoryValues, QPUResultData from qcs_sdk.qvm import QVMResultData from qcs_sdk import ResultData, RegisterData, RegisterMatrix import numpy as np @@ -13,12 +13,15 @@ def test_to_register_map_from_qpu_result_data(self): "ro[1]": "qB", "ro[2]": "qC", } - values = { + readout_values = { "qA": ReadoutValues.from_integer([0, 1]), "qB": ReadoutValues.from_integer([1, 2]), "qC": ReadoutValues.from_integer([2, 3]), } - result_data = ResultData.from_qpu(QPUResultData(mappings, values)) + memory_values = { + "ro": MemoryValues.from_integer([1, 2, 3]), + } + result_data = ResultData.from_qpu(QPUResultData(mappings, readout_values, memory_values)) register_map = result_data.to_register_map() ro = register_map.get_register_matrix("ro") assert ro is not None, "'ro' should exist in the register map" @@ -39,7 +42,7 @@ def test_to_register_map_from_jagged_qpu_result_data(self): "qB": ReadoutValues.from_integer([1]), "qC": ReadoutValues.from_integer([2, 3]), } - result_data = ResultData.from_qpu(QPUResultData(mappings, values)) + result_data = ResultData.from_qpu(QPUResultData(mappings, values, {})) with pytest.raises(ValueError): result_data.to_register_map() diff --git a/crates/python/tests/qpu/test_qpu.py b/crates/python/tests/qpu/test_qpu.py index 7f64f5b04..bb18c5d48 100644 --- a/crates/python/tests/qpu/test_qpu.py +++ b/crates/python/tests/qpu/test_qpu.py @@ -3,6 +3,7 @@ from qcs_sdk.qpu import ( QPUResultData, ReadoutValues, + MemoryValues, ListQuantumProcessorsError, list_quantum_processors, list_quantum_processors_async, @@ -26,13 +27,18 @@ def test_readout_values(): def test_qpu_result_data(): mappings = {"a": "_q0"} readout_values = {"a": ReadoutValues.from_integer([0, 1])} - result_data = QPUResultData(mappings, readout_values) + memory_values = { "int": MemoryValues.from_integer([2, 3]), "real": MemoryValues.from_real([3.0, 4.0]), "binary": MemoryValues.from_binary([0, 1]) } + result_data = QPUResultData(mappings, readout_values, memory_values) assert result_data.mappings == mappings assert result_data.readout_values["a"].as_integer() == readout_values["a"].as_integer() + assert result_data.memory_values["int"].as_integer() == memory_values["int"].as_integer() + assert result_data.memory_values["binary"].as_integer() == memory_values["binary"].as_integer() + assert result_data.memory_values["real"].as_integer() == memory_values["real"].as_integer() raw_data = result_data.to_raw_readout_data() assert raw_data.mappings == {"a": "_q0"} assert raw_data.readout_values == {"a": [0, 1]} + assert raw_data.memory_values == {"int": [2, 3], "binary": [0, 1], "real": [3.0, 4.0]} @pytest.mark.qcs_session