Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[QHC-57] Implement platform.execute_qprogram() for quantum machines #648

Merged
Merged
Show file tree
Hide file tree
Changes from 67 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
08e7fa9
changes in buses
jjmartinezQT Nov 13, 2023
d097f89
Merge branch 'main' into feature/QM_config_translation
jjmartinezQT Nov 13, 2023
7bc7e5d
removing extra settings on buses
jjmartinezQT Nov 14, 2023
4c4d34f
changes in runcard and driver for QM manager
jjmartinezQT Nov 14, 2023
b3a0071
progress in runcard translation
jjmartinezQT Nov 14, 2023
d3f2ebe
runcard example
jjmartinezQT Nov 14, 2023
2645a78
changes in qmm
jjmartinezQT Nov 15, 2023
2efa79b
changes in driver and runcard
jjmartinezQT Nov 16, 2023
0aeb266
temporary changes and files
jjmartinezQT Nov 17, 2023
94057e2
removing import
jjmartinezQT Nov 17, 2023
92c37d6
Merge branch 'main' into feature/QM_config_translation
jjmartinezQT Nov 21, 2023
651ec7a
using dictionary comprehension
jjmartinezQT Nov 21, 2023
a91c27e
code formatting
jjmartinezQT Nov 21, 2023
8b427dd
adding octaves back
jjmartinezQT Nov 21, 2023
c9ebdba
test runcard
jjmartinezQT Nov 21, 2023
1de56a6
test notebook
jjmartinezQT Nov 21, 2023
06451b9
Merge branch 'main' into feature/QM_config_translation
fedonman Dec 11, 2023
b38b7b6
Edit runcard to qua config transpilation
fedonman Dec 13, 2023
30b0eea
Edit runcard to qua config transpilation
fedonman Dec 13, 2023
36de577
updates in runcard
fedonman Dec 18, 2023
a97bc90
update QMM and move function to merge dictionaries to utils
fedonman Dec 18, 2023
cb0432f
update tests
fedonman Dec 18, 2023
0749368
remove runcards
fedonman Dec 18, 2023
ededed5
update tests
fedonman Dec 18, 2023
f04489d
fix pylint
fedonman Dec 18, 2023
0e321c3
update tests
fedonman Dec 18, 2023
234cebe
Update QM after integration testing
fedonman Dec 19, 2023
b0f035f
rename instrument and controller
fedonman Dec 20, 2023
2ab68c2
merge from main
fedonman Dec 20, 2023
527f494
update tests
fedonman Dec 20, 2023
173f690
Merge branch 'main' into feature/QM_config_translation
fedonman Dec 20, 2023
1a670d6
implement platform.execute_qprogram for quantum machines
fedonman Dec 20, 2023
604da42
remove unused imports
fedonman Dec 20, 2023
ba00f0b
fix comprehensions
fedonman Dec 20, 2023
dafaa99
update docstrings and make properties private
fedonman Dec 21, 2023
2c865f0
update qm_qua to 1.1.6
fedonman Dec 21, 2023
ff9eeb3
fix tests
fedonman Dec 21, 2023
a53343b
Merge branch 'feature/QM_config_translation' into qhc-57-implement-pl…
fedonman Dec 21, 2023
be4df16
Merge branch 'main' into qhc-57-implement-platformexecute_qprogram-fo…
fedonman Dec 21, 2023
b0c65d4
add entries to changelog
fedonman Dec 21, 2023
cea8851
merge from main
fedonman Jan 8, 2024
a722232
update changelog
fedonman Jan 8, 2024
60d7a0d
merge from main
fedonman Jan 11, 2024
3036cd3
add store_scope_acquisition to qblox execution
fedonman Jan 11, 2024
dff2038
updates in execution
fedonman Jan 15, 2024
94a6d17
change stop condition on for loop
fedonman Jan 15, 2024
ed54362
merge from main
fedonman Jan 16, 2024
91df95f
update implementation of QM
fedonman Jan 16, 2024
6c506c8
update implementation of QM
fedonman Jan 16, 2024
dc7b047
merge from main
fedonman Jan 16, 2024
7d69d33
update implementation of QM
fedonman Jan 16, 2024
842ceba
yokogawa fix
fedonman Jan 17, 2024
1e42d0e
fix frequencies in QM
fedonman Jan 17, 2024
2af8400
improvements in qprogram
fedonman Jan 17, 2024
c8e35a6
update qm set/get parameters
fedonman Jan 19, 2024
b434b39
fix and improve tests
fedonman Jan 22, 2024
edbaae7
Merge branch 'main' into qhc-57-implement-platformexecute_qprogram-fo…
fedonman Jan 22, 2024
2d0a73b
fix typo
fedonman Jan 22, 2024
fd5d01b
add tests
fedonman Jan 22, 2024
29602d4
improve tests
fedonman Jan 22, 2024
7c4fcd6
improve tests
fedonman Jan 22, 2024
e5f3aac
Fix deleting acquisition data and add changelog entries
fedonman Jan 23, 2024
a7f5f46
change debug message
fedonman Jan 23, 2024
5dfa8a3
review fixes
fedonman Jan 23, 2024
77e9b6a
pylint fix
fedonman Jan 23, 2024
5696bd9
improve implementation of set_parameter
fedonman Jan 23, 2024
1075054
add test case
fedonman Jan 24, 2024
d6e1375
add requires_domain to set_offset
fedonman Jan 24, 2024
8451d04
Merge branch 'main' into qhc-57-implement-platformexecute_qprogram-fo…
fedonman Jan 24, 2024
898bbb2
fix test after merge from main
fedonman Jan 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

