Skip to content

Commit

Permalink
[HOTFIX] Make QProgramResult Serializable (#688)
Browse files Browse the repository at this point in the history
* implementation

* update implementation, arange structure, change qiboconnection version
  • Loading branch information
fedonman authored Feb 19, 2024
1 parent 077ae31 commit e1d7a98
Show file tree
Hide file tree
Showing 17 changed files with 317 additions and 307 deletions.
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
qiboconnection
pandas==1.5.3
qibo==0.1.12
qblox-instruments==0.10.1
Expand All @@ -6,7 +7,6 @@ qcodes_contrib_drivers==0.18.0
PyVISA-py==0.7.1
tqdm==4.64.1
urllib3==1.26.12
qiboconnection==0.12.0
qpysequence==0.10.0
lmfit==1.1.0
rustworkx==0.12.1
Expand Down
9 changes: 5 additions & 4 deletions src/qililab/instruments/qblox/qblox_qrm.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
from qililab.instruments.instrument import Instrument, ParameterNotFound
from qililab.instruments.qblox.qblox_module import QbloxModule
from qililab.instruments.utils import InstrumentFactory
from qililab.result.qblox_results import QbloxQProgramMeasurementResult, QbloxResult
from qililab.result.qblox_results import QbloxResult
from qililab.result.qprogram.qblox_measurement_result import QbloxMeasurementResult
from qililab.typings.enums import AcquireTriggerMode, InstrumentName, Parameter


Expand Down Expand Up @@ -118,7 +119,7 @@ def acquire_result(self) -> QbloxResult:
"""
return self.get_acquisitions()

def acquire_qprogram_results(self, acquisitions: list[str]) -> list[QbloxQProgramMeasurementResult]: # type: ignore
def acquire_qprogram_results(self, acquisitions: list[str]) -> list[QbloxMeasurementResult]: # type: ignore
"""Read the result from the AWG instrument
Args:
Expand All @@ -130,7 +131,7 @@ def acquire_qprogram_results(self, acquisitions: list[str]) -> list[QbloxQProgra
return self._get_qprogram_acquisitions(acquisitions=acquisitions)

@Instrument.CheckDeviceInitialized
def _get_qprogram_acquisitions(self, acquisitions: list[str]) -> list[QbloxQProgramMeasurementResult]:
def _get_qprogram_acquisitions(self, acquisitions: list[str]) -> list[QbloxMeasurementResult]:
results = []
for acquisition in acquisitions:
for sequencer in self.awg_sequencers:
Expand All @@ -143,7 +144,7 @@ def _get_qprogram_acquisitions(self, acquisitions: list[str]) -> list[QbloxQProg
raw_measurement_data = self.device.get_acquisitions(sequencer=sequencer.identifier)[acquisition][
"acquisition"
]
measurement_result = QbloxQProgramMeasurementResult(raw_measurement_data=raw_measurement_data)
measurement_result = QbloxMeasurementResult(raw_measurement_data=raw_measurement_data)
results.append(measurement_result)
for sequencer in self.awg_sequencers:
self.device.delete_acquisition_data(sequencer=sequencer.identifier, all=True)
Expand Down
25 changes: 13 additions & 12 deletions src/qililab/platform/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@
from qililab.pulse import QbloxCompiler as PulseQbloxCompiler
from qililab.qprogram import QbloxCompiler, QProgram, QuantumMachinesCompiler
from qililab.result import Result
from qililab.result.qblox_results import QbloxResult
from qililab.result.quantum_machines_results import QuantumMachinesMeasurementResult
from qililab.result.qblox_results.qblox_result import QbloxResult
from qililab.result.qprogram.qprogram_results import QProgramResults
from qililab.result.qprogram.quantum_machines_measurement_result import QuantumMachinesMeasurementResult
from qililab.settings import Runcard
from qililab.system_control import ReadoutSystemControl
from qililab.typings.enums import InstrumentName, Line, Parameter
Expand Down Expand Up @@ -599,7 +600,7 @@ def __str__(self) -> str:

def execute_qprogram(
self, qprogram: QProgram, bus_mapping: dict[str, str] | None = None, debug: bool = False
) -> dict[str, list[Result]]:
) -> QProgramResults:
"""Execute a QProgram using the platform instruments.
Args:
Expand Down Expand Up @@ -631,7 +632,7 @@ def execute_qprogram(

def _execute_qprogram_with_qblox(
self, qprogram: QProgram, bus_mapping: dict[str, str] | None = None, debug: bool = False
) -> dict[str, list[Result]]:
) -> QProgramResults:
# Compile QProgram
qblox_compiler = QbloxCompiler()
sequences = qblox_compiler.compile(qprogram=qprogram, bus_mapping=bus_mapping)
Expand All @@ -656,12 +657,13 @@ def _execute_qprogram_with_qblox(
buses[bus_alias].run()

# Acquire results
results: dict[str, list[Result]] = {}
results = QProgramResults()
for bus_alias in buses:
if isinstance(buses[bus_alias].system_control, ReadoutSystemControl):
acquisitions = list(sequences[bus_alias].todict()["acquisitions"])
bus_results = buses[bus_alias].acquire_qprogram_results(acquisitions=acquisitions)
results[bus_alias] = bus_results
for bus_result in bus_results:
results.append_result(bus=bus_alias, result=bus_result)

# Reset instrument settings
for instrument in self.instruments.elements:
Expand All @@ -677,7 +679,7 @@ def _execute_qprogram_with_quantum_machines( # pylint: disable=too-many-locals
qprogram: QProgram,
bus_mapping: dict[str, str] | None = None,
debug: bool = False,
) -> dict[str, list[Result]]:
) -> QProgramResults:
compiler = QuantumMachinesCompiler()
qua_program, configuration, measurements = compiler.compile(qprogram=qprogram, bus_mapping=bus_mapping)

Expand All @@ -696,13 +698,12 @@ def _execute_qprogram_with_quantum_machines( # pylint: disable=too-many-locals

acquisitions = cluster.get_acquisitions(job=job)

results: dict[str, list[Result]] = {}
results = QProgramResults()
for measurement in measurements:
if measurement.bus not in results:
results[measurement.bus] = []
results[measurement.bus].append(
QuantumMachinesMeasurementResult(*[acquisitions[handle] for handle in measurement.result_handles])
measurement_result = QuantumMachinesMeasurementResult(
*[acquisitions[handle] for handle in measurement.result_handles]
)
results.append_result(bus=measurement.bus, result=measurement_result)

return results

Expand Down
1 change: 0 additions & 1 deletion src/qililab/result/qblox_results/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,4 @@
# limitations under the License.

""" Qblox Results format """
from .qblox_qprogram_measurement_result import QbloxQProgramMeasurementResult
from .qblox_result import QbloxResult
4 changes: 4 additions & 0 deletions src/qililab/result/qprogram/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .measurement_result import MeasurementResult
from .qblox_measurement_result import QbloxMeasurementResult
from .qprogram_results import QProgramResults
from .quantum_machines_measurement_result import QuantumMachinesMeasurementResult
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,26 @@
# See the License for the specific language governing permissions and
# limitations under the License.

""" Quantum Machines Results format """
from .quantum_machines_measurement_result import QuantumMachinesMeasurementResult
"""MeasurementResult class."""

from abc import ABC, abstractmethod

import numpy as np

from qililab.typings.enums import ResultName
from qililab.utils.dict_serializable import DictSerializable


class MeasurementResult(DictSerializable, ABC):
"""Result of a single measurement of QProgram."""

name: ResultName

@property
@abstractmethod
def array(self) -> np.ndarray:
"""Returns the results in a numpy array format.
Returns:
np.ndarray: Numpy array containing the results.
"""
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,11 @@
"""QbloxResult class."""
import numpy as np

from qililab.constants import QBLOXMEASUREMENTRESULT, RUNCARD
from qililab.result.result import Result
from qililab.result.qprogram.measurement_result import MeasurementResult
from qililab.typings.enums import ResultName
from qililab.utils.factory import Factory


@Factory.register
class QbloxQProgramMeasurementResult(Result):
class QbloxMeasurementResult(MeasurementResult):
"""QbloxQProgramMeasurementResult class. Contains the acquisitions results for a single measurement obtained from the `Cluster.get_acquisitions` method.
The input to the constructor should be a dictionary with the following structure:
Expand All @@ -42,6 +39,7 @@ class QbloxQProgramMeasurementResult(Result):

def __init__(self, raw_measurement_data: dict):
self.raw_measurement_data = raw_measurement_data
super().__init__()

@property
def array(self) -> np.ndarray:
Expand All @@ -53,13 +51,3 @@ def array(self) -> np.ndarray:
path0 = self.raw_measurement_data["bins"]["integration"]["path0"]
path1 = self.raw_measurement_data["bins"]["integration"]["path1"]
return np.array([path0, path1])

def to_dict(self) -> dict:
"""
Returns:
dict: Dictionary containing all the class information.
"""
return {
RUNCARD.NAME: self.name.value,
QBLOXMEASUREMENTRESULT.RAW_MEASUREMENT_DATA: self.raw_measurement_data,
}
35 changes: 35 additions & 0 deletions src/qililab/result/qprogram/qprogram_results.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copyright 2023 Qilimanjaro Quantum Tech
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""MeasurementResult class."""
from qililab.result.qprogram.measurement_result import MeasurementResult
from qililab.utils.dict_serializable import DictSerializable


class QProgramResults(DictSerializable):
"""Results from a single execution of QProgram."""

def __init__(self) -> None:
self.results: dict[str, list[MeasurementResult]] = {}

def append_result(self, bus: str, result: MeasurementResult):
"""Append a measurement result to bus's results list.
Args:
bus (str): The bus alias
result (MeasurementResult): The measurement result to append.
"""
if bus not in self.results:
self.results[bus] = []
self.results[bus].append(result)
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,11 @@
"""QuantumMachinesResult class."""
import numpy as np

from qililab.constants import QMRESULT, RUNCARD
from qililab.result.counts import Counts
from qililab.result.result import Result
from qililab.result.qprogram.measurement_result import MeasurementResult
from qililab.typings.enums import ResultName
from qililab.utils.factory import Factory


@Factory.register
class QuantumMachinesMeasurementResult(Result):
class QuantumMachinesMeasurementResult(MeasurementResult):
"""Contains the data obtained from a single measurment in Quantum Machines hardware.
Args:
Expand All @@ -42,6 +38,7 @@ def __init__(
self.Q = Q
self.adc1 = adc1
self.adc2 = adc2
super().__init__()

@property
def array(self) -> np.ndarray:
Expand All @@ -56,37 +53,3 @@ def array(self) -> np.ndarray:
if self.Q is not None
else self.I.reshape(1, *self.I.shape)
)

def to_dict(self) -> dict:
"""Returns a serialized dictionary of the QuantumMachinesResult class.
Returns:
dict[str: str | np.ndarray]: Dictionary containing all the class information.
"""
return {
RUNCARD.NAME: self.name.value,
QMRESULT.I: self.I,
QMRESULT.Q: self.Q,
QMRESULT.ADC1: self.adc1,
QMRESULT.ADC2: self.adc2,
}

def probabilities(self) -> dict[str, float]:
"""Return probabilities of being in the ground and excited state.
Returns:
dict[str, float]: Dictionary containing the quantum states as the keys of the dictionary, and the
probabilities obtained for each state as the values of the dictionary.
"""
raise NotImplementedError("Probabilities are not yet supported for Quantum Machines instruments.")

def counts_object(self) -> Counts:
"""Returns a Counts object containing the amount of times each state was measured.
Raises:
NotImplementedError: Not implemented.
Returns:
Counts: Counts object containing the amount of times each state was measured.
"""
raise NotImplementedError("Counts are not yet supported for Quantum Machines instruments.")
8 changes: 8 additions & 0 deletions src/qililab/utils/dict_serializable.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ def process_element(element): # pylint: disable=too-many-return-statements
return {"type": set.__name__, "elements": [process_element(item) for item in element]}
if isinstance(element, np.ndarray):
return {"type": np.ndarray.__name__, "elements": [process_element(item) for item in element.tolist()]}
if isinstance(element, dict):
return {
"type": dict.__name__,
"keys": list(element),
"elements": [process_element(element[key]) for key in element],
}
if isinstance(element, DictSerializable):
return element.to_dict()
return element
Expand Down Expand Up @@ -161,6 +167,8 @@ def process_attribute(attribute): # pylint: disable=too-many-return-statements
return set(process_attribute(item) for item in attribute["elements"])
if isinstance(attribute, dict) and "type" in attribute and attribute["type"] == np.ndarray.__name__:
return np.array([process_attribute(item) for item in attribute["elements"]])
if isinstance(attribute, dict) and "type" in attribute and attribute["type"] == dict.__name__:
return {key: process_attribute(item) for key, item in zip(attribute["keys"], attribute["elements"])}
if is_dict_serializable_object(attribute):
return from_dict(attribute)
return attribute
Expand Down
32 changes: 16 additions & 16 deletions tests/platform/test_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from qililab.pulse import Drag, Pulse, PulseEvent, PulseSchedule, Rectangular
from qililab.qprogram import QProgram
from qililab.result.qblox_results import QbloxResult
from qililab.result.quantum_machines_results import QuantumMachinesMeasurementResult
from qililab.result.qprogram.quantum_machines_measurement_result import QuantumMachinesMeasurementResult
from qililab.settings import Runcard
from qililab.settings.gate_event_settings import GateEventSettings
from qililab.system_control import ReadoutSystemControl
Expand Down Expand Up @@ -350,10 +350,10 @@ def test_execute_qprogram_with_qblox(self, platform: Platform):
patch.object(Bus, "acquire_qprogram_results") as acquire_qprogram_results,
patch.object(QbloxModule, "desync_sequencers") as desync,
):
acquire_qprogram_results.return_value = 123
acquire_qprogram_results.return_value = [123]
first_execution_results = platform.execute_qprogram(qprogram=qprogram)

acquire_qprogram_results.return_value = 456
acquire_qprogram_results.return_value = [456]
second_execution_results = platform.execute_qprogram(qprogram=qprogram)

_ = platform.execute_qprogram(qprogram=qprogram, debug=True)
Expand All @@ -365,8 +365,8 @@ def test_execute_qprogram_with_qblox(self, platform: Platform):
assert run.call_count == 6
assert acquire_qprogram_results.call_count == 3 # only readout buses
assert desync.call_count == 9
assert first_execution_results == {"feedline_input_output_bus": 123}
assert second_execution_results == {"feedline_input_output_bus": 456}
assert first_execution_results.results["feedline_input_output_bus"] == [123]
assert second_execution_results.results["feedline_input_output_bus"] == [456]

# assure only one debug was called
assert patched_open.call_count == 1
Expand Down Expand Up @@ -411,17 +411,17 @@ def test_execute_qprogram_with_quantum_machines(
assert run_compiled_program.call_count == 3
assert get_acquisitions.call_count == 3

assert "readout_q0_rf" in first_execution_results
assert len(first_execution_results["readout_q0_rf"]) == 1
assert isinstance(first_execution_results["readout_q0_rf"][0], QuantumMachinesMeasurementResult)
np.testing.assert_array_equal(first_execution_results["readout_q0_rf"][0].I, np.array([1, 2, 3]))
np.testing.assert_array_equal(first_execution_results["readout_q0_rf"][0].Q, np.array([4, 5, 6]))

assert "readout_q0_rf" in second_execution_results
assert len(second_execution_results["readout_q0_rf"]) == 1
assert isinstance(second_execution_results["readout_q0_rf"][0], QuantumMachinesMeasurementResult)
np.testing.assert_array_equal(second_execution_results["readout_q0_rf"][0].I, np.array([3, 2, 1]))
np.testing.assert_array_equal(second_execution_results["readout_q0_rf"][0].Q, np.array([6, 5, 4]))
assert "readout_q0_rf" in first_execution_results.results
assert len(first_execution_results.results["readout_q0_rf"]) == 1
assert isinstance(first_execution_results.results["readout_q0_rf"][0], QuantumMachinesMeasurementResult)
np.testing.assert_array_equal(first_execution_results.results["readout_q0_rf"][0].I, np.array([1, 2, 3]))
np.testing.assert_array_equal(first_execution_results.results["readout_q0_rf"][0].Q, np.array([4, 5, 6]))

assert "readout_q0_rf" in second_execution_results.results
assert len(second_execution_results.results["readout_q0_rf"]) == 1
assert isinstance(second_execution_results.results["readout_q0_rf"][0], QuantumMachinesMeasurementResult)
np.testing.assert_array_equal(second_execution_results.results["readout_q0_rf"][0].I, np.array([3, 2, 1]))
np.testing.assert_array_equal(second_execution_results.results["readout_q0_rf"][0].Q, np.array([6, 5, 4]))

# assure only one debug was called
assert patched_open.call_count == 1
Expand Down
Empty file.
Loading

0 comments on commit e1d7a98

Please sign in to comment.