### New features since last release

- Allow execution of `QProgram` through `platform.execute_qprogram` method for Quantum Machines hardware.
[#648](https://github.com/qilimanjaro-tech/qililab/pull/648)

### Improvements

- Added `bus_mapping` parameter in `QbloxCompiler.compile` method to allow changing the bus names of the compiled output.
visagim marked this conversation as resolved.
Show resolved Hide resolved
[#648](https://github.com/qilimanjaro-tech/qililab/pull/648)

- Improved `QuantumMachinesCluster` instrument functionality.
[#648](https://github.com/qilimanjaro-tech/qililab/pull/648)

- Improved execution times of `QProgram` when used inside a software loop by using caching mechanism.
[#648](https://github.com/qilimanjaro-tech/qililab/pull/648)

- Added `DictSerializable` protocol and `from_dict` utility function to enable (de)serialization (from)to dictionary for any class.
[#659](https://github.com/qilimanjaro-tech/qililab/pull/659)

Expand All @@ -28,3 +42,9 @@
### Documentation

### Bug fixes

- Added the temporary parameter `wait_time` to QProgram's `play` method. This allows the user to emulate a `time_of_flight` duration for measurement until this is added as a setting in runcard.
[#648](https://github.com/qilimanjaro-tech/qililab/pull/648)

- Fixed issue with Yokogawa GS200 instrument, that raised an error during initial_setup when the instrument's output was on.
[#648](https://github.com/qilimanjaro-tech/qililab/pull/648)
4 changes: 3 additions & 1 deletion src/qililab/instruments/qblox/qblox_qrm.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,14 @@ def _get_qprogram_acquisitions(self, acquisitions: list[str]) -> list[QbloxQProg
sequencer=sequencer.identifier,
timeout=cast(AWGQbloxADCSequencer, sequencer).acquisition_timeout,
)
self.device.store_scope_acquisition(sequencer=sequencer.identifier, name=acquisition)
raw_measurement_data = self.device.get_acquisitions(sequencer=sequencer.identifier)[acquisition][
"acquisition"
]
measurement_result = QbloxQProgramMeasurementResult(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)
fedonman marked this conversation as resolved.
Show resolved Hide resolved
return results

def _set_device_hardware_demodulation(self, value: bool, sequencer_id: int):
Expand Down
121 changes: 101 additions & 20 deletions src/qililab/instruments/quantum_machines/quantum_machines_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@
"""Quantum Machines Manager class."""
import os
from dataclasses import dataclass
from typing import Any, Dict
from typing import Any, Dict, cast

import numpy as np
from qm import DictQuaConfig, QuantumMachine, QuantumMachinesManager, SimulationConfig
from qm.jobs.running_qm_job import RunningQmJob
from qm.octave import QmOctaveConfig
from qm.qua import Program

from qililab.instruments.instrument import Instrument
from qililab.instruments.instrument import Instrument, ParameterNotFound
from qililab.instruments.utils import InstrumentFactory
from qililab.typings import InstrumentName, Parameter, QMMDriver
from qililab.utils import merge_dictionaries
Expand Down Expand Up @@ -244,10 +244,12 @@ def _get_elements_and_mixers_config(self) -> tuple:
_qm: QuantumMachine
_config: DictQuaConfig
_octave_config: QmOctaveConfig | None = None
_is_connected_to_qm: bool = False

def _is_connected_to_qm(self):
"""Check if a connection to Quantum Machine is open."""
return hasattr(self, "_qm")
@property
def config(self) -> DictQuaConfig:
"""Get the QUA config dictionary."""
return self._config

@Instrument.CheckDeviceInitialized
def initial_setup(self):
Expand All @@ -269,8 +271,9 @@ def initial_setup(self):
@Instrument.CheckDeviceInitialized
def turn_on(self):
"""Turns on the instrument."""
if not self._is_connected_to_qm():
if not self._is_connected_to_qm:
self._qm = self._qmm.open_qm(config=self._config, close_other_machines=True)
self._is_connected_to_qm = True

if self.settings.run_octave_calibration:
self.run_octave_calibration()
Expand All @@ -282,34 +285,112 @@ def reset(self):
@Instrument.CheckDeviceInitialized
def turn_off(self):
"""Turns off an instrument."""
if self._is_connected_to_qm():
if self._is_connected_to_qm:
self._qm.close()
self._is_connected_to_qm = False

def update_configuration(self, compilation_config: dict):
def append_configuration(self, configuration: dict):
"""Update the configuration dictionary by appending the configuration generated by compilation."""
self._config = merge_dictionaries(dict(self._config), compilation_config)
# If we are already connected, reopen the connection with the new configuration
if self._is_connected_to_qm():
self._qm = self._qmm.open_qm(config=self._config, close_other_machines=True)
merged_configuration = merge_dictionaries(dict(self._config), configuration)
if self._config != merged_configuration:
self._config = cast(DictQuaConfig, merged_configuration)
# If we are already connected, reopen the connection with the new configuration
if self._is_connected_to_qm:
self._qm = self._qmm.open_qm(config=self._config, close_other_machines=True)
visagim marked this conversation as resolved.
Show resolved Hide resolved

def run_octave_calibration(self):
"""Run calibration procedure for the buses with octaves, if any."""
elements = [element for element in self._config["elements"] if "RF_inputs" in self._config["elements"][element]]
for element in elements:
self._qm.calibrate_element(element)

def set_parameter(self, parameter: Parameter, value: float | str | bool, channel_id: int | None = None):
"""Sets the parameter of a specific instrument.
def set_parameter_of_bus(self, bus: str, parameter: Parameter, value: float | str | bool):
"""Sets the parameter of the instrument

Args:
parameter (Parameter): parameter settings of the instrument to update
value (float | str | bool): value to update
channel_id (int | None, optional): instrument channel to update, if multiple. Defaults to None.
bus (str): The assossiated bus to change parameter
parameter (Parameter): The parameter to update
value (float | str | bool): The new value of the parameter

Returns:
bool: True if the parameter is set correctly, False otherwise
Raises:
NotImplementedError: Raised if not connected to Quantum Machines
ParameterNotFound: Raised if parameter does not exist
"""
# TODO: Change private QM API to public when implemented.
if not self._is_connected_to_qm:
raise NotImplementedError(f"You should be connected to {self.name} in order to change a parameter.")

element = next((element for element in self.settings.elements if element["bus"] == bus), None)
if element is None:
raise ValueError(f"Bus {bus} was not found in {self.name} settings.")

if parameter in [Parameter.LO_FREQUENCY, Parameter.GAIN]:
if "rf_inputs" not in element:
visagim marked this conversation as resolved.
Show resolved Hide resolved
raise ValueError(
f"Trying to change parameter {parameter.name} in {self.name}, however bus {bus} is not connected to an octave."
)
octave_name = element["rf_inputs"]["octave"]
out_port = element["rf_inputs"]["port"]
in_port = element["rf_outputs"]["port"] if "rf_outputs" in element else None
settings_octave = next(octave for octave in self.settings.octaves if octave["name"] == octave_name)
settings_octave_rf_output = next(
rf_output for rf_output in settings_octave["rf_outputs"] if rf_output["port"] == out_port
)

if parameter == Parameter.LO_FREQUENCY:
lo_frequency = float(value)
self._qm.octave.set_lo_frequency(element=bus, lo_frequency=lo_frequency)
settings_octave_rf_output["lo_frequency"] = lo_frequency
self._config["octaves"][octave_name]["RF_outputs"][out_port]["LO_frequency"] = lo_frequency

if in_port is not None:
settings_octave_rf_input = next(
rf_input for rf_input in settings_octave["rf_inputs"] if rf_input["port"] == in_port
)
settings_octave_rf_input["lo_frequency"] = lo_frequency
self._config["octaves"][octave_name]["RF_inputs"][in_port]["LO_frequency"] = lo_frequency
return
if parameter == Parameter.GAIN:
gain_in_db = float(value)
self._qm.octave.set_rf_output_gain(element=bus, gain_in_db=gain_in_db)
settings_octave_rf_output["gain"] = gain_in_db
self._config["octaves"][octave_name]["RF_outputs"][out_port]["gain"] = gain_in_db
return
if parameter == Parameter.IF:
intermediate_frequency = float(value)
self._qm.set_intermediate_frequency(element=bus, freq=intermediate_frequency)
element["intermediate_frequency"] = intermediate_frequency
self._config["elements"][bus]["intermediate_frequency"] = intermediate_frequency
return
raise ParameterNotFound(f"Could not find parameter {parameter} in instrument {self.name}.")

def get_parameter_of_bus(self, bus: str, parameter: Parameter):
"""Gets the value of a parameter

Args:
bus (str): The assossiated bus of the parameter
parameter (Parameter): The parameter to get value

Raises:
ParameterNotFound: Raised if parameter does not exist
"""
raise NotImplementedError("Setting a parameter is not supported for Quantum Machines yet.")
# TODO: Change private QM API to public when implemented.
if parameter == Parameter.LO_FREQUENCY:
return self._qm._elements[bus].input.lo_frequency # type: ignore[union-attr] # pylint: disable=protected-access
if parameter == Parameter.GAIN:
return self._qm._elements[bus].input.gain # type: ignore[union-attr] # pylint: disable=protected-access
if parameter == Parameter.IF:
return self._qm._elements[bus].intermediate_frequency # pylint: disable=protected-access
raise ParameterNotFound(f"Could not find parameter {parameter} in instrument {self.name}")

def compile(self, program: Program) -> str:
"""Compiles a QUA program and returns the id."""
return self._qm.compile(program)

def run_compiled_program(self, compiled_program_id: str) -> RunningQmJob:
"""Runs a compiled QUA Program."""
pending_job = self._qm.queue.add_compiled(compiled_program_id)
return pending_job.wait_for_execution()

def run(self, program: Program) -> RunningQmJob:
"""Runs the QUA Program.
Expand Down
1 change: 1 addition & 0 deletions src/qililab/instruments/yokogawa/gs200.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ def setup(self, parameter: Parameter, value: float | str | bool, channel_id: int
@Instrument.CheckDeviceInitialized
def initial_setup(self):
"""Performs an initial setup."""
self.device.off()
self.source_mode = self.settings.source_mode
visagim marked this conversation as resolved.
Show resolved Hide resolved
self.span = self.settings.span[0]
self.ramping_rate = self.settings.ramp_rate[0]
Expand Down
4 changes: 2 additions & 2 deletions src/qililab/platform/components/bus.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def __post_init__(self, platform_instruments: Instruments): # type: ignore # py
super().__post_init__()

self.distortions = [
PulseDistortion.from_dict(distortion) for distortion in self.distortions if isinstance(distortion, dict)
PulseDistortion.from_dict(distortion) for distortion in self.distortions if isinstance(distortion, dict) # type: ignore[arg-type]
]

settings: BusSettings
Expand Down Expand Up @@ -176,7 +176,7 @@ def set_parameter(self, parameter: Parameter, value: int | float | str | bool, c
else:
try:
self.system_control.set_parameter(
parameter=parameter, value=value, channel_id=channel_id, port_id=self.port
parameter=parameter, value=value, channel_id=channel_id, port_id=self.port, bus_alias=self.alias
)
except ParameterNotFound as error:
raise ParameterNotFound(
Expand Down
93 changes: 86 additions & 7 deletions src/qililab/platform/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

from qibo.models import Circuit
from qiboconnection.api import API
from qm import generate_qua_script
from qpysequence import Sequence as QpySequence
from ruamel.yaml import YAML

Expand All @@ -34,15 +35,17 @@
from qililab.instruments.instrument import Instrument
from qililab.instruments.instruments import Instruments
from qililab.instruments.qblox import QbloxModule
from qililab.instruments.quantum_machines import QuantumMachinesCluster
from qililab.instruments.utils import InstrumentFactory
from qililab.pulse import PulseSchedule
from qililab.pulse import QbloxCompiler as PulseQbloxCompiler
from qililab.qprogram.qblox_compiler import QbloxCompiler as QProgramQbloxCompiler
from qililab.qprogram.qprogram import QProgram
from qililab.qprogram import QbloxCompiler, QProgram, QuantumMachinesCompiler
from qililab.result import Result
from qililab.result.quantum_machines_results import QuantumMachinesMeasurementResult
from qililab.settings import Runcard
from qililab.system_control import ReadoutSystemControl
from qililab.typings.enums import InstrumentName, Line, Parameter
from qililab.utils import hash_qpy_sequence, hash_qua_program

from .components import Bus, Buses

Expand Down Expand Up @@ -304,6 +307,12 @@ def __init__(self, runcard: Runcard, connection: API | None = None):
self.compiler = PulseQbloxCompiler(platform=self) # TODO: integrate with qprogram compiler
"""Compiler to translate given programs to instructions for a given awg vendor."""

self._qua_program_cache: dict[str, str] = {}
"""Dictionary for caching compiled qua programs."""

self._qpy_sequence_cache: dict[str, str] = {}
"""Dictionary for caching qpysequences."""

def connect(self, manual_override=False):
"""Connects to all the instruments and blocks the connection for other users.

Expand Down Expand Up @@ -570,7 +579,9 @@ def __str__(self) -> str:
"""
return str(YAML().dump(self.to_dict(), io.BytesIO()))

def execute_qprogram(self, qprogram: QProgram) -> dict[str, list[Result]]:
def execute_qprogram(
self, qprogram: QProgram, bus_mapping: dict[str, str] | None = None, debug: bool = False
) -> dict[str, list[Result]]:
"""Execute a QProgram using the platform instruments.

Args:
Expand All @@ -579,15 +590,48 @@ def execute_qprogram(self, qprogram: QProgram) -> dict[str, list[Result]]:
Returns:
dict[str, list[Result]]: A dictionary of measurement results. The keys correspond to the buses a measurement were performed upon, and the values are the list of measurement results in chronological order.
"""
bus_aliases = {bus_mapping[bus] if bus_mapping and bus in bus_mapping else bus for bus in qprogram.buses}
buses = [self._get_bus_by_alias(alias=bus_alias) for bus_alias in bus_aliases]
instruments = {
instrument
for bus in buses
for instrument in bus.system_control.instruments
if isinstance(instrument, (QbloxModule, QuantumMachinesCluster))
}
if all(isinstance(instrument, QbloxModule) for instrument in instruments):
return self._execute_qprogram_with_qblox(qprogram=qprogram, bus_mapping=bus_mapping, debug=debug)
if all(isinstance(instrument, QuantumMachinesCluster) for instrument in instruments):
if len(instruments) != 1:
raise NotImplementedError(
"Executing QProgram in more than one Quantum Machines Cluster is not supported."
)
cluster: QuantumMachinesCluster = instruments.pop() # type: ignore[assignment]
return self._execute_qprogram_with_quantum_machines(
cluster=cluster, qprogram=qprogram, bus_mapping=bus_mapping, debug=debug
)
raise NotImplementedError("Executing QProgram in a mixture of instruments is not supported.")

def _execute_qprogram_with_qblox(
self, qprogram: QProgram, bus_mapping: dict[str, str] | None = None, debug: bool = False
) -> dict[str, list[Result]]:
# Compile QProgram
qblox_compiler = QProgramQbloxCompiler()
sequences = qblox_compiler.compile(qprogram=qprogram)
qblox_compiler = QbloxCompiler()
sequences = qblox_compiler.compile(qprogram=qprogram, bus_mapping=bus_mapping)
buses = {bus_alias: self._get_bus_by_alias(alias=bus_alias) for bus_alias in sequences}

if debug:
with open("debug_qblox_execution.txt", "w", encoding="utf-8") as sourceFile:
for bus_alias in sequences:
print(f"Bus {bus_alias}:", file=sourceFile)
print(str(sequences[bus_alias]._program), file=sourceFile) # pylint: disable=protected-access
print(file=sourceFile)

# Upload sequences
for bus_alias in sequences:
buses[bus_alias].upload_qpysequence(qpysequence=sequences[bus_alias])
sequence_hash = hash_qpy_sequence(sequence=sequences[bus_alias])
if bus_alias not in self._qpy_sequence_cache or self._qpy_sequence_cache[bus_alias] != sequence_hash:
buses[bus_alias].upload_qpysequence(qpysequence=sequences[bus_alias])
self._qpy_sequence_cache[bus_alias] = sequence_hash

# Execute sequences
for bus_alias in sequences:
Expand All @@ -604,11 +648,46 @@ def execute_qprogram(self, qprogram: QProgram) -> dict[str, list[Result]]:
# Reset instrument settings
for instrument in self.instruments.elements:
if isinstance(instrument, QbloxModule):
instrument.clear_cache()
# instrument.clear_cache()
instrument.desync_sequencers()

return results

def _execute_qprogram_with_quantum_machines( # pylint: disable=too-many-locals
self,
cluster: QuantumMachinesCluster,
qprogram: QProgram,
bus_mapping: dict[str, str] | None = None,
debug: bool = False,
) -> dict[str, list[Result]]:
compiler = QuantumMachinesCompiler()
qua_program, configuration, measurements = compiler.compile(qprogram=qprogram, bus_mapping=bus_mapping)

cluster.append_configuration(configuration=configuration)

if debug:
with open("debug_qm_execution.py", "w", encoding="utf-8") as sourceFile:
print(generate_qua_script(qua_program, cluster.config), file=sourceFile)

qua_program_hash = hash_qua_program(program=qua_program)
if qua_program_hash not in self._qua_program_cache:
self._qua_program_cache[qua_program_hash] = cluster.compile(program=qua_program)
compiled_program_id = self._qua_program_cache[qua_program_hash]

job = cluster.run_compiled_program(compiled_program_id=compiled_program_id)

acquisitions = cluster.get_acquisitions(job=job)

results: dict[str, list[Result]] = {}
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])
)

return results

def execute(
self,
program: PulseSchedule | Circuit,
Expand Down
Loading
Loading