From c4d9f98aa573ff116c7910ec4b379d14f1a3fa6a Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Mon, 23 Oct 2023 17:09:44 +0400 Subject: [PATCH 01/66] fix probabilities --- src/qibolab/instruments/zhinst.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 52a3ba2c46..2f28a7eee7 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -528,6 +528,7 @@ def play(self, qubits, couplers, sequence, options): data = ( np.array([exp_res]) if options.averaging_mode is AveragingMode.CYCLIC else np.array(exp_res) ) + data = np.ones(data.shape) - data.real # FIXME: Probability inversion results[ropulse.pulse.serial] = options.results_type(data) results[ropulse.pulse.qubit] = options.results_type(data) else: @@ -1043,6 +1044,7 @@ def sweep(self, qubits, couplers, sequence: PulseSequence, options, *sweepers): np.array([exp_res]) if options.averaging_mode is AveragingMode.CYCLIC else np.array(exp_res) ) data = data.real + data = np.ones(data.shape) - data # FIXME: Probability inversion results[self.sequence[f"readout{q}"][i].pulse.serial] = options.results_type(data) results[self.sequence[f"readout{q}"][i].pulse.qubit] = options.results_type(data) else: From 899dc25835ff565e12b622f62be6ac2df4ab4b92 Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Wed, 25 Oct 2023 13:38:14 +0400 Subject: [PATCH 02/66] prototype for kernels --- src/qibolab/instruments/zhinst.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 2f28a7eee7..92c30f0580 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -4,6 +4,7 @@ import os from collections import defaultdict from dataclasses import dataclass, replace +from pathlib import Path from typing import Tuple import laboneq._token @@ -17,11 +18,13 @@ from qibo.config import log from qibolab import AcquisitionType, AveragingMode, ExecutionParameters -from qibolab.instruments.abstract import INSTRUMENTS_DATA_FOLDER, Controller +from qibolab.instruments.abstract import Controller from qibolab.instruments.port import Port from qibolab.pulses import CouplerFluxPulse, FluxPulse, PulseSequence, PulseType from qibolab.sweeper import Parameter +KERNELS_FOLDER = Path.home() / "qibolab" / "src" / "qibolab" / "instruments" / "data" + # this env var just needs to be set os.environ["LABONEQ_TOKEN"] = "not required" laboneq._token.is_valid_token = lambda _token: True # pylint: disable=W0212 @@ -895,19 +898,26 @@ def measure_relax(self, exp, qubits, relaxation_time, acquisition_type): pulse.zhpulse.uid += str(i) # Integration weights definition or load from the chip folder - weights_file = ( - INSTRUMENTS_DATA_FOLDER / f"{self.chip}/weights/integration_weights_optimization_qubit_{q}.npy" - ) + weights_file = KERNELS_FOLDER / f"{self.chip}/weights/kernels.npz" if weights_file.is_file(): - samples = np.load( - weights_file, - allow_pickle=True, - ) + # samples = np.load( + # weights_file, + # allow_pickle=True, + # ) + from qibocal.auto.serialize import load + + raw_data_dict = dict(np.load(weights_file)) + samples = {} + + for data_key, array in raw_data_dict.items(): + samples[load(data_key)] = np.rec.array(array) + if acquisition_type == lo.AcquisitionType.DISCRIMINATION: weight = lo.pulse_library.sampled_pulse_complex( uid="weight" + pulse.zhpulse.uid, # samples=samples[0] * np.exp(1j * qubit.iq_angle), - samples=samples[0] * np.exp(1j * iq_angle), + # samples=samples[0] * np.exp(1j * iq_angle), + samples=samples[q], ) else: weight = lo.pulse_library.sampled_pulse_complex( From f49378d7b2c1b6c3ef9d82811121c05d81d3b372 Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Mon, 23 Oct 2023 17:09:44 +0400 Subject: [PATCH 03/66] reorder --- src/qibolab/instruments/zhinst.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 92c30f0580..72ff1b94fa 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -531,7 +531,6 @@ def play(self, qubits, couplers, sequence, options): data = ( np.array([exp_res]) if options.averaging_mode is AveragingMode.CYCLIC else np.array(exp_res) ) - data = np.ones(data.shape) - data.real # FIXME: Probability inversion results[ropulse.pulse.serial] = options.results_type(data) results[ropulse.pulse.qubit] = options.results_type(data) else: @@ -900,10 +899,6 @@ def measure_relax(self, exp, qubits, relaxation_time, acquisition_type): # Integration weights definition or load from the chip folder weights_file = KERNELS_FOLDER / f"{self.chip}/weights/kernels.npz" if weights_file.is_file(): - # samples = np.load( - # weights_file, - # allow_pickle=True, - # ) from qibocal.auto.serialize import load raw_data_dict = dict(np.load(weights_file)) @@ -915,8 +910,6 @@ def measure_relax(self, exp, qubits, relaxation_time, acquisition_type): if acquisition_type == lo.AcquisitionType.DISCRIMINATION: weight = lo.pulse_library.sampled_pulse_complex( uid="weight" + pulse.zhpulse.uid, - # samples=samples[0] * np.exp(1j * qubit.iq_angle), - # samples=samples[0] * np.exp(1j * iq_angle), samples=samples[q], ) else: @@ -1054,7 +1047,6 @@ def sweep(self, qubits, couplers, sequence: PulseSequence, options, *sweepers): np.array([exp_res]) if options.averaging_mode is AveragingMode.CYCLIC else np.array(exp_res) ) data = data.real - data = np.ones(data.shape) - data # FIXME: Probability inversion results[self.sequence[f"readout{q}"][i].pulse.serial] = options.results_type(data) results[self.sequence[f"readout{q}"][i].pulse.qubit] = options.results_type(data) else: From 0da941a95db26a7f5c104a909f9ae21f0b9d0584 Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Fri, 1 Dec 2023 15:36:07 +0400 Subject: [PATCH 04/66] fixes --- src/qibolab/instruments/zhinst.py | 47 +++++++++++-------------------- 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 55a0fbd346..75b0a60fca 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -928,31 +928,18 @@ def measure_relax(self, exp, qubits, relaxation_time, acquisition_type): time=self.sequence_qibo.start * NANO_TO_SECONDS, ) - if i == 0: - # Integration weights definition or load from the chip folder - weights_file = weights_file = KERNELS_FOLDER / f"{self.chip}/weights/kernels.npz" - if weights_file.is_file(): - from qibocal.auto.serialize import load - - raw_data_dict = dict(np.load(weights_file)) - samples = {} - - for data_key, array in raw_data_dict.items(): - samples[load(data_key)] = np.rec.array(array) + weights_file = KERNELS_FOLDER / str(self.chip) / "weights" / "kernels.npz" + if weights_file.is_file() and acquisition_type == lo.AcquisitionType.DISCRIMINATION: + kernels = np.load(weights_file) + weight = lo.pulse_library.sampled_pulse_complex( + uid="weight" + str(q), + # Do we need the angle ? + samples=kernels[str(q)] * np.exp(1j * iq_angle), + # samples=kernels[str(q)], + ) - if acquisition_type == lo.AcquisitionType.DISCRIMINATION: - weight = lo.pulse_library.sampled_pulse_complex( - uid="weight" + str(q), - # samples=samples[q] * np.exp(1j * iq_angle), - samples=samples[q], - ) - else: - weight = lo.pulse_library.sampled_pulse_complex( - uid="weight" + str(q), - samples=samples[q], - ) - else: - # We adjust for smearing and remove smearing/2 at the end + else: + if i == 0: exp.delay( signal=f"acquire{q}", time=self.smearing * NANO_TO_SECONDS, @@ -975,12 +962,12 @@ def measure_relax(self, exp, qubits, relaxation_time, acquisition_type): amplitude=1, ) weights[q] = weight - elif i != 0: - exp.delay( - signal=f"acquire{q}", - time=self.smearing * NANO_TO_SECONDS, - ) - weight = weights[q] + elif i != 0: + exp.delay( + signal=f"acquire{q}", + time=self.smearing * NANO_TO_SECONDS, + ) + weight = weights[q] measure_pulse_parameters = {"phase": 0} From c310a179fe989855433214329a1955d42488ac26 Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Mon, 4 Dec 2023 11:00:33 +0400 Subject: [PATCH 05/66] play_after = None for measurement sequences --- src/qibolab/instruments/zhinst.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index f28856eca9..4743554c69 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -907,6 +907,7 @@ def measure_relax(self, exp, qubits, relaxation_time, acquisition_type): ): if i != 0: play_after = f"sequence_measure_{i-1}" + play_after = None # Section on the outside loop allows for multiplex with exp.section(uid=f"sequence_measure_{i}", play_after=play_after): for pulse, q, iq_angle in zip(pulses, qubits, iq_angles): @@ -918,11 +919,11 @@ def measure_relax(self, exp, qubits, relaxation_time, acquisition_type): if play_after is None: exp.delay( signal=f"measure{q}", - time=self.sequence_qibo.start * NANO_TO_SECONDS, + time=pulse.pulse.start * NANO_TO_SECONDS, ) exp.delay( signal=f"acquire{q}", - time=self.sequence_qibo.start * NANO_TO_SECONDS, + time=pulse.pulse.start * NANO_TO_SECONDS, ) if i == 0: From 91e4a7963019db3ed3a977211ac57def92290f05 Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Mon, 4 Dec 2023 11:28:02 +0400 Subject: [PATCH 06/66] Remove commented code and update kernel file name --- src/qibolab/instruments/zhinst.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 75b0a60fca..c6de57ae05 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -441,7 +441,7 @@ def register_readout_line(self, qubit, intermediate_frequency): ), range=qubit.feedback.power_range, port_delay=self.time_of_flight * NANO_TO_SECONDS, - threshold=qubit.threshold, + # threshold=qubit.threshold, #TODO: remove ) def register_drive_line(self, qubit, intermediate_frequency): @@ -928,22 +928,18 @@ def measure_relax(self, exp, qubits, relaxation_time, acquisition_type): time=self.sequence_qibo.start * NANO_TO_SECONDS, ) - weights_file = KERNELS_FOLDER / str(self.chip) / "weights" / "kernels.npz" + weights_file = KERNELS_FOLDER / str(self.chip) / "weights" / f"kernels_q{q}.npz" if weights_file.is_file() and acquisition_type == lo.AcquisitionType.DISCRIMINATION: kernels = np.load(weights_file) weight = lo.pulse_library.sampled_pulse_complex( uid="weight" + str(q), # Do we need the angle ? - samples=kernels[str(q)] * np.exp(1j * iq_angle), - # samples=kernels[str(q)], + # samples=kernels[str(q)] * np.exp(1j * iq_angle), + samples=kernels[str(q)], ) else: if i == 0: - exp.delay( - signal=f"acquire{q}", - time=self.smearing * NANO_TO_SECONDS, - ) if acquisition_type == lo.AcquisitionType.DISCRIMINATION: weight = lo.pulse_library.sampled_pulse_complex( samples=np.ones( @@ -961,12 +957,9 @@ def measure_relax(self, exp, qubits, relaxation_time, acquisition_type): - 1.5 * self.smearing * NANO_TO_SECONDS, amplitude=1, ) + weights[q] = weight elif i != 0: - exp.delay( - signal=f"acquire{q}", - time=self.smearing * NANO_TO_SECONDS, - ) weight = weights[q] measure_pulse_parameters = {"phase": 0} @@ -989,7 +982,6 @@ def measure_relax(self, exp, qubits, relaxation_time, acquisition_type): measure_pulse_parameters=measure_pulse_parameters, measure_pulse_amplitude=None, acquire_delay=self.time_of_flight * NANO_TO_SECONDS, - # reset_delay=relaxation_time * NANO_TO_SECONDS, reset_delay=reset_delay, ) From f0df31a0edb401a11a1720b96eced211a9f2ff0e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 07:28:55 +0000 Subject: [PATCH 07/66] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibolab/instruments/zhinst.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index c6de57ae05..74a0184886 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -957,7 +957,7 @@ def measure_relax(self, exp, qubits, relaxation_time, acquisition_type): - 1.5 * self.smearing * NANO_TO_SECONDS, amplitude=1, ) - + weights[q] = weight elif i != 0: weight = weights[q] From 885f0b787e88f41f3602f0daadf923d7032aac97 Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Mon, 4 Dec 2023 11:59:52 +0400 Subject: [PATCH 08/66] Check measurement pulse start --- tests/test_instruments_zhinst.py | 42 ++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/test_instruments_zhinst.py b/tests/test_instruments_zhinst.py index e7cb3e6aeb..c72ea8931e 100644 --- a/tests/test_instruments_zhinst.py +++ b/tests/test_instruments_zhinst.py @@ -1,3 +1,5 @@ +import math + import numpy as np import pytest @@ -767,3 +769,43 @@ def test_experiment_sweep_2d_specific(connected_platform, instrument): ) assert len(results[ro_pulses[qubit].serial]) > 0 + + +def test_experiment_measurement_sequence(dummy_qrc): + platform = create_platform("zurich") + platform.setup() + IQM5q = platform.instruments["EL_ZURO"] + + sequence = PulseSequence() + qubits = {0: platform.qubits[0]} + platform.qubits = qubits + couplers = {} + + readout_pulse_start = 50 + + for qubit in qubits: + qubit_drive_pulse_1 = platform.create_qubit_drive_pulse(qubit, start=0, duration=40) + ro_pulse = platform.create_qubit_readout_pulse(qubit, start=readout_pulse_start) + qubit_drive_pulse_2 = platform.create_qubit_drive_pulse(qubit, start=100, duration=40) + sequence.add(qubit_drive_pulse_1) + sequence.add(ro_pulse) + sequence.add(qubit_drive_pulse_2) + + options = ExecutionParameters( + relaxation_time=4, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC + ) + + IQM5q.experiment_flow(qubits, couplers, sequence, options) + measure_start = 0 + for section in IQM5q.experiment.sections[0].children: + if section.uid == "sequence_measure_0": + assert section.play_after is None + for pulse in section.children: + try: + if pulse.signal == "measure0": + measure_start += pulse.time + except AttributeError: + # not a laboneq delay class object, skipping + pass + + assert math.isclose(measure_start * 1e9, readout_pulse_start, rel_tol=1e-4) From f4803e4e6a883b1822f6a040e57d99324ea07b3f Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Mon, 4 Dec 2023 12:02:59 +0400 Subject: [PATCH 09/66] Fixed test --- tests/test_instruments_zhinst.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_instruments_zhinst.py b/tests/test_instruments_zhinst.py index c72ea8931e..d02e7296c1 100644 --- a/tests/test_instruments_zhinst.py +++ b/tests/test_instruments_zhinst.py @@ -786,7 +786,7 @@ def test_experiment_measurement_sequence(dummy_qrc): for qubit in qubits: qubit_drive_pulse_1 = platform.create_qubit_drive_pulse(qubit, start=0, duration=40) ro_pulse = platform.create_qubit_readout_pulse(qubit, start=readout_pulse_start) - qubit_drive_pulse_2 = platform.create_qubit_drive_pulse(qubit, start=100, duration=40) + qubit_drive_pulse_2 = platform.create_qubit_drive_pulse(qubit, start=readout_pulse_start + 50, duration=40) sequence.add(qubit_drive_pulse_1) sequence.add(ro_pulse) sequence.add(qubit_drive_pulse_2) From 1dac5dc1c048382b306269461d708014257e2e15 Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Mon, 4 Dec 2023 13:01:01 +0400 Subject: [PATCH 10/66] Fix register_readout_line options bug --- src/qibolab/instruments/zhinst.py | 41 ++++++++++++++++++------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 74a0184886..dff17f9b04 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -380,6 +380,7 @@ def calibration_step(self, qubits, couplers, options): self.register_readout_line( qubit=qubit, intermediate_frequency=qubit.readout_frequency - qubit.readout.local_oscillator.frequency, + options=options, ) if options.fast_reset is not False: if len(self.sequence[f"drive{qubit.name}"]) == 0: @@ -389,7 +390,7 @@ def calibration_step(self, qubits, couplers, options): ) self.device_setup.set_calibration(self.calibration) - def register_readout_line(self, qubit, intermediate_frequency): + def register_readout_line(self, qubit, intermediate_frequency, options): """Registers qubit measure and acquire lines to calibration and signal map. Note @@ -430,19 +431,27 @@ def register_readout_line(self, qubit, intermediate_frequency): self.signal_map[f"acquire{q}"] = self.device_setup.logical_signal_groups[f"q{q}"].logical_signals[ "acquire_line" ] - self.calibration[f"/logical_signal_groups/q{q}/acquire_line"] = lo.SignalCalibration( - oscillator=lo.Oscillator( - frequency=intermediate_frequency, - modulation_type=lo.ModulationType.SOFTWARE, - ), - local_oscillator=lo.Oscillator( - uid="lo_shfqa_a" + str(q), - frequency=int(qubit.readout.local_oscillator.frequency), - ), - range=qubit.feedback.power_range, - port_delay=self.time_of_flight * NANO_TO_SECONDS, - # threshold=qubit.threshold, #TODO: remove - ) + weights_file = KERNELS_FOLDER / str(self.chip) / "weights" / f"kernels_q{q}.npz" + if weights_file.is_file() and options.acquisition_type == AcquisitionType.DISCRIMINATION: + # Remove software modulation as it's already included on the kernels + self.calibration[f"/logical_signal_groups/q{q}/acquire_line"] = lo.SignalCalibration( + oscillator=None, + local_oscillator=None, + range=qubit.feedback.power_range, + port_delay=self.time_of_flight * NANO_TO_SECONDS, + # threshold=qubit.threshold, #TODO: remove + ) + else: + self.calibration[f"/logical_signal_groups/q{q}/acquire_line"] = lo.SignalCalibration( + oscillator=lo.Oscillator( + frequency=intermediate_frequency, + modulation_type=lo.ModulationType.SOFTWARE, + ), + local_oscillator=None, + range=qubit.feedback.power_range, + port_delay=self.time_of_flight * NANO_TO_SECONDS, + # threshold=qubit.threshold, #TODO: remove + ) def register_drive_line(self, qubit, intermediate_frequency): """Registers qubit drive line to calibration and signal map.""" @@ -933,9 +942,7 @@ def measure_relax(self, exp, qubits, relaxation_time, acquisition_type): kernels = np.load(weights_file) weight = lo.pulse_library.sampled_pulse_complex( uid="weight" + str(q), - # Do we need the angle ? - # samples=kernels[str(q)] * np.exp(1j * iq_angle), - samples=kernels[str(q)], + samples=kernels[str(q)] * np.exp(1j * iq_angle), ) else: From 4951df9b0626abc004b5b1f8027eeb35e3be676d Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Mon, 4 Dec 2023 14:58:11 +0400 Subject: [PATCH 11/66] keep compatibility with threshold --- src/qibolab/instruments/zhinst.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index dff17f9b04..cc467e59e6 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -434,23 +434,30 @@ def register_readout_line(self, qubit, intermediate_frequency, options): weights_file = KERNELS_FOLDER / str(self.chip) / "weights" / f"kernels_q{q}.npz" if weights_file.is_file() and options.acquisition_type == AcquisitionType.DISCRIMINATION: # Remove software modulation as it's already included on the kernels + print("aqui") self.calibration[f"/logical_signal_groups/q{q}/acquire_line"] = lo.SignalCalibration( oscillator=None, - local_oscillator=None, range=qubit.feedback.power_range, port_delay=self.time_of_flight * NANO_TO_SECONDS, - # threshold=qubit.threshold, #TODO: remove ) - else: + elif weights_file.is_file() and not options.acquisition_type == AcquisitionType.DISCRIMINATION: + self.calibration[f"/logical_signal_groups/q{q}/acquire_line"] = lo.SignalCalibration( + oscillator=lo.Oscillator( + frequency=intermediate_frequency, + modulation_type=lo.ModulationType.SOFTWARE, + ), + range=qubit.feedback.power_range, + port_delay=self.time_of_flight * NANO_TO_SECONDS, + threshold=qubit.threshold, # To keep compatibility with angle and threshold discrimination + ) + elif not weights_file.is_file() and not options.acquisition_type == AcquisitionType.DISCRIMINATION: self.calibration[f"/logical_signal_groups/q{q}/acquire_line"] = lo.SignalCalibration( oscillator=lo.Oscillator( frequency=intermediate_frequency, modulation_type=lo.ModulationType.SOFTWARE, ), - local_oscillator=None, range=qubit.feedback.power_range, port_delay=self.time_of_flight * NANO_TO_SECONDS, - # threshold=qubit.threshold, #TODO: remove ) def register_drive_line(self, qubit, intermediate_frequency): @@ -552,7 +559,7 @@ def play(self, qubits, couplers, sequence, options): data = ( np.array([exp_res]) if options.averaging_mode is AveragingMode.CYCLIC else np.array(exp_res) ) - data = np.ones(data.shape) - data.real # Probability inversion patch + data = np.ones(data.shape) - data.real # Probability inversion results[ropulse.pulse.serial] = options.results_type(data) results[ropulse.pulse.qubit] = options.results_type(data) else: @@ -1077,8 +1084,7 @@ def sweep(self, qubits, couplers, sequence: PulseSequence, options, *sweepers): data = ( np.array([exp_res]) if options.averaging_mode is AveragingMode.CYCLIC else np.array(exp_res) ) - data = data.real - data = np.ones(data.shape) - data # Probability inversion patch + data = np.ones(data.shape) - data.real # Probability inversion results[self.sequence[f"readout{q}"][i].pulse.serial] = options.results_type(data) results[self.sequence[f"readout{q}"][i].pulse.qubit] = options.results_type(data) else: From 451e815a4ffcc07b6218d528ce1472e53215ffe7 Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Mon, 4 Dec 2023 15:03:03 +0400 Subject: [PATCH 12/66] smearing --- src/qibolab/instruments/zhinst.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index cc467e59e6..1315be6d05 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -944,6 +944,11 @@ def measure_relax(self, exp, qubits, relaxation_time, acquisition_type): time=self.sequence_qibo.start * NANO_TO_SECONDS, ) + exp.delay( + signal=f"acquire{q}", + time=self.smearing * NANO_TO_SECONDS, + ) + weights_file = KERNELS_FOLDER / str(self.chip) / "weights" / f"kernels_q{q}.npz" if weights_file.is_file() and acquisition_type == lo.AcquisitionType.DISCRIMINATION: kernels = np.load(weights_file) From 698e4c9bb2a4a28f498a177da3d7234a8feff4d8 Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Mon, 4 Dec 2023 15:19:37 +0400 Subject: [PATCH 13/66] Add options parameter to register_readout_line test function --- tests/test_instruments_zhinst.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_instruments_zhinst.py b/tests/test_instruments_zhinst.py index e7cb3e6aeb..b63fae22d0 100644 --- a/tests/test_instruments_zhinst.py +++ b/tests/test_instruments_zhinst.py @@ -172,7 +172,12 @@ def test_zhinst_register_readout_line(dummy_qrc): platform = create_platform("zurich") platform.setup() IQM5q = platform.instruments["EL_ZURO"] - IQM5q.register_readout_line(platform.qubits[0], intermediate_frequency=int(1e6)) + + options = ExecutionParameters( + relaxation_time=300e-6, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC + ) + + IQM5q.register_readout_line(platform.qubits[0], intermediate_frequency=int(1e6), options=options) assert "measure0" in IQM5q.signal_map assert "acquire0" in IQM5q.signal_map From 0859a5858a2970261de4269d161ce8b69c8e9b62 Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Mon, 4 Dec 2023 18:17:26 +0400 Subject: [PATCH 14/66] Update kernel path for qubits --- src/qibolab/instruments/zhinst.py | 6 ++++-- src/qibolab/qubits.py | 3 +++ tests/dummy_qrc/zurich.yml | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 1315be6d05..4c7031390e 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -431,7 +431,8 @@ def register_readout_line(self, qubit, intermediate_frequency, options): self.signal_map[f"acquire{q}"] = self.device_setup.logical_signal_groups[f"q{q}"].logical_signals[ "acquire_line" ] - weights_file = KERNELS_FOLDER / str(self.chip) / "weights" / f"kernels_q{q}.npz" + # weights_file = KERNELS_FOLDER / str(self.chip) / "weights" / f"kernels_q{q}.npz" + weights_file = qubit.kernel_path / f"kernels_q{q}.npz" if weights_file.is_file() and options.acquisition_type == AcquisitionType.DISCRIMINATION: # Remove software modulation as it's already included on the kernels print("aqui") @@ -949,7 +950,8 @@ def measure_relax(self, exp, qubits, relaxation_time, acquisition_type): time=self.smearing * NANO_TO_SECONDS, ) - weights_file = KERNELS_FOLDER / str(self.chip) / "weights" / f"kernels_q{q}.npz" + # weights_file = KERNELS_FOLDER / str(self.chip) / "weights" / f"kernels_q{q}.npz" + weights_file = qubits[q].kernel_path / f"kernels_q{q}.npz" if weights_file.is_file() and acquisition_type == lo.AcquisitionType.DISCRIMINATION: kernels = np.load(weights_file) weight = lo.pulse_library.sampled_pulse_complex( diff --git a/src/qibolab/qubits.py b/src/qibolab/qubits.py index 77bca66f6d..9893cba565 100644 --- a/src/qibolab/qubits.py +++ b/src/qibolab/qubits.py @@ -1,4 +1,5 @@ from dataclasses import dataclass, field, fields +from pathlib import Path from typing import List, Optional, Tuple, Union from qibolab.channels import Channel @@ -89,6 +90,8 @@ class Qubit: native_gates: SingleQubitNatives = field(default_factory=SingleQubitNatives) + kernel_path: Optional[Path] = None + @property def channels(self): for name in CHANNEL_NAMES: diff --git a/tests/dummy_qrc/zurich.yml b/tests/dummy_qrc/zurich.yml index c092a38ca8..f9fa5cf72b 100644 --- a/tests/dummy_qrc/zurich.yml +++ b/tests/dummy_qrc/zurich.yml @@ -1,5 +1,6 @@ nqubits: 5 qubits: [0, 1, 2, 3, 4] +kernel_path: "qibolab_platforms_qrc/iqm5q_kernels" couplers: [0, 1, 3, 4] topology: {0: [0, 2], 1: [1, 2], 3: [2, 3], 4: [2, 4]} settings: From 4a920ccd30b08aa939d6f8f69e8cf81917ec6487 Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Tue, 5 Dec 2023 15:10:16 +0400 Subject: [PATCH 15/66] Add kernel path to Qubit class and update Zurich Controller to use kernel path --- src/qibolab/instruments/zhinst.py | 20 +++++++++----------- src/qibolab/qubits.py | 3 +-- src/qibolab/serialize.py | 3 +++ 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 4c7031390e..2bcd9e8e19 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -313,6 +313,7 @@ def __init__(self, name, device_setup, use_emulation=False, time_of_flight=0.0, self.smearing = smearing self.chip = "iqm5q" "Parameters read from the runcard not part of ExecutionParameters" + self.kernels = defaultdict(Path) self.exp = None self.experiment = None @@ -431,17 +432,16 @@ def register_readout_line(self, qubit, intermediate_frequency, options): self.signal_map[f"acquire{q}"] = self.device_setup.logical_signal_groups[f"q{q}"].logical_signals[ "acquire_line" ] - # weights_file = KERNELS_FOLDER / str(self.chip) / "weights" / f"kernels_q{q}.npz" - weights_file = qubit.kernel_path / f"kernels_q{q}.npz" - if weights_file.is_file() and options.acquisition_type == AcquisitionType.DISCRIMINATION: - # Remove software modulation as it's already included on the kernels - print("aqui") + + if qubit.kernel_path: + self.kernels[q] = qubit.kernel_path / f"kernels_q{q}.npz" + if self.kernels[q].is_file() and options.acquisition_type == AcquisitionType.DISCRIMINATION: self.calibration[f"/logical_signal_groups/q{q}/acquire_line"] = lo.SignalCalibration( oscillator=None, range=qubit.feedback.power_range, port_delay=self.time_of_flight * NANO_TO_SECONDS, ) - elif weights_file.is_file() and not options.acquisition_type == AcquisitionType.DISCRIMINATION: + elif self.kernels[q].is_file() and not options.acquisition_type == AcquisitionType.DISCRIMINATION: self.calibration[f"/logical_signal_groups/q{q}/acquire_line"] = lo.SignalCalibration( oscillator=lo.Oscillator( frequency=intermediate_frequency, @@ -451,7 +451,7 @@ def register_readout_line(self, qubit, intermediate_frequency, options): port_delay=self.time_of_flight * NANO_TO_SECONDS, threshold=qubit.threshold, # To keep compatibility with angle and threshold discrimination ) - elif not weights_file.is_file() and not options.acquisition_type == AcquisitionType.DISCRIMINATION: + elif not self.kernels[q].is_file() and not options.acquisition_type == AcquisitionType.DISCRIMINATION: self.calibration[f"/logical_signal_groups/q{q}/acquire_line"] = lo.SignalCalibration( oscillator=lo.Oscillator( frequency=intermediate_frequency, @@ -950,10 +950,8 @@ def measure_relax(self, exp, qubits, relaxation_time, acquisition_type): time=self.smearing * NANO_TO_SECONDS, ) - # weights_file = KERNELS_FOLDER / str(self.chip) / "weights" / f"kernels_q{q}.npz" - weights_file = qubits[q].kernel_path / f"kernels_q{q}.npz" - if weights_file.is_file() and acquisition_type == lo.AcquisitionType.DISCRIMINATION: - kernels = np.load(weights_file) + if self.kernels[q].is_file() and acquisition_type == lo.AcquisitionType.DISCRIMINATION: + kernels = np.load(self.kernels[q]) weight = lo.pulse_library.sampled_pulse_complex( uid="weight" + str(q), samples=kernels[str(q)] * np.exp(1j * iq_angle), diff --git a/src/qibolab/qubits.py b/src/qibolab/qubits.py index 9893cba565..8757faacb9 100644 --- a/src/qibolab/qubits.py +++ b/src/qibolab/qubits.py @@ -38,6 +38,7 @@ class Qubit: """ name: QubitId + kernel_path: Optional[Path] = None bare_resonator_frequency: int = 0 readout_frequency: int = 0 @@ -90,8 +91,6 @@ class Qubit: native_gates: SingleQubitNatives = field(default_factory=SingleQubitNatives) - kernel_path: Optional[Path] = None - @property def channels(self): for name in CHANNEL_NAMES: diff --git a/src/qibolab/serialize.py b/src/qibolab/serialize.py index 23ffb9c820..039f35f9bd 100644 --- a/src/qibolab/serialize.py +++ b/src/qibolab/serialize.py @@ -41,6 +41,9 @@ def load_qubits(runcard: dict) -> Tuple[QubitMap, CouplerMap, QubitPairMap]: objects. """ qubits = {q: Qubit(q, **char) for q, char in runcard["characterization"]["single_qubit"].items()} + if runcard["kernel_path"]: + for qubit in qubits.values(): + qubit.kernel_path = Path(runcard["kernel_path"]) couplers = {} pairs = {} From 79fa12fa7d4a0352e3950b9467a0d83f34faf919 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 11:10:55 +0000 Subject: [PATCH 16/66] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibolab/instruments/zhinst.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 2bcd9e8e19..e5198abe89 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -313,7 +313,7 @@ def __init__(self, name, device_setup, use_emulation=False, time_of_flight=0.0, self.smearing = smearing self.chip = "iqm5q" "Parameters read from the runcard not part of ExecutionParameters" - self.kernels = defaultdict(Path) + self.kernels = defaultdict(Path) self.exp = None self.experiment = None @@ -432,7 +432,7 @@ def register_readout_line(self, qubit, intermediate_frequency, options): self.signal_map[f"acquire{q}"] = self.device_setup.logical_signal_groups[f"q{q}"].logical_signals[ "acquire_line" ] - + if qubit.kernel_path: self.kernels[q] = qubit.kernel_path / f"kernels_q{q}.npz" if self.kernels[q].is_file() and options.acquisition_type == AcquisitionType.DISCRIMINATION: From 4b99f6e38e8a2007db8f5a0c0dfe1c9ecd7223d7 Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Tue, 5 Dec 2023 15:22:39 +0400 Subject: [PATCH 17/66] fix --- src/qibolab/serialize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibolab/serialize.py b/src/qibolab/serialize.py index 039f35f9bd..1746dcb89f 100644 --- a/src/qibolab/serialize.py +++ b/src/qibolab/serialize.py @@ -41,7 +41,7 @@ def load_qubits(runcard: dict) -> Tuple[QubitMap, CouplerMap, QubitPairMap]: objects. """ qubits = {q: Qubit(q, **char) for q, char in runcard["characterization"]["single_qubit"].items()} - if runcard["kernel_path"]: + if "kernel_path" in runcard: for qubit in qubits.values(): qubit.kernel_path = Path(runcard["kernel_path"]) From a4b6fd5180d288af0dd8dd80c321b661272155b3 Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Tue, 5 Dec 2023 15:24:42 +0400 Subject: [PATCH 18/66] remove unused --- src/qibolab/instruments/zhinst.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index e5198abe89..19dde9c413 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -25,8 +25,6 @@ from qibolab.qubits import Qubit from qibolab.sweeper import Parameter -KERNELS_FOLDER = Path.home() / "qibolab" / "src" / "qibolab" / "instruments" / "data" - # this env var just needs to be set os.environ["LABONEQ_TOKEN"] = "not required" laboneq._token.is_valid_token = lambda _token: True # pylint: disable=W0212 From 4c1f0fec8609e548412a893d3b5bf339a0900c3c Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Tue, 5 Dec 2023 16:57:03 +0400 Subject: [PATCH 19/66] Add kernel_folder attribute to Platform class --- src/qibolab/platform.py | 2 ++ src/qibolab/qubits.py | 2 +- src/qibolab/serialize.py | 18 ++++++++++++++---- tests/dummy_qrc/zurich.py | 2 ++ tests/dummy_qrc/zurich.yml | 15 +++++++-------- 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/qibolab/platform.py b/src/qibolab/platform.py index e3b61ed72a..12efb5da2c 100644 --- a/src/qibolab/platform.py +++ b/src/qibolab/platform.py @@ -54,6 +54,8 @@ class Platform: couplers: CouplerMap = field(default_factory=dict) """Dictionary mapping coupler names to :class:`qibolab.couplers.Coupler` objects.""" + kernel_folder: Optional[str] = None + """Folder where each qubit kernels are stored""" is_connected: bool = False """Flag for whether we are connected to the physical instruments.""" diff --git a/src/qibolab/qubits.py b/src/qibolab/qubits.py index 8757faacb9..beb5a2b3f9 100644 --- a/src/qibolab/qubits.py +++ b/src/qibolab/qubits.py @@ -38,7 +38,6 @@ class Qubit: """ name: QubitId - kernel_path: Optional[Path] = None bare_resonator_frequency: int = 0 readout_frequency: int = 0 @@ -77,6 +76,7 @@ class Qubit: # parameters for single shot classification threshold: Optional[float] = None iq_angle: float = 0.0 + kernel_path: Optional[Path] = None # required for mixers (not sure if it should be here) mixer_drive_g: float = 0.0 mixer_drive_phi: float = 0.0 diff --git a/src/qibolab/serialize.py b/src/qibolab/serialize.py index 1746dcb89f..18defb4e27 100644 --- a/src/qibolab/serialize.py +++ b/src/qibolab/serialize.py @@ -41,9 +41,11 @@ def load_qubits(runcard: dict) -> Tuple[QubitMap, CouplerMap, QubitPairMap]: objects. """ qubits = {q: Qubit(q, **char) for q, char in runcard["characterization"]["single_qubit"].items()} - if "kernel_path" in runcard: + if "kernel_folder" in runcard: for qubit in qubits.values(): - qubit.kernel_path = Path(runcard["kernel_path"]) + qubit.kernel_path = Path( + runcard["kernel_folder"] + runcard["characterization"]["single_qubit"][qubit.name]["kernel_path"] + ) couplers = {} pairs = {} @@ -94,7 +96,7 @@ def load_instrument_settings(runcard: dict, instruments: InstrumentMap) -> Instr return instruments -def dump_qubits(qubits: QubitMap, pairs: QubitPairMap, couplers: CouplerMap = None) -> dict: +def dump_qubits(qubits: QubitMap, pairs: QubitPairMap, couplers: CouplerMap = None, kernel_folder: str = None) -> dict: """Dump qubit and pair objects to a dictionary following the runcard format.""" native_gates = {"single_qubit": {q: qubit.native_gates.raw for q, qubit in qubits.items()}} @@ -111,6 +113,11 @@ def dump_qubits(qubits: QubitMap, pairs: QubitPairMap, couplers: CouplerMap = No characterization = { "single_qubit": {q: qubit.characterization for q, qubit in qubits.items()}, } + if kernel_folder: + for q in qubits: + characterization["single_qubit"][q]["kernel_path"] = str( + characterization["single_qubit"][q]["kernel_path"] + ).replace(kernel_folder, "") if couplers: characterization["coupler"] = {c.name: {"sweetspot": c.sweetspot} for c in couplers.values()} @@ -147,9 +154,12 @@ def dump_runcard(platform: Platform, path: Path): "topology": [list(pair) for pair in platform.pairs], "instruments": dump_instruments(platform.instruments), } + + if platform.kernel_folder: + settings["kernel_folder"] = platform.kernel_folder if platform.couplers: settings["couplers"] = list(platform.couplers) settings["topology"] = {coupler: list(pair) for pair, coupler in zip(platform.pairs, platform.couplers)} - settings.update(dump_qubits(platform.qubits, platform.pairs, platform.couplers)) + settings.update(dump_qubits(platform.qubits, platform.pairs, platform.couplers, platform.kernel_folder)) path.write_text(yaml.dump(settings, sort_keys=False, indent=4, default_flow_style=None)) diff --git a/tests/dummy_qrc/zurich.py b/tests/dummy_qrc/zurich.py index b5d94c4e86..d7d2a65a24 100644 --- a/tests/dummy_qrc/zurich.py +++ b/tests/dummy_qrc/zurich.py @@ -19,6 +19,7 @@ RUNCARD = pathlib.Path(__file__).parent / "zurich.yml" N_QUBITS = 5 +KERNEL_FOLDER = "qibolab_platforms_qrc/iqm5q_kernels/" def create(runcard_path=RUNCARD): @@ -165,4 +166,5 @@ def create(runcard_path=RUNCARD): settings, resonator_type="2D", couplers=couplers, + kernel_folder=KERNEL_FOLDER, ) diff --git a/tests/dummy_qrc/zurich.yml b/tests/dummy_qrc/zurich.yml index f9fa5cf72b..e9c8122fb1 100644 --- a/tests/dummy_qrc/zurich.yml +++ b/tests/dummy_qrc/zurich.yml @@ -1,6 +1,5 @@ nqubits: 5 qubits: [0, 1, 2, 3, 4] -kernel_path: "qibolab_platforms_qrc/iqm5q_kernels" couplers: [0, 1, 3, 4] topology: {0: [0, 2], 1: [1, 2], 3: [2, 3], 4: [2, 4]} settings: @@ -8,6 +7,8 @@ settings: sampling_rate: 2.e+9 relaxation_time: 300_000 +kernel_folder: "qibolab_platforms_qrc/iqm5q_kernels/" + instruments: lo_readout: frequency: 5_500_000_000 @@ -246,12 +247,7 @@ characterization: # parameters for single shot classification threshold: 0.8836 iq_angle: -1.551 - # alpha: 217.492 MHz - # To save power values on the runcard - # ro_range_lp: -15 - # ro_range_hp: -15 - # qd_range: 0 - # flux_range: -0 + kernel_path: "kernel_q0" 1: readout_frequency: 4_931_000_000 @@ -261,6 +257,7 @@ characterization: sweetspot: 0.0 mean_gnd_states: [0, 0] mean_exc_states: [0, 0] + kernel_path: "kernel_q1" 2: readout_frequency: 6.109e+9 #6_112_000_000 drive_frequency: 4_300_587_281 # 4_401_600_000 #4_541_100_000 @@ -272,7 +269,7 @@ characterization: # parameters for single shot classification threshold: -0.0593 iq_angle: -0.667 - # alpha: 208 MHz + kernel_path: "kernel_q2" 3: readout_frequency: 5_783_000_000 drive_frequency: 4_100_000_000 @@ -281,6 +278,7 @@ characterization: sweetspot: 0.0 mean_gnd_states: [0, 0] mean_exc_states: [0, 0] + kernel_path: "kernel_q3" 4: readout_frequency: 5_515_000_000 drive_frequency: 4_196_800_000 @@ -292,6 +290,7 @@ characterization: # parameters for single shot classification threshold: 0.233806 #0.370954 #0.350665 iq_angle: 0.481 # -91.712 #191.016 + kernel_path: "kernel_q4" coupler: 0: sweetspot: 0.0 From be0c312fc07977865e2d4b80746459d6c415e397 Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Tue, 5 Dec 2023 17:33:18 +0400 Subject: [PATCH 20/66] Fix kernel_folder path in load_qubits function --- src/qibolab/serialize.py | 15 +++++++++------ tests/dummy_qrc/zurich.py | 4 +++- tests/dummy_qrc/zurich.yml | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/qibolab/serialize.py b/src/qibolab/serialize.py index 18defb4e27..bd1707951d 100644 --- a/src/qibolab/serialize.py +++ b/src/qibolab/serialize.py @@ -33,6 +33,11 @@ def load_settings(runcard: dict) -> Settings: return Settings(**runcard["settings"]) +def load_path(runcard: dict) -> Path: + """Load platform settings section from the runcard.""" + return Path(runcard["kernel_folder"]) + + def load_qubits(runcard: dict) -> Tuple[QubitMap, CouplerMap, QubitPairMap]: """Load qubits and pairs from the runcard. @@ -43,8 +48,8 @@ def load_qubits(runcard: dict) -> Tuple[QubitMap, CouplerMap, QubitPairMap]: qubits = {q: Qubit(q, **char) for q, char in runcard["characterization"]["single_qubit"].items()} if "kernel_folder" in runcard: for qubit in qubits.values(): - qubit.kernel_path = Path( - runcard["kernel_folder"] + runcard["characterization"]["single_qubit"][qubit.name]["kernel_path"] + qubit.kernel_path = ( + Path(runcard["kernel_folder"]) / runcard["characterization"]["single_qubit"][qubit.name]["kernel_path"] ) couplers = {} @@ -115,9 +120,7 @@ def dump_qubits(qubits: QubitMap, pairs: QubitPairMap, couplers: CouplerMap = No } if kernel_folder: for q in qubits: - characterization["single_qubit"][q]["kernel_path"] = str( - characterization["single_qubit"][q]["kernel_path"] - ).replace(kernel_folder, "") + characterization["single_qubit"][q]["kernel_path"] = characterization["single_qubit"][q]["kernel_path"].name if couplers: characterization["coupler"] = {c.name: {"sweetspot": c.sweetspot} for c in couplers.values()} @@ -156,7 +159,7 @@ def dump_runcard(platform: Platform, path: Path): } if platform.kernel_folder: - settings["kernel_folder"] = platform.kernel_folder + settings["kernel_folder"] = str(platform.kernel_folder) if platform.couplers: settings["couplers"] = list(platform.couplers) settings["topology"] = {coupler: list(pair) for pair, coupler in zip(platform.pairs, platform.couplers)} diff --git a/tests/dummy_qrc/zurich.py b/tests/dummy_qrc/zurich.py index d7d2a65a24..5b9af29bc5 100644 --- a/tests/dummy_qrc/zurich.py +++ b/tests/dummy_qrc/zurich.py @@ -11,6 +11,7 @@ from qibolab.instruments.zhinst import Zurich from qibolab.serialize import ( load_instrument_settings, + load_path, load_qubits, load_runcard, load_settings, @@ -140,6 +141,7 @@ def create(runcard_path=RUNCARD): runcard = load_runcard(runcard_path) qubits, couplers, pairs = load_qubits(runcard) settings = load_settings(runcard) + kernel_folder = load_path(runcard) # assign channels to qubits and sweetspots(operating points) for q in range(0, 5): @@ -166,5 +168,5 @@ def create(runcard_path=RUNCARD): settings, resonator_type="2D", couplers=couplers, - kernel_folder=KERNEL_FOLDER, + kernel_folder=kernel_folder, ) diff --git a/tests/dummy_qrc/zurich.yml b/tests/dummy_qrc/zurich.yml index e9c8122fb1..8760e92052 100644 --- a/tests/dummy_qrc/zurich.yml +++ b/tests/dummy_qrc/zurich.yml @@ -7,7 +7,7 @@ settings: sampling_rate: 2.e+9 relaxation_time: 300_000 -kernel_folder: "qibolab_platforms_qrc/iqm5q_kernels/" +kernel_folder: "qibolab_platforms_qrc/iqm5q_kernels" instruments: lo_readout: From aed50ebf241bc385ac697c9984c18a4799f4fd52 Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Tue, 5 Dec 2023 17:44:59 +0400 Subject: [PATCH 21/66] Fix kernel_folder path in Platform class --- src/qibolab/platform.py | 3 ++- src/qibolab/serialize.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/qibolab/platform.py b/src/qibolab/platform.py index 12efb5da2c..739031d235 100644 --- a/src/qibolab/platform.py +++ b/src/qibolab/platform.py @@ -1,6 +1,7 @@ """A platform for executing quantum algorithms.""" from dataclasses import dataclass, field, replace +from pathlib import Path from typing import Dict, List, Optional import networkx as nx @@ -54,7 +55,7 @@ class Platform: couplers: CouplerMap = field(default_factory=dict) """Dictionary mapping coupler names to :class:`qibolab.couplers.Coupler` objects.""" - kernel_folder: Optional[str] = None + kernel_folder: Optional[Path] = None """Folder where each qubit kernels are stored""" is_connected: bool = False diff --git a/src/qibolab/serialize.py b/src/qibolab/serialize.py index bd1707951d..8f889dbcfc 100644 --- a/src/qibolab/serialize.py +++ b/src/qibolab/serialize.py @@ -5,6 +5,7 @@ example for more details. """ from dataclasses import asdict +from os.path import normcase from pathlib import Path from typing import Tuple @@ -159,7 +160,7 @@ def dump_runcard(platform: Platform, path: Path): } if platform.kernel_folder: - settings["kernel_folder"] = str(platform.kernel_folder) + settings["kernel_folder"] = str(normcase(platform.kernel_folder)) if platform.couplers: settings["couplers"] = list(platform.couplers) settings["topology"] = {coupler: list(pair) for pair, coupler in zip(platform.pairs, platform.couplers)} From b8a1b6af37e7084b88705086a7758a563eb1c649 Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Wed, 6 Dec 2023 10:18:03 +0400 Subject: [PATCH 22/66] Refactor load_qubits function to accept kernel_folder as an optional argument --- src/qibolab/serialize.py | 24 ++++++++---------------- tests/dummy_qrc/zurich.py | 8 ++------ tests/dummy_qrc/zurich.yml | 2 -- 3 files changed, 10 insertions(+), 24 deletions(-) diff --git a/src/qibolab/serialize.py b/src/qibolab/serialize.py index 8f889dbcfc..7f8fd6264e 100644 --- a/src/qibolab/serialize.py +++ b/src/qibolab/serialize.py @@ -5,7 +5,6 @@ example for more details. """ from dataclasses import asdict -from os.path import normcase from pathlib import Path from typing import Tuple @@ -34,12 +33,7 @@ def load_settings(runcard: dict) -> Settings: return Settings(**runcard["settings"]) -def load_path(runcard: dict) -> Path: - """Load platform settings section from the runcard.""" - return Path(runcard["kernel_folder"]) - - -def load_qubits(runcard: dict) -> Tuple[QubitMap, CouplerMap, QubitPairMap]: +def load_qubits(runcard: dict, kernel_folder: Path = None) -> Tuple[QubitMap, CouplerMap, QubitPairMap]: """Load qubits and pairs from the runcard. Uses the native gate and characterization sections of the runcard @@ -47,12 +41,9 @@ def load_qubits(runcard: dict) -> Tuple[QubitMap, CouplerMap, QubitPairMap]: objects. """ qubits = {q: Qubit(q, **char) for q, char in runcard["characterization"]["single_qubit"].items()} - if "kernel_folder" in runcard: + if kernel_folder is not None: for qubit in qubits.values(): - qubit.kernel_path = ( - Path(runcard["kernel_folder"]) / runcard["characterization"]["single_qubit"][qubit.name]["kernel_path"] - ) - + qubit.kernel_path = kernel_folder / runcard["characterization"]["single_qubit"][qubit.name]["kernel_path"] couplers = {} pairs = {} if "coupler" in runcard["characterization"]: @@ -119,9 +110,10 @@ def dump_qubits(qubits: QubitMap, pairs: QubitPairMap, couplers: CouplerMap = No characterization = { "single_qubit": {q: qubit.characterization for q, qubit in qubits.items()}, } - if kernel_folder: - for q in qubits: + for q in qubits: + if "kernel_path" in characterization["single_qubit"][q]: characterization["single_qubit"][q]["kernel_path"] = characterization["single_qubit"][q]["kernel_path"].name + if couplers: characterization["coupler"] = {c.name: {"sweetspot": c.sweetspot} for c in couplers.values()} @@ -159,8 +151,8 @@ def dump_runcard(platform: Platform, path: Path): "instruments": dump_instruments(platform.instruments), } - if platform.kernel_folder: - settings["kernel_folder"] = str(normcase(platform.kernel_folder)) + print(platform) + if platform.couplers: settings["couplers"] = list(platform.couplers) settings["topology"] = {coupler: list(pair) for pair, coupler in zip(platform.pairs, platform.couplers)} diff --git a/tests/dummy_qrc/zurich.py b/tests/dummy_qrc/zurich.py index 5b9af29bc5..9a4bf77a3b 100644 --- a/tests/dummy_qrc/zurich.py +++ b/tests/dummy_qrc/zurich.py @@ -11,16 +11,14 @@ from qibolab.instruments.zhinst import Zurich from qibolab.serialize import ( load_instrument_settings, - load_path, load_qubits, load_runcard, load_settings, ) RUNCARD = pathlib.Path(__file__).parent / "zurich.yml" - +KERNEL_FOLDER = pathlib.Path(__file__).parent / "iqm5q_kernels/" N_QUBITS = 5 -KERNEL_FOLDER = "qibolab_platforms_qrc/iqm5q_kernels/" def create(runcard_path=RUNCARD): @@ -139,9 +137,8 @@ def create(runcard_path=RUNCARD): # create qubit objects runcard = load_runcard(runcard_path) - qubits, couplers, pairs = load_qubits(runcard) + qubits, couplers, pairs = load_qubits(runcard, KERNEL_FOLDER) settings = load_settings(runcard) - kernel_folder = load_path(runcard) # assign channels to qubits and sweetspots(operating points) for q in range(0, 5): @@ -168,5 +165,4 @@ def create(runcard_path=RUNCARD): settings, resonator_type="2D", couplers=couplers, - kernel_folder=kernel_folder, ) diff --git a/tests/dummy_qrc/zurich.yml b/tests/dummy_qrc/zurich.yml index 8760e92052..13b893f420 100644 --- a/tests/dummy_qrc/zurich.yml +++ b/tests/dummy_qrc/zurich.yml @@ -7,8 +7,6 @@ settings: sampling_rate: 2.e+9 relaxation_time: 300_000 -kernel_folder: "qibolab_platforms_qrc/iqm5q_kernels" - instruments: lo_readout: frequency: 5_500_000_000 From 8393d1442ec0e6aad606bd92c1d1665877cba297 Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Wed, 6 Dec 2023 10:27:14 +0400 Subject: [PATCH 23/66] fix --- src/qibolab/serialize.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/qibolab/serialize.py b/src/qibolab/serialize.py index 7f8fd6264e..e512845a1f 100644 --- a/src/qibolab/serialize.py +++ b/src/qibolab/serialize.py @@ -93,7 +93,7 @@ def load_instrument_settings(runcard: dict, instruments: InstrumentMap) -> Instr return instruments -def dump_qubits(qubits: QubitMap, pairs: QubitPairMap, couplers: CouplerMap = None, kernel_folder: str = None) -> dict: +def dump_qubits(qubits: QubitMap, pairs: QubitPairMap, couplers: CouplerMap = None) -> dict: """Dump qubit and pair objects to a dictionary following the runcard format.""" native_gates = {"single_qubit": {q: qubit.native_gates.raw for q, qubit in qubits.items()}} @@ -111,8 +111,9 @@ def dump_qubits(qubits: QubitMap, pairs: QubitPairMap, couplers: CouplerMap = No "single_qubit": {q: qubit.characterization for q, qubit in qubits.items()}, } for q in qubits: - if "kernel_path" in characterization["single_qubit"][q]: - characterization["single_qubit"][q]["kernel_path"] = characterization["single_qubit"][q]["kernel_path"].name + kernel_path = characterization["single_qubit"][q].pop("kernel_path") + if kernel_path is not None: + characterization["single_qubit"][q]["kernel_path"] = kernel_path.name if couplers: characterization["coupler"] = {c.name: {"sweetspot": c.sweetspot} for c in couplers.values()} @@ -157,5 +158,5 @@ def dump_runcard(platform: Platform, path: Path): settings["couplers"] = list(platform.couplers) settings["topology"] = {coupler: list(pair) for pair, coupler in zip(platform.pairs, platform.couplers)} - settings.update(dump_qubits(platform.qubits, platform.pairs, platform.couplers, platform.kernel_folder)) + settings.update(dump_qubits(platform.qubits, platform.pairs, platform.couplers)) path.write_text(yaml.dump(settings, sort_keys=False, indent=4, default_flow_style=None)) From bc13c8c4ae442c58c9a9e991cda2bb239cf162fa Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Wed, 6 Dec 2023 12:07:24 +0400 Subject: [PATCH 24/66] Update load_qubits function to use extras_folder instead of kernel_folder --- src/qibolab/serialize.py | 6 +++--- tests/dummy_qrc/zurich.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/qibolab/serialize.py b/src/qibolab/serialize.py index e512845a1f..979362238c 100644 --- a/src/qibolab/serialize.py +++ b/src/qibolab/serialize.py @@ -33,7 +33,7 @@ def load_settings(runcard: dict) -> Settings: return Settings(**runcard["settings"]) -def load_qubits(runcard: dict, kernel_folder: Path = None) -> Tuple[QubitMap, CouplerMap, QubitPairMap]: +def load_qubits(runcard: dict, extras_folder: Path = None) -> Tuple[QubitMap, CouplerMap, QubitPairMap]: """Load qubits and pairs from the runcard. Uses the native gate and characterization sections of the runcard @@ -41,9 +41,9 @@ def load_qubits(runcard: dict, kernel_folder: Path = None) -> Tuple[QubitMap, Co objects. """ qubits = {q: Qubit(q, **char) for q, char in runcard["characterization"]["single_qubit"].items()} - if kernel_folder is not None: + if extras_folder is not None: for qubit in qubits.values(): - qubit.kernel_path = kernel_folder / runcard["characterization"]["single_qubit"][qubit.name]["kernel_path"] + qubit.kernel_path = extras_folder / runcard["characterization"]["single_qubit"][qubit.name]["kernel_path"] couplers = {} pairs = {} if "coupler" in runcard["characterization"]: diff --git a/tests/dummy_qrc/zurich.py b/tests/dummy_qrc/zurich.py index 9a4bf77a3b..dfb70700ab 100644 --- a/tests/dummy_qrc/zurich.py +++ b/tests/dummy_qrc/zurich.py @@ -17,7 +17,7 @@ ) RUNCARD = pathlib.Path(__file__).parent / "zurich.yml" -KERNEL_FOLDER = pathlib.Path(__file__).parent / "iqm5q_kernels/" +FOLDER = pathlib.Path(__file__).parent / "iqm5q/" N_QUBITS = 5 @@ -137,7 +137,7 @@ def create(runcard_path=RUNCARD): # create qubit objects runcard = load_runcard(runcard_path) - qubits, couplers, pairs = load_qubits(runcard, KERNEL_FOLDER) + qubits, couplers, pairs = load_qubits(runcard, FOLDER) settings = load_settings(runcard) # assign channels to qubits and sweetspots(operating points) From cf8afb756618a4db5c5225746c02d86913f3a998 Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Wed, 6 Dec 2023 12:31:22 +0400 Subject: [PATCH 25/66] Multiple measurements + fixed sweepers --- src/qibolab/instruments/zhinst.py | 135 ++++++++++++++++-------------- 1 file changed, 74 insertions(+), 61 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 4743554c69..7e3fe76571 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -444,7 +444,10 @@ def register_readout_line(self, qubit, intermediate_frequency): def register_drive_line(self, qubit, intermediate_frequency): """Registers qubit drive line to calibration and signal map.""" q = qubit.name # pylint: disable=C0103 - self.signal_map[f"drive{q}"] = self.device_setup.logical_signal_groups[f"q{q}"].logical_signals["drive_line"] + for i, _ in enumerate(self.sub_sequences[f"drive{q}"]): + self.signal_map[f"drive{q}_{i}"] = self.device_setup.logical_signal_groups[f"q{q}"].logical_signals[ + "drive_line" + ] self.calibration[f"/logical_signal_groups/q{q}/drive_line"] = lo.SignalCalibration( oscillator=lo.Oscillator( frequency=intermediate_frequency, @@ -512,6 +515,21 @@ def experiment_flow(self, qubits, couplers, sequence, options, sweepers=[]): Translation, Calibration, Experiment Definition. """ self.sequence_zh(sequence, qubits, couplers, sweepers) + + # TODO: Move this part to a different function + self.sub_sequences = {} # initialize it in __init__? + for qubit in qubits.values(): + q = qubit.name # pylint: disable=C0103 + measurements = self.sequence[f"readout{q}"] + drives = self.sequence[f"drive{q}"] + drive_sequences = [[] for _ in range(len(measurements))] + measurement_index = 0 + for drive in drives: + if drive.pulse.finish > measurements[measurement_index].pulse.start: + measurement_index += 1 + drive_sequences[measurement_index].append(drive) + self.sub_sequences[f"drive{q}"] = drive_sequences + self.calibration_step(qubits, couplers, options) self.create_exp(qubits, couplers, options) @@ -636,7 +654,8 @@ def create_exp(self, qubits, couplers, options): for qubit in qubits.values(): q = qubit.name # pylint: disable=C0103 if len(self.sequence[f"drive{q}"]) != 0: - signals.append(lo.ExperimentSignal(f"drive{q}")) + for i, _ in enumerate(self.sub_sequences[f"drive{q}"]): + signals.append(lo.ExperimentSignal(f"drive{q}_{i}")) if qubit.flux is not None: signals.append(lo.ExperimentSignal(f"flux{q}")) if len(self.sequence[f"readout{q}"]) != 0: @@ -706,28 +725,28 @@ def play_sweep_select_single(exp, qubit, pulse, section, parameters, partial_swe pulse.zhpulse.amplitude *= max(pulse.zhsweeper.values) pulse.zhsweeper.values /= max(pulse.zhsweeper.values) exp.play( - signal=f"{section}{qubit.name}", + signal=f"{section}{qubit.name}_0", pulse=pulse.zhpulse, amplitude=pulse.zhsweeper, phase=pulse.pulse.relative_phase, ) elif any("duration" in param for param in parameters): exp.play( - signal=f"{section}{qubit.name}", + signal=f"{section}{qubit.name}_0", pulse=pulse.zhpulse, length=pulse.zhsweeper, phase=pulse.pulse.relative_phase, ) elif any("relative_phase" in param for param in parameters): exp.play( - signal=f"{section}{qubit.name}", + signal=f"{section}{qubit.name}_0", pulse=pulse.zhpulse, phase=pulse.zhsweeper, # I believe this is the global phase sweep # increment_oscillator_phase=pulse.zhsweeper, # I believe this is the relative phase sweep ) elif "frequency" in partial_sweep.uid or partial_sweep.uid == "start": exp.play( - signal=f"{section}{qubit.name}", + signal=f"{section}{qubit.name}_0", pulse=pulse.zhpulse, phase=pulse.pulse.relative_phase, ) @@ -747,7 +766,7 @@ def play_sweep_select_dual(exp, qubit, pulse, section, parameters): sweeper_dur_index = pulse.zhsweepers.index(sweeper) exp.play( - signal=f"{section}{qubit.name}", + signal=f"{section}{qubit.name}_0", pulse=pulse.zhpulse, amplitude=pulse.zhsweepers[sweeper_amp_index], length=pulse.zhsweepers[sweeper_dur_index], @@ -829,34 +848,38 @@ def drive(self, exp, qubits): time = 0 i = 0 if len(self.sequence[f"drive{q}"]) != 0: - with exp.section(uid=f"sequence_drive{q}"): - for pulse in self.sequence[f"drive{q}"]: - if not isinstance(pulse, ZhSweeperLine): - exp.delay( - signal=f"drive{q}", - time=round(pulse.pulse.start * NANO_TO_SECONDS, 9) - time, - ) - time = round(pulse.pulse.duration * NANO_TO_SECONDS, 9) + round( - pulse.pulse.start * NANO_TO_SECONDS, 9 - ) - pulse.zhpulse.uid += str(i) - if isinstance(pulse, ZhSweeper): - self.play_sweep(exp, qubit, pulse, section="drive") - elif isinstance(pulse, ZhPulse): - exp.play( - signal=f"drive{q}", - pulse=pulse.zhpulse, - phase=pulse.pulse.relative_phase, + play_after = None + for j, sequence in enumerate(self.sub_sequences[f"drive{q}"]): + time = 0 + with exp.section(uid=f"sequence_drive{q}_{j}", play_after=play_after): + for pulse in sequence: + if not isinstance(pulse, ZhSweeperLine): + exp.delay( + signal=f"drive{q}_{j}", + time=round(pulse.pulse.start * NANO_TO_SECONDS, 9) - time, + ) + time = round(pulse.pulse.duration * NANO_TO_SECONDS, 9) + round( + pulse.pulse.start * NANO_TO_SECONDS, 9 ) - i += 1 - elif isinstance(pulse, ZhSweeperLine): - exp.delay(signal=f"drive{q}", time=pulse.zhsweeper) + pulse.zhpulse.uid += str(i) + if isinstance(pulse, ZhSweeper): + self.play_sweep(exp, qubit, pulse, section="drive") + elif isinstance(pulse, ZhPulse): + exp.play( + signal=f"drive{q}_{j}", + pulse=pulse.zhpulse, + phase=pulse.pulse.relative_phase, + ) + i += 1 + elif isinstance(pulse, ZhSweeperLine): + exp.delay(signal=f"drive{q}_{j}", time=pulse.zhsweeper) + play_after = f"sequence_drive{q}_{j}" # Patch for T1 start, general ? if len(self.sequence[f"readout{q}"]) > 0 and isinstance( self.sequence[f"readout{q}"][0], ZhSweeperLine ): - exp.delay(signal=f"drive{q}", time=self.sequence[f"readout{q}"][0].zhsweeper) + exp.delay(signal=f"drive{q}_{j}", time=self.sequence[f"readout{q}"][0].zhsweeper) self.sequence[f"readout{q}"].remove(self.sequence[f"readout{q}"][0]) @staticmethod @@ -869,26 +892,23 @@ def play_after_set(sequence, ptype): qubit_after = pulse.qubit return f"sequence_{ptype}{qubit_after}" + def find_subsequence_finish(self, i, line, qubits): + time_finish = 0 + qubit_finish = None + for qubit in qubits: + if len(self.sub_sequences[f"{line}{qubit}"]) <= i: + continue + for pulse in self.sub_sequences[f"{line}{qubit}"][i]: + if pulse.pulse.finish > time_finish: + time_finish = pulse.pulse.finish + qubit_finish = qubit + return time_finish, qubit_finish + # For pulsed spectroscopy, set integration_length and either measure_pulse or measure_pulse_length. # For CW spectroscopy, set only integration_length and do not specify the measure signal. # For all other measurements, set either length or pulse for both the measure pulse and integration kernel. def measure_relax(self, exp, qubits, relaxation_time, acquisition_type): """qubit readout pulse, data acquisition and qubit relaxation""" - play_after = None - - # TODO: if we use duration sweepers, the code might not behave as expected - # i.e.: self.sequence_qibo will contain the a pulse or sweeper with a static duration that may screw the comparison - qf_finish = self.sequence_qibo.qf_pulses.finish - qd_finish = self.sequence_qibo.qd_pulses.finish - cf_finish = self.sequence_qibo.cf_pulses.finish - - if qf_finish > qd_finish and qf_finish > cf_finish: - play_after = self.play_after_set(self.sequence_qibo.qf_pulses, "bias") - elif qd_finish > qf_finish and qd_finish > cf_finish: - play_after = self.play_after_set(self.sequence_qibo.qd_pulses, "drive") - elif cf_finish > qf_finish and cf_finish > qd_finish: - play_after = self.play_after_set(self.sequence_qibo.cf_pulses, "bias_coupler") - readout_schedule = defaultdict(list) qubit_readout_schedule = defaultdict(list) iq_angle_readout_schedule = defaultdict(list) @@ -905,27 +925,17 @@ def measure_relax(self, exp, qubits, relaxation_time, acquisition_type): for i, (pulses, qubits, iq_angles) in enumerate( zip(readout_schedule.values(), qubit_readout_schedule.values(), iq_angle_readout_schedule.values()) ): - if i != 0: - play_after = f"sequence_measure_{i-1}" + _, qd_finish_q = self.find_subsequence_finish(i, "drive", qubits) play_after = None + if qd_finish_q is None: + play_after = None + else: + play_after = f"sequence_drive{qd_finish_q}_{i}" # Section on the outside loop allows for multiplex with exp.section(uid=f"sequence_measure_{i}", play_after=play_after): for pulse, q, iq_angle in zip(pulses, qubits, iq_angles): pulse.zhpulse.uid += str(i) - # TODO: if the measure sequence starts after the last pulse, add a delay - # keep in mind that the signal might start before the last pulse - # if sweepers are involved - if play_after is None: - exp.delay( - signal=f"measure{q}", - time=pulse.pulse.start * NANO_TO_SECONDS, - ) - exp.delay( - signal=f"acquire{q}", - time=pulse.pulse.start * NANO_TO_SECONDS, - ) - if i == 0: # Integration weights definition or load from the chip folder weights_file = ( @@ -1020,7 +1030,7 @@ def fast_reset(self, exp, qubits, fast_reset): pass with exp.case(state=1): pulse = ZhPulse(qubit.native_gates.RX.pulse(0, 0)) - exp.play(signal=f"drive{q}", pulse=pulse.zhpulse) + exp.play(signal=f"drive{q}_0", pulse=pulse.zhpulse) @staticmethod def rearrange_sweepers(sweepers): @@ -1125,7 +1135,10 @@ def sweep_recursion(self, qubits, couplers, exp, exp_calib, exp_options): line = "drive" if pulse.type is PulseType.DRIVE else "measure" zhsweeper = ZhSweeper(pulse, sweeper, qubits[sweeper.pulses[0].qubit]).zhsweeper zhsweeper.uid = "frequency" # Changing the name from "frequency" breaks it f"frequency_{i} - exp_calib[f"{line}{pulse.qubit}"] = lo.SignalCalibration( + line_name = f"{line}{pulse.qubit}" + if line == "drive": + line_name += "_0" + exp_calib[line_name] = lo.SignalCalibration( oscillator=lo.Oscillator( frequency=zhsweeper, modulation_type=lo.ModulationType.HARDWARE, From 1b0c5f186546e0b417d16eae9295f52a2cf69c8b Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Mon, 4 Dec 2023 11:00:33 +0400 Subject: [PATCH 26/66] play_after = None for measurement sequences --- src/qibolab/instruments/zhinst.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 8997fb3122..2276cf2052 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -911,6 +911,7 @@ def measure_relax(self, exp, qubits, relaxation_time, acquisition_type): ): if i != 0: play_after = f"sequence_measure_{i-1}" + play_after = None # Section on the outside loop allows for multiplex with exp.section(uid=f"sequence_measure_{i}", play_after=play_after): for pulse, q, iq_angle in zip(pulses, qubits, iq_angles): @@ -922,11 +923,11 @@ def measure_relax(self, exp, qubits, relaxation_time, acquisition_type): if play_after is None: exp.delay( signal=f"measure{q}", - time=self.sequence_qibo.start * NANO_TO_SECONDS, + time=pulse.pulse.start * NANO_TO_SECONDS, ) exp.delay( signal=f"acquire{q}", - time=self.sequence_qibo.start * NANO_TO_SECONDS, + time=pulse.pulse.start * NANO_TO_SECONDS, ) if i == 0: From 79da632f8e6c127fa43ba13ebc83c5842261f644 Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Mon, 4 Dec 2023 11:59:52 +0400 Subject: [PATCH 27/66] Check measurement pulse start --- tests/test_instruments_zhinst.py | 42 ++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/test_instruments_zhinst.py b/tests/test_instruments_zhinst.py index d32f1b54f1..387a10c80a 100644 --- a/tests/test_instruments_zhinst.py +++ b/tests/test_instruments_zhinst.py @@ -1,3 +1,5 @@ +import math + import numpy as np import pytest @@ -785,3 +787,43 @@ def test_experiment_sweep_2d_specific(connected_platform, instrument): ) assert len(results[ro_pulses[qubit].serial]) > 0 + + +def test_experiment_measurement_sequence(dummy_qrc): + platform = create_platform("zurich") + platform.setup() + IQM5q = platform.instruments["EL_ZURO"] + + sequence = PulseSequence() + qubits = {0: platform.qubits[0]} + platform.qubits = qubits + couplers = {} + + readout_pulse_start = 50 + + for qubit in qubits: + qubit_drive_pulse_1 = platform.create_qubit_drive_pulse(qubit, start=0, duration=40) + ro_pulse = platform.create_qubit_readout_pulse(qubit, start=readout_pulse_start) + qubit_drive_pulse_2 = platform.create_qubit_drive_pulse(qubit, start=100, duration=40) + sequence.add(qubit_drive_pulse_1) + sequence.add(ro_pulse) + sequence.add(qubit_drive_pulse_2) + + options = ExecutionParameters( + relaxation_time=4, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC + ) + + IQM5q.experiment_flow(qubits, couplers, sequence, options) + measure_start = 0 + for section in IQM5q.experiment.sections[0].children: + if section.uid == "sequence_measure_0": + assert section.play_after is None + for pulse in section.children: + try: + if pulse.signal == "measure0": + measure_start += pulse.time + except AttributeError: + # not a laboneq delay class object, skipping + pass + + assert math.isclose(measure_start * 1e9, readout_pulse_start, rel_tol=1e-4) From 4eec7fb05e6e1f5f9853281e0dae41aa209d0ea0 Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Mon, 4 Dec 2023 12:02:59 +0400 Subject: [PATCH 28/66] Fixed test --- tests/test_instruments_zhinst.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_instruments_zhinst.py b/tests/test_instruments_zhinst.py index 387a10c80a..452b37e811 100644 --- a/tests/test_instruments_zhinst.py +++ b/tests/test_instruments_zhinst.py @@ -804,7 +804,7 @@ def test_experiment_measurement_sequence(dummy_qrc): for qubit in qubits: qubit_drive_pulse_1 = platform.create_qubit_drive_pulse(qubit, start=0, duration=40) ro_pulse = platform.create_qubit_readout_pulse(qubit, start=readout_pulse_start) - qubit_drive_pulse_2 = platform.create_qubit_drive_pulse(qubit, start=100, duration=40) + qubit_drive_pulse_2 = platform.create_qubit_drive_pulse(qubit, start=readout_pulse_start + 50, duration=40) sequence.add(qubit_drive_pulse_1) sequence.add(ro_pulse) sequence.add(qubit_drive_pulse_2) From 77019ab9d9b1dce62d80a080d4a34f42bba97875 Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Wed, 6 Dec 2023 12:31:22 +0400 Subject: [PATCH 29/66] Multiple measurements + fixed sweepers --- src/qibolab/instruments/zhinst.py | 135 ++++++++++++++++-------------- 1 file changed, 74 insertions(+), 61 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 2276cf2052..3a623c6f49 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -448,7 +448,10 @@ def register_readout_line(self, qubit, intermediate_frequency): def register_drive_line(self, qubit, intermediate_frequency): """Registers qubit drive line to calibration and signal map.""" q = qubit.name # pylint: disable=C0103 - self.signal_map[f"drive{q}"] = self.device_setup.logical_signal_groups[f"q{q}"].logical_signals["drive_line"] + for i, _ in enumerate(self.sub_sequences[f"drive{q}"]): + self.signal_map[f"drive{q}_{i}"] = self.device_setup.logical_signal_groups[f"q{q}"].logical_signals[ + "drive_line" + ] self.calibration[f"/logical_signal_groups/q{q}/drive_line"] = lo.SignalCalibration( oscillator=lo.Oscillator( frequency=intermediate_frequency, @@ -516,6 +519,21 @@ def experiment_flow(self, qubits, couplers, sequence, options, sweepers=[]): Translation, Calibration, Experiment Definition. """ self.sequence_zh(sequence, qubits, couplers, sweepers) + + # TODO: Move this part to a different function + self.sub_sequences = {} # initialize it in __init__? + for qubit in qubits.values(): + q = qubit.name # pylint: disable=C0103 + measurements = self.sequence[f"readout{q}"] + drives = self.sequence[f"drive{q}"] + drive_sequences = [[] for _ in range(len(measurements))] + measurement_index = 0 + for drive in drives: + if drive.pulse.finish > measurements[measurement_index].pulse.start: + measurement_index += 1 + drive_sequences[measurement_index].append(drive) + self.sub_sequences[f"drive{q}"] = drive_sequences + self.calibration_step(qubits, couplers, options) self.create_exp(qubits, couplers, options) @@ -640,7 +658,8 @@ def create_exp(self, qubits, couplers, options): for qubit in qubits.values(): q = qubit.name # pylint: disable=C0103 if len(self.sequence[f"drive{q}"]) != 0: - signals.append(lo.ExperimentSignal(f"drive{q}")) + for i, _ in enumerate(self.sub_sequences[f"drive{q}"]): + signals.append(lo.ExperimentSignal(f"drive{q}_{i}")) if qubit.flux is not None: signals.append(lo.ExperimentSignal(f"flux{q}")) if len(self.sequence[f"readout{q}"]) != 0: @@ -710,28 +729,28 @@ def play_sweep_select_single(exp, qubit, pulse, section, parameters, partial_swe pulse.zhpulse.amplitude *= max(pulse.zhsweeper.values) pulse.zhsweeper.values /= max(pulse.zhsweeper.values) exp.play( - signal=f"{section}{qubit.name}", + signal=f"{section}{qubit.name}_0", pulse=pulse.zhpulse, amplitude=pulse.zhsweeper, phase=pulse.pulse.relative_phase, ) elif any("duration" in param for param in parameters): exp.play( - signal=f"{section}{qubit.name}", + signal=f"{section}{qubit.name}_0", pulse=pulse.zhpulse, length=pulse.zhsweeper, phase=pulse.pulse.relative_phase, ) elif any("relative_phase" in param for param in parameters): exp.play( - signal=f"{section}{qubit.name}", + signal=f"{section}{qubit.name}_0", pulse=pulse.zhpulse, phase=pulse.zhsweeper, # I believe this is the global phase sweep # increment_oscillator_phase=pulse.zhsweeper, # I believe this is the relative phase sweep ) elif "frequency" in partial_sweep.uid or partial_sweep.uid == "start": exp.play( - signal=f"{section}{qubit.name}", + signal=f"{section}{qubit.name}_0", pulse=pulse.zhpulse, phase=pulse.pulse.relative_phase, ) @@ -751,7 +770,7 @@ def play_sweep_select_dual(exp, qubit, pulse, section, parameters): sweeper_dur_index = pulse.zhsweepers.index(sweeper) exp.play( - signal=f"{section}{qubit.name}", + signal=f"{section}{qubit.name}_0", pulse=pulse.zhpulse, amplitude=pulse.zhsweepers[sweeper_amp_index], length=pulse.zhsweepers[sweeper_dur_index], @@ -833,34 +852,38 @@ def drive(self, exp, qubits): time = 0 i = 0 if len(self.sequence[f"drive{q}"]) != 0: - with exp.section(uid=f"sequence_drive{q}"): - for pulse in self.sequence[f"drive{q}"]: - if not isinstance(pulse, ZhSweeperLine): - exp.delay( - signal=f"drive{q}", - time=round(pulse.pulse.start * NANO_TO_SECONDS, 9) - time, - ) - time = round(pulse.pulse.duration * NANO_TO_SECONDS, 9) + round( - pulse.pulse.start * NANO_TO_SECONDS, 9 - ) - pulse.zhpulse.uid += str(i) - if isinstance(pulse, ZhSweeper): - self.play_sweep(exp, qubit, pulse, section="drive") - elif isinstance(pulse, ZhPulse): - exp.play( - signal=f"drive{q}", - pulse=pulse.zhpulse, - phase=pulse.pulse.relative_phase, + play_after = None + for j, sequence in enumerate(self.sub_sequences[f"drive{q}"]): + time = 0 + with exp.section(uid=f"sequence_drive{q}_{j}", play_after=play_after): + for pulse in sequence: + if not isinstance(pulse, ZhSweeperLine): + exp.delay( + signal=f"drive{q}_{j}", + time=round(pulse.pulse.start * NANO_TO_SECONDS, 9) - time, + ) + time = round(pulse.pulse.duration * NANO_TO_SECONDS, 9) + round( + pulse.pulse.start * NANO_TO_SECONDS, 9 ) - i += 1 - elif isinstance(pulse, ZhSweeperLine): - exp.delay(signal=f"drive{q}", time=pulse.zhsweeper) + pulse.zhpulse.uid += str(i) + if isinstance(pulse, ZhSweeper): + self.play_sweep(exp, qubit, pulse, section="drive") + elif isinstance(pulse, ZhPulse): + exp.play( + signal=f"drive{q}_{j}", + pulse=pulse.zhpulse, + phase=pulse.pulse.relative_phase, + ) + i += 1 + elif isinstance(pulse, ZhSweeperLine): + exp.delay(signal=f"drive{q}_{j}", time=pulse.zhsweeper) + play_after = f"sequence_drive{q}_{j}" # Patch for T1 start, general ? if len(self.sequence[f"readout{q}"]) > 0 and isinstance( self.sequence[f"readout{q}"][0], ZhSweeperLine ): - exp.delay(signal=f"drive{q}", time=self.sequence[f"readout{q}"][0].zhsweeper) + exp.delay(signal=f"drive{q}_{j}", time=self.sequence[f"readout{q}"][0].zhsweeper) self.sequence[f"readout{q}"].remove(self.sequence[f"readout{q}"][0]) @staticmethod @@ -873,26 +896,23 @@ def play_after_set(sequence, ptype): qubit_after = pulse.qubit return f"sequence_{ptype}{qubit_after}" + def find_subsequence_finish(self, i, line, qubits): + time_finish = 0 + qubit_finish = None + for qubit in qubits: + if len(self.sub_sequences[f"{line}{qubit}"]) <= i: + continue + for pulse in self.sub_sequences[f"{line}{qubit}"][i]: + if pulse.pulse.finish > time_finish: + time_finish = pulse.pulse.finish + qubit_finish = qubit + return time_finish, qubit_finish + # For pulsed spectroscopy, set integration_length and either measure_pulse or measure_pulse_length. # For CW spectroscopy, set only integration_length and do not specify the measure signal. # For all other measurements, set either length or pulse for both the measure pulse and integration kernel. def measure_relax(self, exp, qubits, relaxation_time, acquisition_type): """qubit readout pulse, data acquisition and qubit relaxation""" - play_after = None - - # TODO: if we use duration sweepers, the code might not behave as expected - # i.e.: self.sequence_qibo will contain the a pulse or sweeper with a static duration that may screw the comparison - qf_finish = self.sequence_qibo.qf_pulses.finish - qd_finish = self.sequence_qibo.qd_pulses.finish - cf_finish = self.sequence_qibo.cf_pulses.finish - - if qf_finish > qd_finish and qf_finish > cf_finish: - play_after = self.play_after_set(self.sequence_qibo.qf_pulses, "bias") - elif qd_finish > qf_finish and qd_finish > cf_finish: - play_after = self.play_after_set(self.sequence_qibo.qd_pulses, "drive") - elif cf_finish > qf_finish and cf_finish > qd_finish: - play_after = self.play_after_set(self.sequence_qibo.cf_pulses, "bias_coupler") - readout_schedule = defaultdict(list) qubit_readout_schedule = defaultdict(list) iq_angle_readout_schedule = defaultdict(list) @@ -909,27 +929,17 @@ def measure_relax(self, exp, qubits, relaxation_time, acquisition_type): for i, (pulses, qubits, iq_angles) in enumerate( zip(readout_schedule.values(), qubit_readout_schedule.values(), iq_angle_readout_schedule.values()) ): - if i != 0: - play_after = f"sequence_measure_{i-1}" + _, qd_finish_q = self.find_subsequence_finish(i, "drive", qubits) play_after = None + if qd_finish_q is None: + play_after = None + else: + play_after = f"sequence_drive{qd_finish_q}_{i}" # Section on the outside loop allows for multiplex with exp.section(uid=f"sequence_measure_{i}", play_after=play_after): for pulse, q, iq_angle in zip(pulses, qubits, iq_angles): pulse.zhpulse.uid += str(i) - # TODO: if the measure sequence starts after the last pulse, add a delay - # keep in mind that the signal might start before the last pulse - # if sweepers are involved - if play_after is None: - exp.delay( - signal=f"measure{q}", - time=pulse.pulse.start * NANO_TO_SECONDS, - ) - exp.delay( - signal=f"acquire{q}", - time=pulse.pulse.start * NANO_TO_SECONDS, - ) - if i == 0: # Integration weights definition or load from the chip folder weights_file = ( @@ -1024,7 +1034,7 @@ def fast_reset(self, exp, qubits, fast_reset): pass with exp.case(state=1): pulse = ZhPulse(qubit.native_gates.RX.pulse(0, 0)) - exp.play(signal=f"drive{q}", pulse=pulse.zhpulse) + exp.play(signal=f"drive{q}_0", pulse=pulse.zhpulse) @staticmethod def rearrange_sweepers(sweepers): @@ -1129,7 +1139,10 @@ def sweep_recursion(self, qubits, couplers, exp, exp_calib, exp_options): line = "drive" if pulse.type is PulseType.DRIVE else "measure" zhsweeper = ZhSweeper(pulse, sweeper, qubits[sweeper.pulses[0].qubit]).zhsweeper zhsweeper.uid = "frequency" # Changing the name from "frequency" breaks it f"frequency_{i} - exp_calib[f"{line}{pulse.qubit}"] = lo.SignalCalibration( + line_name = f"{line}{pulse.qubit}" + if line == "drive": + line_name += "_0" + exp_calib[line_name] = lo.SignalCalibration( oscillator=lo.Oscillator( frequency=zhsweeper, modulation_type=lo.ModulationType.HARDWARE, From da7351ac576d0a238e7369c6264d0fdafb3d3d14 Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Thu, 7 Dec 2023 10:29:45 +0400 Subject: [PATCH 30/66] Merging multiple drive lines into a single one --- src/qibolab/instruments/zhinst.py | 33 ++++++++++++------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 3a623c6f49..b068d13873 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -448,10 +448,7 @@ def register_readout_line(self, qubit, intermediate_frequency): def register_drive_line(self, qubit, intermediate_frequency): """Registers qubit drive line to calibration and signal map.""" q = qubit.name # pylint: disable=C0103 - for i, _ in enumerate(self.sub_sequences[f"drive{q}"]): - self.signal_map[f"drive{q}_{i}"] = self.device_setup.logical_signal_groups[f"q{q}"].logical_signals[ - "drive_line" - ] + self.signal_map[f"drive{q}"] = self.device_setup.logical_signal_groups[f"q{q}"].logical_signals["drive_line"] self.calibration[f"/logical_signal_groups/q{q}/drive_line"] = lo.SignalCalibration( oscillator=lo.Oscillator( frequency=intermediate_frequency, @@ -658,8 +655,7 @@ def create_exp(self, qubits, couplers, options): for qubit in qubits.values(): q = qubit.name # pylint: disable=C0103 if len(self.sequence[f"drive{q}"]) != 0: - for i, _ in enumerate(self.sub_sequences[f"drive{q}"]): - signals.append(lo.ExperimentSignal(f"drive{q}_{i}")) + signals.append(lo.ExperimentSignal(f"drive{q}")) if qubit.flux is not None: signals.append(lo.ExperimentSignal(f"flux{q}")) if len(self.sequence[f"readout{q}"]) != 0: @@ -729,28 +725,28 @@ def play_sweep_select_single(exp, qubit, pulse, section, parameters, partial_swe pulse.zhpulse.amplitude *= max(pulse.zhsweeper.values) pulse.zhsweeper.values /= max(pulse.zhsweeper.values) exp.play( - signal=f"{section}{qubit.name}_0", + signal=f"{section}{qubit.name}", pulse=pulse.zhpulse, amplitude=pulse.zhsweeper, phase=pulse.pulse.relative_phase, ) elif any("duration" in param for param in parameters): exp.play( - signal=f"{section}{qubit.name}_0", + signal=f"{section}{qubit.name}", pulse=pulse.zhpulse, length=pulse.zhsweeper, phase=pulse.pulse.relative_phase, ) elif any("relative_phase" in param for param in parameters): exp.play( - signal=f"{section}{qubit.name}_0", + signal=f"{section}{qubit.name}", pulse=pulse.zhpulse, phase=pulse.zhsweeper, # I believe this is the global phase sweep # increment_oscillator_phase=pulse.zhsweeper, # I believe this is the relative phase sweep ) elif "frequency" in partial_sweep.uid or partial_sweep.uid == "start": exp.play( - signal=f"{section}{qubit.name}_0", + signal=f"{section}{qubit.name}", pulse=pulse.zhpulse, phase=pulse.pulse.relative_phase, ) @@ -770,7 +766,7 @@ def play_sweep_select_dual(exp, qubit, pulse, section, parameters): sweeper_dur_index = pulse.zhsweepers.index(sweeper) exp.play( - signal=f"{section}{qubit.name}_0", + signal=f"{section}{qubit.name}", pulse=pulse.zhpulse, amplitude=pulse.zhsweepers[sweeper_amp_index], length=pulse.zhsweepers[sweeper_dur_index], @@ -859,7 +855,7 @@ def drive(self, exp, qubits): for pulse in sequence: if not isinstance(pulse, ZhSweeperLine): exp.delay( - signal=f"drive{q}_{j}", + signal=f"drive{q}", time=round(pulse.pulse.start * NANO_TO_SECONDS, 9) - time, ) time = round(pulse.pulse.duration * NANO_TO_SECONDS, 9) + round( @@ -870,20 +866,20 @@ def drive(self, exp, qubits): self.play_sweep(exp, qubit, pulse, section="drive") elif isinstance(pulse, ZhPulse): exp.play( - signal=f"drive{q}_{j}", + signal=f"drive{q}", pulse=pulse.zhpulse, phase=pulse.pulse.relative_phase, ) i += 1 elif isinstance(pulse, ZhSweeperLine): - exp.delay(signal=f"drive{q}_{j}", time=pulse.zhsweeper) + exp.delay(signal=f"drive{q}", time=pulse.zhsweeper) play_after = f"sequence_drive{q}_{j}" # Patch for T1 start, general ? if len(self.sequence[f"readout{q}"]) > 0 and isinstance( self.sequence[f"readout{q}"][0], ZhSweeperLine ): - exp.delay(signal=f"drive{q}_{j}", time=self.sequence[f"readout{q}"][0].zhsweeper) + exp.delay(signal=f"drive{q}", time=self.sequence[f"readout{q}"][0].zhsweeper) self.sequence[f"readout{q}"].remove(self.sequence[f"readout{q}"][0]) @staticmethod @@ -1034,7 +1030,7 @@ def fast_reset(self, exp, qubits, fast_reset): pass with exp.case(state=1): pulse = ZhPulse(qubit.native_gates.RX.pulse(0, 0)) - exp.play(signal=f"drive{q}_0", pulse=pulse.zhpulse) + exp.play(signal=f"drive{q}", pulse=pulse.zhpulse) @staticmethod def rearrange_sweepers(sweepers): @@ -1139,10 +1135,7 @@ def sweep_recursion(self, qubits, couplers, exp, exp_calib, exp_options): line = "drive" if pulse.type is PulseType.DRIVE else "measure" zhsweeper = ZhSweeper(pulse, sweeper, qubits[sweeper.pulses[0].qubit]).zhsweeper zhsweeper.uid = "frequency" # Changing the name from "frequency" breaks it f"frequency_{i} - line_name = f"{line}{pulse.qubit}" - if line == "drive": - line_name += "_0" - exp_calib[line_name] = lo.SignalCalibration( + exp_calib[f"{line}{pulse.qubit}"] = lo.SignalCalibration( oscillator=lo.Oscillator( frequency=zhsweeper, modulation_type=lo.ModulationType.HARDWARE, From 52d879bb4eb1180081efad13e21c3ddf7b092b45 Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Thu, 7 Dec 2023 10:36:46 +0400 Subject: [PATCH 31/66] Removed duplicate code lines --- src/qibolab/instruments/zhinst.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index b068d13873..2b0213a073 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -584,10 +584,7 @@ def sequence_zh(self, sequence, qubits, couplers, sweepers): # Fill the sequences with pulses according to their lines in temporal order for pulse in sequence: - if isinstance(pulse, CouplerFluxPulse): - zhsequence[f"{pulse.type.name.lower()}{pulse.qubit}"].append(ZhPulse(pulse)) - else: - zhsequence[f"{pulse.type.name.lower()}{pulse.qubit}"].append(ZhPulse(pulse)) + zhsequence[f"{pulse.type.name.lower()}{pulse.qubit}"].append(ZhPulse(pulse)) # Mess that gets the sweeper and substitutes the pulse it sweeps in the right place From 89661e18c9a054eb76a4bde57a98e61756794f8d Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Thu, 7 Dec 2023 11:02:19 +0400 Subject: [PATCH 32/66] Fixed drive pulses delays --- src/qibolab/instruments/zhinst.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 2b0213a073..ce3f1b3d77 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -847,7 +847,6 @@ def drive(self, exp, qubits): if len(self.sequence[f"drive{q}"]) != 0: play_after = None for j, sequence in enumerate(self.sub_sequences[f"drive{q}"]): - time = 0 with exp.section(uid=f"sequence_drive{q}_{j}", play_after=play_after): for pulse in sequence: if not isinstance(pulse, ZhSweeperLine): From 4a7247010bd7908d9aa39f204f210f3d6cc1ac52 Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Thu, 7 Dec 2023 16:38:39 +0400 Subject: [PATCH 33/66] Splitting flux and coupler flux into sub_sequences --- src/qibolab/instruments/zhinst.py | 119 ++++++++++++++++++++---------- 1 file changed, 80 insertions(+), 39 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index ce3f1b3d77..05365d50b5 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -510,27 +510,51 @@ def frequency_from_pulses(qubits, sequence): if pulse.type is PulseType.DRIVE: qubit.drive_frequency = pulse.frequency - def experiment_flow(self, qubits, couplers, sequence, options, sweepers=[]): - """ - Create the experiment object for the devices, following the steps separated one on each method: - Translation, Calibration, Experiment Definition. + def create_sub_sequence(self, line_name, qubits): """ - self.sequence_zh(sequence, qubits, couplers, sweepers) + Create a list of sequences for each measurement. - # TODO: Move this part to a different function - self.sub_sequences = {} # initialize it in __init__? + Args: + line_name (str): Name of the line from which extract the sequence. + qubits (dict[str, Qubit]|dict[str, Coupler]): + qubits or couplers for the platform. + """ for qubit in qubits.values(): q = qubit.name # pylint: disable=C0103 measurements = self.sequence[f"readout{q}"] - drives = self.sequence[f"drive{q}"] - drive_sequences = [[] for _ in range(len(measurements))] + pulses = self.sequence[f"{line_name}{q}"] + pulse_sequences = [[] for _ in measurements] measurement_index = 0 - for drive in drives: - if drive.pulse.finish > measurements[measurement_index].pulse.start: + for pulse in pulses: + if pulse.pulse.finish > measurements[measurement_index].pulse.start: measurement_index += 1 - drive_sequences[measurement_index].append(drive) - self.sub_sequences[f"drive{q}"] = drive_sequences + if measurement_index == len(measurements): + pulse_sequences.append([]) + elif measurement_index > len(measurements): + measurement_index = len(measurements) + pulse_sequences[measurement_index].append(pulse) + self.sub_sequences[f"{line_name}{q}"] = pulse_sequences + + def create_sub_sequences(self, qubits, couplers): + """ + Create subsequences for different lines (drive, flux, coupler flux). + Args: + qubits (dict[str, Qubit]): qubits for the platform. + couplers (dict[str, Coupler]): couplers for the platform. + """ + self.sub_sequences = {} # initialize it in __init__? + self.create_sub_sequence("drive", qubits) + self.create_sub_sequence("flux", qubits) + self.create_sub_sequence("couplerflux", couplers) + + def experiment_flow(self, qubits, couplers, sequence, options, sweepers=[]): + """ + Create the experiment object for the devices, following the steps separated one on each method: + Translation, Calibration, Experiment Definition. + """ + self.sequence_zh(sequence, qubits, couplers, sweepers) + self.create_sub_sequences(qubits, couplers) self.calibration_step(qubits, couplers, options) self.create_exp(qubits, couplers, options) @@ -817,26 +841,30 @@ def flux(self, exp, qubits): """qubit flux for bias sweep or pulses""" for qubit in qubits.values(): q = qubit.name # pylint: disable=C0103 - with exp.section(uid=f"sequence_bias{q}"): - i = 0 - time = 0 - for pulse in self.sequence[f"flux{q}"]: - if not isinstance(pulse, ZhSweeperLine): - pulse.zhpulse.uid += str(i) - exp.delay( - signal=f"flux{q}", - time=round(pulse.pulse.start * NANO_TO_SECONDS, 9) - time, - ) - time = round(pulse.pulse.duration * NANO_TO_SECONDS, 9) + round( - pulse.pulse.start * NANO_TO_SECONDS, 9 - ) - if isinstance(pulse, ZhSweeperLine): - self.play_sweep(exp, qubit, pulse, section="flux") - elif isinstance(pulse, ZhSweeper): - self.play_sweep(exp, qubit, pulse, section="flux") - elif isinstance(pulse, ZhPulse): - exp.play(signal=f"flux{q}", pulse=pulse.zhpulse) - i += 1 + time = 0 + i = 0 + if len(self.sequence[f"flux{q}"]) != 0: + play_after = None + for j, sequence in enumerate(self.sub_sequences[f"flux{q}"]): + with exp.section(uid=f"sequence_bias{q}_{j}", play_after=play_after): + for pulse in sequence: + if not isinstance(pulse, ZhSweeperLine): + pulse.zhpulse.uid += str(i) + exp.delay( + signal=f"flux{q}", + time=round(pulse.pulse.start * NANO_TO_SECONDS, 9) - time, + ) + time = round(pulse.pulse.duration * NANO_TO_SECONDS, 9) + round( + pulse.pulse.start * NANO_TO_SECONDS, 9 + ) + if isinstance(pulse, ZhSweeperLine): + self.play_sweep(exp, qubit, pulse, section="flux") + elif isinstance(pulse, ZhSweeper): + self.play_sweep(exp, qubit, pulse, section="flux") + elif isinstance(pulse, ZhPulse): + exp.play(signal=f"flux{q}", pulse=pulse.zhpulse) + i += 1 + play_after = f"sequence_bias{q}_{j}" def drive(self, exp, qubits): """qubit driving pulses""" @@ -888,13 +916,22 @@ def play_after_set(sequence, ptype): qubit_after = pulse.qubit return f"sequence_{ptype}{qubit_after}" - def find_subsequence_finish(self, i, line, qubits): + def find_subsequence_finish(self, measure_number, line, qubits): + """ + Find the finishing time and qubit for a given sequence. + + Args: + measure_number (int): number of the measure pulse. + line (str): line from which measure the finishing time. + e.g.: "drive", "flux", "couplerflux" + qubits (dict[str, Qubit]): qubits from which measure the finishing time. + """ time_finish = 0 qubit_finish = None for qubit in qubits: - if len(self.sub_sequences[f"{line}{qubit}"]) <= i: + if len(self.sub_sequences[f"{line}{qubit}"]) <= measure_number: continue - for pulse in self.sub_sequences[f"{line}{qubit}"][i]: + for pulse in self.sub_sequences[f"{line}{qubit}"][measure_number]: if pulse.pulse.finish > time_finish: time_finish = pulse.pulse.finish qubit_finish = qubit @@ -921,12 +958,16 @@ def measure_relax(self, exp, qubits, relaxation_time, acquisition_type): for i, (pulses, qubits, iq_angles) in enumerate( zip(readout_schedule.values(), qubit_readout_schedule.values(), iq_angle_readout_schedule.values()) ): - _, qd_finish_q = self.find_subsequence_finish(i, "drive", qubits) + # TODO: This newly added section must be improved! + qd_finish_t, qd_finish_q = self.find_subsequence_finish(i, "drive", qubits) + qf_finish_t, qf_finish_q = self.find_subsequence_finish(i, "flux", qubits) play_after = None - if qd_finish_q is None: + if qd_finish_q is None and qf_finish_q is None: play_after = None - else: + elif qd_finish_t > qf_finish_t: play_after = f"sequence_drive{qd_finish_q}_{i}" + else: + play_after = f"sequence_bias{qf_finish_q}_{i}" # Section on the outside loop allows for multiplex with exp.section(uid=f"sequence_measure_{i}", play_after=play_after): for pulse, q, iq_angle in zip(pulses, qubits, iq_angles): From e7992c64b5821e8996eabd0acef63a2832fe402d Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Fri, 8 Dec 2023 11:41:58 +0400 Subject: [PATCH 34/66] Improved detection of longest sub_sequence --- src/qibolab/instruments/zhinst.py | 34 +++++++++++++++++++------------ 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 05365d50b5..5a21c3dc84 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -329,6 +329,8 @@ def __init__(self, name, device_setup, use_emulation=False, time_of_flight=0.0, "Zurich pulse sequence" self.sequence_qibo = None # Remove if able + self.sub_sequences = {} + "Sub sequences between each measurement" self.sweepers = [] self.nt_sweeps = None @@ -543,7 +545,7 @@ def create_sub_sequences(self, qubits, couplers): qubits (dict[str, Qubit]): qubits for the platform. couplers (dict[str, Coupler]): couplers for the platform. """ - self.sub_sequences = {} # initialize it in __init__? + self.sub_sequences = {} self.create_sub_sequence("drive", qubits) self.create_sub_sequence("flux", qubits) self.create_sub_sequence("couplerflux", couplers) @@ -927,20 +929,20 @@ def find_subsequence_finish(self, measure_number, line, qubits): qubits (dict[str, Qubit]): qubits from which measure the finishing time. """ time_finish = 0 - qubit_finish = None + sequence_finish = "None" for qubit in qubits: if len(self.sub_sequences[f"{line}{qubit}"]) <= measure_number: continue for pulse in self.sub_sequences[f"{line}{qubit}"][measure_number]: if pulse.pulse.finish > time_finish: time_finish = pulse.pulse.finish - qubit_finish = qubit - return time_finish, qubit_finish + sequence_finish = f"{line}{qubit}" + return time_finish, sequence_finish # For pulsed spectroscopy, set integration_length and either measure_pulse or measure_pulse_length. # For CW spectroscopy, set only integration_length and do not specify the measure signal. # For all other measurements, set either length or pulse for both the measure pulse and integration kernel. - def measure_relax(self, exp, qubits, relaxation_time, acquisition_type): + def measure_relax(self, exp, qubits, relaxation_time, acquisition_type, couplers): """qubit readout pulse, data acquisition and qubit relaxation""" readout_schedule = defaultdict(list) qubit_readout_schedule = defaultdict(list) @@ -958,16 +960,22 @@ def measure_relax(self, exp, qubits, relaxation_time, acquisition_type): for i, (pulses, qubits, iq_angles) in enumerate( zip(readout_schedule.values(), qubit_readout_schedule.values(), iq_angle_readout_schedule.values()) ): - # TODO: This newly added section must be improved! - qd_finish_t, qd_finish_q = self.find_subsequence_finish(i, "drive", qubits) - qf_finish_t, qf_finish_q = self.find_subsequence_finish(i, "flux", qubits) - play_after = None - if qd_finish_q is None and qf_finish_q is None: + qd_finish = self.find_subsequence_finish(i, "drive", qubits) + qf_finish = self.find_subsequence_finish(i, "flux", qubits) + cf_finish = self.find_subsequence_finish(i, "couplerflux", couplers) + finish_times = np.array( + [ + qd_finish, + qf_finish, + cf_finish, + ], + dtype=[("finish", "i4"), ("line", "U10")], + ) + latest_sequence = finish_times[finish_times["finish"].argmax()] + if latest_sequence["line"] == "None": play_after = None - elif qd_finish_t > qf_finish_t: - play_after = f"sequence_drive{qd_finish_q}_{i}" else: - play_after = f"sequence_bias{qf_finish_q}_{i}" + play_after = f"sequence_{latest_sequence['line']}_{i}" # Section on the outside loop allows for multiplex with exp.section(uid=f"sequence_measure_{i}", play_after=play_after): for pulse, q, iq_angle in zip(pulses, qubits, iq_angles): From a0bcd56d5195ac43778d1f389d16dcc099361321 Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Fri, 8 Dec 2023 11:46:21 +0400 Subject: [PATCH 35/66] Missing function argument --- src/qibolab/instruments/zhinst.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 5a21c3dc84..8492acb1c2 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -737,7 +737,7 @@ def select_exp(self, exp, qubits, couplers, exp_options): self.drive(exp, qubits) elif "flux" in str(self.sequence): self.flux(exp, qubits) - self.measure_relax(exp, qubits, exp_options.relaxation_time, exp_options.acquisition_type) + self.measure_relax(exp, qubits, exp_options.relaxation_time, exp_options.acquisition_type, couplers) if exp_options.fast_reset is not False: self.fast_reset(exp, qubits, exp_options.fast_reset) From a71385293d01fd2da2b8e6b827c7c25cdb868800 Mon Sep 17 00:00:00 2001 From: Juan Cereijo Date: Fri, 8 Dec 2023 12:56:29 +0400 Subject: [PATCH 36/66] Update src/qibolab/serialize.py Co-authored-by: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> --- src/qibolab/serialize.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/qibolab/serialize.py b/src/qibolab/serialize.py index 979362238c..533451abda 100644 --- a/src/qibolab/serialize.py +++ b/src/qibolab/serialize.py @@ -152,8 +152,6 @@ def dump_runcard(platform: Platform, path: Path): "instruments": dump_instruments(platform.instruments), } - print(platform) - if platform.couplers: settings["couplers"] = list(platform.couplers) settings["topology"] = {coupler: list(pair) for pair, coupler in zip(platform.pairs, platform.couplers)} From 4de2fe49e665548ccb35354a1e30433fc38cf6c9 Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Fri, 8 Dec 2023 13:00:00 +0400 Subject: [PATCH 37/66] Comments --- src/qibolab/platform.py | 3 --- src/qibolab/serialize.py | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/qibolab/platform.py b/src/qibolab/platform.py index a77f791aa5..770683c64e 100644 --- a/src/qibolab/platform.py +++ b/src/qibolab/platform.py @@ -2,7 +2,6 @@ from collections import defaultdict from dataclasses import dataclass, field, replace -from pathlib import Path from typing import Dict, List, Optional, Tuple import networkx as nx @@ -96,8 +95,6 @@ class Platform: couplers: CouplerMap = field(default_factory=dict) """Dictionary mapping coupler names to :class:`qibolab.couplers.Coupler` objects.""" - kernel_folder: Optional[Path] = None - """Folder where each qubit kernels are stored""" is_connected: bool = False """Flag for whether we are connected to the physical instruments.""" diff --git a/src/qibolab/serialize.py b/src/qibolab/serialize.py index 533451abda..77096f465e 100644 --- a/src/qibolab/serialize.py +++ b/src/qibolab/serialize.py @@ -42,8 +42,9 @@ def load_qubits(runcard: dict, extras_folder: Path = None) -> Tuple[QubitMap, Co """ qubits = {q: Qubit(q, **char) for q, char in runcard["characterization"]["single_qubit"].items()} if extras_folder is not None: + single_qubit = runcard["characterization"]["single_qubit"] for qubit in qubits.values(): - qubit.kernel_path = extras_folder / runcard["characterization"]["single_qubit"][qubit.name]["kernel_path"] + qubit.kernel_path = extras_folder / single_qubit[qubit.name]["kernel_path"] couplers = {} pairs = {} if "coupler" in runcard["characterization"]: From f5595e0d12888e8a71b9478b25e0032c6793e280 Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Fri, 8 Dec 2023 14:17:16 +0400 Subject: [PATCH 38/66] Improved sub_sequences definition --- src/qibolab/instruments/zhinst.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 8492acb1c2..d2d0f27fb8 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -526,14 +526,12 @@ def create_sub_sequence(self, line_name, qubits): measurements = self.sequence[f"readout{q}"] pulses = self.sequence[f"{line_name}{q}"] pulse_sequences = [[] for _ in measurements] + pulse_sequences.append([]) measurement_index = 0 for pulse in pulses: - if pulse.pulse.finish > measurements[measurement_index].pulse.start: - measurement_index += 1 - if measurement_index == len(measurements): - pulse_sequences.append([]) - elif measurement_index > len(measurements): - measurement_index = len(measurements) + if measurement_index < len(measurements): + if pulse.pulse.finish > measurements[measurement_index].pulse.start: + measurement_index += 1 pulse_sequences[measurement_index].append(pulse) self.sub_sequences[f"{line_name}{q}"] = pulse_sequences From 1b12c1032bb2cd4c82661359776835da5900d089 Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Fri, 8 Dec 2023 15:00:21 +0400 Subject: [PATCH 39/66] Fixed measure start time test --- tests/test_instruments_zhinst.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/test_instruments_zhinst.py b/tests/test_instruments_zhinst.py index 452b37e811..cff18ed4ab 100644 --- a/tests/test_instruments_zhinst.py +++ b/tests/test_instruments_zhinst.py @@ -1,4 +1,5 @@ import math +import re import numpy as np import pytest @@ -788,6 +789,30 @@ def test_experiment_sweep_2d_specific(connected_platform, instrument): assert len(results[ro_pulses[qubit].serial]) > 0 + def get_previous_subsequence_finish(name): + """ + Look recursively for sub_section finish times. + """ + signal_name = re.sub("sequence_", "", name) + signal_name = re.sub(r"_\d+$", "", signal_name) + signal_name = re.sub(r"flux", "bias", signal_name) + finish = 0 + for section in IQM5q.experiment.sections[0].children: + if section.uid == name: + for pulse in section.children: + if pulse.signal == signal_name: + try: + finish += pulse.time + except AttributeError: + # not a laboneq Delay class object, skipping + pass + try: + finish += pulse.pulse.length + except AttributeError: + # not a laboneq PlayPulse class object, skipping + pass + return finish + def test_experiment_measurement_sequence(dummy_qrc): platform = create_platform("zurich") @@ -817,6 +842,7 @@ def test_experiment_measurement_sequence(dummy_qrc): measure_start = 0 for section in IQM5q.experiment.sections[0].children: if section.uid == "sequence_measure_0": + measure_start += get_previous_subsequence_finish(section.play_after) assert section.play_after is None for pulse in section.children: try: From 2d8c18d9cb5fe14b5b7eb6d6ed331b279c0ef600 Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Fri, 8 Dec 2023 15:06:21 +0400 Subject: [PATCH 40/66] Indent fix --- tests/test_instruments_zhinst.py | 47 ++++++++++++++++---------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/tests/test_instruments_zhinst.py b/tests/test_instruments_zhinst.py index cff18ed4ab..c94669f165 100644 --- a/tests/test_instruments_zhinst.py +++ b/tests/test_instruments_zhinst.py @@ -789,29 +789,30 @@ def test_experiment_sweep_2d_specific(connected_platform, instrument): assert len(results[ro_pulses[qubit].serial]) > 0 - def get_previous_subsequence_finish(name): - """ - Look recursively for sub_section finish times. - """ - signal_name = re.sub("sequence_", "", name) - signal_name = re.sub(r"_\d+$", "", signal_name) - signal_name = re.sub(r"flux", "bias", signal_name) - finish = 0 - for section in IQM5q.experiment.sections[0].children: - if section.uid == name: - for pulse in section.children: - if pulse.signal == signal_name: - try: - finish += pulse.time - except AttributeError: - # not a laboneq Delay class object, skipping - pass - try: - finish += pulse.pulse.length - except AttributeError: - # not a laboneq PlayPulse class object, skipping - pass - return finish + +def get_previous_subsequence_finish(name): + """ + Look recursively for sub_section finish times. + """ + signal_name = re.sub("sequence_", "", name) + signal_name = re.sub(r"_\d+$", "", signal_name) + signal_name = re.sub(r"flux", "bias", signal_name) + finish = 0 + for section in IQM5q.experiment.sections[0].children: + if section.uid == name: + for pulse in section.children: + if pulse.signal == signal_name: + try: + finish += pulse.time + except AttributeError: + # not a laboneq Delay class object, skipping + pass + try: + finish += pulse.pulse.length + except AttributeError: + # not a laboneq PlayPulse class object, skipping + pass + return finish def test_experiment_measurement_sequence(dummy_qrc): From 66a3e65ba9447bb8b6e8042471e790fdbe034721 Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Fri, 8 Dec 2023 15:16:14 +0400 Subject: [PATCH 41/66] Fixed test --- tests/test_instruments_zhinst.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_instruments_zhinst.py b/tests/test_instruments_zhinst.py index c94669f165..145ebe411e 100644 --- a/tests/test_instruments_zhinst.py +++ b/tests/test_instruments_zhinst.py @@ -790,7 +790,7 @@ def test_experiment_sweep_2d_specific(connected_platform, instrument): assert len(results[ro_pulses[qubit].serial]) > 0 -def get_previous_subsequence_finish(name): +def get_previous_subsequence_finish(instrument, name): """ Look recursively for sub_section finish times. """ @@ -798,7 +798,7 @@ def get_previous_subsequence_finish(name): signal_name = re.sub(r"_\d+$", "", signal_name) signal_name = re.sub(r"flux", "bias", signal_name) finish = 0 - for section in IQM5q.experiment.sections[0].children: + for section in instrument.experiment.sections[0].children: if section.uid == name: for pulse in section.children: if pulse.signal == signal_name: @@ -843,7 +843,7 @@ def test_experiment_measurement_sequence(dummy_qrc): measure_start = 0 for section in IQM5q.experiment.sections[0].children: if section.uid == "sequence_measure_0": - measure_start += get_previous_subsequence_finish(section.play_after) + measure_start += get_previous_subsequence_finish(IQM5q, section.play_after) assert section.play_after is None for pulse in section.children: try: From c8dc6819deef96dad37c6f5e613995b65b2d6dd9 Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Fri, 8 Dec 2023 15:43:45 +0400 Subject: [PATCH 42/66] Removed assertion for play_after --- tests/test_instruments_zhinst.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_instruments_zhinst.py b/tests/test_instruments_zhinst.py index 145ebe411e..129b488ea3 100644 --- a/tests/test_instruments_zhinst.py +++ b/tests/test_instruments_zhinst.py @@ -844,7 +844,6 @@ def test_experiment_measurement_sequence(dummy_qrc): for section in IQM5q.experiment.sections[0].children: if section.uid == "sequence_measure_0": measure_start += get_previous_subsequence_finish(IQM5q, section.play_after) - assert section.play_after is None for pulse in section.children: try: if pulse.signal == "measure0": From a7bba091bc8b00262d869a21088d2e488fd71d3e Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Fri, 8 Dec 2023 15:57:12 +0400 Subject: [PATCH 43/66] Measurement pulse starts after drive --- tests/test_instruments_zhinst.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_instruments_zhinst.py b/tests/test_instruments_zhinst.py index 129b488ea3..e1b747cab7 100644 --- a/tests/test_instruments_zhinst.py +++ b/tests/test_instruments_zhinst.py @@ -825,7 +825,7 @@ def test_experiment_measurement_sequence(dummy_qrc): platform.qubits = qubits couplers = {} - readout_pulse_start = 50 + readout_pulse_start = 40 for qubit in qubits: qubit_drive_pulse_1 = platform.create_qubit_drive_pulse(qubit, start=0, duration=40) From 58a359936248cdd82272f71576ecfd2120bbcc27 Mon Sep 17 00:00:00 2001 From: PiergiorgioButtarini Date: Mon, 11 Dec 2023 14:32:47 +0400 Subject: [PATCH 44/66] works with flux channel disconnected --- src/qibolab/instruments/qblox/cluster_qcm_bb.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/qibolab/instruments/qblox/cluster_qcm_bb.py b/src/qibolab/instruments/qblox/cluster_qcm_bb.py index 52dc85b90b..b9e795dc61 100644 --- a/src/qibolab/instruments/qblox/cluster_qcm_bb.py +++ b/src/qibolab/instruments/qblox/cluster_qcm_bb.py @@ -295,9 +295,11 @@ def _get_next_sequencer(self, port, frequency, qubits: dict): Exception = If attempting to set a parameter without a connection to the instrument. """ # select the qubit relative to specific port + qubit = None for _qubit in qubits.values(): - if _qubit.flux.port.name == port and _qubit.flux.port.module.name == self.name: - qubit = _qubit + if hasattr(_qubit.flux, "port"): + if _qubit.flux.port.name == port and _qubit.flux.port.module.name == self.name: + qubit = _qubit # select a new sequencer and configure it as required next_sequencer_number = self._free_sequencers_numbers.pop(0) if next_sequencer_number != self.DEFAULT_SEQUENCERS[port]: @@ -328,11 +330,10 @@ def _get_next_sequencer(self, port, frequency, qubits: dict): # value=qubits[qubit].sweetspot, # ) - self.ports[port].offset = qubit.sweetspot - + self.ports[port].offset = qubit.sweetspot if qubit else 0 # create sequencer wrapper sequencer = Sequencer(next_sequencer_number) - sequencer.qubit = qubit.name + sequencer.qubit = qubit.name if qubit else None return sequencer def get_if(self, pulse): From e1cc401b67c37d85cf79ece15e9144fc6ee22a0b Mon Sep 17 00:00:00 2001 From: Gabriele Palazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Mon, 11 Dec 2023 14:56:21 +0400 Subject: [PATCH 45/66] Apply suggestions from code review Co-authored-by: Juan Cereijo --- src/qibolab/instruments/zhinst.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 185e277846..67f5d7a431 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -722,7 +722,7 @@ def select_exp(self, exp, qubits, couplers, exp_options): self.drive(exp, qubits) elif "flux" in str(self.sequence): self.flux(exp, qubits) - self.measure_relax(exp, qubits, exp_options.relaxation_time, exp_options.acquisition_type, couplers) + self.measure_relax(exp, qubits, couplers, exp_options.relaxation_time, exp_options.acquisition_type) if exp_options.fast_reset is not False: self.fast_reset(exp, qubits, exp_options.fast_reset) @@ -911,7 +911,7 @@ def find_subsequence_finish(self, measure_number, line, qubits): measure_number (int): number of the measure pulse. line (str): line from which measure the finishing time. e.g.: "drive", "flux", "couplerflux" - qubits (dict[str, Qubit]): qubits from which measure the finishing time. + quantum_elements (dict[str, Qubit]|dict[str, Coupler]): qubits or couplers from which measure the finishing time. """ time_finish = 0 sequence_finish = "None" From 9cc90fbbee3f768f8b3af3b6aca6c82cb2216b62 Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Mon, 11 Dec 2023 15:25:15 +0400 Subject: [PATCH 46/66] Fixed review suggestions + added type hints --- src/qibolab/instruments/zhinst.py | 46 ++++++++++++++----------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 67f5d7a431..5d25bf401e 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -4,7 +4,7 @@ import os from collections import defaultdict from dataclasses import dataclass, replace -from typing import Tuple +from typing import Dict, Tuple, Union import laboneq._token import laboneq.simple as lo @@ -512,17 +512,16 @@ def frequency_from_pulses(qubits, sequence): if pulse.type is PulseType.DRIVE: qubit.drive_frequency = pulse.frequency - def create_sub_sequence(self, line_name, qubits): + def create_sub_sequence(self, line_name: str, quantum_elements: Union[Dict[str, Qubit], Dict[str, Coupler]]): """ Create a list of sequences for each measurement. Args: line_name (str): Name of the line from which extract the sequence. - qubits (dict[str, Qubit]|dict[str, Coupler]): - qubits or couplers for the platform. + quantum_elements (dict[str, Qubit]|dict[str, Coupler]): qubits or couplers for the platform. """ - for qubit in qubits.values(): - q = qubit.name # pylint: disable=C0103 + for quantum_element in quantum_elements.values(): + q = quantum_element.name # pylint: disable=C0103 measurements = self.sequence[f"readout{q}"] pulses = self.sequence[f"{line_name}{q}"] pulse_sequences = [[] for _ in measurements] @@ -535,7 +534,7 @@ def create_sub_sequence(self, line_name, qubits): pulse_sequences[measurement_index].append(pulse) self.sub_sequences[f"{line_name}{q}"] = pulse_sequences - def create_sub_sequences(self, qubits, couplers): + def create_sub_sequences(self, qubits: Dict[str, Qubit], couplers: Dict[str, Coupler]): """ Create subsequences for different lines (drive, flux, coupler flux). @@ -893,41 +892,38 @@ def drive(self, exp, qubits): exp.delay(signal=f"drive{q}", time=self.sequence[f"readout{q}"][0].zhsweeper) self.sequence[f"readout{q}"].remove(self.sequence[f"readout{q}"][0]) - @staticmethod - def play_after_set(sequence, ptype): - """Selects after which section the measurement goes""" - longest = 0 - for pulse in sequence: - if longest < pulse.finish: - longest = pulse.finish - qubit_after = pulse.qubit - return f"sequence_{ptype}{qubit_after}" - - def find_subsequence_finish(self, measure_number, line, qubits): + def find_subsequence_finish( + self, measurament_number: int, line: str, quantum_elements: Union[Dict[str, Qubit], Dict[str, Coupler]] + ) -> Tuple[int, str]: """ Find the finishing time and qubit for a given sequence. Args: - measure_number (int): number of the measure pulse. + measurament_number (int): number of the measure pulse. line (str): line from which measure the finishing time. e.g.: "drive", "flux", "couplerflux" - quantum_elements (dict[str, Qubit]|dict[str, Coupler]): qubits or couplers from which measure the finishing time. + quantum_elements (dict[str, Qubit]|dict[str, Coupler]): qubits or couplers from which measure the finishing time. + + Returns: + time_finish (int): Finish time of the last pulse of the subsequence before the measurement. + sequence_finish (str): Name of the last subsequence before measurement. + If there are no sequences after the previous measurement, use "None". """ time_finish = 0 sequence_finish = "None" - for qubit in qubits: - if len(self.sub_sequences[f"{line}{qubit}"]) <= measure_number: + for quantum_element in quantum_elements: + if len(self.sub_sequences[f"{line}{quantum_element}"]) <= measurament_number: continue - for pulse in self.sub_sequences[f"{line}{qubit}"][measure_number]: + for pulse in self.sub_sequences[f"{line}{quantum_element}"][measurament_number]: if pulse.pulse.finish > time_finish: time_finish = pulse.pulse.finish - sequence_finish = f"{line}{qubit}" + sequence_finish = f"{line}{quantum_element}" return time_finish, sequence_finish # For pulsed spectroscopy, set integration_length and either measure_pulse or measure_pulse_length. # For CW spectroscopy, set only integration_length and do not specify the measure signal. # For all other measurements, set either length or pulse for both the measure pulse and integration kernel. - def measure_relax(self, exp, qubits, relaxation_time, acquisition_type, couplers): + def measure_relax(self, exp, qubits, couplers, relaxation_time, acquisition_type): """qubit readout pulse, data acquisition and qubit relaxation""" readout_schedule = defaultdict(list) qubit_readout_schedule = defaultdict(list) From 89aef4a024b9deadf68a81881d3ce43136e54331 Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Mon, 11 Dec 2023 15:28:23 +0400 Subject: [PATCH 47/66] Typo in variable name --- src/qibolab/instruments/zhinst.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 5d25bf401e..b151aed4b1 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -893,13 +893,13 @@ def drive(self, exp, qubits): self.sequence[f"readout{q}"].remove(self.sequence[f"readout{q}"][0]) def find_subsequence_finish( - self, measurament_number: int, line: str, quantum_elements: Union[Dict[str, Qubit], Dict[str, Coupler]] + self, measurement_number: int, line: str, quantum_elements: Union[Dict[str, Qubit], Dict[str, Coupler]] ) -> Tuple[int, str]: """ Find the finishing time and qubit for a given sequence. Args: - measurament_number (int): number of the measure pulse. + measurement_number (int): number of the measure pulse. line (str): line from which measure the finishing time. e.g.: "drive", "flux", "couplerflux" quantum_elements (dict[str, Qubit]|dict[str, Coupler]): qubits or couplers from which measure the finishing time. @@ -912,9 +912,9 @@ def find_subsequence_finish( time_finish = 0 sequence_finish = "None" for quantum_element in quantum_elements: - if len(self.sub_sequences[f"{line}{quantum_element}"]) <= measurament_number: + if len(self.sub_sequences[f"{line}{quantum_element}"]) <= measurement_number: continue - for pulse in self.sub_sequences[f"{line}{quantum_element}"][measurament_number]: + for pulse in self.sub_sequences[f"{line}{quantum_element}"][measurement_number]: if pulse.pulse.finish > time_finish: time_finish = pulse.pulse.finish sequence_finish = f"{line}{quantum_element}" From 63e0c872ada64ae0ba87648365e445feeea0187a Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Mon, 11 Dec 2023 15:36:48 +0400 Subject: [PATCH 48/66] updated memory limit unrolling --- src/qibolab/instruments/zhinst.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 185e277846..b20b73a9b2 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -20,7 +20,7 @@ from qibolab.couplers import Coupler from qibolab.instruments.abstract import INSTRUMENTS_DATA_FOLDER, Controller from qibolab.instruments.port import Port -from qibolab.instruments.unrolling import batch_max_readout +from qibolab.instruments.unrolling import batch_max_sequences from qibolab.pulses import CouplerFluxPulse, FluxPulse, PulseSequence, PulseType from qibolab.qubits import Qubit from qibolab.sweeper import Parameter @@ -54,8 +54,8 @@ SWEEPER_BIAS = {"bias"} SWEEPER_START = {"start"} -MAX_MEASUREMENTS = 32 -"""Maximum number of readout pulses in a single sequence.""" +MAX_SEQUENCES = 150 +"""Maximum number of subsequences in a single sequence.""" def select_pulse(pulse, pulse_type): @@ -583,7 +583,7 @@ def play(self, qubits, couplers, sequence, options): results[serial] = results[qubit] = options.results_type(data) # html containing the pulse sequence schedule - # lo.show_pulse_sheet("pulses", self.exp) + lo.show_pulse_sheet("pulses", self.exp) return results def sequence_zh(self, sequence, qubits, couplers, sweepers): @@ -1125,7 +1125,7 @@ def sweep(self, qubits, couplers, sequence: PulseSequence, options, *sweepers): self.offsets_off() # html containing the pulse sequence schedule - # lo.show_pulse_sheet("pulses", self.exp) + lo.show_pulse_sheet("pulses", self.exp) return results def sweep_recursion(self, qubits, couplers, exp, exp_calib, exp_options): @@ -1237,7 +1237,7 @@ def sweep_recursion_nt(self, qubits, couplers, options, exp, exp_calib): self.define_exp(qubits, couplers, options, exp, exp_calib) def split_batches(self, sequences): - return batch_max_readout(sequences, MAX_MEASUREMENTS) + return batch_max_sequences(sequences, MAX_SEQUENCES) def play_sim(self, qubits, sequence, options, sim_time): """Play pulse sequence""" From 93ff2a13ba5a192d75f752dfe7a3c2d3674e6338 Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Mon, 11 Dec 2023 15:37:49 +0400 Subject: [PATCH 49/66] comment pulse sheet --- src/qibolab/instruments/zhinst.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 40bc2e0d29..d70c04cadb 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -582,7 +582,7 @@ def play(self, qubits, couplers, sequence, options): results[serial] = results[qubit] = options.results_type(data) # html containing the pulse sequence schedule - lo.show_pulse_sheet("pulses", self.exp) + # lo.show_pulse_sheet("pulses", self.exp) return results def sequence_zh(self, sequence, qubits, couplers, sweepers): @@ -1121,7 +1121,7 @@ def sweep(self, qubits, couplers, sequence: PulseSequence, options, *sweepers): self.offsets_off() # html containing the pulse sequence schedule - lo.show_pulse_sheet("pulses", self.exp) + # lo.show_pulse_sheet("pulses", self.exp) return results def sweep_recursion(self, qubits, couplers, exp, exp_calib, exp_options): From 41edbd948d2a4c1f03b08e1494246f4b925f475f Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Mon, 11 Dec 2023 15:51:21 +0400 Subject: [PATCH 50/66] fix tests --- tests/test_instruments_zhinst.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_instruments_zhinst.py b/tests/test_instruments_zhinst.py index ac7b95b7ad..2f091aa306 100644 --- a/tests/test_instruments_zhinst.py +++ b/tests/test_instruments_zhinst.py @@ -681,10 +681,10 @@ def test_split_batches(dummy_qrc): sequence.add(platform.create_MZ_pulse(0, start=measurement_start)) sequence.add(platform.create_MZ_pulse(1, start=measurement_start)) - batches = list(instrument.split_batches(20 * [sequence])) + batches = list(instrument.split_batches(200 * [sequence])) assert len(batches) == 2 - assert len(batches[0]) == 16 - assert len(batches[1]) == 4 + assert len(batches[0]) == 150 + assert len(batches[1]) == 50 @pytest.fixture(scope="module") From 8a4e302356989e0a1d3f6c40a834cba63fc42f00 Mon Sep 17 00:00:00 2001 From: PiergiorgioButtarini Date: Mon, 11 Dec 2023 17:44:38 +0400 Subject: [PATCH 51/66] fix: added compatibility with empty flux channels --- src/qibolab/instruments/qblox/cluster_qcm_bb.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/qibolab/instruments/qblox/cluster_qcm_bb.py b/src/qibolab/instruments/qblox/cluster_qcm_bb.py index b9e795dc61..858cdaa3f2 100644 --- a/src/qibolab/instruments/qblox/cluster_qcm_bb.py +++ b/src/qibolab/instruments/qblox/cluster_qcm_bb.py @@ -297,9 +297,11 @@ def _get_next_sequencer(self, port, frequency, qubits: dict): # select the qubit relative to specific port qubit = None for _qubit in qubits.values(): - if hasattr(_qubit.flux, "port"): + if _qubit.flux.port is not None: if _qubit.flux.port.name == port and _qubit.flux.port.module.name == self.name: qubit = _qubit + else: + log.warning(f"Qubit {_qubit.name} has no flux line connected") # select a new sequencer and configure it as required next_sequencer_number = self._free_sequencers_numbers.pop(0) if next_sequencer_number != self.DEFAULT_SEQUENCERS[port]: From 275b114e873adb5aab662adc15f74da716cbc4d6 Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Mon, 11 Dec 2023 18:05:06 +0400 Subject: [PATCH 52/66] fix old kernel_path --- src/qibolab/instruments/zhinst.py | 2 +- tests/dummy_qrc/zurich.yml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 6f3c846c92..dd391e870a 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -436,7 +436,7 @@ def register_readout_line(self, qubit, intermediate_frequency, options): ] if qubit.kernel_path: - self.kernels[q] = qubit.kernel_path / f"kernels_q{q}.npz" + self.kernels[q] = qubit.kernel_path if self.kernels[q].is_file() and options.acquisition_type == AcquisitionType.DISCRIMINATION: self.calibration[f"/logical_signal_groups/q{q}/acquire_line"] = lo.SignalCalibration( oscillator=None, diff --git a/tests/dummy_qrc/zurich.yml b/tests/dummy_qrc/zurich.yml index 13b893f420..4046262910 100644 --- a/tests/dummy_qrc/zurich.yml +++ b/tests/dummy_qrc/zurich.yml @@ -245,7 +245,7 @@ characterization: # parameters for single shot classification threshold: 0.8836 iq_angle: -1.551 - kernel_path: "kernel_q0" + kernel_path: "kernel_q0.npz" 1: readout_frequency: 4_931_000_000 @@ -255,7 +255,7 @@ characterization: sweetspot: 0.0 mean_gnd_states: [0, 0] mean_exc_states: [0, 0] - kernel_path: "kernel_q1" + kernel_path: "kernel_q1.npz" 2: readout_frequency: 6.109e+9 #6_112_000_000 drive_frequency: 4_300_587_281 # 4_401_600_000 #4_541_100_000 @@ -267,7 +267,7 @@ characterization: # parameters for single shot classification threshold: -0.0593 iq_angle: -0.667 - kernel_path: "kernel_q2" + kernel_path: "kernel_q2.npz" 3: readout_frequency: 5_783_000_000 drive_frequency: 4_100_000_000 @@ -276,7 +276,7 @@ characterization: sweetspot: 0.0 mean_gnd_states: [0, 0] mean_exc_states: [0, 0] - kernel_path: "kernel_q3" + kernel_path: "kernel_q3.npz" 4: readout_frequency: 5_515_000_000 drive_frequency: 4_196_800_000 @@ -288,7 +288,7 @@ characterization: # parameters for single shot classification threshold: 0.233806 #0.370954 #0.350665 iq_angle: 0.481 # -91.712 #191.016 - kernel_path: "kernel_q4" + kernel_path: "kernel_q4.npz" coupler: 0: sweetspot: 0.0 From 585fb5bbf2d3ac066ae8acdaa2c5bb0041edcf14 Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Mon, 11 Dec 2023 18:21:09 +0400 Subject: [PATCH 53/66] simplify code --- src/qibolab/instruments/zhinst.py | 46 ++++++++++++++----------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index dd391e870a..20b64bfc6a 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -437,31 +437,27 @@ def register_readout_line(self, qubit, intermediate_frequency, options): if qubit.kernel_path: self.kernels[q] = qubit.kernel_path - if self.kernels[q].is_file() and options.acquisition_type == AcquisitionType.DISCRIMINATION: - self.calibration[f"/logical_signal_groups/q{q}/acquire_line"] = lo.SignalCalibration( - oscillator=None, - range=qubit.feedback.power_range, - port_delay=self.time_of_flight * NANO_TO_SECONDS, - ) - elif self.kernels[q].is_file() and not options.acquisition_type == AcquisitionType.DISCRIMINATION: - self.calibration[f"/logical_signal_groups/q{q}/acquire_line"] = lo.SignalCalibration( - oscillator=lo.Oscillator( - frequency=intermediate_frequency, - modulation_type=lo.ModulationType.SOFTWARE, - ), - range=qubit.feedback.power_range, - port_delay=self.time_of_flight * NANO_TO_SECONDS, - threshold=qubit.threshold, # To keep compatibility with angle and threshold discrimination - ) - elif not self.kernels[q].is_file() and not options.acquisition_type == AcquisitionType.DISCRIMINATION: - self.calibration[f"/logical_signal_groups/q{q}/acquire_line"] = lo.SignalCalibration( - oscillator=lo.Oscillator( - frequency=intermediate_frequency, - modulation_type=lo.ModulationType.SOFTWARE, - ), - range=qubit.feedback.power_range, - port_delay=self.time_of_flight * NANO_TO_SECONDS, - ) + + oscillator = lo.Oscillator( + frequency=intermediate_frequency, + modulation_type=lo.ModulationType.SOFTWARE, + ) + threshold = None + + if options.acquisition_type == AcquisitionType.DISCRIMINATION: + if self.kernels[q].is_file(): + # Kernels don't work with the software modulation on the acquire signal + oscillator = None + elif not self.kernels[q].is_file(): + # To keep compatibility with angle and threshold discrimination (Remove when possible) + threshold = (qubit.threshold,) + + self.calibration[f"/logical_signal_groups/q{q}/acquire_line"] = lo.SignalCalibration( + oscillator=oscillator, + range=qubit.feedback.power_range, + port_delay=self.time_of_flight * NANO_TO_SECONDS, + threshold=threshold, + ) def register_drive_line(self, qubit, intermediate_frequency): """Registers qubit drive line to calibration and signal map.""" From 85b8bde348e956225aa9dea06ee1deb4845617cc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 21:09:49 +0000 Subject: [PATCH 54/66] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/isort: 5.12.0 → 5.13.1](https://github.com/pycqa/isort/compare/5.12.0...5.13.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 75df67bc71..e52e7e9e8d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: args: - --line-length=120 - repo: https://github.com/pycqa/isort - rev: 5.12.0 + rev: 5.13.1 hooks: - id: isort args: ["--profile", "black"] From 406bc663676971cf729c971d160275f6ee91bd28 Mon Sep 17 00:00:00 2001 From: Juan Cereijo Date: Tue, 12 Dec 2023 10:13:01 +0400 Subject: [PATCH 55/66] Update src/qibolab/instruments/zhinst.py Co-authored-by: Alessandro Candido --- src/qibolab/instruments/zhinst.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 20b64bfc6a..8582d5d39e 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -448,7 +448,7 @@ def register_readout_line(self, qubit, intermediate_frequency, options): if self.kernels[q].is_file(): # Kernels don't work with the software modulation on the acquire signal oscillator = None - elif not self.kernels[q].is_file(): + else: # To keep compatibility with angle and threshold discrimination (Remove when possible) threshold = (qubit.threshold,) From 879827b03d711d2e287f9c8485fff456dbb162d6 Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Tue, 12 Dec 2023 10:26:04 +0400 Subject: [PATCH 56/66] Fixed t1 pulse --- src/qibolab/instruments/zhinst.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index d70c04cadb..fbe41ac91f 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -883,14 +883,14 @@ def drive(self, exp, qubits): i += 1 elif isinstance(pulse, ZhSweeperLine): exp.delay(signal=f"drive{q}", time=pulse.zhsweeper) - play_after = f"sequence_drive{q}_{j}" - # Patch for T1 start, general ? - if len(self.sequence[f"readout{q}"]) > 0 and isinstance( - self.sequence[f"readout{q}"][0], ZhSweeperLine - ): - exp.delay(signal=f"drive{q}", time=self.sequence[f"readout{q}"][0].zhsweeper) - self.sequence[f"readout{q}"].remove(self.sequence[f"readout{q}"][0]) + if len(self.sequence[f"readout{q}"]) > 0 and isinstance( + self.sequence[f"readout{q}"][0], ZhSweeperLine + ): + exp.delay(signal=f"drive{q}", time=self.sequence[f"readout{q}"][0].zhsweeper) + self.sequence[f"readout{q}"].remove(self.sequence[f"readout{q}"][0]) + + play_after = f"sequence_drive{q}_{j}" def find_subsequence_finish( self, measurement_number: int, line: str, quantum_elements: Union[Dict[str, Qubit], Dict[str, Coupler]] From 337151e73fc0efaf835a9b26b397b2e7f27e6ec4 Mon Sep 17 00:00:00 2001 From: Juan Cereijo Date: Tue, 12 Dec 2023 12:40:50 +0400 Subject: [PATCH 57/66] Update src/qibolab/serialize.py Co-authored-by: Alessandro Candido --- src/qibolab/serialize.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/qibolab/serialize.py b/src/qibolab/serialize.py index 77096f465e..0d8b27f7db 100644 --- a/src/qibolab/serialize.py +++ b/src/qibolab/serialize.py @@ -112,9 +112,10 @@ def dump_qubits(qubits: QubitMap, pairs: QubitPairMap, couplers: CouplerMap = No "single_qubit": {q: qubit.characterization for q, qubit in qubits.items()}, } for q in qubits: - kernel_path = characterization["single_qubit"][q].pop("kernel_path") + qubit = characterization["single_qubit"][q] + kernel_path = qubit["kernel_path"] if kernel_path is not None: - characterization["single_qubit"][q]["kernel_path"] = kernel_path.name + qubit["kernel_path"] = kernel_path.name if couplers: characterization["coupler"] = {c.name: {"sweetspot": c.sweetspot} for c in couplers.values()} From 2ca99bf5a4d6a49592f7a483ae24dcd253222b78 Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Wed, 13 Dec 2023 09:19:51 +0400 Subject: [PATCH 58/66] Swapping correct sweepers axes with singleshot averaging_mode --- src/qibolab/instruments/zhinst.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 276479798b..909d8891e8 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -5,7 +5,7 @@ from collections import defaultdict from dataclasses import dataclass, replace from pathlib import Path -from typing import Dict, Tuple, Union +from typing import Dict, List, Tuple, Union import laboneq._token import laboneq.simple as lo @@ -24,7 +24,7 @@ from qibolab.instruments.unrolling import batch_max_sequences from qibolab.pulses import CouplerFluxPulse, FluxPulse, PulseSequence, PulseType from qibolab.qubits import Qubit -from qibolab.sweeper import Parameter +from qibolab.sweeper import Parameter, Sweeper # this env var just needs to be set os.environ["LABONEQ_TOKEN"] = "not required" @@ -1055,14 +1055,14 @@ def fast_reset(self, exp, qubits, fast_reset): exp.play(signal=f"drive{q}", pulse=pulse.zhpulse) @staticmethod - def rearrange_sweepers(sweepers): + def rearrange_sweepers(sweepers: List[Sweeper]) -> Tuple[List[int], List[Sweeper]]: """Rearranges sweepers from qibocal based on device hardware limitations""" - rearranging_axes = [[], []] + rearranging_axes = [0] * 2 if len(sweepers) == 2: if sweepers[1].parameter is Parameter.frequency: if sweepers[0].parameter is Parameter.bias: - rearranging_axes[0] += [sweepers.index(sweepers[1])] - rearranging_axes[1] += [0] + rearranging_axes[0] = sweepers.index(sweepers[1]) + rearranging_axes[1] = 0 sweeper_changed = sweepers[1] sweepers.remove(sweeper_changed) sweepers.insert(0, sweeper_changed) @@ -1071,8 +1071,8 @@ def rearrange_sweepers(sweepers): not sweepers[0].parameter is Parameter.amplitude and sweepers[0].pulses[0].type is not PulseType.READOUT ): - rearranging_axes[0] += [sweepers.index(sweepers[1])] - rearranging_axes[1] += [0] + rearranging_axes[0] = sweepers.index(sweepers[1]) + rearranging_axes[1] = 0 sweeper_changed = sweepers[1] sweepers.remove(sweeper_changed) sweepers.insert(0, sweeper_changed) @@ -1107,6 +1107,8 @@ def sweep(self, qubits, couplers, sequence: PulseSequence, options, *sweepers): for i, ropulse in enumerate(self.sequence[f"readout{q}"]): exp_res = self.results.get_data(f"sequence{q}_{i}") # Reorder dimensions + if options.averaging_mode is AveragingMode.SINGLESHOT: + rearranging_axes = [rearranging_axis + 1 for rearranging_axis in rearranging_axes] data = np.moveaxis(exp_res, rearranging_axes[0], rearranging_axes[1]) if options.acquisition_type is AcquisitionType.DISCRIMINATION: data = np.ones(data.shape) - data.real # Probability inversion patch From 972419165934ddaaea80425141c5d4e3ebb1c53d Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Wed, 13 Dec 2023 09:26:30 +0400 Subject: [PATCH 59/66] Using numpy array instead of list --- src/qibolab/instruments/zhinst.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 909d8891e8..b4b0a96796 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -1055,14 +1055,13 @@ def fast_reset(self, exp, qubits, fast_reset): exp.play(signal=f"drive{q}", pulse=pulse.zhpulse) @staticmethod - def rearrange_sweepers(sweepers: List[Sweeper]) -> Tuple[List[int], List[Sweeper]]: + def rearrange_sweepers(sweepers: List[Sweeper]) -> Tuple[np.ndarray, List[Sweeper]]: """Rearranges sweepers from qibocal based on device hardware limitations""" - rearranging_axes = [0] * 2 + rearranging_axes = np.zeros(2, dtype=int) if len(sweepers) == 2: if sweepers[1].parameter is Parameter.frequency: if sweepers[0].parameter is Parameter.bias: - rearranging_axes[0] = sweepers.index(sweepers[1]) - rearranging_axes[1] = 0 + rearranging_axes[:] = [sweepers.index(sweepers[1]), 0] sweeper_changed = sweepers[1] sweepers.remove(sweeper_changed) sweepers.insert(0, sweeper_changed) @@ -1071,8 +1070,7 @@ def rearrange_sweepers(sweepers: List[Sweeper]) -> Tuple[List[int], List[Sweeper not sweepers[0].parameter is Parameter.amplitude and sweepers[0].pulses[0].type is not PulseType.READOUT ): - rearranging_axes[0] = sweepers.index(sweepers[1]) - rearranging_axes[1] = 0 + rearranging_axes[:] = [sweepers.index(sweepers[1]), 0] sweeper_changed = sweepers[1] sweepers.remove(sweeper_changed) sweepers.insert(0, sweeper_changed) @@ -1108,7 +1106,7 @@ def sweep(self, qubits, couplers, sequence: PulseSequence, options, *sweepers): exp_res = self.results.get_data(f"sequence{q}_{i}") # Reorder dimensions if options.averaging_mode is AveragingMode.SINGLESHOT: - rearranging_axes = [rearranging_axis + 1 for rearranging_axis in rearranging_axes] + rearranging_axes += 1 data = np.moveaxis(exp_res, rearranging_axes[0], rearranging_axes[1]) if options.acquisition_type is AcquisitionType.DISCRIMINATION: data = np.ones(data.shape) - data.real # Probability inversion patch From 9e233f4b2db3cf854dbc35ada2e89bb3f45648c5 Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Wed, 13 Dec 2023 10:37:00 +0400 Subject: [PATCH 60/66] Updated docstrings and comments --- src/qibolab/instruments/zhinst.py | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index b4b0a96796..631c36d01b 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -916,12 +916,14 @@ def find_subsequence_finish( measurement_number (int): number of the measure pulse. line (str): line from which measure the finishing time. e.g.: "drive", "flux", "couplerflux" - quantum_elements (dict[str, Qubit]|dict[str, Coupler]): qubits or couplers from which measure the finishing time. + quantum_elements (dict[str, Qubit]|dict[str, Coupler]): qubits or couplers from + which measure the finishing time. Returns: - time_finish (int): Finish time of the last pulse of the subsequence before the measurement. - sequence_finish (str): Name of the last subsequence before measurement. - If there are no sequences after the previous measurement, use "None". + time_finish (int): Finish time of the last pulse of the subsequence + before the measurement. + sequence_finish (str): Name of the last subsequence before measurement. If + there are no sequences after the previous measurement, use "None". """ time_finish = 0 sequence_finish = "None" @@ -1056,7 +1058,21 @@ def fast_reset(self, exp, qubits, fast_reset): @staticmethod def rearrange_sweepers(sweepers: List[Sweeper]) -> Tuple[np.ndarray, List[Sweeper]]: - """Rearranges sweepers from qibocal based on device hardware limitations""" + """ + Rearranges sweepers from qibocal based on device hardware limitations. + + Frequency sweepers must be applied before bias or amplitude sweepers. + + Args: + sweepers (list[Sweeper]): list of sweepers used in the experiment. + + Returns: + rearranging_axes (np.ndarray): array of shape (2,) and dtype=int containing + the indexes of the sweepers to be swapped. Defaults to np.array([0, 0]) + if no swap is needed. + sweepers (list[Sweeper]): updated list of sweepers used in the experiment. If + sweepers must be swapped, the list is updated accordingly. + """ rearranging_axes = np.zeros(2, dtype=int) if len(sweepers) == 2: if sweepers[1].parameter is Parameter.frequency: @@ -1104,9 +1120,12 @@ def sweep(self, qubits, couplers, sequence: PulseSequence, options, *sweepers): if len(self.sequence[f"readout{q}"]) != 0: for i, ropulse in enumerate(self.sequence[f"readout{q}"]): exp_res = self.results.get_data(f"sequence{q}_{i}") - # Reorder dimensions + # if using singleshot, the first axis contains shots, + # i.e.: (nshots, sweeper_1, sweeper_2) + # if using integration: (sweeper_1, sweeper_2) if options.averaging_mode is AveragingMode.SINGLESHOT: rearranging_axes += 1 + # Reorder dimensions data = np.moveaxis(exp_res, rearranging_axes[0], rearranging_axes[1]) if options.acquisition_type is AcquisitionType.DISCRIMINATION: data = np.ones(data.shape) - data.real # Probability inversion patch From 45f9e191841d17c8c29c8db27d6bf685182aff95 Mon Sep 17 00:00:00 2001 From: GabrielePalazzo <73099233+GabrielePalazzo@users.noreply.github.com> Date: Wed, 13 Dec 2023 11:12:30 +0400 Subject: [PATCH 61/66] Removed duplicate code --- src/qibolab/instruments/zhinst.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 631c36d01b..8cdc07662c 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -1076,13 +1076,7 @@ def rearrange_sweepers(sweepers: List[Sweeper]) -> Tuple[np.ndarray, List[Sweepe rearranging_axes = np.zeros(2, dtype=int) if len(sweepers) == 2: if sweepers[1].parameter is Parameter.frequency: - if sweepers[0].parameter is Parameter.bias: - rearranging_axes[:] = [sweepers.index(sweepers[1]), 0] - sweeper_changed = sweepers[1] - sweepers.remove(sweeper_changed) - sweepers.insert(0, sweeper_changed) - log.warning("Sweepers were reordered") - elif ( + if (sweepers[0].parameter is Parameter.bias) or ( not sweepers[0].parameter is Parameter.amplitude and sweepers[0].pulses[0].type is not PulseType.READOUT ): From f7950590fc27993de9272e91fa41615aadcc3b87 Mon Sep 17 00:00:00 2001 From: Jacfomg Date: Wed, 13 Dec 2023 17:52:38 +0400 Subject: [PATCH 62/66] update laboneq dependancy --- poetry.lock | 511 +++++++++++++++--------------- pyproject.toml | 4 +- src/qibolab/instruments/zhinst.py | 1 - 3 files changed, 260 insertions(+), 256 deletions(-) diff --git a/poetry.lock b/poetry.lock index c830a5ec43..57fa191943 100644 --- a/poetry.lock +++ b/poetry.lock @@ -24,17 +24,17 @@ files = [ [[package]] name = "ansi2html" -version = "1.8.0" -description = "" +version = "1.9.1" +description = "Convert text with ANSI color codes to HTML or to LaTeX" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "ansi2html-1.8.0-py3-none-any.whl", hash = "sha256:ef9cc9682539dbe524fbf8edad9c9462a308e04bce1170c32daa8fdfd0001785"}, - {file = "ansi2html-1.8.0.tar.gz", hash = "sha256:38b82a298482a1fa2613f0f9c9beb3db72a8f832eeac58eb2e47bf32cd37f6d5"}, + {file = "ansi2html-1.9.1-py3-none-any.whl", hash = "sha256:29ccdb1e83520d648ebdc9c9544059ea4d424ecc33d3ef723657f7f5a9ae5225"}, + {file = "ansi2html-1.9.1.tar.gz", hash = "sha256:5c6837a13ecc1903aab7a545353312049dfedfe5105362ad3a8d9d207871ec71"}, ] [package.extras] -docs = ["Sphinx", "setuptools-scm", "sphinx-rtd-theme"] +docs = ["mkdocs", "mkdocs-material", "mkdocs-material-extensions", "mkdocstrings", "mkdocstrings-python", "pymdown-extensions"] test = ["pytest", "pytest-cov"] [[package]] @@ -50,13 +50,13 @@ files = [ [[package]] name = "astroid" -version = "3.0.1" +version = "3.0.2" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.8.0" files = [ - {file = "astroid-3.0.1-py3-none-any.whl", hash = "sha256:7d5895c9825e18079c5aeac0572bc2e4c83205c95d416e0b4fee8bc361d2d9ca"}, - {file = "astroid-3.0.1.tar.gz", hash = "sha256:86b0bb7d7da0be1a7c4aedb7974e391b32d4ed89e33de6ed6902b4b15c97577e"}, + {file = "astroid-3.0.2-py3-none-any.whl", hash = "sha256:d6e62862355f60e716164082d6b4b041d38e2a8cf1c7cd953ded5108bac8ff5c"}, + {file = "astroid-3.0.2.tar.gz", hash = "sha256:4a61cf0a59097c7bb52689b0fd63717cd2a8a14dc9f1eee97b82d814881c8c91"}, ] [package.dependencies] @@ -136,13 +136,13 @@ msal-extensions = ">=0.3.0,<2.0.0" [[package]] name = "babel" -version = "2.13.1" +version = "2.14.0" description = "Internationalization utilities" optional = false python-versions = ">=3.7" files = [ - {file = "Babel-2.13.1-py3-none-any.whl", hash = "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed"}, - {file = "Babel-2.13.1.tar.gz", hash = "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900"}, + {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, + {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, ] [package.extras] @@ -814,13 +814,13 @@ files = [ [[package]] name = "datadog-api-client" -version = "2.19.0" +version = "2.20.0" description = "Collection of all Datadog Public endpoints" optional = false python-versions = ">=3.7" files = [ - {file = "datadog-api-client-2.19.0.tar.gz", hash = "sha256:f7a7d9d3a8a061a029b711e6afa0657612f939f79ea0597e8b55a30ccab3d371"}, - {file = "datadog_api_client-2.19.0-py3-none-any.whl", hash = "sha256:0291c5c337f8d310f304723197e6ed1c268d2d3cb55694a48edc77c3f76a2c92"}, + {file = "datadog-api-client-2.20.0.tar.gz", hash = "sha256:f00ec539ec67336cff9d702f8476783a573792e46bf1ff84b8657bfcd5940237"}, + {file = "datadog_api_client-2.20.0-py3-none-any.whl", hash = "sha256:fbdabc8d2920dead81b0568f7a87ecb69cf27105a21133c00d5e67b3736709c1"}, ] [package.dependencies] @@ -1153,13 +1153,13 @@ files = [ [[package]] name = "google-api-core" -version = "2.14.0" +version = "2.15.0" description = "Google API client core library" optional = false python-versions = ">=3.7" files = [ - {file = "google-api-core-2.14.0.tar.gz", hash = "sha256:5368a4502b793d9bbf812a5912e13e4e69f9bd87f6efb508460c43f5bbd1ce41"}, - {file = "google_api_core-2.14.0-py3-none-any.whl", hash = "sha256:de2fb50ed34d47ddbb2bd2dcf680ee8fead46279f4ed6b16de362aca23a18952"}, + {file = "google-api-core-2.15.0.tar.gz", hash = "sha256:abc978a72658f14a2df1e5e12532effe40f94f868f6e23d95133bd6abcca35ca"}, + {file = "google_api_core-2.15.0-py3-none-any.whl", hash = "sha256:2aa56d2be495551e66bbff7f729b790546f87d5c90e74781aa77233bcb395a8a"}, ] [package.dependencies] @@ -1175,13 +1175,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-auth" -version = "2.24.0" +version = "2.25.2" description = "Google Authentication Library" optional = false python-versions = ">=3.7" files = [ - {file = "google-auth-2.24.0.tar.gz", hash = "sha256:2ec7b2a506989d7dbfdbe81cb8d0ead8876caaed14f86d29d34483cbe99c57af"}, - {file = "google_auth-2.24.0-py2.py3-none-any.whl", hash = "sha256:9b82d5c8d3479a5391ea0a46d81cca698d328459da31d4a459d4e901a5d927e0"}, + {file = "google-auth-2.25.2.tar.gz", hash = "sha256:42f707937feb4f5e5a39e6c4f343a17300a459aaf03141457ba505812841cc40"}, + {file = "google_auth-2.25.2-py2.py3-none-any.whl", hash = "sha256:473a8dfd0135f75bb79d878436e568f2695dce456764bf3a02b6f8c540b1d256"}, ] [package.dependencies] @@ -1198,13 +1198,13 @@ requests = ["requests (>=2.20.0,<3.0.0.dev0)"] [[package]] name = "googleapis-common-protos" -version = "1.61.0" +version = "1.62.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" files = [ - {file = "googleapis-common-protos-1.61.0.tar.gz", hash = "sha256:8a64866a97f6304a7179873a465d6eee97b7a24ec6cfd78e0f575e96b821240b"}, - {file = "googleapis_common_protos-1.61.0-py2.py3-none-any.whl", hash = "sha256:22f1915393bb3245343f6efe87f6fe868532efc12aa26b391b15132e1279f1c0"}, + {file = "googleapis-common-protos-1.62.0.tar.gz", hash = "sha256:83f0ece9f94e5672cced82f592d2a5edf527a96ed1794f0bab36d5735c996277"}, + {file = "googleapis_common_protos-1.62.0-py2.py3-none-any.whl", hash = "sha256:4750113612205514f9f6aa4cb00d523a94f3e8c06c5ad2fee466387dc4875f07"}, ] [package.dependencies] @@ -1215,69 +1215,69 @@ grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] [[package]] name = "grpcio" -version = "1.59.3" +version = "1.60.0" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.7" files = [ - {file = "grpcio-1.59.3-cp310-cp310-linux_armv7l.whl", hash = "sha256:aca028a6c7806e5b61e5f9f4232432c52856f7fcb98e330b20b6bc95d657bdcc"}, - {file = "grpcio-1.59.3-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:19ad26a7967f7999c8960d2b9fe382dae74c55b0c508c613a6c2ba21cddf2354"}, - {file = "grpcio-1.59.3-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:72b71dad2a3d1650e69ad42a5c4edbc59ee017f08c32c95694172bc501def23c"}, - {file = "grpcio-1.59.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c0f0a11d82d0253656cc42e04b6a149521e02e755fe2e4edd21123de610fd1d4"}, - {file = "grpcio-1.59.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60cddafb70f9a2c81ba251b53b4007e07cca7389e704f86266e22c4bffd8bf1d"}, - {file = "grpcio-1.59.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6c75a1fa0e677c1d2b6d4196ad395a5c381dfb8385f07ed034ef667cdcdbcc25"}, - {file = "grpcio-1.59.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e1d8e01438d5964a11167eec1edb5f85ed8e475648f36c834ed5db4ffba24ac8"}, - {file = "grpcio-1.59.3-cp310-cp310-win32.whl", hash = "sha256:c4b0076f0bf29ee62335b055a9599f52000b7941f577daa001c7ef961a1fbeab"}, - {file = "grpcio-1.59.3-cp310-cp310-win_amd64.whl", hash = "sha256:b1f00a3e6e0c3dccccffb5579fc76ebfe4eb40405ba308505b41ef92f747746a"}, - {file = "grpcio-1.59.3-cp311-cp311-linux_armv7l.whl", hash = "sha256:3996aaa21231451161dc29df6a43fcaa8b332042b6150482c119a678d007dd86"}, - {file = "grpcio-1.59.3-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:cb4e9cbd9b7388fcb06412da9f188c7803742d06d6f626304eb838d1707ec7e3"}, - {file = "grpcio-1.59.3-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:8022ca303d6c694a0d7acfb2b472add920217618d3a99eb4b14edc7c6a7e8fcf"}, - {file = "grpcio-1.59.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b36683fad5664283755a7f4e2e804e243633634e93cd798a46247b8e54e3cb0d"}, - {file = "grpcio-1.59.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8239b853226e4824e769517e1b5232e7c4dda3815b200534500338960fcc6118"}, - {file = "grpcio-1.59.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0511af8653fbda489ff11d542a08505d56023e63cafbda60e6e00d4e0bae86ea"}, - {file = "grpcio-1.59.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e78dc982bda74cef2ddfce1c91d29b96864c4c680c634e279ed204d51e227473"}, - {file = "grpcio-1.59.3-cp311-cp311-win32.whl", hash = "sha256:6a5c3a96405966c023e139c3bcccb2c7c776a6f256ac6d70f8558c9041bdccc3"}, - {file = "grpcio-1.59.3-cp311-cp311-win_amd64.whl", hash = "sha256:ed26826ee423b11477297b187371cdf4fa1eca874eb1156422ef3c9a60590dd9"}, - {file = "grpcio-1.59.3-cp312-cp312-linux_armv7l.whl", hash = "sha256:45dddc5cb5227d30fa43652d8872dc87f086d81ab4b500be99413bad0ae198d7"}, - {file = "grpcio-1.59.3-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:1736496d74682e53dd0907fd515f2694d8e6a96c9a359b4080b2504bf2b2d91b"}, - {file = "grpcio-1.59.3-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:ddbd1a16138e52e66229047624de364f88a948a4d92ba20e4e25ad7d22eef025"}, - {file = "grpcio-1.59.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcfa56f8d031ffda902c258c84c4b88707f3a4be4827b4e3ab8ec7c24676320d"}, - {file = "grpcio-1.59.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2eb8f0c7c0c62f7a547ad7a91ba627a5aa32a5ae8d930783f7ee61680d7eb8d"}, - {file = "grpcio-1.59.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8d993399cc65e3a34f8fd48dd9ad7a376734564b822e0160dd18b3d00c1a33f9"}, - {file = "grpcio-1.59.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0bd141f4f41907eb90bda74d969c3cb21c1c62779419782a5b3f5e4b5835718"}, - {file = "grpcio-1.59.3-cp312-cp312-win32.whl", hash = "sha256:33b8fd65d4e97efa62baec6171ce51f9cf68f3a8ba9f866f4abc9d62b5c97b79"}, - {file = "grpcio-1.59.3-cp312-cp312-win_amd64.whl", hash = "sha256:0e735ed002f50d4f3cb9ecfe8ac82403f5d842d274c92d99db64cfc998515e07"}, - {file = "grpcio-1.59.3-cp37-cp37m-linux_armv7l.whl", hash = "sha256:ea40ce4404e7cca0724c91a7404da410f0144148fdd58402a5942971e3469b94"}, - {file = "grpcio-1.59.3-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:83113bcc393477b6f7342b9f48e8a054330c895205517edc66789ceea0796b53"}, - {file = "grpcio-1.59.3-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:73afbac602b8f1212a50088193601f869b5073efa9855b3e51aaaec97848fc8a"}, - {file = "grpcio-1.59.3-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:575d61de1950b0b0699917b686b1ca108690702fcc2df127b8c9c9320f93e069"}, - {file = "grpcio-1.59.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd76057b5c9a4d68814610ef9226925f94c1231bbe533fdf96f6181f7d2ff9e"}, - {file = "grpcio-1.59.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:95d6fd804c81efe4879e38bfd84d2b26e339a0a9b797e7615e884ef4686eb47b"}, - {file = "grpcio-1.59.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0d42048b8a3286ea4134faddf1f9a59cf98192b94aaa10d910a25613c5eb5bfb"}, - {file = "grpcio-1.59.3-cp37-cp37m-win_amd64.whl", hash = "sha256:4619fea15c64bcdd9d447cdbdde40e3d5f1da3a2e8ae84103d94a9c1df210d7e"}, - {file = "grpcio-1.59.3-cp38-cp38-linux_armv7l.whl", hash = "sha256:95b5506e70284ac03b2005dd9ffcb6708c9ae660669376f0192a710687a22556"}, - {file = "grpcio-1.59.3-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:9e17660947660ccfce56c7869032910c179a5328a77b73b37305cd1ee9301c2e"}, - {file = "grpcio-1.59.3-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:00912ce19914d038851be5cd380d94a03f9d195643c28e3ad03d355cc02ce7e8"}, - {file = "grpcio-1.59.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e58b3cadaa3c90f1efca26ba33e0d408b35b497307027d3d707e4bcd8de862a6"}, - {file = "grpcio-1.59.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d787ecadea865bdf78f6679f6f5bf4b984f18f659257ba612979df97a298b3c3"}, - {file = "grpcio-1.59.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0814942ba1bba269db4e760a34388640c601dece525c6a01f3b4ff030cc0db69"}, - {file = "grpcio-1.59.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fb111aa99d3180c361a35b5ae1e2c63750220c584a1344229abc139d5c891881"}, - {file = "grpcio-1.59.3-cp38-cp38-win32.whl", hash = "sha256:eb8ba504c726befe40a356ecbe63c6c3c64c9a439b3164f5a718ec53c9874da0"}, - {file = "grpcio-1.59.3-cp38-cp38-win_amd64.whl", hash = "sha256:cdbc6b32fadab9bebc6f49d3e7ec4c70983c71e965497adab7f87de218e84391"}, - {file = "grpcio-1.59.3-cp39-cp39-linux_armv7l.whl", hash = "sha256:c82ca1e4be24a98a253d6dbaa216542e4163f33f38163fc77964b0f0d255b552"}, - {file = "grpcio-1.59.3-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:36636babfda14f9e9687f28d5b66d349cf88c1301154dc71c6513de2b6c88c59"}, - {file = "grpcio-1.59.3-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:5f9b2e591da751ac7fdd316cc25afafb7a626dededa9b414f90faad7f3ccebdb"}, - {file = "grpcio-1.59.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a93a82876a4926bf451db82ceb725bd87f42292bacc94586045261f501a86994"}, - {file = "grpcio-1.59.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce31fa0bfdd1f2bb15b657c16105c8652186eab304eb512e6ae3b99b2fdd7d13"}, - {file = "grpcio-1.59.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:16da0e40573962dab6cba16bec31f25a4f468e6d05b658e589090fe103b03e3d"}, - {file = "grpcio-1.59.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d1d1a17372fd425addd5812049fa7374008ffe689585f27f802d0935522cf4b7"}, - {file = "grpcio-1.59.3-cp39-cp39-win32.whl", hash = "sha256:52cc38a7241b5f7b4a91aaf9000fdd38e26bb00d5e8a71665ce40cfcee716281"}, - {file = "grpcio-1.59.3-cp39-cp39-win_amd64.whl", hash = "sha256:b491e5bbcad3020a96842040421e508780cade35baba30f402df9d321d1c423e"}, - {file = "grpcio-1.59.3.tar.gz", hash = "sha256:7800f99568a74a06ebdccd419dd1b6e639b477dcaf6da77ea702f8fb14ce5f80"}, + {file = "grpcio-1.60.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:d020cfa595d1f8f5c6b343530cd3ca16ae5aefdd1e832b777f9f0eb105f5b139"}, + {file = "grpcio-1.60.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:b98f43fcdb16172dec5f4b49f2fece4b16a99fd284d81c6bbac1b3b69fcbe0ff"}, + {file = "grpcio-1.60.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:20e7a4f7ded59097c84059d28230907cd97130fa74f4a8bfd1d8e5ba18c81491"}, + {file = "grpcio-1.60.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452ca5b4afed30e7274445dd9b441a35ece656ec1600b77fff8c216fdf07df43"}, + {file = "grpcio-1.60.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43e636dc2ce9ece583b3e2ca41df5c983f4302eabc6d5f9cd04f0562ee8ec1ae"}, + {file = "grpcio-1.60.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e306b97966369b889985a562ede9d99180def39ad42c8014628dd3cc343f508"}, + {file = "grpcio-1.60.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f897c3b127532e6befdcf961c415c97f320d45614daf84deba0a54e64ea2457b"}, + {file = "grpcio-1.60.0-cp310-cp310-win32.whl", hash = "sha256:b87efe4a380887425bb15f220079aa8336276398dc33fce38c64d278164f963d"}, + {file = "grpcio-1.60.0-cp310-cp310-win_amd64.whl", hash = "sha256:a9c7b71211f066908e518a2ef7a5e211670761651039f0d6a80d8d40054047df"}, + {file = "grpcio-1.60.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:fb464479934778d7cc5baf463d959d361954d6533ad34c3a4f1d267e86ee25fd"}, + {file = "grpcio-1.60.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:4b44d7e39964e808b071714666a812049765b26b3ea48c4434a3b317bac82f14"}, + {file = "grpcio-1.60.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:90bdd76b3f04bdb21de5398b8a7c629676c81dfac290f5f19883857e9371d28c"}, + {file = "grpcio-1.60.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91229d7203f1ef0ab420c9b53fe2ca5c1fbeb34f69b3bc1b5089466237a4a134"}, + {file = "grpcio-1.60.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b36a2c6d4920ba88fa98075fdd58ff94ebeb8acc1215ae07d01a418af4c0253"}, + {file = "grpcio-1.60.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:297eef542156d6b15174a1231c2493ea9ea54af8d016b8ca7d5d9cc65cfcc444"}, + {file = "grpcio-1.60.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:87c9224acba0ad8bacddf427a1c2772e17ce50b3042a789547af27099c5f751d"}, + {file = "grpcio-1.60.0-cp311-cp311-win32.whl", hash = "sha256:95ae3e8e2c1b9bf671817f86f155c5da7d49a2289c5cf27a319458c3e025c320"}, + {file = "grpcio-1.60.0-cp311-cp311-win_amd64.whl", hash = "sha256:467a7d31554892eed2aa6c2d47ded1079fc40ea0b9601d9f79204afa8902274b"}, + {file = "grpcio-1.60.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:a7152fa6e597c20cb97923407cf0934e14224af42c2b8d915f48bc3ad2d9ac18"}, + {file = "grpcio-1.60.0-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:7db16dd4ea1b05ada504f08d0dca1cd9b926bed3770f50e715d087c6f00ad748"}, + {file = "grpcio-1.60.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:b0571a5aef36ba9177e262dc88a9240c866d903a62799e44fd4aae3f9a2ec17e"}, + {file = "grpcio-1.60.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fd9584bf1bccdfff1512719316efa77be235469e1e3295dce64538c4773840b"}, + {file = "grpcio-1.60.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6a478581b1a1a8fdf3318ecb5f4d0cda41cacdffe2b527c23707c9c1b8fdb55"}, + {file = "grpcio-1.60.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:77c8a317f0fd5a0a2be8ed5cbe5341537d5c00bb79b3bb27ba7c5378ba77dbca"}, + {file = "grpcio-1.60.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1c30bb23a41df95109db130a6cc1b974844300ae2e5d68dd4947aacba5985aa5"}, + {file = "grpcio-1.60.0-cp312-cp312-win32.whl", hash = "sha256:2aef56e85901c2397bd557c5ba514f84de1f0ae5dd132f5d5fed042858115951"}, + {file = "grpcio-1.60.0-cp312-cp312-win_amd64.whl", hash = "sha256:e381fe0c2aa6c03b056ad8f52f8efca7be29fb4d9ae2f8873520843b6039612a"}, + {file = "grpcio-1.60.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:92f88ca1b956eb8427a11bb8b4a0c0b2b03377235fc5102cb05e533b8693a415"}, + {file = "grpcio-1.60.0-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:e278eafb406f7e1b1b637c2cf51d3ad45883bb5bd1ca56bc05e4fc135dfdaa65"}, + {file = "grpcio-1.60.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:a48edde788b99214613e440fce495bbe2b1e142a7f214cce9e0832146c41e324"}, + {file = "grpcio-1.60.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de2ad69c9a094bf37c1102b5744c9aec6cf74d2b635558b779085d0263166454"}, + {file = "grpcio-1.60.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:073f959c6f570797272f4ee9464a9997eaf1e98c27cb680225b82b53390d61e6"}, + {file = "grpcio-1.60.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c826f93050c73e7769806f92e601e0efdb83ec8d7c76ddf45d514fee54e8e619"}, + {file = "grpcio-1.60.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9e30be89a75ee66aec7f9e60086fadb37ff8c0ba49a022887c28c134341f7179"}, + {file = "grpcio-1.60.0-cp37-cp37m-win_amd64.whl", hash = "sha256:b0fb2d4801546598ac5cd18e3ec79c1a9af8b8f2a86283c55a5337c5aeca4b1b"}, + {file = "grpcio-1.60.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:9073513ec380434eb8d21970e1ab3161041de121f4018bbed3146839451a6d8e"}, + {file = "grpcio-1.60.0-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:74d7d9fa97809c5b892449b28a65ec2bfa458a4735ddad46074f9f7d9550ad13"}, + {file = "grpcio-1.60.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:1434ca77d6fed4ea312901122dc8da6c4389738bf5788f43efb19a838ac03ead"}, + {file = "grpcio-1.60.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e61e76020e0c332a98290323ecfec721c9544f5b739fab925b6e8cbe1944cf19"}, + {file = "grpcio-1.60.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675997222f2e2f22928fbba640824aebd43791116034f62006e19730715166c0"}, + {file = "grpcio-1.60.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5208a57eae445ae84a219dfd8b56e04313445d146873117b5fa75f3245bc1390"}, + {file = "grpcio-1.60.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:428d699c8553c27e98f4d29fdc0f0edc50e9a8a7590bfd294d2edb0da7be3629"}, + {file = "grpcio-1.60.0-cp38-cp38-win32.whl", hash = "sha256:83f2292ae292ed5a47cdcb9821039ca8e88902923198f2193f13959360c01860"}, + {file = "grpcio-1.60.0-cp38-cp38-win_amd64.whl", hash = "sha256:705a68a973c4c76db5d369ed573fec3367d7d196673fa86614b33d8c8e9ebb08"}, + {file = "grpcio-1.60.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:c193109ca4070cdcaa6eff00fdb5a56233dc7610216d58fb81638f89f02e4968"}, + {file = "grpcio-1.60.0-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:676e4a44e740deaba0f4d95ba1d8c5c89a2fcc43d02c39f69450b1fa19d39590"}, + {file = "grpcio-1.60.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:5ff21e000ff2f658430bde5288cb1ac440ff15c0d7d18b5fb222f941b46cb0d2"}, + {file = "grpcio-1.60.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c86343cf9ff7b2514dd229bdd88ebba760bd8973dac192ae687ff75e39ebfab"}, + {file = "grpcio-1.60.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fd3b3968ffe7643144580f260f04d39d869fcc2cddb745deef078b09fd2b328"}, + {file = "grpcio-1.60.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:30943b9530fe3620e3b195c03130396cd0ee3a0d10a66c1bee715d1819001eaf"}, + {file = "grpcio-1.60.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b10241250cb77657ab315270b064a6c7f1add58af94befa20687e7c8d8603ae6"}, + {file = "grpcio-1.60.0-cp39-cp39-win32.whl", hash = "sha256:79a050889eb8d57a93ed21d9585bb63fca881666fc709f5d9f7f9372f5e7fd03"}, + {file = "grpcio-1.60.0-cp39-cp39-win_amd64.whl", hash = "sha256:8a97a681e82bc11a42d4372fe57898d270a2707f36c45c6676e49ce0d5c41353"}, + {file = "grpcio-1.60.0.tar.gz", hash = "sha256:2199165a1affb666aa24adf0c97436686d0a61bc5fc113c037701fb7c7fceb96"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.59.3)"] +protobuf = ["grpcio-tools (>=1.60.0)"] [[package]] name = "grpclib" @@ -1569,21 +1569,15 @@ test = ["ipykernel", "jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"] [[package]] name = "isort" -version = "5.12.0" +version = "5.13.1" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.8.0" files = [ - {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, - {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, + {file = "isort-5.13.1-py3-none-any.whl", hash = "sha256:56a51732c25f94ca96f6721be206dd96a95f42950502eb26c1015d333bc6edb7"}, + {file = "isort-5.13.1.tar.gz", hash = "sha256:aaed790b463e8703fb1eddb831dfa8e8616bacde2c083bd557ef73c8189b7263"}, ] -[package.extras] -colors = ["colorama (>=0.4.3)"] -pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] -plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] - [[package]] name = "itsdangerous" version = "2.1.2" @@ -1868,12 +1862,12 @@ files = [ [[package]] name = "laboneq" -version = "2.20.1" +version = "2.21.0" description = "Zurich Instruments LabOne Q software framework for quantum computing control" optional = false python-versions = ">=3.9" files = [ - {file = "laboneq-2.20.1-py3-none-any.whl", hash = "sha256:b74a597efea0f72e2cfa27722ab58f21f3328f8c7f406142c9c622315dfbadd8"}, + {file = "laboneq-2.21.0-py3-none-any.whl", hash = "sha256:9d20c10778927ed259e34488da472526d6839bd1085cc6fb38df8e0ca7a24e67"}, ] [package.dependencies] @@ -1885,7 +1879,6 @@ intervaltree = "*" jsonschema = "*" lagom = "*" matplotlib = "*" -nest-asyncio = "*" numpy = "*" openpulse = "*" openqasm3 = "*" @@ -1901,9 +1894,10 @@ sortedcollections = "*" sortedcontainers = "*" sqlitedict = "*" typing-extensions = "*" -zhinst-core = ">=23.10.49450,<23.11.0" -zhinst-toolkit = ">=0.6.0,<0.7.0" -zhinst-utils = "0.3.5" +unsync = "1.4.0" +zhinst-core = ">=23.10.51605,<23.11.0" +zhinst-toolkit = ">=0.6.2,<0.7.0" +zhinst-utils = "0.3.6" [[package]] name = "lagom" @@ -2225,13 +2219,13 @@ tests = ["pytest (>=4.6)"] [[package]] name = "msal" -version = "1.25.0" -description = "The Microsoft Authentication Library (MSAL) for Python library" +version = "1.26.0" +description = "The Microsoft Authentication Library (MSAL) for Python library enables your app to access the Microsoft Cloud by supporting authentication of users with Microsoft Azure Active Directory accounts (AAD) and Microsoft Accounts (MSA) using industry standard OAuth2 and OpenID Connect." optional = false python-versions = ">=2.7" files = [ - {file = "msal-1.25.0-py2.py3-none-any.whl", hash = "sha256:386df621becb506bc315a713ec3d4d5b5d6163116955c7dde23622f156b81af6"}, - {file = "msal-1.25.0.tar.gz", hash = "sha256:f44329fdb59f4f044c779164a34474b8a44ad9e4940afbc4c3a3a2bbe90324d9"}, + {file = "msal-1.26.0-py2.py3-none-any.whl", hash = "sha256:be77ba6a8f49c9ff598bbcdc5dfcf1c9842f3044300109af738e8c3e371065b5"}, + {file = "msal-1.26.0.tar.gz", hash = "sha256:224756079fe338be838737682b49f8ebc20a87c1c5eeaf590daae4532b83de15"}, ] [package.dependencies] @@ -2244,20 +2238,21 @@ broker = ["pymsalruntime (>=0.13.2,<0.14)"] [[package]] name = "msal-extensions" -version = "1.0.0" +version = "1.1.0" description = "Microsoft Authentication Library extensions (MSAL EX) provides a persistence API that can save your data on disk, encrypted on Windows, macOS and Linux. Concurrent data access will be coordinated by a file lock mechanism." optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "msal-extensions-1.0.0.tar.gz", hash = "sha256:c676aba56b0cce3783de1b5c5ecfe828db998167875126ca4b47dc6436451354"}, - {file = "msal_extensions-1.0.0-py2.py3-none-any.whl", hash = "sha256:91e3db9620b822d0ed2b4d1850056a0f133cba04455e62f11612e40f5502f2ee"}, + {file = "msal-extensions-1.1.0.tar.gz", hash = "sha256:6ab357867062db7b253d0bd2df6d411c7891a0ee7308d54d1e4317c1d1c54252"}, + {file = "msal_extensions-1.1.0-py3-none-any.whl", hash = "sha256:01be9711b4c0b1a151450068eeb2c4f0997df3bba085ac299de3a66f585e382f"}, ] [package.dependencies] msal = ">=0.4.1,<2.0.0" +packaging = "*" portalocker = [ - {version = ">=1.0,<3", markers = "python_version >= \"3.5\" and platform_system != \"Windows\""}, - {version = ">=1.6,<3", markers = "python_version >= \"3.5\" and platform_system == \"Windows\""}, + {version = ">=1.0,<3", markers = "platform_system != \"Windows\""}, + {version = ">=1.6,<3", markers = "platform_system == \"Windows\""}, ] [[package]] @@ -2367,13 +2362,13 @@ test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>= [[package]] name = "nbconvert" -version = "7.11.0" +version = "7.12.0" description = "Converting Jupyter Notebooks" optional = false python-versions = ">=3.8" files = [ - {file = "nbconvert-7.11.0-py3-none-any.whl", hash = "sha256:d1d417b7f34a4e38887f8da5bdfd12372adf3b80f995d57556cb0972c68909fe"}, - {file = "nbconvert-7.11.0.tar.gz", hash = "sha256:abedc01cf543177ffde0bfc2a69726d5a478f6af10a332fc1bf29fcb4f0cf000"}, + {file = "nbconvert-7.12.0-py3-none-any.whl", hash = "sha256:5b6c848194d270cc55fb691169202620d7b52a12fec259508d142ecbe4219310"}, + {file = "nbconvert-7.12.0.tar.gz", hash = "sha256:b1564bd89f69a74cd6398b0362da94db07aafb991b7857216a766204a71612c0"}, ] [package.dependencies] @@ -2820,13 +2815,13 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa [[package]] name = "platformdirs" -version = "4.0.0" +version = "4.1.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"}, - {file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"}, + {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, + {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, ] [package.extras] @@ -2898,13 +2893,13 @@ colorama = "*" [[package]] name = "prompt-toolkit" -version = "3.0.41" +version = "3.0.43" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" files = [ - {file = "prompt_toolkit-3.0.41-py3-none-any.whl", hash = "sha256:f36fe301fafb7470e86aaf90f036eef600a3210be4decf461a5b1ca8403d3cb2"}, - {file = "prompt_toolkit-3.0.41.tar.gz", hash = "sha256:941367d97fc815548822aa26c2a269fdc4eb21e9ec05fc5d447cf09bad5d75f0"}, + {file = "prompt_toolkit-3.0.43-py3-none-any.whl", hash = "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"}, + {file = "prompt_toolkit-3.0.43.tar.gz", hash = "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d"}, ] [package.dependencies] @@ -3389,13 +3384,13 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pylint" -version = "3.0.2" +version = "3.0.3" description = "python code static checker" optional = false python-versions = ">=3.8.0" files = [ - {file = "pylint-3.0.2-py3-none-any.whl", hash = "sha256:60ed5f3a9ff8b61839ff0348b3624ceeb9e6c2a92c514d81c9cc273da3b6bcda"}, - {file = "pylint-3.0.2.tar.gz", hash = "sha256:0d4c286ef6d2f66c8bfb527a7f8a629009e42c99707dec821a03e1b51a4c1496"}, + {file = "pylint-3.0.3-py3-none-any.whl", hash = "sha256:7a1585285aefc5165db81083c3e06363a27448f6b467b3b0f30dbd0ac1f73810"}, + {file = "pylint-3.0.3.tar.gz", hash = "sha256:58c2398b0301e049609a8429789ec6edf3aabe9b6c5fec916acd18639c16de8b"}, ] [package.dependencies] @@ -3405,7 +3400,7 @@ dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, ] -isort = ">=4.2.5,<6" +isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" mccabe = ">=0.6,<0.8" platformdirs = ">=2.2.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} @@ -3745,104 +3740,104 @@ files = [ [[package]] name = "pyzmq" -version = "25.1.1" +version = "25.1.2" description = "Python bindings for 0MQ" optional = false python-versions = ">=3.6" files = [ - {file = "pyzmq-25.1.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:381469297409c5adf9a0e884c5eb5186ed33137badcbbb0560b86e910a2f1e76"}, - {file = "pyzmq-25.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:955215ed0604dac5b01907424dfa28b40f2b2292d6493445dd34d0dfa72586a8"}, - {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:985bbb1316192b98f32e25e7b9958088431d853ac63aca1d2c236f40afb17c83"}, - {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:afea96f64efa98df4da6958bae37f1cbea7932c35878b185e5982821bc883369"}, - {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76705c9325d72a81155bb6ab48d4312e0032bf045fb0754889133200f7a0d849"}, - {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:77a41c26205d2353a4c94d02be51d6cbdf63c06fbc1295ea57dad7e2d3381b71"}, - {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:12720a53e61c3b99d87262294e2b375c915fea93c31fc2336898c26d7aed34cd"}, - {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:57459b68e5cd85b0be8184382cefd91959cafe79ae019e6b1ae6e2ba8a12cda7"}, - {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:292fe3fc5ad4a75bc8df0dfaee7d0babe8b1f4ceb596437213821f761b4589f9"}, - {file = "pyzmq-25.1.1-cp310-cp310-win32.whl", hash = "sha256:35b5ab8c28978fbbb86ea54958cd89f5176ce747c1fb3d87356cf698048a7790"}, - {file = "pyzmq-25.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:11baebdd5fc5b475d484195e49bae2dc64b94a5208f7c89954e9e354fc609d8f"}, - {file = "pyzmq-25.1.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:d20a0ddb3e989e8807d83225a27e5c2eb2260eaa851532086e9e0fa0d5287d83"}, - {file = "pyzmq-25.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e1c1be77bc5fb77d923850f82e55a928f8638f64a61f00ff18a67c7404faf008"}, - {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d89528b4943d27029a2818f847c10c2cecc79fa9590f3cb1860459a5be7933eb"}, - {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90f26dc6d5f241ba358bef79be9ce06de58d477ca8485e3291675436d3827cf8"}, - {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2b92812bd214018e50b6380ea3ac0c8bb01ac07fcc14c5f86a5bb25e74026e9"}, - {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:2f957ce63d13c28730f7fd6b72333814221c84ca2421298f66e5143f81c9f91f"}, - {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:047a640f5c9c6ade7b1cc6680a0e28c9dd5a0825135acbd3569cc96ea00b2505"}, - {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7f7e58effd14b641c5e4dec8c7dab02fb67a13df90329e61c869b9cc607ef752"}, - {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c2910967e6ab16bf6fbeb1f771c89a7050947221ae12a5b0b60f3bca2ee19bca"}, - {file = "pyzmq-25.1.1-cp311-cp311-win32.whl", hash = "sha256:76c1c8efb3ca3a1818b837aea423ff8a07bbf7aafe9f2f6582b61a0458b1a329"}, - {file = "pyzmq-25.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:44e58a0554b21fc662f2712814a746635ed668d0fbc98b7cb9d74cb798d202e6"}, - {file = "pyzmq-25.1.1-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:e1ffa1c924e8c72778b9ccd386a7067cddf626884fd8277f503c48bb5f51c762"}, - {file = "pyzmq-25.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1af379b33ef33757224da93e9da62e6471cf4a66d10078cf32bae8127d3d0d4a"}, - {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cff084c6933680d1f8b2f3b4ff5bbb88538a4aac00d199ac13f49d0698727ecb"}, - {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2400a94f7dd9cb20cd012951a0cbf8249e3d554c63a9c0cdfd5cbb6c01d2dec"}, - {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d81f1ddae3858b8299d1da72dd7d19dd36aab654c19671aa8a7e7fb02f6638a"}, - {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:255ca2b219f9e5a3a9ef3081512e1358bd4760ce77828e1028b818ff5610b87b"}, - {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a882ac0a351288dd18ecae3326b8a49d10c61a68b01419f3a0b9a306190baf69"}, - {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:724c292bb26365659fc434e9567b3f1adbdb5e8d640c936ed901f49e03e5d32e"}, - {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ca1ed0bb2d850aa8471387882247c68f1e62a4af0ce9c8a1dbe0d2bf69e41fb"}, - {file = "pyzmq-25.1.1-cp312-cp312-win32.whl", hash = "sha256:b3451108ab861040754fa5208bca4a5496c65875710f76789a9ad27c801a0075"}, - {file = "pyzmq-25.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:eadbefd5e92ef8a345f0525b5cfd01cf4e4cc651a2cffb8f23c0dd184975d787"}, - {file = "pyzmq-25.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:db0b2af416ba735c6304c47f75d348f498b92952f5e3e8bff449336d2728795d"}, - {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c133e93b405eb0d36fa430c94185bdd13c36204a8635470cccc200723c13bb"}, - {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:273bc3959bcbff3f48606b28229b4721716598d76b5aaea2b4a9d0ab454ec062"}, - {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cbc8df5c6a88ba5ae385d8930da02201165408dde8d8322072e3e5ddd4f68e22"}, - {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:18d43df3f2302d836f2a56f17e5663e398416e9dd74b205b179065e61f1a6edf"}, - {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:73461eed88a88c866656e08f89299720a38cb4e9d34ae6bf5df6f71102570f2e"}, - {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:34c850ce7976d19ebe7b9d4b9bb8c9dfc7aac336c0958e2651b88cbd46682123"}, - {file = "pyzmq-25.1.1-cp36-cp36m-win32.whl", hash = "sha256:d2045d6d9439a0078f2a34b57c7b18c4a6aef0bee37f22e4ec9f32456c852c71"}, - {file = "pyzmq-25.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:458dea649f2f02a0b244ae6aef8dc29325a2810aa26b07af8374dc2a9faf57e3"}, - {file = "pyzmq-25.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7cff25c5b315e63b07a36f0c2bab32c58eafbe57d0dce61b614ef4c76058c115"}, - {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1579413ae492b05de5a6174574f8c44c2b9b122a42015c5292afa4be2507f28"}, - {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3d0a409d3b28607cc427aa5c30a6f1e4452cc44e311f843e05edb28ab5e36da0"}, - {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:21eb4e609a154a57c520e3d5bfa0d97e49b6872ea057b7c85257b11e78068222"}, - {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:034239843541ef7a1aee0c7b2cb7f6aafffb005ede965ae9cbd49d5ff4ff73cf"}, - {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f8115e303280ba09f3898194791a153862cbf9eef722ad8f7f741987ee2a97c7"}, - {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1a5d26fe8f32f137e784f768143728438877d69a586ddeaad898558dc971a5ae"}, - {file = "pyzmq-25.1.1-cp37-cp37m-win32.whl", hash = "sha256:f32260e556a983bc5c7ed588d04c942c9a8f9c2e99213fec11a031e316874c7e"}, - {file = "pyzmq-25.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:abf34e43c531bbb510ae7e8f5b2b1f2a8ab93219510e2b287a944432fad135f3"}, - {file = "pyzmq-25.1.1-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:87e34f31ca8f168c56d6fbf99692cc8d3b445abb5bfd08c229ae992d7547a92a"}, - {file = "pyzmq-25.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c9c6c9b2c2f80747a98f34ef491c4d7b1a8d4853937bb1492774992a120f475d"}, - {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5619f3f5a4db5dbb572b095ea3cb5cc035335159d9da950830c9c4db2fbb6995"}, - {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5a34d2395073ef862b4032343cf0c32a712f3ab49d7ec4f42c9661e0294d106f"}, - {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25f0e6b78220aba09815cd1f3a32b9c7cb3e02cb846d1cfc526b6595f6046618"}, - {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3669cf8ee3520c2f13b2e0351c41fea919852b220988d2049249db10046a7afb"}, - {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2d163a18819277e49911f7461567bda923461c50b19d169a062536fffe7cd9d2"}, - {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:df27ffddff4190667d40de7beba4a950b5ce78fe28a7dcc41d6f8a700a80a3c0"}, - {file = "pyzmq-25.1.1-cp38-cp38-win32.whl", hash = "sha256:a382372898a07479bd34bda781008e4a954ed8750f17891e794521c3e21c2e1c"}, - {file = "pyzmq-25.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:52533489f28d62eb1258a965f2aba28a82aa747202c8fa5a1c7a43b5db0e85c1"}, - {file = "pyzmq-25.1.1-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:03b3f49b57264909aacd0741892f2aecf2f51fb053e7d8ac6767f6c700832f45"}, - {file = "pyzmq-25.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:330f9e188d0d89080cde66dc7470f57d1926ff2fb5576227f14d5be7ab30b9fa"}, - {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2ca57a5be0389f2a65e6d3bb2962a971688cbdd30b4c0bd188c99e39c234f414"}, - {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d457aed310f2670f59cc5b57dcfced452aeeed77f9da2b9763616bd57e4dbaae"}, - {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c56d748ea50215abef7030c72b60dd723ed5b5c7e65e7bc2504e77843631c1a6"}, - {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8f03d3f0d01cb5a018debeb412441996a517b11c5c17ab2001aa0597c6d6882c"}, - {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:820c4a08195a681252f46926de10e29b6bbf3e17b30037bd4250d72dd3ddaab8"}, - {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17ef5f01d25b67ca8f98120d5fa1d21efe9611604e8eb03a5147360f517dd1e2"}, - {file = "pyzmq-25.1.1-cp39-cp39-win32.whl", hash = "sha256:04ccbed567171579ec2cebb9c8a3e30801723c575601f9a990ab25bcac6b51e2"}, - {file = "pyzmq-25.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:e61f091c3ba0c3578411ef505992d356a812fb200643eab27f4f70eed34a29ef"}, - {file = "pyzmq-25.1.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ade6d25bb29c4555d718ac6d1443a7386595528c33d6b133b258f65f963bb0f6"}, - {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0c95ddd4f6e9fca4e9e3afaa4f9df8552f0ba5d1004e89ef0a68e1f1f9807c7"}, - {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48e466162a24daf86f6b5ca72444d2bf39a5e58da5f96370078be67c67adc978"}, - {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abc719161780932c4e11aaebb203be3d6acc6b38d2f26c0f523b5b59d2fc1996"}, - {file = "pyzmq-25.1.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ccf825981640b8c34ae54231b7ed00271822ea1c6d8ba1090ebd4943759abf5"}, - {file = "pyzmq-25.1.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c2f20ce161ebdb0091a10c9ca0372e023ce24980d0e1f810f519da6f79c60800"}, - {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:deee9ca4727f53464daf089536e68b13e6104e84a37820a88b0a057b97bba2d2"}, - {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aa8d6cdc8b8aa19ceb319aaa2b660cdaccc533ec477eeb1309e2a291eaacc43a"}, - {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:019e59ef5c5256a2c7378f2fb8560fc2a9ff1d315755204295b2eab96b254d0a"}, - {file = "pyzmq-25.1.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:b9af3757495c1ee3b5c4e945c1df7be95562277c6e5bccc20a39aec50f826cd0"}, - {file = "pyzmq-25.1.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:548d6482dc8aadbe7e79d1b5806585c8120bafa1ef841167bc9090522b610fa6"}, - {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:057e824b2aae50accc0f9a0570998adc021b372478a921506fddd6c02e60308e"}, - {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2243700cc5548cff20963f0ca92d3e5e436394375ab8a354bbea2b12911b20b0"}, - {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79986f3b4af059777111409ee517da24a529bdbd46da578b33f25580adcff728"}, - {file = "pyzmq-25.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:11d58723d44d6ed4dd677c5615b2ffb19d5c426636345567d6af82be4dff8a55"}, - {file = "pyzmq-25.1.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:49d238cf4b69652257db66d0c623cd3e09b5d2e9576b56bc067a396133a00d4a"}, - {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fedbdc753827cf014c01dbbee9c3be17e5a208dcd1bf8641ce2cd29580d1f0d4"}, - {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc16ac425cc927d0a57d242589f87ee093884ea4804c05a13834d07c20db203c"}, - {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11c1d2aed9079c6b0c9550a7257a836b4a637feb334904610f06d70eb44c56d2"}, - {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e8a701123029cc240cea61dd2d16ad57cab4691804143ce80ecd9286b464d180"}, - {file = "pyzmq-25.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:61706a6b6c24bdece85ff177fec393545a3191eeda35b07aaa1458a027ad1304"}, - {file = "pyzmq-25.1.1.tar.gz", hash = "sha256:259c22485b71abacdfa8bf79720cd7bcf4b9d128b30ea554f01ae71fdbfdaa23"}, + {file = "pyzmq-25.1.2-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:e624c789359f1a16f83f35e2c705d07663ff2b4d4479bad35621178d8f0f6ea4"}, + {file = "pyzmq-25.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:49151b0efece79f6a79d41a461d78535356136ee70084a1c22532fc6383f4ad0"}, + {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9a5f194cf730f2b24d6af1f833c14c10f41023da46a7f736f48b6d35061e76e"}, + {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:faf79a302f834d9e8304fafdc11d0d042266667ac45209afa57e5efc998e3872"}, + {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f51a7b4ead28d3fca8dda53216314a553b0f7a91ee8fc46a72b402a78c3e43d"}, + {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0ddd6d71d4ef17ba5a87becf7ddf01b371eaba553c603477679ae817a8d84d75"}, + {file = "pyzmq-25.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:246747b88917e4867e2367b005fc8eefbb4a54b7db363d6c92f89d69abfff4b6"}, + {file = "pyzmq-25.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:00c48ae2fd81e2a50c3485de1b9d5c7c57cd85dc8ec55683eac16846e57ac979"}, + {file = "pyzmq-25.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5a68d491fc20762b630e5db2191dd07ff89834086740f70e978bb2ef2668be08"}, + {file = "pyzmq-25.1.2-cp310-cp310-win32.whl", hash = "sha256:09dfe949e83087da88c4a76767df04b22304a682d6154de2c572625c62ad6886"}, + {file = "pyzmq-25.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:fa99973d2ed20417744fca0073390ad65ce225b546febb0580358e36aa90dba6"}, + {file = "pyzmq-25.1.2-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:82544e0e2d0c1811482d37eef297020a040c32e0687c1f6fc23a75b75db8062c"}, + {file = "pyzmq-25.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:01171fc48542348cd1a360a4b6c3e7d8f46cdcf53a8d40f84db6707a6768acc1"}, + {file = "pyzmq-25.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc69c96735ab501419c432110016329bf0dea8898ce16fab97c6d9106dc0b348"}, + {file = "pyzmq-25.1.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3e124e6b1dd3dfbeb695435dff0e383256655bb18082e094a8dd1f6293114642"}, + {file = "pyzmq-25.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7598d2ba821caa37a0f9d54c25164a4fa351ce019d64d0b44b45540950458840"}, + {file = "pyzmq-25.1.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d1299d7e964c13607efd148ca1f07dcbf27c3ab9e125d1d0ae1d580a1682399d"}, + {file = "pyzmq-25.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4e6f689880d5ad87918430957297c975203a082d9a036cc426648fcbedae769b"}, + {file = "pyzmq-25.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cc69949484171cc961e6ecd4a8911b9ce7a0d1f738fcae717177c231bf77437b"}, + {file = "pyzmq-25.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9880078f683466b7f567b8624bfc16cad65077be046b6e8abb53bed4eeb82dd3"}, + {file = "pyzmq-25.1.2-cp311-cp311-win32.whl", hash = "sha256:4e5837af3e5aaa99a091302df5ee001149baff06ad22b722d34e30df5f0d9097"}, + {file = "pyzmq-25.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:25c2dbb97d38b5ac9fd15586e048ec5eb1e38f3d47fe7d92167b0c77bb3584e9"}, + {file = "pyzmq-25.1.2-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:11e70516688190e9c2db14fcf93c04192b02d457b582a1f6190b154691b4c93a"}, + {file = "pyzmq-25.1.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:313c3794d650d1fccaaab2df942af9f2c01d6217c846177cfcbc693c7410839e"}, + {file = "pyzmq-25.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b3cbba2f47062b85fe0ef9de5b987612140a9ba3a9c6d2543c6dec9f7c2ab27"}, + {file = "pyzmq-25.1.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc31baa0c32a2ca660784d5af3b9487e13b61b3032cb01a115fce6588e1bed30"}, + {file = "pyzmq-25.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02c9087b109070c5ab0b383079fa1b5f797f8d43e9a66c07a4b8b8bdecfd88ee"}, + {file = "pyzmq-25.1.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f8429b17cbb746c3e043cb986328da023657e79d5ed258b711c06a70c2ea7537"}, + {file = "pyzmq-25.1.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5074adeacede5f810b7ef39607ee59d94e948b4fd954495bdb072f8c54558181"}, + {file = "pyzmq-25.1.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7ae8f354b895cbd85212da245f1a5ad8159e7840e37d78b476bb4f4c3f32a9fe"}, + {file = "pyzmq-25.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b264bf2cc96b5bc43ce0e852be995e400376bd87ceb363822e2cb1964fcdc737"}, + {file = "pyzmq-25.1.2-cp312-cp312-win32.whl", hash = "sha256:02bbc1a87b76e04fd780b45e7f695471ae6de747769e540da909173d50ff8e2d"}, + {file = "pyzmq-25.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:ced111c2e81506abd1dc142e6cd7b68dd53747b3b7ae5edbea4578c5eeff96b7"}, + {file = "pyzmq-25.1.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:7b6d09a8962a91151f0976008eb7b29b433a560fde056ec7a3db9ec8f1075438"}, + {file = "pyzmq-25.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:967668420f36878a3c9ecb5ab33c9d0ff8d054f9c0233d995a6d25b0e95e1b6b"}, + {file = "pyzmq-25.1.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5edac3f57c7ddaacdb4d40f6ef2f9e299471fc38d112f4bc6d60ab9365445fb0"}, + {file = "pyzmq-25.1.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0dabfb10ef897f3b7e101cacba1437bd3a5032ee667b7ead32bbcdd1a8422fe7"}, + {file = "pyzmq-25.1.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2c6441e0398c2baacfe5ba30c937d274cfc2dc5b55e82e3749e333aabffde561"}, + {file = "pyzmq-25.1.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:16b726c1f6c2e7625706549f9dbe9b06004dfbec30dbed4bf50cbdfc73e5b32a"}, + {file = "pyzmq-25.1.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:a86c2dd76ef71a773e70551a07318b8e52379f58dafa7ae1e0a4be78efd1ff16"}, + {file = "pyzmq-25.1.2-cp36-cp36m-win32.whl", hash = "sha256:359f7f74b5d3c65dae137f33eb2bcfa7ad9ebefd1cab85c935f063f1dbb245cc"}, + {file = "pyzmq-25.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:55875492f820d0eb3417b51d96fea549cde77893ae3790fd25491c5754ea2f68"}, + {file = "pyzmq-25.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b8c8a419dfb02e91b453615c69568442e897aaf77561ee0064d789705ff37a92"}, + {file = "pyzmq-25.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8807c87fa893527ae8a524c15fc505d9950d5e856f03dae5921b5e9aa3b8783b"}, + {file = "pyzmq-25.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5e319ed7d6b8f5fad9b76daa0a68497bc6f129858ad956331a5835785761e003"}, + {file = "pyzmq-25.1.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3c53687dde4d9d473c587ae80cc328e5b102b517447456184b485587ebd18b62"}, + {file = "pyzmq-25.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9add2e5b33d2cd765ad96d5eb734a5e795a0755f7fc49aa04f76d7ddda73fd70"}, + {file = "pyzmq-25.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e690145a8c0c273c28d3b89d6fb32c45e0d9605b2293c10e650265bf5c11cfec"}, + {file = "pyzmq-25.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:00a06faa7165634f0cac1abb27e54d7a0b3b44eb9994530b8ec73cf52e15353b"}, + {file = "pyzmq-25.1.2-cp37-cp37m-win32.whl", hash = "sha256:0f97bc2f1f13cb16905a5f3e1fbdf100e712d841482b2237484360f8bc4cb3d7"}, + {file = "pyzmq-25.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6cc0020b74b2e410287e5942e1e10886ff81ac77789eb20bec13f7ae681f0fdd"}, + {file = "pyzmq-25.1.2-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:bef02cfcbded83473bdd86dd8d3729cd82b2e569b75844fb4ea08fee3c26ae41"}, + {file = "pyzmq-25.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e10a4b5a4b1192d74853cc71a5e9fd022594573926c2a3a4802020360aa719d8"}, + {file = "pyzmq-25.1.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8c5f80e578427d4695adac6fdf4370c14a2feafdc8cb35549c219b90652536ae"}, + {file = "pyzmq-25.1.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5dde6751e857910c1339890f3524de74007958557593b9e7e8c5f01cd919f8a7"}, + {file = "pyzmq-25.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea1608dd169da230a0ad602d5b1ebd39807ac96cae1845c3ceed39af08a5c6df"}, + {file = "pyzmq-25.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0f513130c4c361201da9bc69df25a086487250e16b5571ead521b31ff6b02220"}, + {file = "pyzmq-25.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:019744b99da30330798bb37df33549d59d380c78e516e3bab9c9b84f87a9592f"}, + {file = "pyzmq-25.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2e2713ef44be5d52dd8b8e2023d706bf66cb22072e97fc71b168e01d25192755"}, + {file = "pyzmq-25.1.2-cp38-cp38-win32.whl", hash = "sha256:07cd61a20a535524906595e09344505a9bd46f1da7a07e504b315d41cd42eb07"}, + {file = "pyzmq-25.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb7e49a17fb8c77d3119d41a4523e432eb0c6932187c37deb6fbb00cc3028088"}, + {file = "pyzmq-25.1.2-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:94504ff66f278ab4b7e03e4cba7e7e400cb73bfa9d3d71f58d8972a8dc67e7a6"}, + {file = "pyzmq-25.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6dd0d50bbf9dca1d0bdea219ae6b40f713a3fb477c06ca3714f208fd69e16fd8"}, + {file = "pyzmq-25.1.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:004ff469d21e86f0ef0369717351073e0e577428e514c47c8480770d5e24a565"}, + {file = "pyzmq-25.1.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c0b5ca88a8928147b7b1e2dfa09f3b6c256bc1135a1338536cbc9ea13d3b7add"}, + {file = "pyzmq-25.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c9a79f1d2495b167119d02be7448bfba57fad2a4207c4f68abc0bab4b92925b"}, + {file = "pyzmq-25.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:518efd91c3d8ac9f9b4f7dd0e2b7b8bf1a4fe82a308009016b07eaa48681af82"}, + {file = "pyzmq-25.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1ec23bd7b3a893ae676d0e54ad47d18064e6c5ae1fadc2f195143fb27373f7f6"}, + {file = "pyzmq-25.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db36c27baed588a5a8346b971477b718fdc66cf5b80cbfbd914b4d6d355e44e2"}, + {file = "pyzmq-25.1.2-cp39-cp39-win32.whl", hash = "sha256:39b1067f13aba39d794a24761e385e2eddc26295826530a8c7b6c6c341584289"}, + {file = "pyzmq-25.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:8e9f3fabc445d0ce320ea2c59a75fe3ea591fdbdeebec5db6de530dd4b09412e"}, + {file = "pyzmq-25.1.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a8c1d566344aee826b74e472e16edae0a02e2a044f14f7c24e123002dcff1c05"}, + {file = "pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:759cfd391a0996345ba94b6a5110fca9c557ad4166d86a6e81ea526c376a01e8"}, + {file = "pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c61e346ac34b74028ede1c6b4bcecf649d69b707b3ff9dc0fab453821b04d1e"}, + {file = "pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cb8fc1f8d69b411b8ec0b5f1ffbcaf14c1db95b6bccea21d83610987435f1a4"}, + {file = "pyzmq-25.1.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3c00c9b7d1ca8165c610437ca0c92e7b5607b2f9076f4eb4b095c85d6e680a1d"}, + {file = "pyzmq-25.1.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:df0c7a16ebb94452d2909b9a7b3337940e9a87a824c4fc1c7c36bb4404cb0cde"}, + {file = "pyzmq-25.1.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:45999e7f7ed5c390f2e87ece7f6c56bf979fb213550229e711e45ecc7d42ccb8"}, + {file = "pyzmq-25.1.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ac170e9e048b40c605358667aca3d94e98f604a18c44bdb4c102e67070f3ac9b"}, + {file = "pyzmq-25.1.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1b604734bec94f05f81b360a272fc824334267426ae9905ff32dc2be433ab96"}, + {file = "pyzmq-25.1.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:a793ac733e3d895d96f865f1806f160696422554e46d30105807fdc9841b9f7d"}, + {file = "pyzmq-25.1.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0806175f2ae5ad4b835ecd87f5f85583316b69f17e97786f7443baaf54b9bb98"}, + {file = "pyzmq-25.1.2-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ef12e259e7bc317c7597d4f6ef59b97b913e162d83b421dd0db3d6410f17a244"}, + {file = "pyzmq-25.1.2-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea253b368eb41116011add00f8d5726762320b1bda892f744c91997b65754d73"}, + {file = "pyzmq-25.1.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b9b1f2ad6498445a941d9a4fee096d387fee436e45cc660e72e768d3d8ee611"}, + {file = "pyzmq-25.1.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:8b14c75979ce932c53b79976a395cb2a8cd3aaf14aef75e8c2cb55a330b9b49d"}, + {file = "pyzmq-25.1.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:889370d5174a741a62566c003ee8ddba4b04c3f09a97b8000092b7ca83ec9c49"}, + {file = "pyzmq-25.1.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a18fff090441a40ffda8a7f4f18f03dc56ae73f148f1832e109f9bffa85df15"}, + {file = "pyzmq-25.1.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99a6b36f95c98839ad98f8c553d8507644c880cf1e0a57fe5e3a3f3969040882"}, + {file = "pyzmq-25.1.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4345c9a27f4310afbb9c01750e9461ff33d6fb74cd2456b107525bbeebcb5be3"}, + {file = "pyzmq-25.1.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3516e0b6224cf6e43e341d56da15fd33bdc37fa0c06af4f029f7d7dfceceabbc"}, + {file = "pyzmq-25.1.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:146b9b1f29ead41255387fb07be56dc29639262c0f7344f570eecdcd8d683314"}, + {file = "pyzmq-25.1.2.tar.gz", hash = "sha256:93f1aa311e8bb912e34f004cf186407a4e90eec4f0ecc0efd26056bf7eda0226"}, ] [package.dependencies] @@ -4083,13 +4078,13 @@ sphinx = ">=1.3.1" [[package]] name = "referencing" -version = "0.31.1" +version = "0.32.0" description = "JSON Referencing + Python" optional = false python-versions = ">=3.8" files = [ - {file = "referencing-0.31.1-py3-none-any.whl", hash = "sha256:c19c4d006f1757e3dd75c4f784d38f8698d87b649c54f9ace14e5e8c9667c01d"}, - {file = "referencing-0.31.1.tar.gz", hash = "sha256:81a1471c68c9d5e3831c30ad1dd9815c45b558e596653db751a2bfdd17b3b9ec"}, + {file = "referencing-0.32.0-py3-none-any.whl", hash = "sha256:bdcd3efb936f82ff86f993093f6da7435c7de69a3b3a5a06678a6050184bee99"}, + {file = "referencing-0.32.0.tar.gz", hash = "sha256:689e64fe121843dcfd57b71933318ef1f91188ffb45367332700a86ac8fd6161"}, ] [package.dependencies] @@ -4936,13 +4931,13 @@ test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0, [[package]] name = "typing-extensions" -version = "4.8.0" +version = "4.9.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, - {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, ] [[package]] @@ -4965,6 +4960,16 @@ docs = ["sphinx"] optional = ["numpy"] tests = ["nose", "numpy"] +[[package]] +name = "unsync" +version = "1.4.0" +description = "Unsynchronize asyncio" +optional = false +python-versions = "*" +files = [ + {file = "unsync-1.4.0.tar.gz", hash = "sha256:a29e0f8952ffb0b3a0453ce436819a5a1ba2febbb5caa707c319f6f98d35f3c5"}, +] + [[package]] name = "urllib3" version = "2.1.0" @@ -5224,13 +5229,13 @@ files = [ [[package]] name = "xarray" -version = "2023.11.0" +version = "2023.12.0" description = "N-D labeled arrays and datasets in Python" optional = false python-versions = ">=3.9" files = [ - {file = "xarray-2023.11.0-py3-none-any.whl", hash = "sha256:933b5101e965120ed58e29525667ab34aafcea1886c236ade72a34d7bb465d9c"}, - {file = "xarray-2023.11.0.tar.gz", hash = "sha256:9a45e10741844b5f948d8e1e768b460df7e90696d18e2eff2c1d47f5d9d50252"}, + {file = "xarray-2023.12.0-py3-none-any.whl", hash = "sha256:3c22b6824681762b6c3fcad86dfd18960a617bccbc7f456ce21b43a20e455fb9"}, + {file = "xarray-2023.12.0.tar.gz", hash = "sha256:4565dbc890de47e278346c44d6b33bb07d3427383e077a7ca8ab6606196fd433"}, ] [package.dependencies] @@ -5247,38 +5252,38 @@ viz = ["matplotlib", "nc-time-axis", "seaborn"] [[package]] name = "zhinst-core" -version = "23.10.49450" +version = "23.10.51605" description = "Python API for Zurich Instruments Devices" optional = false python-versions = "*" files = [ - {file = "zhinst_core-23.10.49450-cp310-cp310-macosx_10_11_x86_64.whl", hash = "sha256:ea77c827b6f3afc4ca81145124c92aeff55892e8f664f6298a7909696337f4bb"}, - {file = "zhinst_core-23.10.49450-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:97013a32122ab48ec3ee177a816f92a9e457667f88525cb01d2264015b04e409"}, - {file = "zhinst_core-23.10.49450-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:5c4148e541172a1f00fe1534d44d4313929efafd40c19cca968b6b8c6c23ef58"}, - {file = "zhinst_core-23.10.49450-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:38c59e68148d3e2441846d69cbff76820c3a6ac31000ba9dce9a1156282f910d"}, - {file = "zhinst_core-23.10.49450-cp310-cp310-win_amd64.whl", hash = "sha256:c8635b356907fce9632d42b26bab866af7a14162ddd60397b2adef3a65af0d89"}, - {file = "zhinst_core-23.10.49450-cp311-cp311-macosx_10_11_x86_64.whl", hash = "sha256:de54dc00cb095513e0e27f8b22ca53304172b9b84f4550c8639b5224186b55c0"}, - {file = "zhinst_core-23.10.49450-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0b4f9235e8f335875ef3f507989b929b578103139265bc0ead213f328a663c1d"}, - {file = "zhinst_core-23.10.49450-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:cee5e4501670e2a6924f4d3d7a73e0c252a01ea4ea22a45f57ab9d9bc3abe452"}, - {file = "zhinst_core-23.10.49450-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:9af7a0535e7eaadd2c75d2b5ec339c1e96dc5bc28c5e753795559a554f23bd0a"}, - {file = "zhinst_core-23.10.49450-cp311-cp311-win_amd64.whl", hash = "sha256:e00f97560d9169c3c944ee4ec07a09033caba60edfb82a78bb115e41f9474e98"}, - {file = "zhinst_core-23.10.49450-cp312-cp312-macosx_10_11_x86_64.whl", hash = "sha256:ce6a77c6db2cc4b62204603c20854c0ac7242ee5bcf262e77248e5886e8cf501"}, - {file = "zhinst_core-23.10.49450-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7751954126a71d33476b9f9ea923433b7a85cb9824a2546ca75e19fe4014e014"}, - {file = "zhinst_core-23.10.49450-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:e100dbaed4a97d1809309c8280206224ff01605a9d3461da4cbe535064cd0ba5"}, - {file = "zhinst_core-23.10.49450-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:e873a71776dff7c2dffb20127c9600deadc3dfb50ee49e459d5ef9ad72e2906c"}, - {file = "zhinst_core-23.10.49450-cp312-cp312-win_amd64.whl", hash = "sha256:e155c82b833f84941bcebc96e322c13d2615b3dcd79b5208e9137af9788382df"}, - {file = "zhinst_core-23.10.49450-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f23784febf01dec2688d343dc55d1093c890743c9766927da2c5d9df4f2b79ff"}, - {file = "zhinst_core-23.10.49450-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:ed7827aa5ba89a3250f8993fca2cc1be77828b7dc0d9c0836acd638b216a01a9"}, - {file = "zhinst_core-23.10.49450-cp37-cp37m-win_amd64.whl", hash = "sha256:be966393c841f292d6daa026905c546db6bcf4119cdafc782736ebfd50a3e52c"}, - {file = "zhinst_core-23.10.49450-cp38-cp38-macosx_10_11_x86_64.whl", hash = "sha256:41e2785ea882d09c5b839756910ed1e321992715df873b36353bad1229fd3afd"}, - {file = "zhinst_core-23.10.49450-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:841e70c8453a4e46000912bfbdb4b55d4e1b2faaa900a79a9d028446427cf186"}, - {file = "zhinst_core-23.10.49450-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2d5a7c581b1d0c98d20a790c9d9dbf7df5a8cfa03e35b4b83bc87a935ff672c0"}, - {file = "zhinst_core-23.10.49450-cp38-cp38-win_amd64.whl", hash = "sha256:e186d864aa48ce6a6275f3b64766d8f72a68c05eba9bc545700a7a7d520b660c"}, - {file = "zhinst_core-23.10.49450-cp39-cp39-macosx_10_11_x86_64.whl", hash = "sha256:a890f7715c448d9dcf7b2c25fc6c6329cdad82d6f9376331f6ae6070dbe69d4a"}, - {file = "zhinst_core-23.10.49450-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a6932dcbcf8f68adbc72d0816c7b0952dcc0eb5ffbbcc21f2aa625aeec38398a"}, - {file = "zhinst_core-23.10.49450-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:9c80ed10ad427cd0bc09acc6f667a01b1f26a8b2e89ff6be187350c4206fd887"}, - {file = "zhinst_core-23.10.49450-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:c457612e02ab2efdff2da0446a59bf9b5d9a9dcb299c383a2aff0e4dd8c0a888"}, - {file = "zhinst_core-23.10.49450-cp39-cp39-win_amd64.whl", hash = "sha256:7783ca8e00b9798814de305f14b2f8b05e8ee719a5c71683b8e98080487c9020"}, + {file = "zhinst_core-23.10.51605-cp310-cp310-macosx_10_11_x86_64.whl", hash = "sha256:13780f35ffd57f81318e1e934171f6386272ac7517ee8880d49d6064e0ceb2db"}, + {file = "zhinst_core-23.10.51605-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d2a1246ec568a757548b41f836d518dbe14d9b77e231f848863d555ca243949e"}, + {file = "zhinst_core-23.10.51605-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:509594b17cc7523be2905a4ec7a6ff5877fbe3b22573b409df9477a244fe41c4"}, + {file = "zhinst_core-23.10.51605-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:fb4168f87163afb725686681552ddd8ae35592336484f8a44e66c4fe924eb939"}, + {file = "zhinst_core-23.10.51605-cp310-cp310-win_amd64.whl", hash = "sha256:635c0b14d136b30a4adf9993c11478d72c53c6dbe403057842d0e2ced4e0f6ed"}, + {file = "zhinst_core-23.10.51605-cp311-cp311-macosx_10_11_x86_64.whl", hash = "sha256:aea35ff7b4013b826470dec1bd72ad55eee6339d5c460e54cbf8e1bdb0f323d5"}, + {file = "zhinst_core-23.10.51605-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3d9edca3dc06229769ec32b9eb4e4784c3b1e8378530b91f3b5661b6d8ff6b1"}, + {file = "zhinst_core-23.10.51605-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:a4c7991f66b92d2c931a625f1442924323d5bb8460be24ef9aa0208ea8f810c7"}, + {file = "zhinst_core-23.10.51605-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:ededd0c8e2e3758fa897374b717df80e670e82687639dc2c1aca886b65c19467"}, + {file = "zhinst_core-23.10.51605-cp311-cp311-win_amd64.whl", hash = "sha256:63ea7e81b8be0ddcecd0e8ee8c4fa374cfb2b7036a0a8bf799a6cd28856ed96c"}, + {file = "zhinst_core-23.10.51605-cp312-cp312-macosx_10_11_x86_64.whl", hash = "sha256:76107c0578e939cf5fe20ac6c71ce306f2ca0ce72157a395a32120ecf004678b"}, + {file = "zhinst_core-23.10.51605-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bc6b9e4b52059c1be8eb7fc58b5d2917d1d45b0eac330299e0ed64d89818784f"}, + {file = "zhinst_core-23.10.51605-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:805c424828cbd90130a0d4624fa95af535ff1b8c5844f1ce89c03aa792778732"}, + {file = "zhinst_core-23.10.51605-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:d6c905db7062d661e92402d54417c593a619aa1f57a6634f97a00931c717c10e"}, + {file = "zhinst_core-23.10.51605-cp312-cp312-win_amd64.whl", hash = "sha256:d128a346853369e13888d7d983a52279cb47271d5e775264f86d86b177e9a17a"}, + {file = "zhinst_core-23.10.51605-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:be65db0380cbba67f85861911385b11f2dfa85fa93df679e355e7e187e94a80d"}, + {file = "zhinst_core-23.10.51605-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d1f63c08f00e91659c5f644aa3cfef596eecb24a48c1669560c96e7afaaedef5"}, + {file = "zhinst_core-23.10.51605-cp37-cp37m-win_amd64.whl", hash = "sha256:11310840e01b3cdb3f6a2fc9df9d254eb0b171c362510bd3f50d87e2a66197c9"}, + {file = "zhinst_core-23.10.51605-cp38-cp38-macosx_10_11_x86_64.whl", hash = "sha256:e73b210b4e1067a0f36ed926fa58e520419c96a126877e4d6b211da1c8c61199"}, + {file = "zhinst_core-23.10.51605-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:c6e64c0a223fee776e6e139059b8da9bdde2c40eaecf8932fe08abb6fa5de311"}, + {file = "zhinst_core-23.10.51605-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:0ab44131e3e12ed48616f1f00e33c516e9b247eb338f8c5545a15c5e55254dc7"}, + {file = "zhinst_core-23.10.51605-cp38-cp38-win_amd64.whl", hash = "sha256:e49ad0093eb43b583f448ad7eefba6c4a7e0900b56b535d188d14f6fcb11e1dd"}, + {file = "zhinst_core-23.10.51605-cp39-cp39-macosx_10_11_x86_64.whl", hash = "sha256:81270491ae969848b5ac6c9f7ec105f9c7fe5b83d275048a8b343607b171b3a9"}, + {file = "zhinst_core-23.10.51605-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6ee6d8af5f059f933d748408c949d2e94e388abc33a76f39aa7214698bde1e07"}, + {file = "zhinst_core-23.10.51605-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:031da42a0e82b9de43b2af3bdc4563a6f40a8ff469c9c12518479759f6fb4cf3"}, + {file = "zhinst_core-23.10.51605-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:2bc8727c2bda6e2a5f47b040357f9b1a2d56095d5e6afaf847c3c6afbc150850"}, + {file = "zhinst_core-23.10.51605-cp39-cp39-win_amd64.whl", hash = "sha256:a75be4f848036a1ae0cd4cf82156d81aad99f9c2fb2b6eac861a02d6dac78c76"}, ] [package.dependencies] @@ -5307,12 +5312,12 @@ zhinst-utils = ">=0.3.1" [[package]] name = "zhinst-utils" -version = "0.3.5" +version = "0.3.6" description = "Zurich Instruments utils for device control" optional = false python-versions = ">=3.7" files = [ - {file = "zhinst_utils-0.3.5-py3-none-any.whl", hash = "sha256:50dd90b118ad6b7cca4906856c5d3b6bc751ce5b08100877cae3de543a19e4cd"}, + {file = "zhinst_utils-0.3.6-py3-none-any.whl", hash = "sha256:3d13ea883d64996984214169284fffc017afd28244c9d0adabc73498ae631c39"}, ] [package.dependencies] @@ -5344,4 +5349,4 @@ zh = ["laboneq"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.12" -content-hash = "c3f1b3b5ab95b4eecc406d96c0a44e97a0821c4b6972b2d409cce72c75ae8cd5" +content-hash = "2dab07219f7c824c3f992fa25e9282b09b9ea3f22e0c2733ce33603f33b3533f" diff --git a/pyproject.toml b/pyproject.toml index ec9b564434..97743b07af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ pyvisa-py = { version = "0.5.3", optional = true } qm-qua = { version = "==1.1.1", optional = true } qualang-tools = { version = "==0.14.0", optional = true} setuptools = { version = ">67.0.0", optional = true } -laboneq = { version = "==2.20.1", optional = true } +laboneq = { version = "==2.21.0", optional = true } qibosoq = { version = ">=0.0.4,<0.2", optional = true } [tool.poetry.group.docs] @@ -55,7 +55,7 @@ qcodes = "^0.37.0" qcodes_contrib_drivers = "0.18.0" qibosoq = ">=0.0.4,<0.2" qualang-tools = "==0.14.0" -laboneq = "==2.20.1" +laboneq = "==2.21.0" [tool.poetry.group.tests] optional = true diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 276479798b..39830fe132 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -32,7 +32,6 @@ NANO_TO_SECONDS = 1e-9 COMPILER_SETTINGS = { - "SHFSG_FORCE_COMMAND_TABLE": True, "SHFSG_MIN_PLAYWAVE_HINT": 32, "SHFSG_MIN_PLAYZERO_HINT": 32, "HDAWG_MIN_PLAYWAVE_HINT": 64, From bbddc3dc462b2281700a3b639cdd53cbb2b8935c Mon Sep 17 00:00:00 2001 From: Alessandro Candido Date: Thu, 14 Dec 2023 15:11:25 +0100 Subject: [PATCH 63/66] Restore black default settings --- .pre-commit-config.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e52e7e9e8d..60cdd9a537 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,8 +13,6 @@ repos: rev: 23.11.0 hooks: - id: black - args: - - --line-length=120 - repo: https://github.com/pycqa/isort rev: 5.13.1 hooks: From a6ff3e63ef863d6509e73683e1de8abf4a3a3aa3 Mon Sep 17 00:00:00 2001 From: Alessandro Candido Date: Thu, 14 Dec 2023 15:11:06 +0100 Subject: [PATCH 64/66] Add docformatter hook --- .pre-commit-config.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 60cdd9a537..8a7708d971 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,6 +18,12 @@ repos: hooks: - id: isort args: ["--profile", "black"] + - repo: https://github.com/PyCQA/docformatter + rev: v1.7.1 + hooks: + - id: docformatter + additional_dependencies: [tomli] + args: [--in-place, --config, ./pyproject.toml] - repo: https://github.com/asottile/pyupgrade rev: v3.15.0 hooks: From 40d3980667cc261dc00aa615d66c9397868c5f9e Mon Sep 17 00:00:00 2001 From: Alessandro Candido Date: Thu, 14 Dec 2023 23:55:04 +0100 Subject: [PATCH 65/66] Pre-commit all --- examples/fidelity_example.py | 4 +- src/qibolab/backends.py | 22 +- src/qibolab/channels.py | 40 +- src/qibolab/compilers/compiler.py | 20 +- src/qibolab/compilers/default.py | 12 +- src/qibolab/couplers.py | 5 +- src/qibolab/dummy.py | 21 +- src/qibolab/execution_parameters.py | 38 +- src/qibolab/instruments/abstract.py | 6 +- src/qibolab/instruments/dummy.py | 21 +- src/qibolab/instruments/erasynth.py | 45 +- src/qibolab/instruments/icarusq.py | 56 ++- src/qibolab/instruments/icarusqfpga.py | 37 +- src/qibolab/instruments/oscillator.py | 7 +- src/qibolab/instruments/port.py | 14 +- src/qibolab/instruments/qblox/acquisition.py | 57 ++- src/qibolab/instruments/qblox/cluster.py | 38 +- .../instruments/qblox/cluster_qcm_bb.py | 324 ++++++++++---- .../instruments/qblox/cluster_qcm_rf.py | 254 ++++++++--- .../instruments/qblox/cluster_qrm_rf.py | 364 +++++++++++---- src/qibolab/instruments/qblox/controller.py | 125 ++++-- src/qibolab/instruments/qblox/debug.py | 13 +- src/qibolab/instruments/qblox/port.py | 135 ++++-- src/qibolab/instruments/qblox/q1asm.py | 71 ++- src/qibolab/instruments/qblox/sequencer.py | 25 +- src/qibolab/instruments/qblox/sweeper.py | 81 +++- src/qibolab/instruments/qm/acquisition.py | 14 +- src/qibolab/instruments/qm/config.py | 75 +++- src/qibolab/instruments/qm/driver.py | 20 +- src/qibolab/instruments/qm/sequence.py | 86 +++- src/qibolab/instruments/qm/simulator.py | 7 +- src/qibolab/instruments/qm/sweepers.py | 26 +- src/qibolab/instruments/qutech.py | 19 +- src/qibolab/instruments/rfsoc/convert.py | 36 +- src/qibolab/instruments/rfsoc/driver.py | 114 +++-- src/qibolab/instruments/rohde_schwarz.py | 23 +- src/qibolab/instruments/unrolling.py | 6 +- src/qibolab/instruments/zhinst.py | 387 ++++++++++------ src/qibolab/native.py | 81 +++- src/qibolab/platform.py | 139 ++++-- src/qibolab/pulses.py | 414 ++++++++++++------ src/qibolab/qubits.py | 23 +- src/qibolab/result.py | 59 +-- src/qibolab/serialize.py | 59 ++- src/qibolab/sweeper.py | 26 +- src/qibolab/symbolic.py | 50 ++- tests/conftest.py | 6 +- tests/dummy_qrc/qm.py | 21 +- tests/dummy_qrc/zurich.py | 54 ++- tests/test_backends.py | 23 +- tests/test_compilers_default.py | 28 +- tests/test_dummy.py | 61 ++- tests/test_instruments_qblox.py | 3 +- tests/test_instruments_qblox_cluster.py | 9 +- .../test_instruments_qblox_cluster_qcm_bb.py | 13 +- .../test_instruments_qblox_cluster_qcm_rf.py | 65 ++- .../test_instruments_qblox_cluster_qrm_rf.py | 46 +- tests/test_instruments_qm.py | 120 ++++- tests/test_instruments_qmsim.py | 131 ++++-- tests/test_instruments_qutech.py | 4 +- tests/test_instruments_rfsoc.py | 360 +++++++++++---- tests/test_instruments_rohde_schwarz.py | 4 +- tests/test_instruments_zhinst.py | 135 ++++-- tests/test_platform.py | 39 +- tests/test_pulses.py | 96 +++- tests/test_result.py | 30 +- tests/test_result_shapes.py | 23 +- tests/test_symbolic.py | 85 +++- 68 files changed, 3547 insertions(+), 1308 deletions(-) diff --git a/examples/fidelity_example.py b/examples/fidelity_example.py index 55dcb87be5..7238b485ba 100644 --- a/examples/fidelity_example.py +++ b/examples/fidelity_example.py @@ -13,7 +13,9 @@ platform.start() # Executes a pulse sequence. results = platform.measure_fidelity(qubits=[1, 2, 3, 4], nshots=3000) -print(f"results[qubit] (rotation_angle, threshold, fidelity, assignment_fidelity): {results}") +print( + f"results[qubit] (rotation_angle, threshold, fidelity, assignment_fidelity): {results}" +) # Turn off lab instruments platform.stop() # Disconnect from the instruments diff --git a/src/qibolab/backends.py b/src/qibolab/backends.py index aa03e4f125..aa2e56b53d 100644 --- a/src/qibolab/backends.py +++ b/src/qibolab/backends.py @@ -40,7 +40,8 @@ def apply_gate_density_matrix(self, gate, state, nqubits): # pragma: no cover def transpile(self, circuit): """Applies the transpiler to a single circuit. - This transforms the circuit into proper connectivity and native gates. + This transforms the circuit into proper connectivity and native + gates. """ # TODO: Move this method to transpilers if self.transpiler is None or self.transpiler.is_satisfied(circuit): @@ -51,7 +52,8 @@ def transpile(self, circuit): return native_circuit, qubit_map def assign_measurements(self, measurement_map, readout): - """Assigning measurement outcomes to :class:`qibo.states.MeasurementResult` for each gate. + """Assigning measurement outcomes to + :class:`qibo.states.MeasurementResult` for each gate. This allows properly obtaining the measured shots from the :class:`qibolab.pulses.ReadoutPulse` object obtaned after pulse sequence execution. @@ -107,7 +109,8 @@ def execute_circuit(self, circuit, initial_state=None, nshots=1000): return result def execute_circuits(self, circuits, initial_state=None, nshots=1000): - """Executes multiple quantum circuits with a single communication with the control electronics. + """Executes multiple quantum circuits with a single communication with + the control electronics. Circuits are unrolled to a single pulse sequence. @@ -134,7 +137,10 @@ def execute_circuits(self, circuits, initial_state=None, nshots=1000): # TODO: Maybe these loops can be parallelized native_circuits, _ = zip(*(self.transpile(circuit) for circuit in circuits)) sequences, measurement_maps = zip( - *(self.compiler.compile(circuit, self.platform) for circuit in native_circuits) + *( + self.compiler.compile(circuit, self.platform) + for circuit in native_circuits + ) ) if not self.platform.is_connected: @@ -150,9 +156,13 @@ def execute_circuits(self, circuits, initial_state=None, nshots=1000): results = [] readout = {k: deque(v) for k, v in readout.items()} for circuit, measurement_map in zip(circuits, measurement_maps): - results.append(MeasurementOutcomes(circuit.measurements, self, nshots=nshots)) + results.append( + MeasurementOutcomes(circuit.measurements, self, nshots=nshots) + ) for gate, sequence in measurement_map.items(): - samples = [readout[pulse.serial].popleft().samples for pulse in sequence.pulses] + samples = [ + readout[pulse.serial].popleft().samples for pulse in sequence.pulses + ] gate.result.backend = self gate.result.register_samples(np.array(samples).T) return results diff --git a/src/qibolab/channels.py b/src/qibolab/channels.py index d3d18aaae2..9d934f6d1f 100644 --- a/src/qibolab/channels.py +++ b/src/qibolab/channels.py @@ -10,11 +10,13 @@ def check_max_offset(offset, max_offset): """Checks if a given offset value exceeds the maximum supported offset. - This is to avoid sending high currents that could damage lab equipment - such as amplifiers. + This is to avoid sending high currents that could damage lab + equipment such as amplifiers. """ if max_offset is not None and abs(offset) > max_offset: - raise_error(ValueError, f"{offset} exceeds the maximum allowed offset {max_offset}.") + raise_error( + ValueError, f"{offset} exceeds the maximum allowed offset {max_offset}." + ) @dataclass @@ -26,18 +28,19 @@ class Channel: port: Optional[Port] = None """Instrument port that is connected to this channel.""" local_oscillator: Optional[LocalOscillator] = None - """Instrument object controlling the local oscillator connected to this channel. + """Instrument object controlling the local oscillator connected to this + channel. - Not applicable for setups that do not use external local oscillators because the - controller can send sufficiently high frequencies or contains internal local - oscillators. + Not applicable for setups that do not use external local oscillators + because the controller can send sufficiently high frequencies or + contains internal local oscillators. """ max_offset: Optional[float] = None """Maximum DC voltage that we can safely send through this channel. - Sending high voltages for prolonged times may damage amplifiers or other lab equipment. - If the user attempts to send a higher value an error will be raised to prevent - execution in real instruments. + Sending high voltages for prolonged times may damage amplifiers or + other lab equipment. If the user attempts to send a higher value an + error will be raised to prevent execution in real instruments. """ @property @@ -112,11 +115,11 @@ def filters(self, value): @dataclass class ChannelMap: - """Collection of :class:`qibolab.designs.channel.Channel` objects identified by name. + """Collection of :class:`qibolab.designs.channel.Channel` objects + identified by name. Essentially, it allows creating a mapping of names to channels just specifying the names. - """ _channels: Dict[str, Channel] = field(default_factory=dict) @@ -124,9 +127,12 @@ class ChannelMap: def add(self, *items): """Add multiple items to the channel map. - If :class:`qibolab.channels.Channel` objects are given they are added to the channel map. - If a different type is given, a :class:`qibolab.channels.Channel` with the - corresponding name is created and added to the channel map. + If + :class: `qibolab.channels.Channel` objects are given they are + added to the channel map. If a different type is + given, a + :class: `qibolab.channels.Channel` with the corresponding name + is created and added to the channel map. """ for item in items: if isinstance(item, Channel): @@ -140,7 +146,9 @@ def __getitem__(self, name): def __setitem__(self, name, channel): if not isinstance(channel, Channel): - raise_error(TypeError, f"Cannot add channel of type {type(channel)} to ChannelMap.") + raise_error( + TypeError, f"Cannot add channel of type {type(channel)} to ChannelMap." + ) self._channels[name] = channel def __contains__(self, name): diff --git a/src/qibolab/compilers/compiler.py b/src/qibolab/compilers/compiler.py index c2077052e4..57e5f4e0d5 100644 --- a/src/qibolab/compilers/compiler.py +++ b/src/qibolab/compilers/compiler.py @@ -18,7 +18,8 @@ @dataclass class Compiler: - """Compiler that transforms a :class:`qibo.models.Circuit` to a :class:`qibolab.pulses.PulseSequence`. + """Compiler that transforms a :class:`qibo.models.Circuit` to a + :class:`qibolab.pulses.PulseSequence`. The transformation is done using a dictionary of rules which map each Qibo gate to a pulse sequence and some virtual Z-phases. @@ -72,7 +73,10 @@ def __delitem__(self, item): try: del self.rules[item] except KeyError: - raise_error(KeyError, f"Cannot remove {item} from compiler because it does not exist.") + raise_error( + KeyError, + f"Cannot remove {item} from compiler because it does not exist.", + ) def register(self, gate_cls): """Decorator for registering a function as a rule in the compiler. @@ -90,7 +94,9 @@ def inner(func): return inner - def _compile_gate(self, gate, platform, sequence, virtual_z_phases, moment_start, delays): + def _compile_gate( + self, gate, platform, sequence, virtual_z_phases, moment_start, delays + ): """Adds a single gate to the pulse sequence.""" rule = self[gate.__class__] # get local sequence and phases for the current gate @@ -99,7 +105,13 @@ def _compile_gate(self, gate, platform, sequence, virtual_z_phases, moment_start # update global pulse sequence # determine the right start time based on the availability of the qubits involved all_qubits = {*gate_sequence.qubits, *gate.qubits} - start = max(*[sequence.get_qubit_pulses(qubit).finish + delays[qubit] for qubit in all_qubits], moment_start) + start = max( + *[ + sequence.get_qubit_pulses(qubit).finish + delays[qubit] + for qubit in all_qubits + ], + moment_start, + ) # shift start time and phase according to the global sequence for pulse in gate_sequence: pulse.start += start diff --git a/src/qibolab/compilers/default.py b/src/qibolab/compilers/default.py index f85f5dc9da..93d0c4a39d 100644 --- a/src/qibolab/compilers/default.py +++ b/src/qibolab/compilers/default.py @@ -43,14 +43,18 @@ def u3_rule(gate, platform): virtual_z_phases = {qubit: lam} sequence = PulseSequence() # Fetch pi/2 pulse from calibration - RX90_pulse_1 = platform.create_RX90_pulse(qubit, start=0, relative_phase=virtual_z_phases[qubit]) + RX90_pulse_1 = platform.create_RX90_pulse( + qubit, start=0, relative_phase=virtual_z_phases[qubit] + ) # apply RX(pi/2) sequence.add(RX90_pulse_1) # apply RZ(theta) virtual_z_phases[qubit] += theta # Fetch pi/2 pulse from calibration RX90_pulse_2 = platform.create_RX90_pulse( - qubit, start=RX90_pulse_1.finish, relative_phase=virtual_z_phases[qubit] - math.pi + qubit, + start=RX90_pulse_1.finish, + relative_phase=virtual_z_phases[qubit] - math.pi, ) # apply RX(-pi/2) sequence.add(RX90_pulse_2) @@ -63,8 +67,8 @@ def u3_rule(gate, platform): def cz_rule(gate, platform): """CZ applied as defined in the platform runcard. - Applying the CZ gate may involve sending pulses on qubits - that the gate is not directly acting on. + Applying the CZ gate may involve sending pulses on qubits that the + gate is not directly acting on. """ return platform.create_CZ_pulse_sequence(gate.qubits) diff --git a/src/qibolab/couplers.py b/src/qibolab/couplers.py index 8181c7fc70..8b525d4f25 100644 --- a/src/qibolab/couplers.py +++ b/src/qibolab/couplers.py @@ -12,8 +12,9 @@ class Coupler: """Representation of a physical coupler. - Coupler objects are instantiated by :class:`qibolab.platforms.platform.Platform` - but they are passed to instrument designs in order to play pulses. + Coupler objects are instantiated by + :class: `qibolab.platforms.platform.Platform` but they are + passed to instrument designs in order to play pulses. """ name: QubitId diff --git a/src/qibolab/dummy.py b/src/qibolab/dummy.py index 31b01e153a..209f7152c1 100644 --- a/src/qibolab/dummy.py +++ b/src/qibolab/dummy.py @@ -9,7 +9,8 @@ def remove_couplers(runcard): - """Remove coupler sections from runcard to create a dummy platform without couplers.""" + """Remove coupler sections from runcard to create a dummy platform without + couplers.""" runcard["topology"] = list(runcard["topology"].values()) del runcard["couplers"] del runcard["native_gates"]["coupler"] @@ -43,8 +44,12 @@ def create_dummy(with_couplers: bool = True): nqubits = runcard["nqubits"] channels = ChannelMap() channels |= Channel("readout", port=instrument["readout"]) - channels |= (Channel(f"drive-{i}", port=instrument[f"drive-{i}"]) for i in range(nqubits)) - channels |= (Channel(f"flux-{i}", port=instrument[f"flux-{i}"]) for i in range(nqubits)) + channels |= ( + Channel(f"drive-{i}", port=instrument[f"drive-{i}"]) for i in range(nqubits) + ) + channels |= ( + Channel(f"flux-{i}", port=instrument[f"flux-{i}"]) for i in range(nqubits) + ) channels |= Channel("twpa", port=None) if with_couplers: channels |= ( @@ -73,4 +78,12 @@ def create_dummy(with_couplers: bool = True): instrument.sampling_rate = settings.sampling_rate * 1e-9 name = "dummy_couplers" if with_couplers else "dummy" - return Platform(name, qubits, pairs, instruments, settings, resonator_type="2D", couplers=couplers) + return Platform( + name, + qubits, + pairs, + instruments, + settings, + resonator_type="2D", + couplers=couplers, + ) diff --git a/src/qibolab/execution_parameters.py b/src/qibolab/execution_parameters.py index 25a1b20922..b317caf138 100644 --- a/src/qibolab/execution_parameters.py +++ b/src/qibolab/execution_parameters.py @@ -13,25 +13,26 @@ class AcquisitionType(Enum): - """Data acquisition from hardware""" + """Data acquisition from hardware.""" DISCRIMINATION = auto() - """Demodulate, integrate the waveform and discriminate among states based on the voltages""" + """Demodulate, integrate the waveform and discriminate among states based + on the voltages.""" INTEGRATION = auto() - """Demodulate and integrate the waveform""" + """Demodulate and integrate the waveform.""" RAW = auto() - """Acquire the waveform as it is""" + """Acquire the waveform as it is.""" SPECTROSCOPY = auto() - """Zurich Integration mode for RO frequency sweeps""" + """Zurich Integration mode for RO frequency sweeps.""" class AveragingMode(Enum): - """Data averaging modes from hardware""" + """Data averaging modes from hardware.""" CYCLIC = auto() - """Better averaging for short timescale noise""" + """Better averaging for short timescale noise.""" SINGLESHOT = auto() - """SINGLESHOT: No averaging""" + """SINGLESHOT: No averaging.""" SEQUENTIAL = auto() """SEQUENTIAL: Worse averaging for noise[Avoid]""" @@ -52,20 +53,27 @@ class AveragingMode(Enum): @dataclass(frozen=True) class ExecutionParameters: - """Data structure to deal with execution parameters""" + """Data structure to deal with execution parameters.""" nshots: Optional[int] = None - """Number of shots to sample from the experiment. Default is the runcard value.""" + """Number of shots to sample from the experiment. + + Default is the runcard value. + """ relaxation_time: Optional[int] = None - """Time to wait for the qubit to relax to its ground Sample between shots in ns. Default is the runcard value.""" + """Time to wait for the qubit to relax to its ground Sample between shots + in ns. + + Default is the runcard value. + """ fast_reset: bool = False - """Enable or disable fast reset""" + """Enable or disable fast reset.""" acquisition_type: AcquisitionType = AcquisitionType.DISCRIMINATION - """Data acquisition type""" + """Data acquisition type.""" averaging_mode: AveragingMode = AveragingMode.SINGLESHOT - """Data averaging mode""" + """Data averaging mode.""" @property def results_type(self): - """Returns corresponding results class""" + """Returns corresponding results class.""" return RESULTS_TYPE[self.averaging_mode][self.acquisition_type] diff --git a/src/qibolab/instruments/abstract.py b/src/qibolab/instruments/abstract.py index e6db6e5af0..26cfcfe673 100644 --- a/src/qibolab/instruments/abstract.py +++ b/src/qibolab/instruments/abstract.py @@ -98,7 +98,8 @@ def play(self, *args, **kwargs): """ def split_batches(self, sequences): # pragma: no cover - """Split sequences to batches each of which can be unrolled and played as a single sequence. + """Split sequences to batches each of which can be unrolled and played + as a single sequence. Args: sequence (list): List of :class:`qibolab.pulses.PulseSequence` to be played. @@ -108,7 +109,8 @@ def split_batches(self, sequences): # pragma: no cover Default will return all sequences as a single batch. """ raise RuntimeError( - f"Instrument of type {type(self)} does not support " "batch splitting for sequence unrolling." + f"Instrument of type {type(self)} does not support " + "batch splitting for sequence unrolling." ) @abstractmethod diff --git a/src/qibolab/instruments/dummy.py b/src/qibolab/instruments/dummy.py index 40f5753c03..8362da3421 100644 --- a/src/qibolab/instruments/dummy.py +++ b/src/qibolab/instruments/dummy.py @@ -65,7 +65,10 @@ def get_values(self, options, ro_pulse, shape): elif options.acquisition_type is AcquisitionType.RAW: samples = int(ro_pulse.duration * self.sampling_rate) waveform_shape = tuple(samples * dim for dim in shape) - values = np.random.rand(*waveform_shape) * 100 + 1j * np.random.rand(*waveform_shape) * 100 + values = ( + np.random.rand(*waveform_shape) * 100 + + 1j * np.random.rand(*waveform_shape) * 100 + ) elif options.acquisition_type is AcquisitionType.INTEGRATION: values = np.random.rand(*shape) * 100 + 1j * np.random.rand(*shape) * 100 return values @@ -77,13 +80,17 @@ def play( sequence: PulseSequence, options: ExecutionParameters, ): - exp_points = 1 if options.averaging_mode is AveragingMode.CYCLIC else options.nshots + exp_points = ( + 1 if options.averaging_mode is AveragingMode.CYCLIC else options.nshots + ) shape = (exp_points,) results = {} for ro_pulse in sequence.ro_pulses: values = np.squeeze(self.get_values(options, ro_pulse, shape)) - results[ro_pulse.qubit] = results[ro_pulse.serial] = options.results_type(values) + results[ro_pulse.qubit] = results[ro_pulse.serial] = options.results_type( + values + ) return results @@ -101,12 +108,16 @@ def sweep( results = {} if options.averaging_mode is not AveragingMode.CYCLIC: - shape = (options.nshots,) + tuple(len(sweeper.values) for sweeper in sweepers) + shape = (options.nshots,) + tuple( + len(sweeper.values) for sweeper in sweepers + ) else: shape = tuple(len(sweeper.values) for sweeper in sweepers) for ro_pulse in sequence.ro_pulses: values = self.get_values(options, ro_pulse, shape) - results[ro_pulse.qubit] = results[ro_pulse.serial] = options.results_type(values) + results[ro_pulse.qubit] = results[ro_pulse.serial] = options.results_type( + values + ) return results diff --git a/src/qibolab/instruments/erasynth.py b/src/qibolab/instruments/erasynth.py index 4704782735..10e58b8528 100644 --- a/src/qibolab/instruments/erasynth.py +++ b/src/qibolab/instruments/erasynth.py @@ -48,12 +48,15 @@ def power(self, x): self._set_device_parameter("power", x) def connect(self): - """Connects to the instrument using the IP address set in the runcard.""" + """Connects to the instrument using the IP address set in the + runcard.""" if not self.is_connected: for attempt in range(3): try: if not self.ethernet: - self.device = ERASynthPlusPlus(f"{self.name}", f"TCPIP::{self.address}::INSTR") + self.device = ERASynthPlusPlus( + f"{self.name}", f"TCPIP::{self.address}::INSTR" + ) else: self._post("readAll", 1) self._post("readDiagnostic", 0) @@ -75,7 +78,8 @@ def connect(self): self._set_device_parameter("power", self.settings.power) def _set_device_parameter(self, parameter: str, value): - """Sets a parameter of the instrument, if it changed from the last stored in the cache. + """Sets a parameter of the instrument, if it changed from the last + stored in the cache. Args: parameter: str = The parameter to be cached and set. @@ -83,11 +87,16 @@ def _set_device_parameter(self, parameter: str, value): Raises: Exception = If attempting to set a parameter without a connection to the instrument. """ - if not (parameter in self._device_parameters and self._device_parameters[parameter] == value): + if not ( + parameter in self._device_parameters + and self._device_parameters[parameter] == value + ): if self.is_connected: if not self.ethernet: if not hasattr(self.device, parameter): - raise ValueError(f"The instrument {self.name} does not have parameter {parameter}") + raise ValueError( + f"The instrument {self.name} does not have parameter {parameter}" + ) self.device.set(parameter, value) else: if parameter == "power": @@ -96,7 +105,9 @@ def _set_device_parameter(self, parameter: str, value): self._post("frequency", int(value)) self._device_parameters[parameter] = value else: - raise ConnectionError(f"Attempting to set {parameter} without a connection to the instrument") + raise ConnectionError( + f"Attempting to set {parameter} without a connection to the instrument" + ) def _erase_device_parameters_cache(self): """Erases the cache of the instrument parameters.""" @@ -132,7 +143,9 @@ def setup(self, frequency=None, power=None, **kwargs): elif kwargs["reference_clock_source"] == "external": self.device.ref_osc_source("ext") else: - raise ValueError(f"Invalid reference clock source {kwargs['reference_clock_source']}") + raise ValueError( + f"Invalid reference clock source {kwargs['reference_clock_source']}" + ) else: self._post("rfoutput", 0) @@ -141,7 +154,9 @@ def setup(self, frequency=None, power=None, **kwargs): elif kwargs["reference_clock_source"] == "external": self._post("reference_int_ext", 1) else: - raise ValueError(f"Invalid reference clock source {kwargs['reference_clock_source']}") + raise ValueError( + f"Invalid reference clock source {kwargs['reference_clock_source']}" + ) def start(self): self.on() @@ -172,8 +187,7 @@ def close(self): self.is_connected = False def _post(self, name, value): - """ - Post a value to the instrument's web server. + """Post a value to the instrument's web server. Try to post three times, waiting for 0.1 seconds between each attempt. @@ -184,7 +198,9 @@ def _post(self, name, value): value = str(value) for _ in range(MAX_RECONNECTION_ATTEMPTS): try: - response = requests.post(f"http://{self.address}/", data={name: value}, timeout=TIMEOUT) + response = requests.post( + f"http://{self.address}/", data={name: value}, timeout=TIMEOUT + ) if response.status_code == 200: return True break @@ -193,8 +209,7 @@ def _post(self, name, value): raise InstrumentException(self, f"Unable to post {name}={value} to {self.name}") def _get(self, name): - """ - Get a value from the instrument's web server. + """Get a value from the instrument's web server. Try to get three times, waiting for 0.1 seconds between each attempt. @@ -203,7 +218,9 @@ def _get(self, name): """ for _ in range(MAX_RECONNECTION_ATTEMPTS): try: - response = requests.post(f"http://{self.address}/", params={"readAll": 1}, timeout=TIMEOUT) + response = requests.post( + f"http://{self.address}/", params={"readAll": 1}, timeout=TIMEOUT + ) if response.status_code == 200: # reponse.text is a dictonary in string format, convert it to a dictonary diff --git a/src/qibolab/instruments/icarusq.py b/src/qibolab/instruments/icarusq.py index 262bb63028..bde1de050e 100644 --- a/src/qibolab/instruments/icarusq.py +++ b/src/qibolab/instruments/icarusq.py @@ -34,7 +34,9 @@ def connect(self): raise InstrumentException(self, str(exc)) self.is_connected = True else: - raise_error(Exception, "There is an open connection to the instrument already") + raise_error( + Exception, "There is an open connection to the instrument already" + ) def setup(self, **kwargs): if self.is_connected: @@ -52,14 +54,16 @@ def setup(self, **kwargs): awg_ch = getattr(self.device, f"ch{channel}") awg_ch.awg_amplitude(amplitude[idx]) awg_ch.resolution(resolution) - self.device.write(f"SOURCE{channel}:VOLTAGE:LEVEL:IMMEDIATE:OFFSET {offset[idx]}") + self.device.write( + f"SOURCE{channel}:VOLTAGE:LEVEL:IMMEDIATE:OFFSET {offset[idx]}" + ) self.__dict__.update(kwargs) else: raise_error(Exception, "There is no connection to the instrument") def generate_waveforms_from_pulse(self, pulse: Pulse, time_array: np.ndarray): - """Generates a numpy array based on the pulse parameters + """Generates a numpy array based on the pulse parameters. Arguments: pulse (qibolab.pulses.Pulse | qibolab.pulses.ReadoutPulse): Pulse to be compiled @@ -67,17 +71,24 @@ def generate_waveforms_from_pulse(self, pulse: Pulse, time_array: np.ndarray): """ i_ch, q_ch = pulse.channel - i = pulse.envelope_i * np.cos(2 * np.pi * pulse.frequency * time_array + pulse.phase + self.channel_phase[i_ch]) + i = pulse.envelope_i * np.cos( + 2 * np.pi * pulse.frequency * time_array + + pulse.phase + + self.channel_phase[i_ch] + ) q = ( -1 * pulse.envelope_i - * np.sin(2 * np.pi * pulse.frequency * time_array + pulse.phase + self.channel_phase[q_ch]) + * np.sin( + 2 * np.pi * pulse.frequency * time_array + + pulse.phase + + self.channel_phase[q_ch] + ) ) return i, q def translate(self, sequence: List[Pulse], nshots=None): - """ - Translates the pulse sequence into a numpy array. + """Translates the pulse sequence into a numpy array. Arguments: sequence (qibolab.pulses.Pulse[]): Array containing pulses to be fired on this instrument. @@ -98,14 +109,17 @@ def translate(self, sequence: List[Pulse], nshots=None): start_index = bisect(time_array, pulse.start * 1e-9) end_index = bisect(time_array, (pulse.start + pulse.duration) * 1e-9) i_ch, q_ch = pulse.channel - i, q = self.generate_waveforms_from_pulse(pulse, time_array[start_index:end_index]) + i, q = self.generate_waveforms_from_pulse( + pulse, time_array[start_index:end_index] + ) waveform_arrays[i_ch, start_index:end_index] += i waveform_arrays[q_ch, start_index:end_index] += q return waveform_arrays def upload(self, waveform: np.ndarray): - """Uploads a nchannels X nsamples array to the AWG, load it into memory and assign it to the channels for playback.""" + """Uploads a nchannels X nsamples array to the AWG, load it into memory + and assign it to the channels for playback.""" # TODO: Add additional check to ensure all waveforms are of the same size? Should be caught by qcodes driver anyway. if len(waveform) != self.device.num_channels: @@ -169,7 +183,6 @@ def setup(self, attenuation: float, **kwargs): Arguments: attenuation(float ): Attenuation setting in dB. Ranges from 0 to 35. - """ import urllib3 @@ -289,16 +302,17 @@ def setup(self, trigger_volts, **kwargs): def arm(self, nshots, readout_start): with self.device.syncing(): - self.device.trigger_delay(int(int((readout_start * 1e-9 + 4e-6) / 1e-9 / 8) * 8)) + self.device.trigger_delay( + int(int((readout_start * 1e-9 + 4e-6) / 1e-9 / 8) * 8) + ) self.controller.arm(nshots) def play_sequence_and_acquire(self): - """ - this method performs an acquisition, which is the get_cmd for the - acquisiion parameter of this instrument - :return: - """ - raw = self.device.acquire(acquisition_controller=self.controller, **self.controller.acquisitionkwargs) + """This method performs an acquisition, which is the get_cmd for the + acquisiion parameter of this instrument :return:""" + raw = self.device.acquire( + acquisition_controller=self.controller, **self.controller.acquisitionkwargs + ) return self.process_result(raw) def process_result(self, readout_frequency=100e6, readout_channels=[0, 1]): @@ -320,8 +334,12 @@ def process_result(self, readout_frequency=100e6, readout_channels=[0, 1]): it = 0 qt = 0 for i in range(self.device.samples_per_record): - it += input_vec_I[i] * np.cos(2 * np.pi * readout_frequency * self.device.time_array[i]) - qt += input_vec_Q[i] * np.cos(2 * np.pi * readout_frequency * self.device.time_array[i]) + it += input_vec_I[i] * np.cos( + 2 * np.pi * readout_frequency * self.device.time_array[i] + ) + qt += input_vec_Q[i] * np.cos( + 2 * np.pi * readout_frequency * self.device.time_array[i] + ) phase = np.arctan2(qt, it) ampl = np.sqrt(it**2 + qt**2) diff --git a/src/qibolab/instruments/icarusqfpga.py b/src/qibolab/instruments/icarusqfpga.py index fe96580e3e..fc5623c5a8 100644 --- a/src/qibolab/instruments/icarusqfpga.py +++ b/src/qibolab/instruments/icarusqfpga.py @@ -32,7 +32,8 @@ def setup(self, holdtime_ns, pins=None, **kwargs): self._holdtime = holdtime_ns def arm(self, nshots, readout_start=0): - """Arm the PulseBlaster for playback. Sends a signal to the instrument to setup the pulse sequence and repetition. + """Arm the PulseBlaster for playback. Sends a signal to the instrument + to setup the pulse sequence and repetition. Arguments: nshots (int): Number of TTL triggers to repeat. @@ -72,7 +73,9 @@ def disconnect(self): @staticmethod def _hexify(pins): - return int("".join(["1" if i in set(pins) else "0" for i in reversed(range(24))]), 2) + return int( + "".join(["1" if i in set(pins) else "0" for i in reversed(range(24))]), 2 + ) class IcarusQFPGA(Instrument): @@ -95,7 +98,8 @@ def __init__(self, name, address, port=8080): self.nshots = None def setup(self, dac_sampling_rate, adcs_to_read, **kwargs): - """Sets the sampling rate of the RFSoC. May need to be repeated several times due to multi-tile sync error. + """Sets the sampling rate of the RFSoC. May need to be repeated several + times due to multi-tile sync error. Arguments: dac_sampling_rate_id (int): Sampling rate ID to be set on the RFSoC. @@ -111,7 +115,9 @@ def translate(self, sequence: List[Pulse], nshots): waveform = np.zeros((self._dac_nchannels, self._dac_sample_size), dtype="i2") # The global time can first be set as float to handle rounding errors. - time_array = 1 / self._dac_sampling_rate * np.arange(0, self._dac_sample_size, 1) + time_array = ( + 1 / self._dac_sampling_rate * np.arange(0, self._dac_sample_size, 1) + ) for pulse in sequence: # Get array indices corresponding to the start and end of the pulse. Note that the pulse time parameters are in ns and require conversion. @@ -120,9 +126,12 @@ def translate(self, sequence: List[Pulse], nshots): # Create the pulse waveform and cast it to 16-bit. The ampltiude is max signed 14-bit (+- 8191) and the indices should take care of any overlap of pulses. # 2-byte bit shift for downsampling from 16 bit to 14 bit - pulse_waveform = (4 * np.sin(2 * np.pi * pulse.frequency * time_array[start:end] + pulse.phase)).astype( - "i2" - ) + pulse_waveform = ( + 4 + * np.sin( + 2 * np.pi * pulse.frequency * time_array[start:end] + pulse.phase + ) + ).astype("i2") waveform[pulse.channel, start:end] += pulse_waveform self.nshots = nshots @@ -130,7 +139,8 @@ def translate(self, sequence: List[Pulse], nshots): return waveform def upload(self, waveform): - """Uploads a numpy array of size DAC_CHANNELS X DAC_SAMPLE_SIZE to the PL memory. + """Uploads a numpy array of size DAC_CHANNELS X DAC_SAMPLE_SIZE to the + PL memory. Arguments: waveform (numpy.ndarray): Numpy array of size DAC_CHANNELS X DAC_SAMPLE_SIZE with type signed short. @@ -145,7 +155,8 @@ def upload(self, waveform): s.sendall(waveform.tobytes()) def play_sequence(self): - """DACs are automatically armed for playbacked when waveforms are loaded, no need to signal""" + """DACs are automatically armed for playbacked when waveforms are + loaded, no need to signal.""" self._buffer = np.zeros((self._adc_nchannels, self._adc_sample_size)) self._thread = threading.Thread(target=self._play, args=(self.nshots,)) self._thread.start() @@ -153,8 +164,8 @@ def play_sequence(self): time.sleep(0.1) # Use threading lock and socket signals instead of hard sleep? def play_sequence_and_acquire(self): - """Signal the RFSoC to arm the ADC and start data transfer into PS memory. - Starts a thread to listen for ADC data from the RFSoC. + """Signal the RFSoC to arm the ADC and start data transfer into PS + memory. Starts a thread to listen for ADC data from the RFSoC. Arguments: nshots (int): Number of shots. @@ -176,7 +187,9 @@ def _play(self, nshots): # Signal RFSoC to arm ADC and expect `nshots` number of triggers. s.sendall(struct.pack("B", 2)) s.sendall(struct.pack("H", nshots)) - s.sendall(struct.pack("B", len(self._adcs_to_read))) # send number of channels + s.sendall( + struct.pack("B", len(self._adcs_to_read)) + ) # send number of channels for adc in self._adcs_to_read: s.sendall(struct.pack("B", adc)) # send ADC channel to read diff --git a/src/qibolab/instruments/oscillator.py b/src/qibolab/instruments/oscillator.py index 372d27192b..2c7cce116a 100644 --- a/src/qibolab/instruments/oscillator.py +++ b/src/qibolab/instruments/oscillator.py @@ -13,10 +13,9 @@ class LocalOscillatorSettings(InstrumentSettings): class LocalOscillator(Instrument): """Abstraction for local oscillator instruments. - Local oscillators are used to upconvert signals, when - the controllers cannot send sufficiently high frequencies - to address the qubits and resonators. - They cannot be used to play or sweep pulses. + Local oscillators are used to upconvert signals, when the + controllers cannot send sufficiently high frequencies to address the + qubits and resonators. They cannot be used to play or sweep pulses. """ def __init__(self, name, address): diff --git a/src/qibolab/instruments/port.py b/src/qibolab/instruments/port.py index 805b3afab6..51aecff6a8 100644 --- a/src/qibolab/instruments/port.py +++ b/src/qibolab/instruments/port.py @@ -17,10 +17,12 @@ class Port: """DC offset that is applied to this port.""" lo_frequency: float """Local oscillator frequency for the given port. + Relevant only for controllers with internal local oscillators. """ lo_power: float """Local oscillator power for the given port. + Relevant only for controllers with internal local oscillators. """ # TODO: Maybe gain, attenuation and power range can be unified to a single attribute @@ -29,11 +31,13 @@ class Port: attenuation: float """Attenuation that is applied to this port.""" power_range: int - """Similar to attenuation (negative) and gain (positive) for (Zurich instruments).""" + """Similar to attenuation (negative) and gain (positive) for (Zurich + instruments).""" filters: dict - """Filters to be applied to the channel to reduce the distortions when sending flux pulses. + """Filters to be applied to the channel to reduce the distortions when + sending flux pulses. - Useful for two-qubit gates. - Quantum Machines (:class:`qibolab.instruments.qm.QMOPX`) associate filters to channels - but this may not be the case in other instruments. + Useful for two-qubit gates. Quantum Machines ( + :class: `qibolab.instruments.qm.QMOPX`) associate filters to + channels but this may not be the case in other instruments. """ diff --git a/src/qibolab/instruments/qblox/acquisition.py b/src/qibolab/instruments/qblox/acquisition.py index ff5e0f4178..317136d57c 100644 --- a/src/qibolab/instruments/qblox/acquisition.py +++ b/src/qibolab/instruments/qblox/acquisition.py @@ -27,14 +27,17 @@ def demodulate(input_i, input_q, frequency): class AveragedAcquisition: """Software Demodulation. - Every readout pulse triggers an acquisition, where the 16384 i and q samples of the waveform - acquired by the ADC are saved into a dedicated memory within the FPGA. This is what qblox calls - *scoped acquisition*. The results of multiple shots are averaged in this memory, and cannot be - retrieved independently. The resulting waveforms averages (i and q) are then demodulated and - integrated in software (and finally divided by the number of samples). - Since Software Demodulation relies on the data of the scoped acquisition and that data is the - average of all acquisitions, **only one readout pulse per qubit is supported**, so that - the averages all correspond to reading the same quantum state. + Every readout pulse triggers an acquisition, where the 16384 i and q + samples of the waveform acquired by the ADC are saved into a + dedicated memory within the FPGA. This is what qblox calls *scoped + acquisition*. The results of multiple shots are averaged in this + memory, and cannot be retrieved independently. The resulting + waveforms averages (i and q) are then demodulated and integrated in + software (and finally divided by the number of samples). Since + Software Demodulation relies on the data of the scoped acquisition + and that data is the average of all acquisitions, **only one readout + pulse per qubit is supported**, so that the averages all correspond + to reading the same quantum state. """ results: dict @@ -65,7 +68,8 @@ def raw_q(self): def data(self): """Acquisition data to be returned to the platform. - Ignores the data available in acquisition results and returns only i and q voltages. + Ignores the data available in acquisition results and returns + only i and q voltages. """ # TODO: to be updated once the functionality of ExecutionResults is extended if self.i is None or self.q is None: @@ -77,13 +81,16 @@ def data(self): class DemodulatedAcquisition: """Hardware Demodulation. - With hardware demodulation activated, the FPGA can demodulate, integrate (average over time), and classify - each shot individually, saving the results on separate bins. The raw data of each acquisition continues to - be averaged as with software modulation, so there is no way to access the raw data of each shot (unless - executed one shot at a time). The FPGA uses fixed point arithmetic for the demodulation and integration; - if the power level of the signal at the input port is low (the minimum resolution of the ADC is 240uV) - rounding precission errors can accumulate and render wrong results. It is advisable to have a power level - at least higher than 5mV. + With hardware demodulation activated, the FPGA can demodulate, + integrate (average over time), and classify each shot individually, + saving the results on separate bins. The raw data of each + acquisition continues to be averaged as with software modulation, so + there is no way to access the raw data of each shot (unless executed + one shot at a time). The FPGA uses fixed point arithmetic for the + demodulation and integration; if the power level of the signal at + the input port is low (the minimum resolution of the ADC is 240uV) + rounding precission errors can accumulate and render wrong results. + It is advisable to have a power level at least higher than 5mV. """ bins: dict @@ -97,34 +104,40 @@ def integration(self): @property def shots_i(self): - """i-component after demodulating and integrating every shot waveform.""" + """I-component after demodulating and integrating every shot + waveform.""" return np.array(self.integration["path0"]) / self.duration @property def shots_q(self): - """q-component after demodulating and integrating every shot waveform.""" + """Q-component after demodulating and integrating every shot + waveform.""" return np.array(self.integration["path1"]) / self.duration @property def averaged_i(self): - """i-component after demodulating and integrating every shot waveform and then averaging over shots.""" + """I-component after demodulating and integrating every shot waveform + and then averaging over shots.""" return np.mean(self.shots_i) @property def averaged_q(self): - """q-component after demodulating and integrating every shot waveform and then averaging over shots.""" + """Q-component after demodulating and integrating every shot waveform + and then averaging over shots.""" return np.mean(self.shots_q) @property def classified(self): - """List with the results of demodulating, integrating and classifying every shot.""" + """List with the results of demodulating, integrating and classifying + every shot.""" return np.array(self.bins["threshold"]) @property def data(self): """Acquisition data to be returned to the platform. - Ignores the data available in acquisition results and returns only i and q voltages. + Ignores the data available in acquisition results and returns + only i and q voltages. """ # TODO: to be updated once the functionality of ExecutionResults is extended return (self.shots_i, self.shots_q, self.classified) diff --git a/src/qibolab/instruments/qblox/cluster.py b/src/qibolab/instruments/qblox/cluster.py index 564a5614bf..3b8dac38ce 100644 --- a/src/qibolab/instruments/qblox/cluster.py +++ b/src/qibolab/instruments/qblox/cluster.py @@ -1,4 +1,4 @@ -""" Qblox instruments driver. +"""Qblox instruments driver. Supports the following Instruments: @@ -53,29 +53,32 @@ class Cluster_Settings: """Settings of the Cluster instrument.""" reference_clock_source: ReferenceClockSource = ReferenceClockSource.INTERNAL - """Instruct the cluster to use the internal clock source or an external source.""" + """Instruct the cluster to use the internal clock source or an external + source.""" class Cluster(Instrument): """A class to extend the functionality of qblox_instruments Cluster. - The class exposes the attribute `reference_clock_source` to enable the selection of an internal or external clock - source. + The class exposes the attribute `reference_clock_source` to enable + the selection of an internal or external clock source. - The class inherits from :class:`qibolab.instruments.abstract.Instrument` and implements its interface methods: - __init__() - connect() - setup() - start() - stop() + The class inherits from + + :class: `qibolab.instruments.abstract.Instrument` and implements its + interface methods: __init__() connect() setup() start() stop() disconnect() """ - def __init__(self, name: str, address: str, settings: Cluster_Settings = Cluster_Settings()): - """Initialises the instrument storing its name, address and settings.""" + def __init__( + self, name: str, address: str, settings: Cluster_Settings = Cluster_Settings() + ): + """Initialises the instrument storing its name, address and + settings.""" super().__init__(name, address) self.device: QbloxCluster = None - """Reference to the underlying `qblox_instruments.qcodes_drivers.cluster.Cluster` object.""" + """Reference to the underlying + `qblox_instruments.qcodes_drivers.cluster.Cluster` object.""" self.settings: Cluster_Settings = settings """Instrument settings.""" @@ -93,13 +96,16 @@ def reference_clock_source(self) -> ReferenceClockSource: def reference_clock_source(self, value: ReferenceClockSource): self.settings.reference_clock_source = value if self.is_connected: - self.device.set("reference_source", self.settings.reference_clock_source.value) + self.device.set( + "reference_source", self.settings.reference_clock_source.value + ) def connect(self): """Connects to the cluster. - If the connection is successful, it resets the instrument and configures it with the stored settings. - A reference to the underlying object is saved in the attribute `device`. + If the connection is successful, it resets the instrument and + configures it with the stored settings. A reference to the + underlying object is saved in the attribute `device`. """ if not self.is_connected: for attempt in range(3): diff --git a/src/qibolab/instruments/qblox/cluster_qcm_bb.py b/src/qibolab/instruments/qblox/cluster_qcm_bb.py index 858cdaa3f2..c0644a6beb 100644 --- a/src/qibolab/instruments/qblox/cluster_qcm_bb.py +++ b/src/qibolab/instruments/qblox/cluster_qcm_bb.py @@ -1,4 +1,4 @@ -""" Qblox Cluster QCM driver.""" +"""Qblox Cluster QCM driver.""" import json @@ -102,16 +102,19 @@ class ClusterQCM_BB(Instrument): property_wrapper = lambda parent, *parameter: property( lambda self: parent.device.get(parameter[0]), - lambda self, x: parent._set_device_parameter(parent.device, *parameter, value=x), + lambda self, x: parent._set_device_parameter( + parent.device, *parameter, value=x + ), ) sequencer_property_wrapper = lambda parent, sequencer, *parameter: property( lambda self: parent.device.sequencers[sequencer].get(parameter[0]), - lambda self, x: parent._set_device_parameter(parent.device.sequencers[sequencer], *parameter, value=x), + lambda self, x: parent._set_device_parameter( + parent.device.sequencers[sequencer], *parameter, value=x + ), ) def __init__(self, name: str, address: str, cluster: Cluster = None): - """ - Initialize a Qblox QCM baseband module. + """Initialize a Qblox QCM baseband module. Parameters: - name: An arbitrary name to identify the module. @@ -142,24 +145,36 @@ def __init__(self, name: str, address: str, cluster: Cluster = None): self._unused_sequencers_numbers: list[int] = [] def connect(self): - """Connects to the instrument using the instrument settings in the runcard. + """Connects to the instrument using the instrument settings in the + runcard. - Once connected, it creates port classes with properties mapped to various instrument - parameters, and initialises the the underlying device parameters. - It uploads to the module the port settings loaded from the runcard. + Once connected, it creates port classes with properties mapped + to various instrument parameters, and initialises the the + underlying device parameters. It uploads to the module the port + settings loaded from the runcard. """ self._cluster.connect() self.device = self._cluster.device.modules[int(self.address.split(":")[1]) - 1] if not self.is_connected: if not self.device.present(): - raise Exception(f"Module {self.device.name} not connected to cluster {self._cluster.cluster.name}") + raise Exception( + f"Module {self.device.name} not connected to cluster {self._cluster.cluster.name}" + ) self.is_connected = True # once connected, initialise the parameters of the device to the default values - self._set_device_parameter(self.device, "out0_offset", value=0) # Default after reboot = 0 - self._set_device_parameter(self.device, "out1_offset", value=0) # Default after reboot = 0 - self._set_device_parameter(self.device, "out2_offset", value=0) # Default after reboot = 0 - self._set_device_parameter(self.device, "out3_offset", value=0) # Default after reboot = 0 + self._set_device_parameter( + self.device, "out0_offset", value=0 + ) # Default after reboot = 0 + self._set_device_parameter( + self.device, "out1_offset", value=0 + ) # Default after reboot = 0 + self._set_device_parameter( + self.device, "out2_offset", value=0 + ) # Default after reboot = 0 + self._set_device_parameter( + self.device, "out3_offset", value=0 + ) # Default after reboot = 0 # initialise the parameters of the default sequencers to the default values, # the rest of the sequencers are not configured here, but will be configured @@ -171,19 +186,38 @@ def connect(self): self.device.sequencers[self.DEFAULT_SEQUENCERS["o4"]], ]: self._set_device_parameter( - target, "cont_mode_en_awg_path0", "cont_mode_en_awg_path1", value=False + target, + "cont_mode_en_awg_path0", + "cont_mode_en_awg_path1", + value=False, + ) # Default after reboot = False + self._set_device_parameter( + target, + "cont_mode_waveform_idx_awg_path0", + "cont_mode_waveform_idx_awg_path1", + value=0, + ) # Default after reboot = 0 + self._set_device_parameter( + target, "marker_ovr_en", value=True ) # Default after reboot = False self._set_device_parameter( - target, "cont_mode_waveform_idx_awg_path0", "cont_mode_waveform_idx_awg_path1", value=0 + target, "marker_ovr_value", value=15 ) # Default after reboot = 0 - self._set_device_parameter(target, "marker_ovr_en", value=True) # Default after reboot = False - self._set_device_parameter(target, "marker_ovr_value", value=15) # Default after reboot = 0 self._set_device_parameter(target, "mixer_corr_gain_ratio", value=1) - self._set_device_parameter(target, "mixer_corr_phase_offset_degree", value=0) + self._set_device_parameter( + target, "mixer_corr_phase_offset_degree", value=0 + ) self._set_device_parameter(target, "offset_awg_path0", value=0) self._set_device_parameter(target, "offset_awg_path1", value=0) - self._set_device_parameter(target, "sync_en", value=False) # Default after reboot = False - self._set_device_parameter(target, "upsample_rate_awg_path0", "upsample_rate_awg_path1", value=0) + self._set_device_parameter( + target, "sync_en", value=False + ) # Default after reboot = False + self._set_device_parameter( + target, + "upsample_rate_awg_path0", + "upsample_rate_awg_path1", + value=0, + ) for target in [ self.device.sequencers[self.DEFAULT_SEQUENCERS["o1"]], @@ -191,10 +225,18 @@ def connect(self): self.device.sequencers[self.DEFAULT_SEQUENCERS["o3"]], self.device.sequencers[self.DEFAULT_SEQUENCERS["o4"]], ]: - self._set_device_parameter(target, "connect_out0", value="off") # Default after reboot = True - self._set_device_parameter(target, "connect_out1", value="off") # Default after reboot = True - self._set_device_parameter(target, "connect_out2", value="off") # Default after reboot = True - self._set_device_parameter(target, "connect_out3", value="off") # Default after reboot = True + self._set_device_parameter( + target, "connect_out0", value="off" + ) # Default after reboot = True + self._set_device_parameter( + target, "connect_out1", value="off" + ) # Default after reboot = True + self._set_device_parameter( + target, "connect_out2", value="off" + ) # Default after reboot = True + self._set_device_parameter( + target, "connect_out3", value="off" + ) # Default after reboot = True self._set_device_parameter( self.device.sequencers[self.DEFAULT_SEQUENCERS["o1"]], @@ -233,10 +275,13 @@ def connect(self): self.ports[port].nco_freq = 0 self.ports[port].nco_phase_offs = 0 except: - raise RuntimeError(f"Unable to initialize port parameters on module {self.name}") + raise RuntimeError( + f"Unable to initialize port parameters on module {self.name}" + ) def _set_device_parameter(self, target, *parameters, value): - """Sets a parameter of the instrument, if it changed from the last stored in the cache. + """Sets a parameter of the instrument, if it changed from the last + stored in the cache. Args: target = an instance of qblox_instruments.qcodes_drivers.qcm_qrm.QcmQrm or @@ -252,7 +297,9 @@ def _set_device_parameter(self, target, *parameters, value): if key not in self._device_parameters: for parameter in parameters: if not hasattr(target, parameter): - raise Exception(f"The instrument {self.name} does not have parameters {parameter}") + raise Exception( + f"The instrument {self.name} does not have parameters {parameter}" + ) target.set(parameter, value) self._device_parameters[key] = value elif self._device_parameters[key] != value: @@ -265,7 +312,8 @@ def _erase_device_parameters_cache(self): self._device_parameters = {} def setup(self, **settings): - """Cache the settings of the runcard and instantiate the ports of the module. + """Cache the settings of the runcard and instantiate the ports of the + module. Args: **settings: dict = A dictionary of settings loaded from the runcard: @@ -277,7 +325,10 @@ def setup(self, **settings): """ for port_num, port in enumerate(settings): self.ports[port] = QbloxOutputPort( - self, self.DEFAULT_SEQUENCERS[port], port_number=port_num, port_name=port + self, + self.DEFAULT_SEQUENCERS[port], + port_number=port_num, + port_name=port, ) self.settings = settings if settings else self.settings @@ -298,17 +349,24 @@ def _get_next_sequencer(self, port, frequency, qubits: dict): qubit = None for _qubit in qubits.values(): if _qubit.flux.port is not None: - if _qubit.flux.port.name == port and _qubit.flux.port.module.name == self.name: + if ( + _qubit.flux.port.name == port + and _qubit.flux.port.module.name == self.name + ): qubit = _qubit else: log.warning(f"Qubit {_qubit.name} has no flux line connected") # select a new sequencer and configure it as required next_sequencer_number = self._free_sequencers_numbers.pop(0) if next_sequencer_number != self.DEFAULT_SEQUENCERS[port]: - for parameter in self.device.sequencers[self.DEFAULT_SEQUENCERS[port]].parameters: + for parameter in self.device.sequencers[ + self.DEFAULT_SEQUENCERS[port] + ].parameters: # exclude read-only parameter `sequence` if parameter not in ["sequence"]: - value = self.device.sequencers[self.DEFAULT_SEQUENCERS[port]].get(param_name=parameter) + value = self.device.sequencers[self.DEFAULT_SEQUENCERS[port]].get( + param_name=parameter + ) if value: target = self.device.sequencers[next_sequencer_number] self._set_device_parameter(target, parameter, value=value) @@ -339,7 +397,8 @@ def _get_next_sequencer(self, port, frequency, qubits: dict): return sequencer def get_if(self, pulse): - """Returns the intermediate frequency needed to synthesise a pulse based on the port lo frequency.""" + """Returns the intermediate frequency needed to synthesise a pulse + based on the port lo frequency.""" _rf = pulse.frequency _lo = 0 # QCMs do not have local oscillator @@ -360,8 +419,8 @@ def process_pulse_sequence( repetition_duration: int, sweepers=None, ): - """Processes a list of pulses, generating the waveforms and sequence program required by - the instrument to synthesise them. + """Processes a list of pulses, generating the waveforms and sequence + program required by the instrument to synthesise them. The output of the process is a list of sequencers used for each port, configured with the information required to play the sequence. @@ -402,14 +461,22 @@ def process_pulse_sequence( # process the pulses for every port for port in self.ports: # split the collection of instruments pulses by ports - port_channel = [chan.name for chan in self.channel_map.values() if chan.port.name == port] - port_pulses: PulseSequence = instrument_pulses.get_channel_pulses(*port_channel) + port_channel = [ + chan.name + for chan in self.channel_map.values() + if chan.port.name == port + ] + port_pulses: PulseSequence = instrument_pulses.get_channel_pulses( + *port_channel + ) # initialise the list of sequencers required by the port self._sequencers[port] = [] # initialise the list of free sequencer numbers to include the default for each port {'o1': 0, 'o2': 1, 'o3': 2, 'o4': 3} - self._free_sequencers_numbers = [self.DEFAULT_SEQUENCERS[port]] + self._free_sequencers_numbers + self._free_sequencers_numbers = [ + self.DEFAULT_SEQUENCERS[port] + ] + self._free_sequencers_numbers if not port_pulses.is_empty: # split the collection of port pulses in non overlapping pulses @@ -425,7 +492,9 @@ def process_pulse_sequence( ) # get next sequencer sequencer = self._get_next_sequencer( - port=port, frequency=self.get_if(non_overlapping_pulses[0]), qubits=qubits + port=port, + frequency=self.get_if(non_overlapping_pulses[0]), + qubits=qubits, ) # add the sequencer to the list of sequencers required by the port self._sequencers[port].append(sequencer) @@ -436,13 +505,18 @@ def process_pulse_sequence( pulse: Pulse = pulses_to_be_processed[0] # attempt to save the waveforms to the sequencer waveforms buffer try: - sequencer.waveforms_buffer.add_waveforms(pulse, self.ports[port].hardware_mod_en, sweepers) + sequencer.waveforms_buffer.add_waveforms( + pulse, self.ports[port].hardware_mod_en, sweepers + ) sequencer.pulses.add(pulse) pulses_to_be_processed.remove(pulse) # if there is not enough memory in the current sequencer, use another one except WaveformsBuffer.NotEnoughMemory: - if len(pulse.waveform_i) + len(pulse.waveform_q) > WaveformsBuffer.SIZE: + if ( + len(pulse.waveform_i) + len(pulse.waveform_q) + > WaveformsBuffer.SIZE + ): raise NotImplementedError( f"Pulses with waveforms longer than the memory of a sequencer ({WaveformsBuffer.SIZE // 2}) are not supported." ) @@ -452,12 +526,16 @@ def process_pulse_sequence( ) # get next sequencer sequencer = self._get_next_sequencer( - port=port, frequency=self.get_if(non_overlapping_pulses[0]), qubits=qubits + port=port, + frequency=self.get_if(non_overlapping_pulses[0]), + qubits=qubits, ) # add the sequencer to the list of sequencers required by the port self._sequencers[port].append(sequencer) else: - sequencer = self._get_next_sequencer(port=port, frequency=0, qubits=qubits) + sequencer = self._get_next_sequencer( + port=port, frequency=0, qubits=qubits + ) # add the sequencer to the list of sequencers required by the port self._sequencers[port].append(sequencer) @@ -497,32 +575,56 @@ def process_pulse_sequence( for sweeper in sweepers: if sweeper.parameter in pulse_sweeper_parameters: # check if this sequencer takes an active role in the sweep - if sweeper.pulses and set(sequencer.pulses) & set(sweeper.pulses): + if sweeper.pulses and set(sequencer.pulses) & set( + sweeper.pulses + ): # plays an active role reference_value = None - if sweeper.parameter == Parameter.frequency and sequencer.pulses: + if ( + sweeper.parameter == Parameter.frequency + and sequencer.pulses + ): reference_value = self.get_if(sequencer.pulses[0]) if sweeper.parameter == Parameter.amplitude: for pulse in pulses: if pulse in sweeper.pulses: - reference_value = pulse.amplitude # uses the amplitude of the first pulse - if sweeper.parameter == Parameter.duration and pulse in sweeper.pulses: + reference_value = ( + pulse.amplitude + ) # uses the amplitude of the first pulse + if ( + sweeper.parameter == Parameter.duration + and pulse in sweeper.pulses + ): # for duration sweepers bake waveforms sweeper.qs = QbloxSweeper( - program=program, type=QbloxSweeperType.duration, rel_values=pulse.idx_range + program=program, + type=QbloxSweeperType.duration, + rel_values=pulse.idx_range, ) else: # create QbloxSweepers and attach them to qibolab sweeper - if sweeper.type == SweeperType.OFFSET and reference_value: + if ( + sweeper.type == SweeperType.OFFSET + and reference_value + ): sweeper.qs = QbloxSweeper.from_sweeper( - program=program, sweeper=sweeper, add_to=reference_value + program=program, + sweeper=sweeper, + add_to=reference_value, ) - elif sweeper.type == SweeperType.FACTOR and reference_value: + elif ( + sweeper.type == SweeperType.FACTOR + and reference_value + ): sweeper.qs = QbloxSweeper.from_sweeper( - program=program, sweeper=sweeper, multiply_to=reference_value + program=program, + sweeper=sweeper, + multiply_to=reference_value, ) else: - sweeper.qs = QbloxSweeper.from_sweeper(program=program, sweeper=sweeper) + sweeper.qs = QbloxSweeper.from_sweeper( + program=program, sweeper=sweeper + ) # finally attach QbloxSweepers to the pulses being swept sweeper.qs.update_parameters = True @@ -537,19 +639,27 @@ def process_pulse_sequence( ) else: # qubit_sweeper_parameters - if sweeper.qubits and sequencer.qubit in [_.name for _ in sweeper.qubits]: + if sweeper.qubits and sequencer.qubit in [ + _.name for _ in sweeper.qubits + ]: # plays an active role if sweeper.parameter == Parameter.bias: reference_value = self.ports[port].offset # create QbloxSweepers and attach them to qibolab sweeper if sweeper.type == SweeperType.ABSOLUTE: sweeper.qs = QbloxSweeper.from_sweeper( - program=program, sweeper=sweeper, add_to=-reference_value + program=program, + sweeper=sweeper, + add_to=-reference_value, ) elif sweeper.type == SweeperType.OFFSET: - sweeper.qs = QbloxSweeper.from_sweeper(program=program, sweeper=sweeper) + sweeper.qs = QbloxSweeper.from_sweeper( + program=program, sweeper=sweeper + ) elif sweeper.type == SweeperType.FACTOR: - raise Exception("SweeperType.FACTOR for Parameter.bias not supported") + raise Exception( + "SweeperType.FACTOR for Parameter.bias not supported" + ) sweeper.qs.update_parameters = True else: # does not play an active role @@ -567,13 +677,20 @@ def process_pulse_sequence( # never take an active role in those sweeps. # Waveforms - for index, waveform in enumerate(sequencer.waveforms_buffer.unique_waveforms): - sequencer.waveforms[waveform.serial] = {"data": waveform.data.tolist(), "index": index} + for index, waveform in enumerate( + sequencer.waveforms_buffer.unique_waveforms + ): + sequencer.waveforms[waveform.serial] = { + "data": waveform.data.tolist(), + "index": index, + } # Program minimum_delay_between_instructions = 4 - sequence_total_duration = pulses.finish # the minimum delay between instructions is 4ns + sequence_total_duration = ( + pulses.finish + ) # the minimum delay between instructions is 4ns time_between_repetitions = repetition_duration - sequence_total_duration assert time_between_repetitions > minimum_delay_between_instructions # TODO: currently relaxation_time needs to be greater than acquisition_hold_off @@ -599,12 +716,17 @@ def process_pulse_sequence( pulses_block = Block("play") # Add an initial wait instruction for the first pulse of the sequence initial_wait_block = wait_block( - wait_time=pulses.start, register=Register(program), force_multiples_of_four=False + wait_time=pulses.start, + register=Register(program), + force_multiples_of_four=False, ) pulses_block += initial_wait_block for n in range(pulses.count): - if pulses[n].sweeper and pulses[n].sweeper.type == QbloxSweeperType.start: + if ( + pulses[n].sweeper + and pulses[n].sweeper.type == QbloxSweeperType.start + ): pulses_block.append(f"wait {pulses[n].sweeper.register}") if self.ports[port].hardware_mod_en: @@ -613,7 +735,11 @@ def process_pulse_sequence( # pulses_block.append(f"set_freq {convert_frequency(_if)}", f"set intermediate frequency to {_if} Hz") # Set phase - if pulses[n].sweeper and pulses[n].sweeper.type == QbloxSweeperType.relative_phase: + if ( + pulses[n].sweeper + and pulses[n].sweeper.type + == QbloxSweeperType.relative_phase + ): pulses_block.append(f"set_ph {pulses[n].sweeper.register}") else: pulses_block.append( @@ -633,7 +759,10 @@ def process_pulse_sequence( f"The minimum delay between the start of two pulses in the same channel is {minimum_delay_between_instructions}ns." ) - if pulses[n].sweeper and pulses[n].sweeper.type == QbloxSweeperType.duration: + if ( + pulses[n].sweeper + and pulses[n].sweeper.type == QbloxSweeperType.duration + ): RI = pulses[n].sweeper.register if pulses[n].type == PulseType.FLUX: RQ = pulses[n].sweeper.register @@ -655,7 +784,9 @@ def process_pulse_sequence( body_block.append_spacer() final_reset_block = wait_block( - wait_time=time_between_repetitions, register=Register(program), force_multiples_of_four=False + wait_time=time_between_repetitions, + register=Register(program), + force_multiples_of_four=False, ) body_block += final_reset_block @@ -667,32 +798,52 @@ def process_pulse_sequence( body_block = sweeper.qs.block(inner_block=body_block) nshots_block: Block = loop_block( - start=0, stop=nshots, step=1, register=nshots_register, block=body_block + start=0, + stop=nshots, + step=1, + register=nshots_register, + block=body_block, + ) + navgs_block = loop_block( + start=0, + stop=navgs, + step=1, + register=navgs_register, + block=nshots_block, ) - navgs_block = loop_block(start=0, stop=navgs, step=1, register=navgs_register, block=nshots_block) program.add_blocks(header_block, navgs_block, footer_block) sequencer.program = repr(program) def upload(self): - """Uploads waveforms and programs of all sequencers and arms them in preparation for execution. + """Uploads waveforms and programs of all sequencers and arms them in + preparation for execution. This method should be called after `process_pulse_sequence()`. - It configures certain parameters of the instrument based on the needs of resources determined - while processing the pulse sequence. + It configures certain parameters of the instrument based on the + needs of resources determined while processing the pulse + sequence. """ # Setup for sequencer_number in self._used_sequencers_numbers: target = self.device.sequencers[sequencer_number] self._set_device_parameter(target, "sync_en", value=True) - self._set_device_parameter(target, "marker_ovr_en", value=True) # Default after reboot = False - self._set_device_parameter(target, "marker_ovr_value", value=15) # Default after reboot = 0 + self._set_device_parameter( + target, "marker_ovr_en", value=True + ) # Default after reboot = False + self._set_device_parameter( + target, "marker_ovr_value", value=15 + ) # Default after reboot = 0 for sequencer_number in self._unused_sequencers_numbers: target = self.device.sequencers[sequencer_number] self._set_device_parameter(target, "sync_en", value=False) - self._set_device_parameter(target, "marker_ovr_en", value=True) # Default after reboot = False - self._set_device_parameter(target, "marker_ovr_value", value=0) # Default after reboot = 0 + self._set_device_parameter( + target, "marker_ovr_en", value=True + ) # Default after reboot = False + self._set_device_parameter( + target, "marker_ovr_value", value=0 + ) # Default after reboot = 0 if sequencer_number >= 4: # Never disconnect default sequencers self._set_device_parameter(target, "connect_out0", value="off") self._set_device_parameter(target, "connect_out1", value="off") @@ -702,10 +853,18 @@ def upload(self): # There seems to be a bug in qblox that when any of the mappings between paths and outputs is set, # the general offset goes to 0 (eventhough the parameter will still show the right value). # Until that is fixed, I'm going to always set the offset just before playing (bypassing the cache): - self.device.out0_offset(self._device_parameters[self.device.name + "." + "out0_offset"]) - self.device.out1_offset(self._device_parameters[self.device.name + "." + "out1_offset"]) - self.device.out2_offset(self._device_parameters[self.device.name + "." + "out2_offset"]) - self.device.out3_offset(self._device_parameters[self.device.name + "." + "out3_offset"]) + self.device.out0_offset( + self._device_parameters[self.device.name + "." + "out0_offset"] + ) + self.device.out1_offset( + self._device_parameters[self.device.name + "." + "out1_offset"] + ) + self.device.out2_offset( + self._device_parameters[self.device.name + "." + "out2_offset"] + ) + self.device.out3_offset( + self._device_parameters[self.device.name + "." + "out3_offset"] + ) # Upload waveforms and program qblox_dict = {} @@ -725,7 +884,10 @@ def upload(self): # DEBUG: QCM Save sequence to file if self._debug_folder != "": - filename = self._debug_folder + f"Z_{self.name}_sequencer{sequencer.number}_sequence.json" + filename = ( + self._debug_folder + + f"Z_{self.name}_sequencer{sequencer.number}_sequence.json" + ) with open(filename, "w", encoding="utf-8") as file: json.dump(qblox_dict[sequencer], file, indent=4) file.write(sequencer.program) @@ -758,7 +920,7 @@ def start(self): pass def stop(self): - """Stops all sequencers""" + """Stops all sequencers.""" for sequencer_number in self._used_sequencers_numbers: state = self.device.get_sequencer_state(sequencer_number) diff --git a/src/qibolab/instruments/qblox/cluster_qcm_rf.py b/src/qibolab/instruments/qblox/cluster_qcm_rf.py index 70e215e0bd..2c1b7b74bc 100644 --- a/src/qibolab/instruments/qblox/cluster_qcm_rf.py +++ b/src/qibolab/instruments/qblox/cluster_qcm_rf.py @@ -1,4 +1,4 @@ -""" Qblox Cluster QCM-RF driver.""" +"""Qblox Cluster QCM-RF driver.""" import json @@ -112,8 +112,6 @@ class ClusterQCM_RF(Instrument): channels (list): A list of the channels to which the instrument is connected. - - """ DEFAULT_SEQUENCERS = {"o1": 0, "o2": 1} @@ -122,22 +120,25 @@ class ClusterQCM_RF(Instrument): property_wrapper = lambda parent, *parameter: property( lambda self: parent.device.get(parameter[0]), - lambda self, x: parent._set_device_parameter(parent.device, *parameter, value=x), + lambda self, x: parent._set_device_parameter( + parent.device, *parameter, value=x + ), ) property_wrapper.__doc__ = """A lambda function used to create properties that wrap around the device parameters and caches their value using `_set_device_parameter()`. """ sequencer_property_wrapper = lambda parent, sequencer, *parameter: property( lambda self: parent.device.sequencers[sequencer].get(parameter[0]), - lambda self, x: parent._set_device_parameter(parent.device.sequencers[sequencer], *parameter, value=x), + lambda self, x: parent._set_device_parameter( + parent.device.sequencers[sequencer], *parameter, value=x + ), ) sequencer_property_wrapper.__doc__ = """A lambda function used to create properties that wrap around the device sequencer parameters and caches their value using `_set_device_parameter()`. """ def __init__(self, name: str, address: str, cluster: Cluster): - """ - Initialize a Qblox QCM-RF module. + """Initialize a Qblox QCM-RF module. Parameters: - name: An arbitrary name to identify the module. @@ -165,11 +166,13 @@ def __init__(self, name: str, address: str, cluster: Cluster): self._unused_sequencers_numbers: list[int] = [] def connect(self): - """Connects to the instrument using the instrument settings in the runcard. + """Connects to the instrument using the instrument settings in the + runcard. - Once connected, it creates port classes with properties mapped to various instrument - parameters, and initialises the the underlying device parameters. - It uploads to the module the port settings loaded from the runcard. + Once connected, it creates port classes with properties mapped + to various instrument parameters, and initialises the the + underlying device parameters. It uploads to the module the port + settings loaded from the runcard. """ self._cluster.connect() self.device = self._cluster.device.modules[int(self.address.split(":")[1]) - 1] @@ -195,18 +198,39 @@ def connect(self): self.device.sequencers[self.DEFAULT_SEQUENCERS["o1"]], self.device.sequencers[self.DEFAULT_SEQUENCERS["o2"]], ]: - self._set_device_parameter(target, "cont_mode_en_awg_path0", "cont_mode_en_awg_path1", value=False) self._set_device_parameter( - target, "cont_mode_waveform_idx_awg_path0", "cont_mode_waveform_idx_awg_path1", value=0 + target, + "cont_mode_en_awg_path0", + "cont_mode_en_awg_path1", + value=False, ) - self._set_device_parameter(target, "marker_ovr_en", value=True) # Default after reboot = False - self._set_device_parameter(target, "marker_ovr_value", value=15) # Default after reboot = 0 + self._set_device_parameter( + target, + "cont_mode_waveform_idx_awg_path0", + "cont_mode_waveform_idx_awg_path1", + value=0, + ) + self._set_device_parameter( + target, "marker_ovr_en", value=True + ) # Default after reboot = False + self._set_device_parameter( + target, "marker_ovr_value", value=15 + ) # Default after reboot = 0 self._set_device_parameter(target, "mixer_corr_gain_ratio", value=1) - self._set_device_parameter(target, "mixer_corr_phase_offset_degree", value=0) + self._set_device_parameter( + target, "mixer_corr_phase_offset_degree", value=0 + ) self._set_device_parameter(target, "offset_awg_path0", value=0) self._set_device_parameter(target, "offset_awg_path1", value=0) - self._set_device_parameter(target, "sync_en", value=False) # Default after reboot = False - self._set_device_parameter(target, "upsample_rate_awg_path0", "upsample_rate_awg_path1", value=0) + self._set_device_parameter( + target, "sync_en", value=False + ) # Default after reboot = False + self._set_device_parameter( + target, + "upsample_rate_awg_path0", + "upsample_rate_awg_path1", + value=0, + ) self._set_device_parameter( self.device.sequencers[self.DEFAULT_SEQUENCERS["o1"]], @@ -247,16 +271,21 @@ def connect(self): self._sequencers[port] = [] if self.settings[port]["lo_frequency"]: self.ports[port].lo_enabled = True - self.ports[port].lo_frequency = self.settings[port]["lo_frequency"] + self.ports[port].lo_frequency = self.settings[port][ + "lo_frequency" + ] self.ports[port].attenuation = self.settings[port]["attenuation"] self.ports[port].hardware_mod_en = True self.ports[port].nco_freq = 0 self.ports[port].nco_phase_offs = 0 except: - raise RuntimeError(f"Unable to initialize port parameters on module {self.name}") + raise RuntimeError( + f"Unable to initialize port parameters on module {self.name}" + ) def _set_device_parameter(self, target, *parameters, value): - """Sets a parameter of the instrument, if it changed from the last stored in the cache. + """Sets a parameter of the instrument, if it changed from the last + stored in the cache. Args: target = an instance of qblox_instruments.qcodes_drivers.qcm_qrm.QcmQrm or @@ -267,12 +296,16 @@ def _set_device_parameter(self, target, *parameters, value): Exception = If attempting to set a parameter without a connection to the instrument. """ if not self.is_connected: - raise ConnectionError("There is no connection to the instrument {self.name}") + raise ConnectionError( + "There is no connection to the instrument {self.name}" + ) key = f"{target.name}.{parameters[0]}" if key not in self._device_parameters: for parameter in parameters: if not hasattr(target, parameter): - raise ValueError(f"The instrument {self.name} does not have parameters {parameter}") + raise ValueError( + f"The instrument {self.name} does not have parameters {parameter}" + ) target.set(parameter, value) self._device_parameters[key] = value elif self._device_parameters[key] != value: @@ -285,7 +318,8 @@ def _erase_device_parameters_cache(self): self._device_parameters = {} def setup(self, **settings): - """Cache the settings of the runcard and instantiate the ports of the module. + """Cache the settings of the runcard and instantiate the ports of the + module. Args: **settings: dict = A dictionary of settings loaded from the runcard: @@ -304,7 +338,10 @@ def setup(self, **settings): """ for port_num, port in enumerate(settings): self.ports[port] = QbloxOutputPort( - self, self.DEFAULT_SEQUENCERS[port], port_number=port_num, port_name=port + self, + self.DEFAULT_SEQUENCERS[port], + port_number=port_num, + port_name=port, ) self._sequencers[port] = [] self.settings = settings if settings else self.settings @@ -325,10 +362,14 @@ def _get_next_sequencer(self, port, frequency, qubit: None): # select a new sequencer and configure it as required next_sequencer_number = self._free_sequencers_numbers.pop(0) if next_sequencer_number != self.DEFAULT_SEQUENCERS[port]: - for parameter in self.device.sequencers[self.DEFAULT_SEQUENCERS[port]].parameters: + for parameter in self.device.sequencers[ + self.DEFAULT_SEQUENCERS[port] + ].parameters: # exclude read-only parameter `sequence` if parameter not in ["sequence"]: - value = self.device.sequencers[self.DEFAULT_SEQUENCERS[port]].get(param_name=parameter) + value = self.device.sequencers[self.DEFAULT_SEQUENCERS[port]].get( + param_name=parameter + ) if value: target = self.device.sequencers[next_sequencer_number] self._set_device_parameter(target, parameter, value=value) @@ -350,7 +391,8 @@ def _get_next_sequencer(self, port, frequency, qubit: None): return sequencer def get_if(self, pulse): - """Returns the intermediate frequency needed to synthesise a pulse based on the port lo frequency.""" + """Returns the intermediate frequency needed to synthesise a pulse + based on the port lo frequency.""" _rf = pulse.frequency _lo = self.channel_map[pulse.channel].lo_frequency @@ -373,8 +415,8 @@ def process_pulse_sequence( repetition_duration: int, sweepers=None, ): - """Processes a sequence of pulses and sweepers, generating the waveforms and program required by - the instrument to synthesise them. + """Processes a sequence of pulses and sweepers, generating the + waveforms and program required by the instrument to synthesise them. The output of the process is a list of sequencers used for each port, configured with the information required to play the sequence. @@ -415,15 +457,23 @@ def process_pulse_sequence( # process the pulses for every port for port in self.ports: # split the collection of instruments pulses by ports - port_channel = [chan.name for chan in self.channel_map.values() if chan.port.name == port] - port_pulses: PulseSequence = instrument_pulses.get_channel_pulses(*port_channel) + port_channel = [ + chan.name + for chan in self.channel_map.values() + if chan.port.name == port + ] + port_pulses: PulseSequence = instrument_pulses.get_channel_pulses( + *port_channel + ) # initialise the list of sequencers required by the port self._sequencers[port] = [] if not port_pulses.is_empty: # initialise the list of free sequencer numbers to include the default for each port {'o1': 0, 'o2': 1} - self._free_sequencers_numbers = [self.DEFAULT_SEQUENCERS[port]] + self._free_sequencers_numbers + self._free_sequencers_numbers = [ + self.DEFAULT_SEQUENCERS[port] + ] + self._free_sequencers_numbers # split the collection of port pulses in non overlapping pulses non_overlapping_pulses: PulseSequence @@ -450,13 +500,18 @@ def process_pulse_sequence( pulse: Pulse = pulses_to_be_processed[0] # attempt to save the waveforms to the sequencer waveforms buffer try: - sequencer.waveforms_buffer.add_waveforms(pulse, self.ports[port].hardware_mod_en, sweepers) + sequencer.waveforms_buffer.add_waveforms( + pulse, self.ports[port].hardware_mod_en, sweepers + ) sequencer.pulses.add(pulse) pulses_to_be_processed.remove(pulse) # if there is not enough memory in the current sequencer, use another one except WaveformsBuffer.NotEnoughMemory: - if len(pulse.waveform_i) + len(pulse.waveform_q) > WaveformsBuffer.SIZE: + if ( + len(pulse.waveform_i) + len(pulse.waveform_q) + > WaveformsBuffer.SIZE + ): raise NotImplementedError( f"Pulses with waveforms longer than the memory of a sequencer ({WaveformsBuffer.SIZE // 2}) are not supported." ) @@ -509,32 +564,56 @@ def process_pulse_sequence( for sweeper in sweepers: if sweeper.parameter in pulse_sweeper_parameters: # check if this sequencer takes an active role in the sweep - if sweeper.pulses and set(sequencer.pulses) & set(sweeper.pulses): + if sweeper.pulses and set(sequencer.pulses) & set( + sweeper.pulses + ): # plays an active role reference_value = None - if sweeper.parameter == Parameter.frequency and sequencer.pulses: + if ( + sweeper.parameter == Parameter.frequency + and sequencer.pulses + ): reference_value = self.get_if(sequencer.pulses[0]) if sweeper.parameter == Parameter.amplitude: for pulse in pulses: if pulse in sweeper.pulses: - reference_value = pulse.amplitude # uses the amplitude of the first pulse - if sweeper.parameter == Parameter.duration and pulse in sweeper.pulses: + reference_value = ( + pulse.amplitude + ) # uses the amplitude of the first pulse + if ( + sweeper.parameter == Parameter.duration + and pulse in sweeper.pulses + ): # for duration sweepers bake waveforms sweeper.qs = QbloxSweeper( - program=program, type=QbloxSweeperType.duration, rel_values=pulse.idx_range + program=program, + type=QbloxSweeperType.duration, + rel_values=pulse.idx_range, ) else: # create QbloxSweepers and attach them to qibolab sweeper - if sweeper.type == SweeperType.OFFSET and reference_value: + if ( + sweeper.type == SweeperType.OFFSET + and reference_value + ): sweeper.qs = QbloxSweeper.from_sweeper( - program=program, sweeper=sweeper, add_to=reference_value + program=program, + sweeper=sweeper, + add_to=reference_value, ) - elif sweeper.type == SweeperType.FACTOR and reference_value: + elif ( + sweeper.type == SweeperType.FACTOR + and reference_value + ): sweeper.qs = QbloxSweeper.from_sweeper( - program=program, sweeper=sweeper, multiply_to=reference_value + program=program, + sweeper=sweeper, + multiply_to=reference_value, ) else: - sweeper.qs = QbloxSweeper.from_sweeper(program=program, sweeper=sweeper) + sweeper.qs = QbloxSweeper.from_sweeper( + program=program, sweeper=sweeper + ) # finally attach QbloxSweepers to the pulses being swept sweeper.qs.update_parameters = True @@ -585,13 +664,20 @@ def process_pulse_sequence( # # never take an active role in those sweeps. # Waveforms - for index, waveform in enumerate(sequencer.waveforms_buffer.unique_waveforms): - sequencer.waveforms[waveform.serial] = {"data": waveform.data.tolist(), "index": index} + for index, waveform in enumerate( + sequencer.waveforms_buffer.unique_waveforms + ): + sequencer.waveforms[waveform.serial] = { + "data": waveform.data.tolist(), + "index": index, + } # Program minimum_delay_between_instructions = 4 - sequence_total_duration = pulses.finish # the minimum delay between instructions is 4ns + sequence_total_duration = ( + pulses.finish + ) # the minimum delay between instructions is 4ns time_between_repetitions = repetition_duration - sequence_total_duration assert time_between_repetitions > minimum_delay_between_instructions # TODO: currently relaxation_time needs to be greater than acquisition_hold_off @@ -617,12 +703,17 @@ def process_pulse_sequence( pulses_block = Block("play") # Add an initial wait instruction for the first pulse of the sequence initial_wait_block = wait_block( - wait_time=pulses[0].start, register=Register(program), force_multiples_of_four=False + wait_time=pulses[0].start, + register=Register(program), + force_multiples_of_four=False, ) pulses_block += initial_wait_block for n in range(pulses.count): - if pulses[n].sweeper and pulses[n].sweeper.type == QbloxSweeperType.start: + if ( + pulses[n].sweeper + and pulses[n].sweeper.type == QbloxSweeperType.start + ): pulses_block.append(f"wait {pulses[n].sweeper.register}") if self.ports[port].hardware_mod_en: @@ -631,7 +722,11 @@ def process_pulse_sequence( # pulses_block.append(f"set_freq {convert_frequency(_if)}", f"set intermediate frequency to {_if} Hz") # Set phase - if pulses[n].sweeper and pulses[n].sweeper.type == QbloxSweeperType.relative_phase: + if ( + pulses[n].sweeper + and pulses[n].sweeper.type + == QbloxSweeperType.relative_phase + ): pulses_block.append(f"set_ph {pulses[n].sweeper.register}") else: pulses_block.append( @@ -651,7 +746,10 @@ def process_pulse_sequence( f"The minimum delay between the start of two pulses in the same channel is {minimum_delay_between_instructions}ns." ) - if pulses[n].sweeper and pulses[n].sweeper.type == QbloxSweeperType.duration: + if ( + pulses[n].sweeper + and pulses[n].sweeper.type == QbloxSweeperType.duration + ): RI = pulses[n].sweeper.register if pulses[n].type == PulseType.FLUX: RQ = pulses[n].sweeper.register @@ -673,7 +771,9 @@ def process_pulse_sequence( body_block.append_spacer() final_reset_block = wait_block( - wait_time=time_between_repetitions, register=Register(program), force_multiples_of_four=False + wait_time=time_between_repetitions, + register=Register(program), + force_multiples_of_four=False, ) body_block += final_reset_block @@ -686,33 +786,53 @@ def process_pulse_sequence( body_block = sweeper.qs.block(inner_block=body_block) nshots_block: Block = loop_block( - start=0, stop=nshots, step=1, register=nshots_register, block=body_block + start=0, + stop=nshots, + step=1, + register=nshots_register, + block=body_block, ) - navgs_block = loop_block(start=0, stop=navgs, step=1, register=navgs_register, block=nshots_block) + navgs_block = loop_block( + start=0, + stop=navgs, + step=1, + register=navgs_register, + block=nshots_block, + ) program.add_blocks(header_block, navgs_block, footer_block) sequencer.program = repr(program) def upload(self): - """Uploads waveforms and programs of all sequencers and arms them in preparation for execution. + """Uploads waveforms and programs of all sequencers and arms them in + preparation for execution. This method should be called after `process_pulse_sequence()`. - It configures certain parameters of the instrument based on the needs of resources determined - while processing the pulse sequence. + It configures certain parameters of the instrument based on the + needs of resources determined while processing the pulse + sequence. """ # Setup for sequencer_number in self._used_sequencers_numbers: target = self.device.sequencers[sequencer_number] self._set_device_parameter(target, "sync_en", value=True) - self._set_device_parameter(target, "marker_ovr_en", value=True) # Default after reboot = False - self._set_device_parameter(target, "marker_ovr_value", value=15) # Default after reboot = 0 + self._set_device_parameter( + target, "marker_ovr_en", value=True + ) # Default after reboot = False + self._set_device_parameter( + target, "marker_ovr_value", value=15 + ) # Default after reboot = 0 for sequencer_number in self._unused_sequencers_numbers: target = self.device.sequencers[sequencer_number] self._set_device_parameter(target, "sync_en", value=False) - self._set_device_parameter(target, "marker_ovr_en", value=True) # Default after reboot = False - self._set_device_parameter(target, "marker_ovr_value", value=0) # Default after reboot = 0 + self._set_device_parameter( + target, "marker_ovr_en", value=True + ) # Default after reboot = False + self._set_device_parameter( + target, "marker_ovr_value", value=0 + ) # Default after reboot = 0 if sequencer_number >= 2: # Never disconnect default sequencers self._set_device_parameter(target, "connect_out0", value="off") self._set_device_parameter(target, "connect_out1", value="off") @@ -735,7 +855,10 @@ def upload(self): # DEBUG: QCM RF Save sequence to file if self._debug_folder != "": - filename = self._debug_folder + f"Z_{self.name}_sequencer{sequencer.number}_sequence.json" + filename = ( + self._debug_folder + + f"Z_{self.name}_sequencer{sequencer.number}_sequence.json" + ) with open(filename, "w", encoding="utf-8") as file: json.dump(qblox_dict[sequencer], file, indent=4) file.write(sequencer.program) @@ -759,7 +882,8 @@ def upload(self): def play_sequence(self): """Plays the sequence of pulses. - Starts the sequencers needed to play the sequence of pulses.""" + Starts the sequencers needed to play the sequence of pulses. + """ for sequencer_number in self._used_sequencers_numbers: # Start used sequencers @@ -770,7 +894,7 @@ def start(self): pass def stop(self): - """Stops all sequencers""" + """Stops all sequencers.""" from qibo.config import log for sequencer_number in self._used_sequencers_numbers: diff --git a/src/qibolab/instruments/qblox/cluster_qrm_rf.py b/src/qibolab/instruments/qblox/cluster_qrm_rf.py index 74b03f06c1..58cf74e23c 100644 --- a/src/qibolab/instruments/qblox/cluster_qrm_rf.py +++ b/src/qibolab/instruments/qblox/cluster_qrm_rf.py @@ -1,4 +1,4 @@ -""" Qblox Cluster QRM-RF driver.""" +"""Qblox Cluster QRM-RF driver.""" import json import time @@ -119,8 +119,6 @@ class ClusterQRM_RF(Instrument): classification. channels (list): A list of the channels to which the instrument is connected. - - """ DEFAULT_SEQUENCERS: dict = {"o1": 0, "i1": 0} @@ -129,22 +127,25 @@ class ClusterQRM_RF(Instrument): property_wrapper = lambda parent, *parameter: property( lambda self: parent.device.get(parameter[0]), - lambda self, x: parent._set_device_parameter(parent.device, *parameter, value=x), + lambda self, x: parent._set_device_parameter( + parent.device, *parameter, value=x + ), ) property_wrapper.__doc__ = """A lambda function used to create properties that wrap around the device parameters and caches their value using `_set_device_parameter()`. """ sequencer_property_wrapper = lambda parent, sequencer, *parameter: property( lambda self: parent.device.sequencers[sequencer].get(parameter[0]), - lambda self, x: parent._set_device_parameter(parent.device.sequencers[sequencer], *parameter, value=x), + lambda self, x: parent._set_device_parameter( + parent.device.sequencers[sequencer], *parameter, value=x + ), ) sequencer_property_wrapper.__doc__ = """A lambda function used to create properties that wrap around the device sequencer parameters and caches their value using `_set_device_parameter()`. """ def __init__(self, name: str, address: str, cluster: Cluster): - """ - Initialize a Qblox QRM-RF module. + """Initialize a Qblox QRM-RF module. Parameters: - name: An arbitrary name to identify the module. @@ -178,11 +179,13 @@ def __init__(self, name: str, address: str, cluster: Cluster): self._execution_time: float = 0 def connect(self): - """Connects to the instrument using the instrument settings in the runcard. + """Connects to the instrument using the instrument settings in the + runcard. - Once connected, it creates port classes with properties mapped to various instrument - parameters, and initialises the the underlying device parameters. - It uploads to the module the port settings loaded from the runcard. + Once connected, it creates port classes with properties mapped + to various instrument parameters, and initialises the the + underlying device parameters. It uploads to the module the port + settings loaded from the runcard. """ self._cluster.connect() self.device = self._cluster.device.modules[int(self.address.split(":")[1]) - 1] @@ -201,14 +204,27 @@ def connect(self): self.device, "out0_offset_path0", "out0_offset_path1", value=0 ) # Default after reboot = 7.625 self._set_device_parameter( - self.device, "scope_acq_avg_mode_en_path0", "scope_acq_avg_mode_en_path1", value=True + self.device, + "scope_acq_avg_mode_en_path0", + "scope_acq_avg_mode_en_path1", + value=True, ) - self._set_device_parameter(self.device, "scope_acq_sequencer_select", value=self.DEFAULT_SEQUENCERS["i1"]) self._set_device_parameter( - self.device, "scope_acq_trigger_level_path0", "scope_acq_trigger_level_path1", value=0 + self.device, + "scope_acq_sequencer_select", + value=self.DEFAULT_SEQUENCERS["i1"], ) self._set_device_parameter( - self.device, "scope_acq_trigger_mode_path0", "scope_acq_trigger_mode_path1", value="sequencer" + self.device, + "scope_acq_trigger_level_path0", + "scope_acq_trigger_level_path1", + value=0, + ) + self._set_device_parameter( + self.device, + "scope_acq_trigger_mode_path0", + "scope_acq_trigger_mode_path1", + value="sequencer", ) # initialise the parameters of the default sequencer to the default values, @@ -218,18 +234,33 @@ def connect(self): self._set_device_parameter(target, "connect_out0", value="IQ") self._set_device_parameter(target, "connect_acq", value="in0") - self._set_device_parameter(target, "cont_mode_en_awg_path0", "cont_mode_en_awg_path1", value=False) self._set_device_parameter( - target, "cont_mode_waveform_idx_awg_path0", "cont_mode_waveform_idx_awg_path1", value=0 + target, "cont_mode_en_awg_path0", "cont_mode_en_awg_path1", value=False + ) + self._set_device_parameter( + target, + "cont_mode_waveform_idx_awg_path0", + "cont_mode_waveform_idx_awg_path1", + value=0, ) - self._set_device_parameter(target, "marker_ovr_en", value=True) # Default after reboot = False - self._set_device_parameter(target, "marker_ovr_value", value=15) # Default after reboot = 0 + self._set_device_parameter( + target, "marker_ovr_en", value=True + ) # Default after reboot = False + self._set_device_parameter( + target, "marker_ovr_value", value=15 + ) # Default after reboot = 0 self._set_device_parameter(target, "mixer_corr_gain_ratio", value=1) - self._set_device_parameter(target, "mixer_corr_phase_offset_degree", value=0) + self._set_device_parameter( + target, "mixer_corr_phase_offset_degree", value=0 + ) self._set_device_parameter(target, "offset_awg_path0", value=0) self._set_device_parameter(target, "offset_awg_path1", value=0) - self._set_device_parameter(target, "sync_en", value=False) # Default after reboot = False - self._set_device_parameter(target, "upsample_rate_awg_path0", "upsample_rate_awg_path1", value=0) + self._set_device_parameter( + target, "sync_en", value=False + ) # Default after reboot = False + self._set_device_parameter( + target, "upsample_rate_awg_path0", "upsample_rate_awg_path1", value=0 + ) # on initialisation, disconnect all other sequencers from the ports self._device_num_sequencers = len(self.device.sequencers) @@ -249,20 +280,29 @@ def connect(self): self.ports["o1"].attenuation = self.settings["o1"]["attenuation"] if self.settings["o1"]["lo_frequency"]: self.ports["o1"].lo_enabled = True - self.ports["o1"].lo_frequency = self.settings["o1"]["lo_frequency"] + self.ports["o1"].lo_frequency = self.settings["o1"][ + "lo_frequency" + ] self.ports["o1"].hardware_mod_en = True self.ports["o1"].nco_freq = 0 self.ports["o1"].nco_phase_offs = 0 if "i1" in self.settings: self.ports["i1"].hardware_demod_en = True - self.ports["i1"].acquisition_hold_off = self.settings["i1"]["acquisition_hold_off"] - self.ports["i1"].acquisition_duration = self.settings["i1"]["acquisition_duration"] + self.ports["i1"].acquisition_hold_off = self.settings["i1"][ + "acquisition_hold_off" + ] + self.ports["i1"].acquisition_duration = self.settings["i1"][ + "acquisition_duration" + ] except: - raise RuntimeError(f"Unable to initialize port parameters on module {self.name}") + raise RuntimeError( + f"Unable to initialize port parameters on module {self.name}" + ) def _set_device_parameter(self, target, *parameters, value): - """Sets a parameter of the instrument, if it changed from the last stored in the cache. + """Sets a parameter of the instrument, if it changed from the last + stored in the cache. Args: target = an instance of qblox_instruments.qcodes_drivers.qcm_qrm.QcmQrm or @@ -277,7 +317,9 @@ def _set_device_parameter(self, target, *parameters, value): if not key in self._device_parameters: for parameter in parameters: if not hasattr(target, parameter): - raise Exception(f"The instrument {self.name} does not have parameters {parameter}.") + raise Exception( + f"The instrument {self.name} does not have parameters {parameter}." + ) target.set(parameter, value) self._device_parameters[key] = value elif self._device_parameters[key] != value: @@ -292,7 +334,8 @@ def _erase_device_parameters_cache(self): self._device_parameters = {} def setup(self, **settings): - """Cache the settings of the runcard and instantiate the ports of the module. + """Cache the settings of the runcard and instantiate the ports of the + module. Args: **settings: dict = A dictionary of settings loaded from the runcard: @@ -317,11 +360,13 @@ def setup(self, **settings): to account for the time of flight of the pulses from the output port to the input port. - settings['i1']['acquisition_duration'] (int): [0 to 8192 ns] the duration of the acquisition. It is limited by the amount of memory available in the fpga to store i q samples. - """ if "o1" in settings: self.ports["o1"] = QbloxOutputPort( - module=self, sequencer_number=self.DEFAULT_SEQUENCERS["o1"], port_number=0, port_name="o1" + module=self, + sequencer_number=self.DEFAULT_SEQUENCERS["o1"], + port_number=0, + port_name="o1", ) if "i1" in settings: self.ports["i1"] = QbloxInputPort( @@ -351,10 +396,18 @@ def _get_next_sequencer(self, port: str, frequency: int, qubits: dict, qubit: No # select a new sequencer and configure it as required next_sequencer_number = self._free_sequencers_numbers.pop(0) if next_sequencer_number != self.DEFAULT_SEQUENCERS[port]: - for parameter in self.device.sequencers[self.DEFAULT_SEQUENCERS[port]].parameters: + for parameter in self.device.sequencers[ + self.DEFAULT_SEQUENCERS[port] + ].parameters: # exclude read-only parameter `sequence` and others that have wrong default values (qblox bug) - if not parameter in ["sequence", "thresholded_acq_marker_address", "thresholded_acq_trigger_address"]: - value = self.device.sequencers[self.DEFAULT_SEQUENCERS[port]].get(param_name=parameter) + if not parameter in [ + "sequence", + "thresholded_acq_marker_address", + "thresholded_acq_trigger_address", + ]: + value = self.device.sequencers[self.DEFAULT_SEQUENCERS[port]].get( + param_name=parameter + ) if value: target = self.device.sequencers[next_sequencer_number] self._set_device_parameter(target, parameter, value=value) @@ -391,7 +444,8 @@ def _get_next_sequencer(self, port: str, frequency: int, qubits: dict, qubit: No return sequencer def get_if(self, pulse: Pulse): - """Returns the intermediate frequency needed to synthesise a pulse based on the port lo frequency.""" + """Returns the intermediate frequency needed to synthesise a pulse + based on the port lo frequency.""" _rf = pulse.frequency _lo = self.channel_map[pulse.channel].lo_frequency @@ -414,8 +468,8 @@ def process_pulse_sequence( repetition_duration: int, sweepers=None, ): - """Processes a sequence of pulses and sweepers, generating the waveforms and program required by - the instrument to synthesise them. + """Processes a sequence of pulses and sweepers, generating the + waveforms and program required by the instrument to synthesise them. The output of the process is a list of sequencers used for each port, configured with the information required to play the sequence. @@ -460,16 +514,26 @@ def process_pulse_sequence( num_bins *= len(sweeper.values) # estimate the execution time - self._execution_time = navgs * num_bins * ((repetition_duration + 1000 * len(sweepers)) * 1e-9) + self._execution_time = ( + navgs * num_bins * ((repetition_duration + 1000 * len(sweepers)) * 1e-9) + ) port = "o1" # initialise the list of free sequencer numbers to include the default for each port {'o1': 0} - self._free_sequencers_numbers = [self.DEFAULT_SEQUENCERS[port]] + [1, 2, 3, 4, 5] + self._free_sequencers_numbers = [self.DEFAULT_SEQUENCERS[port]] + [ + 1, + 2, + 3, + 4, + 5, + ] # split the collection of instruments pulses by ports # ro_channel = None # feed_channel = None - port_channel = [chan.name for chan in self.channel_map.values() if chan.port.name == port] + port_channel = [ + chan.name for chan in self.channel_map.values() if chan.port.name == port + ] port_pulses: PulseSequence = instrument_pulses.get_channel_pulses(*port_channel) # initialise the list of sequencers required by the port @@ -501,13 +565,18 @@ def process_pulse_sequence( pulse: Pulse = pulses_to_be_processed[0] # attempt to save the waveforms to the sequencer waveforms buffer try: - sequencer.waveforms_buffer.add_waveforms(pulse, self.ports[port].hardware_mod_en, sweepers) + sequencer.waveforms_buffer.add_waveforms( + pulse, self.ports[port].hardware_mod_en, sweepers + ) sequencer.pulses.add(pulse) pulses_to_be_processed.remove(pulse) # if there is not enough memory in the current sequencer, use another one except WaveformsBuffer.NotEnoughMemory: - if len(pulse.waveform_i) + len(pulse.waveform_q) > WaveformsBuffer.SIZE: + if ( + len(pulse.waveform_i) + len(pulse.waveform_q) + > WaveformsBuffer.SIZE + ): raise NotImplementedError( f"Pulses with waveforms longer than the memory of a sequencer ({WaveformsBuffer.SIZE // 2}) are not supported." ) @@ -561,7 +630,9 @@ def process_pulse_sequence( for sweeper in sweepers: if sweeper.parameter in pulse_sweeper_parameters: # check if this sequencer takes an active role in the sweep - if sweeper.pulses and set(sequencer.pulses) & set(sweeper.pulses): + if sweeper.pulses and set(sequencer.pulses) & set( + sweeper.pulses + ): # plays an active role reference_value = None if sweeper.parameter == Parameter.frequency: @@ -572,24 +643,43 @@ def process_pulse_sequence( if sweeper.parameter == Parameter.amplitude: for pulse in pulses: if pulse in sweeper.pulses: - reference_value = pulse.amplitude # uses the amplitude of the first pulse - if sweeper.parameter == Parameter.duration and pulse in sweeper.pulses: + reference_value = ( + pulse.amplitude + ) # uses the amplitude of the first pulse + if ( + sweeper.parameter == Parameter.duration + and pulse in sweeper.pulses + ): # for duration sweepers bake waveforms sweeper.qs = QbloxSweeper( - program=program, type=QbloxSweeperType.duration, rel_values=pulse.idx_range + program=program, + type=QbloxSweeperType.duration, + rel_values=pulse.idx_range, ) else: # create QbloxSweepers and attach them to qibolab sweeper - if sweeper.type == SweeperType.OFFSET and reference_value: + if ( + sweeper.type == SweeperType.OFFSET + and reference_value + ): sweeper.qs = QbloxSweeper.from_sweeper( - program=program, sweeper=sweeper, add_to=reference_value + program=program, + sweeper=sweeper, + add_to=reference_value, ) - elif sweeper.type == SweeperType.FACTOR and reference_value: + elif ( + sweeper.type == SweeperType.FACTOR + and reference_value + ): sweeper.qs = QbloxSweeper.from_sweeper( - program=program, sweeper=sweeper, multiply_to=reference_value + program=program, + sweeper=sweeper, + multiply_to=reference_value, ) else: - sweeper.qs = QbloxSweeper.from_sweeper(program=program, sweeper=sweeper) + sweeper.qs = QbloxSweeper.from_sweeper( + program=program, sweeper=sweeper + ) # finally attach QbloxSweepers to the pulses being swept sweeper.qs.update_parameters = True @@ -640,16 +730,27 @@ def process_pulse_sequence( # # never take an active role in those sweeps. # Waveforms - for index, waveform in enumerate(sequencer.waveforms_buffer.unique_waveforms): - sequencer.waveforms[waveform.serial] = {"data": waveform.data.tolist(), "index": index} + for index, waveform in enumerate( + sequencer.waveforms_buffer.unique_waveforms + ): + sequencer.waveforms[waveform.serial] = { + "data": waveform.data.tolist(), + "index": index, + } # Acquisitions for acquisition_index, pulse in enumerate(sequencer.pulses.ro_pulses): - sequencer.acquisitions[pulse.serial] = {"num_bins": num_bins, "index": acquisition_index} + sequencer.acquisitions[pulse.serial] = { + "num_bins": num_bins, + "index": acquisition_index, + } # Add scope_acquisition to default sequencer if sequencer.number == self.DEFAULT_SEQUENCERS[port]: - sequencer.acquisitions["scope_acquisition"] = {"num_bins": 1, "index": acquisition_index + 1} + sequencer.acquisitions["scope_acquisition"] = { + "num_bins": 1, + "index": acquisition_index + 1, + } # Program minimum_delay_between_instructions = 4 @@ -660,7 +761,9 @@ def process_pulse_sequence( active_reset_pulse_idx_I = 1 active_reset_pulse_idx_Q = 1 - sequence_total_duration = pulses.finish # the minimum delay between instructions is 4ns + sequence_total_duration = ( + pulses.finish + ) # the minimum delay between instructions is 4ns time_between_repetitions = repetition_duration - sequence_total_duration assert time_between_repetitions > minimum_delay_between_instructions # TODO: currently relaxation_time needs to be greater than acquisition_hold_off @@ -679,25 +782,34 @@ def process_pulse_sequence( header_block = Block("setup") if active_reset: header_block.append( - f"set_latch_en {active_reset_address}, 4", f"monitor triggers on address {active_reset_address}" + f"set_latch_en {active_reset_address}, 4", + f"monitor triggers on address {active_reset_address}", ) body_block = Block() body_block.append(f"wait_sync {minimum_delay_between_instructions}") - if self.ports["i1"].hardware_demod_en or self.ports["o1"].hardware_mod_en: + if ( + self.ports["i1"].hardware_demod_en + or self.ports["o1"].hardware_mod_en + ): body_block.append("reset_ph") body_block.append_spacer() pulses_block = Block("play_and_acquire") # Add an initial wait instruction for the first pulse of the sequence initial_wait_block = wait_block( - wait_time=pulses[0].start, register=Register(program), force_multiples_of_four=True + wait_time=pulses[0].start, + register=Register(program), + force_multiples_of_four=True, ) pulses_block += initial_wait_block for n in range(pulses.count): - if pulses[n].sweeper and pulses[n].sweeper.type == QbloxSweeperType.start: + if ( + pulses[n].sweeper + and pulses[n].sweeper.type == QbloxSweeperType.start + ): pulses_block.append(f"wait {pulses[n].sweeper.register}") if self.ports["o1"].hardware_mod_en: @@ -706,7 +818,11 @@ def process_pulse_sequence( # pulses_block.append(f"set_freq {convert_frequency(_if)}", f"set intermediate frequency to {_if} Hz") # Set phase - if pulses[n].sweeper and pulses[n].sweeper.type == QbloxSweeperType.relative_phase: + if ( + pulses[n].sweeper + and pulses[n].sweeper.type + == QbloxSweeperType.relative_phase + ): pulses_block.append(f"set_ph {pulses[n].sweeper.register}") else: pulses_block.append( @@ -720,12 +836,18 @@ def process_pulse_sequence( if len(pulses) > n + 1: # If there are more pulses to be played, the delay is the time between the pulse end and the next pulse start delay_after_acquire = ( - pulses[n + 1].start - pulses[n].start - self.ports["i1"].acquisition_hold_off + pulses[n + 1].start + - pulses[n].start + - self.ports["i1"].acquisition_hold_off ) else: - delay_after_acquire = sequence_total_duration - pulses[n].start + delay_after_acquire = ( + sequence_total_duration - pulses[n].start + ) time_between_repetitions = ( - repetition_duration - sequence_total_duration - self.ports["i1"].acquisition_hold_off + repetition_duration + - sequence_total_duration + - self.ports["i1"].acquisition_hold_off ) assert time_between_repetitions > 0 @@ -734,7 +856,10 @@ def process_pulse_sequence( f"The minimum delay after starting acquisition is {minimum_delay_between_instructions}ns." ) - if pulses[n].sweeper and pulses[n].sweeper.type == QbloxSweeperType.duration: + if ( + pulses[n].sweeper + and pulses[n].sweeper.type == QbloxSweeperType.duration + ): RI = pulses[n].sweeper.register if pulses[n].type == PulseType.FLUX: RQ = pulses[n].sweeper.register @@ -754,8 +879,12 @@ def process_pulse_sequence( # Prepare acquire instruction: acquire acquisition_index, bin_index, delay_next_instruction if active_reset: - pulses_block.append(f"acquire {pulses.ro_pulses.index(pulses[n])},{bin_n},4") - pulses_block.append(f"latch_rst {delay_after_acquire + 300 - 4}") + pulses_block.append( + f"acquire {pulses.ro_pulses.index(pulses[n])},{bin_n},4" + ) + pulses_block.append( + f"latch_rst {delay_after_acquire + 300 - 4}" + ) else: pulses_block.append( f"acquire {pulses.ro_pulses.index(pulses[n])},{bin_n},{delay_after_acquire}" @@ -774,7 +903,10 @@ def process_pulse_sequence( f"The minimum delay between the start of two pulses in the same channel is {minimum_delay_between_instructions}ns." ) - if pulses[n].sweeper and pulses[n].sweeper.type == QbloxSweeperType.duration: + if ( + pulses[n].sweeper + and pulses[n].sweeper.type == QbloxSweeperType.duration + ): RI = pulses[n].sweeper.register if pulses[n].type == PulseType.FLUX: RQ = pulses[n].sweeper.register @@ -797,15 +929,27 @@ def process_pulse_sequence( if active_reset: final_reset_block = Block() - final_reset_block.append(f"set_cond 1, {active_reset_address}, 0, 4", comment="active reset") - final_reset_block.append(f"play {active_reset_pulse_idx_I}, {active_reset_pulse_idx_Q}, 4", level=1) - final_reset_block.append(f"set_cond 0, {active_reset_address}, 0, 4") + final_reset_block.append( + f"set_cond 1, {active_reset_address}, 0, 4", + comment="active reset", + ) + final_reset_block.append( + f"play {active_reset_pulse_idx_I}, {active_reset_pulse_idx_Q}, 4", + level=1, + ) + final_reset_block.append( + f"set_cond 0, {active_reset_address}, 0, 4" + ) else: final_reset_block = wait_block( - wait_time=time_between_repetitions, register=Register(program), force_multiples_of_four=False + wait_time=time_between_repetitions, + register=Register(program), + force_multiples_of_four=False, ) final_reset_block.append_spacer() - final_reset_block.append(f"add {bin_n}, 1, {bin_n}", "increase bin counter") + final_reset_block.append( + f"add {bin_n}, 1, {bin_n}", "increase bin counter" + ) body_block += final_reset_block @@ -817,35 +961,55 @@ def process_pulse_sequence( body_block = sweeper.qs.block(inner_block=body_block) nshots_block: Block = loop_block( - start=0, stop=nshots, step=1, register=nshots_register, block=body_block + start=0, + stop=nshots, + step=1, + register=nshots_register, + block=body_block, ) nshots_block.prepend(f"move 0, {bin_n}", "reset bin counter") nshots_block.append_spacer() - navgs_block = loop_block(start=0, stop=navgs, step=1, register=navgs_register, block=nshots_block) + navgs_block = loop_block( + start=0, + stop=navgs, + step=1, + register=navgs_register, + block=nshots_block, + ) program.add_blocks(header_block, navgs_block, footer_block) sequencer.program = repr(program) def upload(self): - """Uploads waveforms and programs of all sequencers and arms them in preparation for execution. + """Uploads waveforms and programs of all sequencers and arms them in + preparation for execution. This method should be called after `process_pulse_sequence()`. - It configures certain parameters of the instrument based on the needs of resources determined - while processing the pulse sequence. + It configures certain parameters of the instrument based on the + needs of resources determined while processing the pulse + sequence. """ # Setup for sequencer_number in self._used_sequencers_numbers: target = self.device.sequencers[sequencer_number] self._set_device_parameter(target, "sync_en", value=True) - self._set_device_parameter(target, "marker_ovr_en", value=True) # Default after reboot = False - self._set_device_parameter(target, "marker_ovr_value", value=15) # Default after reboot = 0 + self._set_device_parameter( + target, "marker_ovr_en", value=True + ) # Default after reboot = False + self._set_device_parameter( + target, "marker_ovr_value", value=15 + ) # Default after reboot = 0 for sequencer_number in self._unused_sequencers_numbers: target = self.device.sequencers[sequencer_number] self._set_device_parameter(target, "sync_en", value=False) - self._set_device_parameter(target, "marker_ovr_en", value=False) # Default after reboot = False - self._set_device_parameter(target, "marker_ovr_value", value=0) # Default after reboot = 0 + self._set_device_parameter( + target, "marker_ovr_en", value=False + ) # Default after reboot = False + self._set_device_parameter( + target, "marker_ovr_value", value=0 + ) # Default after reboot = 0 if sequencer_number >= 1: # Never disconnect default sequencers self._set_device_parameter(target, "connect_out0", value="off") self._set_device_parameter(target, "connect_acq", value="in0") @@ -867,7 +1031,10 @@ def upload(self): self.device.sequencers[sequencer.number].sequence(qblox_dict[sequencer]) # DEBUG: QRM RF Save sequence to file if self._debug_folder != "": - filename = self._debug_folder + f"Z_{self.name}_sequencer{sequencer.number}_sequence.json" + filename = ( + self._debug_folder + + f"Z_{self.name}_sequencer{sequencer.number}_sequence.json" + ) with open(filename, "w", encoding="utf-8") as file: json.dump(qblox_dict[sequencer], file, indent=4) file.write(sequencer.program) @@ -892,7 +1059,8 @@ def upload(self): def play_sequence(self): """Plays the sequence of pulses. - Starts the sequencers needed to play the sequence of pulses.""" + Starts the sequencers needed to play the sequence of pulses. + """ # Start used sequencers for sequencer_number in self._used_sequencers_numbers: @@ -922,7 +1090,9 @@ def acquire(self): # TODO: check flags for errors break elif time.time() - t > time_out: - log.info(f"Timeout - {self.device.sequencers[sequencer_number].name} state: {state}") + log.info( + f"Timeout - {self.device.sequencers[sequencer_number].name} state: {state}" + ) self.device.stop_sequencer(sequencer_number) break time.sleep(1) @@ -948,16 +1118,20 @@ def acquire(self): for sequencer in self._sequencers[port]: # Store scope acquisition data on 'scope_acquisition' acquisition of the default sequencer if sequencer.number == self.DEFAULT_SEQUENCERS[port]: - self.device.store_scope_acquisition(sequencer.number, "scope_acquisition") - scope = self.device.get_acquisitions(sequencer.number)["scope_acquisition"] + self.device.store_scope_acquisition( + sequencer.number, "scope_acquisition" + ) + scope = self.device.get_acquisitions(sequencer.number)[ + "scope_acquisition" + ] if not hardware_demod_enabled: # Software Demodulation if len(sequencer.pulses.ro_pulses) == 1: pulse = sequencer.pulses.ro_pulses[0] frequency = self.get_if(pulse) - acquisitions[pulse.qubit] = acquisitions[pulse.serial] = AveragedAcquisition( - scope, duration, frequency - ) + acquisitions[pulse.qubit] = acquisitions[ + pulse.serial + ] = AveragedAcquisition(scope, duration, frequency) else: raise RuntimeError( "Software Demodulation only supports one acquisition per channel. " @@ -967,13 +1141,17 @@ def acquire(self): results = self.device.get_acquisitions(sequencer.number) for pulse in sequencer.pulses.ro_pulses: bins = results[pulse.serial]["acquisition"]["bins"] - acquisitions[pulse.qubit] = acquisitions[pulse.serial] = DemodulatedAcquisition(bins, duration) + acquisitions[pulse.qubit] = acquisitions[ + pulse.serial + ] = DemodulatedAcquisition(bins, duration) # Provide Scope Data for verification (assuming memory reseet is being done) if len(sequencer.pulses.ro_pulses) == 1: pulse = sequencer.pulses.ro_pulses[0] frequency = self.get_if(pulse) - acquisitions[pulse.serial].averaged = AveragedAcquisition(scope, duration, frequency) + acquisitions[pulse.serial].averaged = AveragedAcquisition( + scope, duration, frequency + ) # grab only the data required by the platform # TODO: to be updated once the functionality of ExecutionResults is extended @@ -984,7 +1162,7 @@ def start(self): pass def stop(self): - """Stops all sequencers""" + """Stops all sequencers.""" try: self.device.stop_sequencer() except: diff --git a/src/qibolab/instruments/qblox/controller.py b/src/qibolab/instruments/qblox/controller.py index b6e2050056..23856bf055 100644 --- a/src/qibolab/instruments/qblox/controller.py +++ b/src/qibolab/instruments/qblox/controller.py @@ -14,7 +14,8 @@ from qibolab.sweeper import Parameter, Sweeper, SweeperType MAX_BATCH_SIZE = 30 -"""Maximum number of sequences that can be unrolled in a single one (independent of measurements).""" +"""Maximum number of sequences that can be unrolled in a single one +(independent of measurements).""" SEQUENCER_MEMORY = 2**17 @@ -47,7 +48,9 @@ def connect(self): except Exception as exception: raise_error( RuntimeError, - "Cannot establish connection to " f"{self.modules[name]} module. " f"Error captured: '{exception}'", + "Cannot establish connection to " + f"{self.modules[name]} module. " + f"Error captured: '{exception}'", ) # TODO: check for exception 'The module qrm_rf0 does not have parameters in0_att' and reboot the cluster @@ -82,7 +85,8 @@ def stop(self): self.cluster.stop() def _termination_handler(self, signum, frame): - """Calls all modules to stop if the program receives a termination signal.""" + """Calls all modules to stop if the program receives a termination + signal.""" log.warning("Termination signal received, stopping modules.") if self.is_connected: @@ -103,10 +107,12 @@ def disconnect(self): def _set_module_channel_map(self, module: ClusterQRM_RF, qubits: dict): """Retrieve all the channels connected to a specific Qblox module. - This method updates the `channel_port_map` attribute of the specified Qblox module - based on the information contained in the provided qubits dictionary (dict of `qubit` objects). + This method updates the `channel_port_map` attribute of the + specified Qblox module based on the information contained in the + provided qubits dictionary (dict of `qubit` objects). - Return the list of channels connected to module_name""" + Return the list of channels connected to module_name + """ for qubit in qubits.values(): for channel in qubit.channels: if channel.port and channel.port.module.name == module.name: @@ -132,7 +138,9 @@ def _execute_pulse_sequence( sweepers (list(Sweeper)): A list of Sweeper objects defining parameter sweeps. """ if not self.is_connected: - raise_error(RuntimeError, "Execution failed because modules are not connected.") + raise_error( + RuntimeError, "Execution failed because modules are not connected." + ) if options.averaging_mode is AveragingMode.SINGLESHOT: nshots = options.nshots @@ -175,7 +183,14 @@ def _execute_pulse_sequence( pulse._if = int(pulse.frequency - pulse_channel.lo_frequency) # ask each module to generate waveforms & program and upload them to the device - module.process_pulse_sequence(qubits, module_pulses[name], navgs, nshots, repetition_duration, sweepers) + module.process_pulse_sequence( + qubits, + module_pulses[name], + navgs, + nshots, + repetition_duration, + sweepers, + ) # log.info(f"{self.modules[name]}: Uploading pulse sequence") module.upload() @@ -188,7 +203,10 @@ def _execute_pulse_sequence( # retrieve the results acquisition_results = {} for name, module in self.modules.items(): - if isinstance(module, ClusterQRM_RF) and not module_pulses[name].ro_pulses.is_empty: + if ( + isinstance(module, ClusterQRM_RF) + and not module_pulses[name].ro_pulses.is_empty + ): results = module.acquire() existing_keys = set(acquisition_results.keys()) & set(results.keys()) for key, value in results.items(): @@ -229,7 +247,14 @@ def play(self, qubits, couplers, sequence, options): def split_batches(self, sequences): return batch_max_sequences(sequences, MAX_BATCH_SIZE) - def sweep(self, qubits: dict, couplers: dict, sequence: PulseSequence, options: ExecutionParameters, *sweepers): + def sweep( + self, + qubits: dict, + couplers: dict, + sequence: PulseSequence, + options: ExecutionParameters, + *sweepers, + ): """Executes a sequence of pulses while sweeping one or more parameters. The parameters to be swept are defined in :class:`qibolab.sweeper.Sweeper` object. @@ -248,7 +273,11 @@ def sweep(self, qubits: dict, couplers: dict, sequence: PulseSequence, options: sweepers_copy = [] for sweeper in sweepers: if sweeper.pulses: - ps = [sequence_copy[sequence_copy.index(pulse)] for pulse in sweeper.pulses if pulse in sequence_copy] + ps = [ + sequence_copy[sequence_copy.index(pulse)] + for pulse in sweeper.pulses + if pulse in sequence_copy + ] else: ps = None sweepers_copy.append( @@ -350,18 +379,26 @@ def _sweep_recursion( if sweeper.type == SweeperType.ABSOLUTE: qubits[pulse.qubit].readout.lo_frequency = value elif sweeper.type == SweeperType.OFFSET: - qubits[pulse.qubit].readout.lo_frequency = initial[pulse.id] + value + qubits[pulse.qubit].readout.lo_frequency = ( + initial[pulse.id] + value + ) elif sweeper.type == SweeperType.FACTOR: - qubits[pulse.qubit].readout.lo_frequency = initial[pulse.id] * value + qubits[pulse.qubit].readout.lo_frequency = ( + initial[pulse.id] * value + ) elif pulse.type == PulseType.DRIVE: initial[pulse.id] = qubits[pulse.qubit].drive.lo_frequency if sweeper.type == SweeperType.ABSOLUTE: qubits[pulse.qubit].drive.lo_frequency = value elif sweeper.type == SweeperType.OFFSET: - qubits[pulse.qubit].drive.lo_frequency = initial[pulse.id] + value + qubits[pulse.qubit].drive.lo_frequency = ( + initial[pulse.id] + value + ) elif sweeper.type == SweeperType.FACTOR: - qubits[pulse.qubit].drive.lo_frequency = initial[pulse.id] * value + qubits[pulse.qubit].drive.lo_frequency = ( + initial[pulse.id] * value + ) if len(sweepers) > 1: self._sweep_recursion( @@ -372,7 +409,9 @@ def _sweep_recursion( results=results, ) else: - result = self._execute_pulse_sequence(qubits=qubits, sequence=sequence, options=options) + result = self._execute_pulse_sequence( + qubits=qubits, sequence=sequence, options=options + ) for pulse in sequence.ro_pulses: if results[pulse.id]: results[pulse.id] += result[pulse.serial] @@ -395,14 +434,19 @@ def _sweep_recursion( if sweeper.parameter == Parameter.relative_phase: if sweeper.type != SweeperType.ABSOLUTE: - raise_error(ValueError, "relative_phase sweeps other than ABSOLUTE are not supported by qblox yet") + raise_error( + ValueError, + "relative_phase sweeps other than ABSOLUTE are not supported by qblox yet", + ) from qibolab.instruments.qblox.q1asm import convert_phase c_values = np.array([convert_phase(v) for v in sweeper.values]) if any(np.diff(c_values) < 0): split_relative_phase = True _from = 0 - for idx in np.append(np.where(np.diff(c_values) < 0), len(c_values) - 1): + for idx in np.append( + np.where(np.diff(c_values) < 0), len(c_values) - 1 + ): _to = idx + 1 _values = sweeper.values[_from:_to] split_sweeper = Sweeper( @@ -412,16 +456,30 @@ def _sweep_recursion( qubits=sweeper.qubits, ) self._sweep_recursion( - qubits, sequence, options, *((split_sweeper,) + sweepers[1:]), results=results + qubits, + sequence, + options, + *((split_sweeper,) + sweepers[1:]), + results=results, ) _from = _to if not split_relative_phase: if any(s.parameter not in rt_sweepers for s in sweepers): # TODO: reorder the sequence of the sweepers and the results - raise Exception("cannot execute a for-loop sweeper nested inside of a rt sweeper") - nshots = options.nshots if options.averaging_mode == AveragingMode.SINGLESHOT else 1 - navgs = options.nshots if options.averaging_mode != AveragingMode.SINGLESHOT else 1 + raise Exception( + "cannot execute a for-loop sweeper nested inside of a rt sweeper" + ) + nshots = ( + options.nshots + if options.averaging_mode == AveragingMode.SINGLESHOT + else 1 + ) + navgs = ( + options.nshots + if options.averaging_mode != AveragingMode.SINGLESHOT + else 1 + ) num_bins = nshots for sweeper in sweepers: num_bins *= len(sweeper.values) @@ -444,7 +502,9 @@ def _sweep_recursion( # elif pulse.type == PulseType.DRIVE: # qubits[pulse.qubit].drive.gain = 1 - result = self._execute_pulse_sequence(qubits, sequence, options, sweepers) + result = self._execute_pulse_sequence( + qubits, sequence, options, sweepers + ) for pulse in sequence.ro_pulses: if results[pulse.id]: results[pulse.id] += result[pulse.serial] @@ -462,7 +522,9 @@ def _sweep_recursion( num_bins = max_rt_nshots * sweepers_repetitions for sft_iteration in range(num_full_sft_iterations + 1): - _nshots = min(max_rt_nshots, nshots - sft_iteration * max_rt_nshots) + _nshots = min( + max_rt_nshots, nshots - sft_iteration * max_rt_nshots + ) self._sweep_recursion( qubits, sequence, @@ -477,11 +539,16 @@ def _sweep_recursion( num_bins *= len(sweeper.values) sweeper = sweepers[0] max_rt_iterations = (SEQUENCER_MEMORY) // num_bins - num_full_sft_iterations = len(sweeper.values) // max_rt_iterations + num_full_sft_iterations = ( + len(sweeper.values) // max_rt_iterations + ) num_bins = nshots * max_rt_iterations for sft_iteration in range(num_full_sft_iterations + 1): _from = sft_iteration * max_rt_iterations - _to = min((sft_iteration + 1) * max_rt_iterations, len(sweeper.values)) + _to = min( + (sft_iteration + 1) * max_rt_iterations, + len(sweeper.values), + ) _values = sweeper.values[_from:_to] split_sweeper = Sweeper( parameter=sweeper.parameter, @@ -491,5 +558,9 @@ def _sweep_recursion( ) self._sweep_recursion( - qubits, sequence, options, *((split_sweeper,) + sweepers[1:]), results=results + qubits, + sequence, + options, + *((split_sweeper,) + sweepers[1:]), + results=results, ) diff --git a/src/qibolab/instruments/qblox/debug.py b/src/qibolab/instruments/qblox/debug.py index aa4dc23458..e2d582ca8c 100644 --- a/src/qibolab/instruments/qblox/debug.py +++ b/src/qibolab/instruments/qblox/debug.py @@ -1,13 +1,12 @@ import numpy as np -def print_readable_snapshot(device, file, update: bool = False, max_chars: int = 80) -> None: - """ - Prints a readable version of the snapshot. - The readable snapshot includes the name, value and unit of each - parameter. - A convenience function to quickly get an overview of the - status of an instrument. +def print_readable_snapshot( + device, file, update: bool = False, max_chars: int = 80 +) -> None: + """Prints a readable version of the snapshot. The readable snapshot + includes the name, value and unit of each parameter. A convenience function + to quickly get an overview of the status of an instrument. Args: update: If ``True``, update the state by querying the diff --git a/src/qibolab/instruments/qblox/port.py b/src/qibolab/instruments/qblox/port.py index 9466ea520a..47a9454668 100644 --- a/src/qibolab/instruments/qblox/port.py +++ b/src/qibolab/instruments/qblox/port.py @@ -30,9 +30,12 @@ class QbloxInputPort_Settings: class QbloxOutputPort(Port): - """qibolab.instruments.port.Port interface implementation for Qblox instruments""" + """qibolab.instruments.port.Port interface implementation for Qblox + instruments.""" - def __init__(self, module, sequencer_number: int, port_number: int, port_name: str = None): + def __init__( + self, module, sequencer_number: int, port_number: int, port_name: str = None + ): self.name = port_name self.module = module self.sequencer_number: int = sequencer_number @@ -43,7 +46,9 @@ def __init__(self, module, sequencer_number: int, port_number: int, port_name: s def attenuation(self) -> str: """Attenuation that is applied to this port.""" if self.module.device: - self._settings.attenuation = self.module.device.get(f"out{self.port_number}_att") + self._settings.attenuation = self.module.device.get( + f"out{self.port_number}_att" + ) return self._settings.attenuation @attenuation.setter @@ -52,11 +57,15 @@ def attenuation(self, value): value = int(value) if isinstance(value, (int, np.integer)): if value > 60: - log.warning(f"Qblox attenuation needs to be between 0 and 60 dB. Adjusting {value} to 60dB") + log.warning( + f"Qblox attenuation needs to be between 0 and 60 dB. Adjusting {value} to 60dB" + ) value = 60 elif value < 0: - log.warning(f"Qblox attenuation needs to be between 0 and 60 dB. Adjusting {value} to 0") + log.warning( + f"Qblox attenuation needs to be between 0 and 60 dB. Adjusting {value} to 0" + ) value = 0 if (value % 2) != 0: @@ -69,13 +78,17 @@ def attenuation(self, value): self._settings.attenuation = value if self.module.device: - self.module._set_device_parameter(self.module.device, f"out{self.port_number}_att", value=value) + self.module._set_device_parameter( + self.module.device, f"out{self.port_number}_att", value=value + ) @property def offset(self): """DC offset that is applied to this port.""" if self.module.device: - self._settings.offset = self.module.device.get(f"out{self.port_number}_offset") + self._settings.offset = self.module.device.get( + f"out{self.port_number}_offset" + ) return self._settings.offset @offset.setter @@ -84,25 +97,33 @@ def offset(self, value): value = float(value) if isinstance(value, (float, np.floating)): if value > MAX_OFFSET: - log.warning(f"Qblox offset needs to be between -2.5 and 2.5 V. Adjusting {value} to 2.5 V") + log.warning( + f"Qblox offset needs to be between -2.5 and 2.5 V. Adjusting {value} to 2.5 V" + ) value = MAX_OFFSET elif value < -MAX_OFFSET: - log.warning(f"Qblox offset needs to be between -2.5 and 2.5 V. Adjusting {value} to -2.5 V") + log.warning( + f"Qblox offset needs to be between -2.5 and 2.5 V. Adjusting {value} to -2.5 V" + ) value = -MAX_OFFSET else: raise_error(ValueError, f"Invalid offset {value}") self._settings.offset = value if self.module.device: - self.module._set_device_parameter(self.module.device, f"out{self.port_number}_offset", value=value) + self.module._set_device_parameter( + self.module.device, f"out{self.port_number}_offset", value=value + ) # Additional attributes needed by the driver @property def hardware_mod_en(self): """Flag to enable hardware modulation.""" if self.module.device: - self._settings.hardware_mod_en = self.module.device.sequencers[self.sequencer_number].get("mod_en_awg") + self._settings.hardware_mod_en = self.module.device.sequencers[ + self.sequencer_number + ].get("mod_en_awg") return self._settings.hardware_mod_en @hardware_mod_en.setter @@ -113,7 +134,9 @@ def hardware_mod_en(self, value): self._settings.hardware_mod_en = value if self.module.device: self.module._set_device_parameter( - self.module.device.sequencers[self.sequencer_number], "mod_en_awg", value=value + self.module.device.sequencers[self.sequencer_number], + "mod_en_awg", + value=value, ) @property @@ -121,7 +144,9 @@ def nco_freq(self): """nco_freq that is applied to this port.""" if self.module.device: - self._settings.nco_freq = self.module.device.sequencers[self.sequencer_number].get("nco_freq") + self._settings.nco_freq = self.module.device.sequencers[ + self.sequencer_number + ].get("nco_freq") return self._settings.nco_freq @@ -147,7 +172,9 @@ def nco_freq(self, value): self._settings.nco_freq = value if self.module.device: self.module._set_device_parameter( - self.module.device.sequencers[self.sequencer_number], "nco_freq", value=value + self.module.device.sequencers[self.sequencer_number], + "nco_freq", + value=value, ) @property @@ -155,7 +182,9 @@ def nco_phase_offs(self): """nco_phase_offs that is applied to this port.""" if self.module.device: - self._settings.nco_phase_offs = self.module.device.sequencers[self.sequencer_number].get("nco_phase_offs") + self._settings.nco_phase_offs = self.module.device.sequencers[ + self.sequencer_number + ].get("nco_phase_offs") return self._settings.nco_phase_offs @nco_phase_offs.setter @@ -170,7 +199,9 @@ def nco_phase_offs(self, value): self._settings.nco_phase_offs = value if self.module.device: self.module._set_device_parameter( - self.module.device.sequencers[self.sequencer_number], "nco_phase_offs", value=value + self.module.device.sequencers[self.sequencer_number], + "nco_phase_offs", + value=value, ) @property @@ -179,9 +210,13 @@ def lo_enabled(self): if self.module.device: if self.module.device.is_qrm_type: - self._settings.lo_enabled = self.module.device.get(f"out{self.port_number}_in{self.port_number}_lo_en") + self._settings.lo_enabled = self.module.device.get( + f"out{self.port_number}_in{self.port_number}_lo_en" + ) elif self.module.device.is_qcm_type: - self._settings.lo_enabled = self.module.device.get(f"out{self.port_number}_lo_en") + self._settings.lo_enabled = self.module.device.get( + f"out{self.port_number}_lo_en" + ) return self._settings.lo_enabled @lo_enabled.setter @@ -193,10 +228,14 @@ def lo_enabled(self, value): if self.module.device: if self.module.device.is_qrm_type: self.module._set_device_parameter( - self.module.device, f"out{self.port_number}_in{self.port_number}_lo_en", value=value + self.module.device, + f"out{self.port_number}_in{self.port_number}_lo_en", + value=value, ) elif self.module.device.is_qcm_type: - self.module._set_device_parameter(self.module.device, f"out{self.port_number}_lo_en", value=value) + self.module._set_device_parameter( + self.module.device, f"out{self.port_number}_lo_en", value=value + ) @property def lo_frequency(self): @@ -207,7 +246,9 @@ def lo_frequency(self): f"out{self.port_number}_in{self.port_number}_lo_freq" ) elif self.module.device.is_qcm_type: - self._settings.lo_frequency = self.module.device.get(f"out{self.port_number}_lo_freq") + self._settings.lo_frequency = self.module.device.get( + f"out{self.port_number}_lo_freq" + ) return self._settings.lo_frequency @lo_frequency.setter @@ -216,11 +257,15 @@ def lo_frequency(self, value): value = int(value) if isinstance(value, (int, np.integer)): if value > 18e9: - log.warning(f"Qblox lo_frequency needs to be between 2e9 and 18e9 Hz. Adjusting {value} to 18e9 Hz") + log.warning( + f"Qblox lo_frequency needs to be between 2e9 and 18e9 Hz. Adjusting {value} to 18e9 Hz" + ) value = int(18e9) elif value < 2e9: - log.warning(f"Qblox lo_frequency needs to be between 2e9 and 18e9 Hz. Adjusting {value} to 2e9 Hz") + log.warning( + f"Qblox lo_frequency needs to be between 2e9 and 18e9 Hz. Adjusting {value} to 2e9 Hz" + ) value = int(2e9) else: raise_error(ValueError, f"Invalid lo-frequency {value}") @@ -229,10 +274,14 @@ def lo_frequency(self, value): if self.module.device: if self.module.device.is_qrm_type: self.module._set_device_parameter( - self.module.device, f"out{self.port_number}_in{self.port_number}_lo_freq", value=value + self.module.device, + f"out{self.port_number}_in{self.port_number}_lo_freq", + value=value, ) elif self.module.device.is_qcm_type: - self.module._set_device_parameter(self.module.device, f"out{self.port_number}_lo_freq", value=value) + self.module._set_device_parameter( + self.module.device, f"out{self.port_number}_lo_freq", value=value + ) else: pass # TODO: This case regards a connection error of the module @@ -240,7 +289,12 @@ def lo_frequency(self, value): class QbloxInputPort: def __init__( - self, module, output_sequencer_number: int, input_sequencer_number: int, port_number: int, port_name: str = None + self, + module, + output_sequencer_number: int, + input_sequencer_number: int, + port_number: int, + port_name: str = None, ): self.name = port_name self.module = module @@ -259,9 +313,9 @@ def hardware_demod_en(self): """Flag to enable hardware demodulation.""" if self.module.device: - self._settings.hardware_demod_en = self.module.device.sequencers[self.input_sequencer_number].get( - "demod_en_acq" - ) + self._settings.hardware_demod_en = self.module.device.sequencers[ + self.input_sequencer_number + ].get("demod_en_acq") return self._settings.hardware_demod_en @hardware_demod_en.setter @@ -272,17 +326,22 @@ def hardware_demod_en(self, value): self._settings.hardware_demod_en = value if self.module.device: self.module._set_device_parameter( - self.module.device.sequencers[self.input_sequencer_number], "demod_en_acq", value=value + self.module.device.sequencers[self.input_sequencer_number], + "demod_en_acq", + value=value, ) @property def acquisition_duration(self): - """Duration of the pulse acquisition, in ns. It must be > 0 and multiple of 4.""" + """Duration of the pulse acquisition, in ns. + + It must be > 0 and multiple of 4. + """ if self.module.device: - self._settings.acquisition_duration = self.module.device.sequencers[self.output_sequencer_number].get( - "integration_length_acq" - ) + self._settings.acquisition_duration = self.module.device.sequencers[ + self.output_sequencer_number + ].get("integration_length_acq") return self._settings.acquisition_duration @acquisition_duration.setter @@ -291,7 +350,9 @@ def acquisition_duration(self, value): value = int(value) if isinstance(value, (int, np.integer)): if value < MIN_PULSE_DURATION: - log.warning(f"Qblox hardware_demod_en needs to be > 4ns. Adjusting {value} to {MIN_PULSE_DURATION} ns") + log.warning( + f"Qblox hardware_demod_en needs to be > 4ns. Adjusting {value} to {MIN_PULSE_DURATION} ns" + ) value = MIN_PULSE_DURATION if (value % MIN_PULSE_DURATION) != 0: log.warning( @@ -305,5 +366,7 @@ def acquisition_duration(self, value): self._settings.acquisition_duration = value if self.module.device: self.module._set_device_parameter( - self.module.device.sequencers[self.output_sequencer_number], "integration_length_acq", value=value + self.module.device.sequencers[self.output_sequencer_number], + "integration_length_acq", + value=value, ) diff --git a/src/qibolab/instruments/qblox/q1asm.py b/src/qibolab/instruments/qblox/q1asm.py index ceb70becaf..1b0327b88f 100644 --- a/src/qibolab/instruments/qblox/q1asm.py +++ b/src/qibolab/instruments/qblox/q1asm.py @@ -27,7 +27,8 @@ def __init__(self): self._next_register_number: int = -1 def add_blocks(self, *blocks): - """Adds a :class:`qibolab.instruments.qblox.qblox_q1asm.Block` of code to the list of blocks.""" + """Adds a :class:`qibolab.instruments.qblox.qblox_q1asm.Block` of code + to the list of blocks.""" for block in blocks: self._blocks.append(block) @@ -69,11 +70,15 @@ def indentation(self): @indentation.setter def indentation(self, value): if not isinstance(value, int): - raise TypeError(f"indentation type should be int, got {type(value).__name__}") + raise TypeError( + f"indentation type should be int, got {type(value).__name__}" + ) diff = value - self._indentation self._indentation = value - self.lines = [(line, comment, level + diff) for (line, comment, level) in self.lines] + self.lines = [ + (line, comment, level + diff) for (line, comment, level) in self.lines + ] def append(self, line, comment="", level=0): self.lines = self.lines + [(line, comment, self._indentation + level)] @@ -85,7 +90,8 @@ def append_spacer(self): self.lines = self.lines + [("", "", self._indentation)] def __repr__(self) -> str: - """Returns a string with the block of code, taking care of the indentation of instructions and comments.""" + """Returns a string with the block of code, taking care of the + indentation of instructions and comments.""" def comment_col(line, level): col = Block.SPACES_PER_LEVEL * (level + Block.GLOBAL_INDENTATION_LEVEL) @@ -100,16 +106,25 @@ def comment_col(line, level): block_str: str = "" if self.name: block_str += ( - self._indentation_string(self._indentation + Block.GLOBAL_INDENTATION_LEVEL) + self._indentation_string( + self._indentation + Block.GLOBAL_INDENTATION_LEVEL + ) + "# " + self.name + END_OF_LINE ) for line, comment, level in self.lines: - block_str += self._indentation_string(level + Block.GLOBAL_INDENTATION_LEVEL) + line + block_str += ( + self._indentation_string(level + Block.GLOBAL_INDENTATION_LEVEL) + line + ) if comment: - block_str += " " * (max_col - comment_col(line, level) + Block.SPACES_BEFORE_COMMENT) + " # " + comment + block_str += ( + " " + * (max_col - comment_col(line, level) + Block.SPACES_BEFORE_COMMENT) + + " # " + + comment + ) block_str += END_OF_LINE return block_str @@ -170,7 +185,9 @@ def name(self, value): self._name = value -def wait_block(wait_time: int, register: Register, force_multiples_of_four: bool = False): +def wait_block( + wait_time: int, register: Register, force_multiples_of_four: bool = False +): """Generates blocks of code to implement long delays. Arguments: @@ -216,7 +233,9 @@ def wait_block(wait_time: int, register: Register, force_multiples_of_four: bool wait = wait_time % loop_wait if loop_wait < 4 or (wait > 0 and wait < 4): - raise ValueError(f"Unable to decompose {wait_time} into valid (n_loops * loop_wait + wait)") + raise ValueError( + f"Unable to decompose {wait_time} into valid (n_loops * loop_wait + wait)" + ) else: raise ValueError("wait_time > 65535**2 is not supported yet.") @@ -237,7 +256,9 @@ def wait_block(wait_time: int, register: Register, force_multiples_of_four: bool return block -def loop_block(start: int, stop: int, step: int, register: Register, block: Block): # validate values +def loop_block( + start: int, stop: int, step: int, register: Register, block: Block +): # validate values """Generates blocks of code to implement loops. Its behaviour is similar to range(): it includes the first value, but never the last. @@ -275,11 +296,16 @@ def loop_block(start: int, stop: int, step: int, register: Register, block: Bloc if stop > start: footer_block.append(f"add {register}, {step}, {register}") footer_block.append("nop") - footer_block.append(f"jlt {register}, {stop}, @loop_{register}", comment=register.name + " loop") + footer_block.append( + f"jlt {register}, {stop}, @loop_{register}", comment=register.name + " loop" + ) elif stop < start: footer_block.append(f"sub {register}, {abs(step)}, {register}") footer_block.append("nop") - footer_block.append(f"jge {register}, {stop + 1}, @loop_{register}", comment=register.name + " loop") + footer_block.append( + f"jge {register}, {stop + 1}, @loop_{register}", + comment=register.name + " loop", + ) return header_block + body_block + footer_block @@ -287,8 +313,8 @@ def loop_block(start: int, stop: int, step: int, register: Register, block: Bloc def convert_phase(phase_rad: float): """Converts phase values in radiants to the encoding used in qblox FPGAs. - The phase is divided into 1e9 steps between 0° and 360°, - expressed as an integer between 0 and 1e9 (e.g 45°=125e6). + The phase is divided into 1e9 steps between 0° and 360°, expressed + as an integer between 0 and 1e9 (e.g 45°=125e6). https://qblox-qblox-instruments.readthedocs-hosted.com/en/master/api_reference/sequencer.html """ phase_deg = (phase_rad * 360 / (2 * np.pi)) % 360 @@ -310,8 +336,8 @@ def convert_frequency(freq: float): def convert_gain(gain: float): """Converts gain values to the encoding used in qblox FPGAs. - Both gain values are divided in 2**sample path width steps. - QCM DACs resolution 16bits, QRM DACs and ADCs 12 bit + Both gain values are divided in 2**sample path width steps. QCM DACs + resolution 16bits, QRM DACs and ADCs 12 bit https://qblox-qblox-instruments.readthedocs-hosted.com/en/master/api_reference/sequencer.html """ if not (gain >= -1 and gain <= 1): @@ -325,17 +351,20 @@ def convert_gain(gain: float): def convert_offset(offset: float): """Converts offset values to the encoding used in qblox FPGAs. - Both offset values are divided in 2**sample path width steps. - QCM DACs resolution 16bits, QRM DACs and ADCs 12 bit - QCM 5Vpp, QRM 2Vpp + Both offset values are divided in 2**sample path width steps. QCM + DACs resolution 16bits, QRM DACs and ADCs 12 bit QCM 5Vpp, QRM 2Vpp https://qblox-qblox-instruments.readthedocs-hosted.com/en/master/api_reference/sequencer.html """ scale_factor = 1.25 * np.sqrt(2) normalised_offset = offset / scale_factor if not (normalised_offset >= -1 and normalised_offset <= 1): - raise ValueError(f"offset must be a float between {-scale_factor:.3f} and {scale_factor:.3f} V") + raise ValueError( + f"offset must be a float between {-scale_factor:.3f} and {scale_factor:.3f} V" + ) if normalised_offset == 1: return 2**15 - 1 else: - return int(np.floor(normalised_offset * 2**15)) % 2**32 # two's complement 32 bit number? or 12 or 24? + return ( + int(np.floor(normalised_offset * 2**15)) % 2**32 + ) # two's complement 32 bit number? or 12 or 24? diff --git a/src/qibolab/instruments/qblox/sequencer.py b/src/qibolab/instruments/qblox/sequencer.py index 206f186505..af3a9f7428 100644 --- a/src/qibolab/instruments/qblox/sequencer.py +++ b/src/qibolab/instruments/qblox/sequencer.py @@ -7,7 +7,8 @@ class WaveformsBuffer: - """A class to represent a buffer that holds the unique waveforms used by a sequencer. + """A class to represent a buffer that holds the unique waveforms used by a + sequencer. Attributes: unique_waveforms (list): A list of unique Waveform objects. @@ -17,17 +18,21 @@ class WaveformsBuffer: SIZE: int = 16383 class NotEnoughMemory(Exception): - """An error raised when there is not enough memory left to add more waveforms.""" + """An error raised when there is not enough memory left to add more + waveforms.""" class NotEnoughMemoryForBaking(Exception): - """An error raised when there is not enough memory left to bake pulses.""" + """An error raised when there is not enough memory left to bake + pulses.""" def __init__(self): """Initialises the buffer with an empty list of unique waveforms.""" self.unique_waveforms: list = [] # Waveform self.available_memory: int = WaveformsBuffer.SIZE - def add_waveforms(self, pulse: Pulse, hardware_mod_en: bool, sweepers: list[Sweeper]): + def add_waveforms( + self, pulse: Pulse, hardware_mod_en: bool, sweepers: list[Sweeper] + ): """Adds a pair of i and q waveforms to the list of unique waveforms. Waveforms are added to the list if they were not there before. @@ -62,7 +67,10 @@ def add_waveforms(self, pulse: Pulse, hardware_mod_en: bool, sweepers: list[Swee pulse.waveform_i = waveform_i pulse.waveform_q = waveform_q - if waveform_i not in self.unique_waveforms or waveform_q not in self.unique_waveforms: + if ( + waveform_i not in self.unique_waveforms + or waveform_q not in self.unique_waveforms + ): memory_needed = 0 if not waveform_i in self.unique_waveforms: memory_needed += len(waveform_i) @@ -78,12 +86,15 @@ def add_waveforms(self, pulse: Pulse, hardware_mod_en: bool, sweepers: list[Swee else: raise WaveformsBuffer.NotEnoughMemory else: - pulse.idx_range = self.bake_pulse_waveforms(pulse_copy, values, hardware_mod_en) + pulse.idx_range = self.bake_pulse_waveforms( + pulse_copy, values, hardware_mod_en + ) def bake_pulse_waveforms( self, pulse: Pulse, values: list(), hardware_mod_en: bool ): # bake_pulse_waveforms(self, pulse: Pulse, values: list(int), hardware_mod_en: bool): - """Generates and stores a set of i and q waveforms required for a pulse duration sweep. + """Generates and stores a set of i and q waveforms required for a pulse + duration sweep. These waveforms are generated and stored in a predefined order so that they can later be retrieved within the sweeper q1asm code. It bakes pulses from as short as 1ns, padding them at the end with 0s if required so that diff --git a/src/qibolab/instruments/qblox/sweeper.py b/src/qibolab/instruments/qblox/sweeper.py index 048d8e1874..270c17c967 100644 --- a/src/qibolab/instruments/qblox/sweeper.py +++ b/src/qibolab/instruments/qblox/sweeper.py @@ -39,7 +39,8 @@ class QbloxSweeperType(Enum): class QbloxSweeper: - """A custom sweeper object with the data and functionality required by qblox instruments. + """A custom sweeper object with the data and functionality required by + qblox instruments. It is responsible for generating the q1asm code required to execute sweeps in a sequencer. The object can be initialised with either: @@ -75,7 +76,8 @@ def __init__( multiply_to: float = 1, name: str = "", ): - """Creates an instance from a range of values and a sweeper type (:class:`qibolab.instruments.qblox.QbloxSweeperType`). + """Creates an instance from a range of values and a sweeper type + (:class:`qibolab.instruments.qblox.QbloxSweeperType`). Args: program (:class:`qibolab.instruments.qblox_q1asm.Program`): a program object representing the q1asm program @@ -142,18 +144,33 @@ def __init__( # Verify that all values are within acceptable ranges check_values = { QbloxSweeperType.frequency: ( - lambda v: all((-self.FREQUENCY_LIMIT <= x and x <= self.FREQUENCY_LIMIT) for x in v) + lambda v: all( + (-self.FREQUENCY_LIMIT <= x and x <= self.FREQUENCY_LIMIT) + for x in v + ) ), QbloxSweeperType.gain: (lambda v: all((-1 <= x and x <= 1) for x in v)), - QbloxSweeperType.offset: (lambda v: all((-1.25 * np.sqrt(2) <= x and x <= 1.25 * np.sqrt(2)) for x in v)), + QbloxSweeperType.offset: ( + lambda v: all( + (-1.25 * np.sqrt(2) <= x and x <= 1.25 * np.sqrt(2)) for x in v + ) + ), QbloxSweeperType.relative_phase: (lambda v: True), - QbloxSweeperType.start: (lambda v: all((4 <= x and x < 2**16) for x in v)), - QbloxSweeperType.duration: (lambda v: all((0 <= x and x < 2**16) for x in v)), - QbloxSweeperType.number: (lambda v: all((-(2**16) < x and x < 2**16) for x in v)), + QbloxSweeperType.start: ( + lambda v: all((4 <= x and x < 2**16) for x in v) + ), + QbloxSweeperType.duration: ( + lambda v: all((0 <= x and x < 2**16) for x in v) + ), + QbloxSweeperType.number: ( + lambda v: all((-(2**16) < x and x < 2**16) for x in v) + ), } if not check_values[type](np.append(self._abs_values, [self._abs_stop])): - raise ValueError(f"Sweeper {self.name} values are not within the allowed range") + raise ValueError( + f"Sweeper {self.name} values are not within the allowed range" + ) # Convert absolute values to q1asm values convert = { @@ -169,18 +186,30 @@ def __init__( self._con_start = convert[type](self._abs_start) self._con_step = convert[type](self._abs_step) self._con_stop = (self._con_start + self._con_step * (self._n) + 1) % 2**32 - self._con_values = np.array([(self._con_start + self._con_step * m) % 2**32 for m in range(self._n + 1)]) + self._con_values = np.array( + [ + (self._con_start + self._con_step * m) % 2**32 + for m in range(self._n + 1) + ] + ) # log.info(f"Qblox sweeper converted values: {self._con_values}") if not ( - isinstance(self._con_start, int) and isinstance(self._con_stop, int) and isinstance(self._con_step, int) + isinstance(self._con_start, int) + and isinstance(self._con_stop, int) + and isinstance(self._con_step, int) ): raise ValueError("start, stop and step must be int") @classmethod def from_sweeper( - cls, program: Program, sweeper: Sweeper, add_to: float = 0, multiply_to: float = 1, name: str = "" + cls, + program: Program, + sweeper: Sweeper, + add_to: float = 0, + multiply_to: float = 1, + name: str = "", ): """Creates an instance form a :class:`qibolab.sweepers.Sweeper` object. @@ -208,8 +237,17 @@ def from_sweeper( type = type_c[sweeper.parameter] rel_values = sweeper.values else: - raise ValueError(f"Sweeper parameter {sweeper.parameter} is not supported by qblox driver yet.") - return cls(program=program, rel_values=rel_values, type=type, add_to=add_to, multiply_to=multiply_to, name=name) + raise ValueError( + f"Sweeper parameter {sweeper.parameter} is not supported by qblox driver yet." + ) + return cls( + program=program, + rel_values=rel_values, + type=type, + add_to=add_to, + multiply_to=multiply_to, + name=name, + ) def block(self, inner_block: Block): """Generates the block of q1asm code that implements the sweep. @@ -244,7 +282,6 @@ def block(self, inner_block: Block): Args: inner_block (:class:`qibolab.instruments.qblox_q1asm.Block`): the block of q1asm code to be repeated within the loop. - """ # Initialisation header_block = Block() @@ -260,19 +297,27 @@ def block(self, inner_block: Block): update_parameter_block = Block() update_time = 1000 if self.type == QbloxSweeperType.frequency: - update_parameter_block.append(f"set_freq {self.register}") # TODO: move to pulse + update_parameter_block.append( + f"set_freq {self.register}" + ) # TODO: move to pulse update_parameter_block.append(f"upd_param {update_time}") if self.type == QbloxSweeperType.gain: - update_parameter_block.append(f"set_awg_gain {self.register}, {self.register}") # TODO: move to pulse + update_parameter_block.append( + f"set_awg_gain {self.register}, {self.register}" + ) # TODO: move to pulse update_parameter_block.append(f"upd_param {update_time}") if self.type == QbloxSweeperType.offset: - update_parameter_block.append(f"set_awg_offs {self.register}, {self.register}") + update_parameter_block.append( + f"set_awg_offs {self.register}, {self.register}" + ) update_parameter_block.append(f"upd_param {update_time}") if self.type == QbloxSweeperType.start: pass if self.type == QbloxSweeperType.duration: - update_parameter_block.append(f"add {self.register}, 1, {self.aux_register}") + update_parameter_block.append( + f"add {self.register}, 1, {self.aux_register}" + ) if self.type == QbloxSweeperType.time: pass if self.type == QbloxSweeperType.number: diff --git a/src/qibolab/instruments/qm/acquisition.py b/src/qibolab/instruments/qm/acquisition.py index 8ed409a19f..44f1cb61e7 100644 --- a/src/qibolab/instruments/qm/acquisition.py +++ b/src/qibolab/instruments/qm/acquisition.py @@ -22,8 +22,9 @@ class Acquisition(ABC): """QUA variables used for saving of acquisition results. - This class can be instantiated only within a QUA program scope. - Each readout pulse is associated with its own set of acquisition variables. + This class can be instantiated only within a QUA program scope. Each + readout pulse is associated with its own set of acquisition + variables. """ serial: str @@ -70,7 +71,9 @@ def fetch(self): class RawAcquisition(Acquisition): """QUA variables used for raw waveform acquisition.""" - adc_stream: _ResultSource = field(default_factory=lambda: declare_stream(adc_trace=True)) + adc_stream: _ResultSource = field( + default_factory=lambda: declare_stream(adc_trace=True) + ) """Stream to collect raw ADC data.""" def assign_element(self, element): @@ -186,7 +189,10 @@ def measure(self, operation, element): qua.dual_demod.full("cos", "out1", "sin", "out2", self.I), qua.dual_demod.full("minus_sin", "out1", "cos", "out2", self.Q), ) - qua.assign(self.shot, qua.Cast.to_int(self.I * self.cos - self.Q * self.sin > self.threshold)) + qua.assign( + self.shot, + qua.Cast.to_int(self.I * self.cos - self.Q * self.sin > self.threshold), + ) def save(self): qua.save(self.shot, self.shots) diff --git a/src/qibolab/instruments/qm/config.py b/src/qibolab/instruments/qm/config.py index 544fca928c..7fa60732df 100644 --- a/src/qibolab/instruments/qm/config.py +++ b/src/qibolab/instruments/qm/config.py @@ -31,7 +31,9 @@ class QMConfig: elements: dict = field(default_factory=dict) pulses: dict = field(default_factory=dict) waveforms: dict = field(default_factory=dict) - digital_waveforms: dict = field(default_factory=lambda: {"ON": {"samples": [(1, 0)]}}) + digital_waveforms: dict = field( + default_factory=lambda: {"ON": {"samples": [(1, 0)]}} + ) integration_weights: dict = field(default_factory=dict) mixers: dict = field(default_factory=dict) @@ -46,13 +48,18 @@ def register_analog_output_controllers(self, port: QMPort): for con, port_number in port.name: if con not in self.controllers: self.controllers[con] = {"analog_outputs": {}} - self.controllers[con]["analog_outputs"][port_number] = {"offset": port.offset} + self.controllers[con]["analog_outputs"][port_number] = { + "offset": port.offset + } if port.filters is not None: - self.controllers[con]["analog_outputs"][port_number]["filter"] = port.filters + self.controllers[con]["analog_outputs"][port_number][ + "filter" + ] = port.filters @staticmethod def iq_imbalance(g, phi): - """Creates the correction matrix for the mixer imbalance caused by the gain and phase imbalances + """Creates the correction matrix for the mixer imbalance caused by the + gain and phase imbalances. More information here: https://docs.qualang.io/libs/examples/mixer-calibration/#non-ideal-mixer @@ -66,7 +73,9 @@ def iq_imbalance(g, phi): c = np.cos(phi) s = np.sin(phi) N = 1 / ((1 - g**2) * (2 * c**2 - 1)) - return [float(N * x) for x in [(1 - g) * c, (1 + g) * s, (1 - g) * s, (1 + g) * c]] + return [ + float(N * x) for x in [(1 - g) * c, (1 + g) * s, (1 - g) * s, (1 + g) * c] + ] def register_drive_element(self, qubit, intermediate_frequency=0): """Register qubit drive elements and controllers in the QM config. @@ -102,10 +111,16 @@ def register_drive_element(self, qubit, intermediate_frequency=0): } ] else: - self.elements[f"drive{qubit.name}"]["intermediate_frequency"] = intermediate_frequency - self.mixers[f"mixer_drive{qubit.name}"][0]["intermediate_frequency"] = intermediate_frequency - - def register_readout_element(self, qubit, intermediate_frequency=0, time_of_flight=0, smearing=0): + self.elements[f"drive{qubit.name}"][ + "intermediate_frequency" + ] = intermediate_frequency + self.mixers[f"mixer_drive{qubit.name}"][0][ + "intermediate_frequency" + ] = intermediate_frequency + + def register_readout_element( + self, qubit, intermediate_frequency=0, time_of_flight=0, smearing=0 + ): """Register resonator elements and controllers in the QM config. Args: @@ -134,7 +149,10 @@ def register_readout_element(self, qubit, intermediate_frequency=0, time_of_flig } if "analog_inputs" not in controllers[con]: controllers[con]["analog_inputs"] = {} - controllers[con]["analog_inputs"][port_number] = {"offset": 0.0, "gain_db": qubit.feedback.port.gain} + controllers[con]["analog_inputs"][port_number] = { + "offset": 0.0, + "gain_db": qubit.feedback.port.gain, + } # register element lo_frequency = math.floor(qubit.readout.local_oscillator.frequency) self.elements[f"readout{qubit.name}"] = { @@ -163,8 +181,12 @@ def register_readout_element(self, qubit, intermediate_frequency=0, time_of_flig } ] else: - self.elements[f"readout{qubit.name}"]["intermediate_frequency"] = intermediate_frequency - self.mixers[f"mixer_readout{qubit.name}"][0]["intermediate_frequency"] = intermediate_frequency + self.elements[f"readout{qubit.name}"][ + "intermediate_frequency" + ] = intermediate_frequency + self.mixers[f"mixer_readout{qubit.name}"][0][ + "intermediate_frequency" + ] = intermediate_frequency def register_flux_element(self, qubit, intermediate_frequency=0): """Register qubit flux elements and controllers in the QM config. @@ -187,19 +209,25 @@ def register_flux_element(self, qubit, intermediate_frequency=0): "operations": {}, } else: - self.elements[f"flux{qubit.name}"]["intermediate_frequency"] = intermediate_frequency + self.elements[f"flux{qubit.name}"][ + "intermediate_frequency" + ] = intermediate_frequency def register_element(self, qubit, pulse, time_of_flight=0, smearing=0): if pulse.type is PulseType.DRIVE: # register drive element - if_frequency = pulse.frequency - math.floor(qubit.drive.local_oscillator.frequency) + if_frequency = pulse.frequency - math.floor( + qubit.drive.local_oscillator.frequency + ) self.register_drive_element(qubit, if_frequency) # register flux element (if available) if qubit.flux: self.register_flux_element(qubit) elif pulse.type is PulseType.READOUT: # register readout element (if it does not already exist) - if_frequency = pulse.frequency - math.floor(qubit.readout.local_oscillator.frequency) + if_frequency = pulse.frequency - math.floor( + qubit.readout.local_oscillator.frequency + ) self.register_readout_element(qubit, if_frequency, time_of_flight, smearing) # register flux element (if available) if qubit.flux: @@ -231,7 +259,9 @@ def register_pulse(self, qubit, pulse): "waveforms": {"I": serial_i, "Q": serial_q}, } # register drive pulse in elements - self.elements[f"drive{qubit.name}"]["operations"][pulse.serial] = pulse.serial + self.elements[f"drive{qubit.name}"]["operations"][ + pulse.serial + ] = pulse.serial elif pulse.type is PulseType.FLUX: serial = self.register_waveform(pulse) @@ -243,7 +273,9 @@ def register_pulse(self, qubit, pulse): }, } # register flux pulse in elements - self.elements[f"flux{qubit.name}"]["operations"][pulse.serial] = pulse.serial + self.elements[f"flux{qubit.name}"]["operations"][ + pulse.serial + ] = pulse.serial elif pulse.type is PulseType.READOUT: serial_i = self.register_waveform(pulse, "i") @@ -264,7 +296,9 @@ def register_pulse(self, qubit, pulse): "digital_marker": "ON", } # register readout pulse in elements - self.elements[f"readout{qubit.name}"]["operations"][pulse.serial] = pulse.serial + self.elements[f"readout{qubit.name}"]["operations"][ + pulse.serial + ] = pulse.serial else: raise_error(TypeError, f"Unknown pulse type {pulse.type.name}.") @@ -297,7 +331,10 @@ def register_waveform(self, pulse, mode="i"): waveform = getattr(pulse, f"envelope_waveform_{mode}") serial = waveform.serial if serial not in self.waveforms: - self.waveforms[serial] = {"type": "arbitrary", "samples": waveform.data.tolist()} + self.waveforms[serial] = { + "type": "arbitrary", + "samples": waveform.data.tolist(), + } return serial def register_integration_weights(self, qubit, readout_len): diff --git a/src/qibolab/instruments/qm/driver.py b/src/qibolab/instruments/qm/driver.py index 4eda6d2a65..5156a4f0c1 100644 --- a/src/qibolab/instruments/qm/driver.py +++ b/src/qibolab/instruments/qm/driver.py @@ -48,7 +48,9 @@ class QMOPX(Controller): _ports: Dict[IQPortId, QMPort] = field(default_factory=dict) """Dictionary holding the ports of controllers that are connected.""" script_file_name: Optional[str] = "qua_script.txt" - """Name of the file that the QUA program will dumped in that after every execution. + """Name of the file that the QUA program will dumped in that after every + execution. + If ``None`` the program will not be dumped. """ @@ -109,7 +111,9 @@ def fetch_results(result, ro_pulses): results = {} for qmpulse in ro_pulses: pulse = qmpulse.pulse - results[pulse.qubit] = results[pulse.serial] = qmpulse.acquisition.fetch(handles) + results[pulse.qubit] = results[pulse.serial] = qmpulse.acquisition.fetch( + handles + ) return results def play(self, qubits, couplers, sequence, options): @@ -129,7 +133,9 @@ def sweep(self, qubits, couplers, sequence, options, *sweepers): if qubit.flux: self.config.register_flux_element(qubit) - qmsequence = Sequence.create(qubits, sequence, sweepers, self.config, self.time_of_flight, self.smearing) + qmsequence = Sequence.create( + qubits, sequence, sweepers, self.config, self.time_of_flight, self.smearing + ) # play pulses using QUA with qua.program() as experiment: n = declare(int) @@ -139,7 +145,13 @@ def sweep(self, qubits, couplers, sequence, options, *sweepers): qmpulse.declare_output(options, threshold, iq_angle) with for_(n, 0, n < options.nshots, n + 1): - sweep(list(sweepers), qubits, qmsequence, options.relaxation_time, self.config) + sweep( + list(sweepers), + qubits, + qmsequence, + options.relaxation_time, + self.config, + ) with qua.stream_processing(): for qmpulse in qmsequence.ro_pulses: diff --git a/src/qibolab/instruments/qm/sequence.py b/src/qibolab/instruments/qm/sequence.py index 30a20efeef..e8f65c28ab 100644 --- a/src/qibolab/instruments/qm/sequence.py +++ b/src/qibolab/instruments/qm/sequence.py @@ -28,7 +28,8 @@ @dataclass class QMPulse: - """Wrapper around :class:`qibolab.pulses.Pulse` for easier translation to QUA program. + """Wrapper around :class:`qibolab.pulses.Pulse` for easier translation to + QUA program. These pulses are defined when :meth:`qibolab.instruments.qm.QMOPX.play` is called and hold attributes for the ``element`` and ``operation`` that corresponds to each pulse, @@ -38,26 +39,38 @@ class QMPulse: pulse: Pulse """:class:`qibolab.pulses.Pulse` implemting the current pulse.""" element: Optional[str] = None - """Element that the pulse will be played on, as defined in the QM config.""" + """Element that the pulse will be played on, as defined in the QM + config.""" operation: Optional[str] = None - """Name of the operation that is implementing the pulse in the QM config.""" + """Name of the operation that is implementing the pulse in the QM + config.""" relative_phase: Optional[float] = None """Relative phase of the pulse normalized to follow QM convention. - May be overwritten when sweeping phase.""" + + May be overwritten when sweeping phase. + """ wait_time: int = 0 """Time (in clock cycles) to wait before playing this pulse. - Calculated and assigned by :meth:`qibolab.instruments.qm.Sequence.add`.""" + + Calculated and assigned by + :meth: `qibolab.instruments.qm.Sequence.add`. + """ wait_time_variable: Optional[_Variable] = None - """Time (in clock cycles) to wait before playing this pulse when we are sweeping start.""" + """Time (in clock cycles) to wait before playing this pulse when we are + sweeping start.""" swept_duration: Optional[_Variable] = None """Pulse duration when sweeping it.""" acquisition: Optional[Acquisition] = None - """Data class containing the variables required for data acquisition for the instrument.""" + """Data class containing the variables required for data acquisition for + the instrument.""" next_: Set["QMPulse"] = field(default_factory=set) """Pulses that will be played after the current pulse. - These pulses need to be re-aligned if we are sweeping the start or duration.""" + + These pulses need to be re-aligned if we are sweeping the start or + duration. + """ elements_to_align: Set[str] = field(default_factory=set) def __post_init__(self): @@ -71,15 +84,18 @@ def __hash__(self): @property def duration(self): - """Duration of the pulse as defined in the :class:`qibolab.pulses.PulseSequence`. + """Duration of the pulse as defined in the + :class:`qibolab.pulses.PulseSequence`. - Remains constant even when we are sweeping the duration of this pulse. + Remains constant even when we are sweeping the duration of this + pulse. """ return self.pulse.duration @property def wait_cycles(self): - """Instrument clock cycles (1 cycle = 4ns) to wait before playing the pulse. + """Instrument clock cycles (1 cycle = 4ns) to wait before playing the + pulse. This property will be used in the QUA ``wait`` command, so that it is compatible with and without start sweepers. @@ -107,9 +123,13 @@ def declare_output(self, options, threshold=None, angle=None): elif acquisition_type is AcquisitionType.DISCRIMINATION: if threshold is None or angle is None: raise_error( - ValueError, "Cannot use ``AcquisitionType.DISCRIMINATION`` " "if threshold and angle are not given." + ValueError, + "Cannot use ``AcquisitionType.DISCRIMINATION`` " + "if threshold and angle are not given.", ) - self.acquisition = ShotsAcquisition(self.pulse.serial, average, threshold, angle) + self.acquisition = ShotsAcquisition( + self.pulse.serial, average, threshold, angle + ) else: raise_error(ValueError, f"Invalid acquisition type {acquisition_type}.") self.acquisition.assign_element(self.element) @@ -122,7 +142,10 @@ class BakedPulse(QMPulse): segments: List[Baking] = field(default_factory=list) """Baked segments implementing the pulse.""" amplitude: Optional[float] = None - """Amplitude of the baked pulse. Relevant only when sweeping amplitude.""" + """Amplitude of the baked pulse. + + Relevant only when sweeping amplitude. + """ durations: Optional[DurationsType] = None def __hash__(self): @@ -179,7 +202,8 @@ def play(self): def find_duration_sweeper_pulses(sweepers): - """Find all pulses that require baking because we are sweeping their duration.""" + """Find all pulses that require baking because we are sweeping their + duration.""" duration_sweep_pulses = set() for sweeper in sweepers: try: @@ -203,19 +227,24 @@ class Sequence: """ qmpulses: List[QMPulse] = field(default_factory=list) - """List of :class:`qibolab.instruments.qm.QMPulse` objects corresponding to the original pulses.""" + """List of :class:`qibolab.instruments.qm.QMPulse` objects corresponding to + the original pulses.""" ro_pulses: List[QMPulse] = field(default_factory=list) """List of readout pulses used for registering outputs.""" pulse_to_qmpulse: Dict[Pulse, QMPulse] = field(default_factory=dict) """Map from qibolab pulses to QMPulses (useful when sweeping).""" clock: Dict[str, int] = field(default_factory=lambda: collections.defaultdict(int)) - """Dictionary used to keep track of times of each element, in order to calculate wait times.""" - pulse_finish: Dict[int, List[QMPulse]] = field(default_factory=lambda: collections.defaultdict(list)) + """Dictionary used to keep track of times of each element, in order to + calculate wait times.""" + pulse_finish: Dict[int, List[QMPulse]] = field( + default_factory=lambda: collections.defaultdict(list) + ) """Map to find all pulses that finish at a given time (useful for ``_find_previous``).""" @classmethod def create(cls, qubits, sequence, sweepers, config, time_of_flight, smearing): - """Translates a :class:`qibolab.pulses.PulseSequence` to a :class:`qibolab.instruments.qm.sequence.Sequence`. + """Translates a :class:`qibolab.pulses.PulseSequence` to a + :class:`qibolab.instruments.qm.sequence.Sequence`. Args: qubits (list): List of :class:`qibolab.platforms.abstract.Qubit` objects @@ -229,9 +258,17 @@ def create(cls, qubits, sequence, sweepers, config, time_of_flight, smearing): # like we do for readout multiplex duration_sweep_pulses = find_duration_sweeper_pulses(sweepers) qmsequence = cls() - for pulse in sorted(sequence.pulses, key=lambda pulse: (pulse.start, pulse.duration)): - config.register_element(qubits[pulse.qubit], pulse, time_of_flight, smearing) - if pulse.duration % 4 != 0 or pulse.duration < 16 or pulse.serial in duration_sweep_pulses: + for pulse in sorted( + sequence.pulses, key=lambda pulse: (pulse.start, pulse.duration) + ): + config.register_element( + qubits[pulse.qubit], pulse, time_of_flight, smearing + ) + if ( + pulse.duration % 4 != 0 + or pulse.duration < 16 + or pulse.serial in duration_sweep_pulses + ): qmpulse = BakedPulse(pulse) qmpulse.bake(config, durations=[pulse.duration]) else: @@ -300,7 +337,10 @@ def play(self, relaxation_time=0): if pulse.type is PulseType.READOUT: qmpulse.acquisition.measure(qmpulse.operation, qmpulse.element) else: - if not isinstance(qmpulse.relative_phase, float) or qmpulse.relative_phase != 0: + if ( + not isinstance(qmpulse.relative_phase, float) + or qmpulse.relative_phase != 0 + ): qua.frame_rotation_2pi(qmpulse.relative_phase, qmpulse.element) needs_reset = True qmpulse.play() diff --git a/src/qibolab/instruments/qm/simulator.py b/src/qibolab/instruments/qm/simulator.py index 276e666178..9bbe56a66f 100644 --- a/src/qibolab/instruments/qm/simulator.py +++ b/src/qibolab/instruments/qm/simulator.py @@ -35,7 +35,9 @@ def connect(self): if self.cloud: from qm.simulate.credentials import create_credentials - self.manager = QuantumMachinesManager(host, int(port), credentials=create_credentials()) + self.manager = QuantumMachinesManager( + host, int(port), credentials=create_credentials() + ) else: self.manager = QuantumMachinesManager(host, int(port)) @@ -47,6 +49,7 @@ def execute_program(self, program): ncontrollers = len(self.config.controllers) controller_connections = create_simulator_controller_connections(ncontrollers) simulation_config = SimulationConfig( - duration=self.simulation_duration, controller_connections=controller_connections + duration=self.simulation_duration, + controller_connections=controller_connections, ) return self.manager.simulate(self.config.__dict__, program, simulation_config) diff --git a/src/qibolab/instruments/qm/sweepers.py b/src/qibolab/instruments/qm/sweepers.py index a25d8bcc2c..44ed5eef78 100644 --- a/src/qibolab/instruments/qm/sweepers.py +++ b/src/qibolab/instruments/qm/sweepers.py @@ -35,7 +35,10 @@ def _update_baked_pulses(sweeper, qmsequence, config): qmpulse = qmsequence.pulse_to_qmpulse[pulse.serial] if isinstance(qmpulse, BakedPulse): if not is_baked: - raise_error(TypeError, "Duration sweeper cannot contain both baked and not baked pulses.") + raise_error( + TypeError, + "Duration sweeper cannot contain both baked and not baked pulses.", + ) values = np.array(sweeper.values).astype(int) qmpulse.bake(config, values) @@ -49,14 +52,17 @@ def sweep(sweepers, qubits, qmsequence, relaxation_time, config): def _sweep_recursion(sweepers, qubits, qmsequence, relaxation_time): - """Unrolls a list of qibolab sweepers to the corresponding QUA for loops using recursion.""" + """Unrolls a list of qibolab sweepers to the corresponding QUA for loops + using recursion.""" if len(sweepers) > 0: parameter = sweepers[0].parameter.name func_name = f"_sweep_{parameter}" if func_name in globals(): globals()[func_name](sweepers, qubits, qmsequence, relaxation_time) else: - raise_error(NotImplementedError, f"Sweeper for {parameter} is not implemented.") + raise_error( + NotImplementedError, f"Sweeper for {parameter} is not implemented." + ) else: qmsequence.play(relaxation_time) @@ -71,14 +77,20 @@ def _sweep_frequency(sweepers, qubits, qmsequence, relaxation_time): elif pulse.type is PulseType.READOUT: lo_frequency = math.floor(qubit.readout.local_oscillator.frequency) else: - raise_error(NotImplementedError, f"Cannot sweep frequency of pulse of type {pulse.type}.") + raise_error( + NotImplementedError, + f"Cannot sweep frequency of pulse of type {pulse.type}.", + ) # convert to IF frequency for readout and drive pulses f0 = math.floor(pulse.frequency - lo_frequency) freqs0.append(declare(int, value=f0)) # check if sweep is within the supported bandwidth [-400, 400] MHz max_freq = maximum_sweep_value(sweeper.values, f0) if max_freq > 4e8: - raise_error(ValueError, f"Frequency {max_freq} for qubit {qubit.name} is beyond instrument bandwidth.") + raise_error( + ValueError, + f"Frequency {max_freq} for qubit {qubit.name} is beyond instrument bandwidth.", + ) # is it fine to have this declaration inside the ``nshots`` QUA loop? f = declare(int) @@ -94,7 +106,9 @@ def _sweep_amplitude(sweepers, qubits, qmsequence, relaxation_time): sweeper = sweepers[0] # TODO: Consider sweeping amplitude without multiplication if min(sweeper.values) < -2: - raise_error(ValueError, "Amplitude sweep values are <-2 which is not supported.") + raise_error( + ValueError, "Amplitude sweep values are <-2 which is not supported." + ) if max(sweeper.values) > 2: raise_error(ValueError, "Amplitude sweep values are >2 which is not supported.") diff --git a/src/qibolab/instruments/qutech.py b/src/qibolab/instruments/qutech.py index 8921464cd8..585b011c9b 100644 --- a/src/qibolab/instruments/qutech.py +++ b/src/qibolab/instruments/qutech.py @@ -20,7 +20,8 @@ def __init__(self, name, address): self.device_parameters = {} def connect(self): - """Connects to the instrument using the IP address set in the runcard.""" + """Connects to the instrument using the IP address set in the + runcard.""" if not self.is_connected: for attempt in range(3): try: @@ -35,7 +36,9 @@ def connect(self): if not self.is_connected: raise InstrumentException(self, f"Unable to connect to {self.name}") else: - raise_error(Exception, "There is an open connection to the instrument already") + raise_error( + Exception, "There is an open connection to the instrument already" + ) def _set_device_parameter(self, target, *parameters, value): if self.is_connected: @@ -43,7 +46,9 @@ def _set_device_parameter(self, target, *parameters, value): if not key in self.device_parameters: for parameter in parameters: if not hasattr(target, parameter): - raise Exception(f"The instrument {self.name} does not have parameters {parameter}") + raise Exception( + f"The instrument {self.name} does not have parameters {parameter}" + ) target.set(parameter, value) self.device_parameters[key] = value elif self.device_parameters[key] != value: @@ -73,7 +78,9 @@ def setup(self, **kwargs): current = settings[2] if not module_name in self.device.instrument_modules: self.device.add_spi_module(settings[0], "S4g", module_name) - device = self.device.instrument_modules[module_name].instrument_modules["dac" + str(port_number - 1)] + device = self.device.instrument_modules[module_name].instrument_modules[ + "dac" + str(port_number - 1) + ] self.dacs[channel] = type( "S4g_dac", (), @@ -92,7 +99,9 @@ def setup(self, **kwargs): voltage = settings[2] if not module_name in self.device.instrument_modules: self.device.add_spi_module(settings[0], "D5a", module_name) - device = self.device.instrument_modules[module_name].instrument_modules["dac" + str(port_number - 1)] + device = self.device.instrument_modules[module_name].instrument_modules[ + "dac" + str(port_number - 1) + ] self.dacs[channel] = type( "D5a_dac", (), diff --git a/src/qibolab/instruments/rfsoc/convert.py b/src/qibolab/instruments/rfsoc/convert.py index 4222aff8b6..ccf14291db 100644 --- a/src/qibolab/instruments/rfsoc/convert.py +++ b/src/qibolab/instruments/rfsoc/convert.py @@ -14,11 +14,15 @@ NS_TO_US = 1e-3 -def replace_pulse_shape(rfsoc_pulse: rfsoc_pulses.Pulse, shape: PulseShape) -> rfsoc_pulses.Pulse: +def replace_pulse_shape( + rfsoc_pulse: rfsoc_pulses.Pulse, shape: PulseShape +) -> rfsoc_pulses.Pulse: """Set pulse shape parameters in rfsoc_pulses pulse object.""" if shape.name not in {"Gaussian", "Drag", "Rectangular", "Exponential"}: new_pulse = rfsoc_pulses.Arbitrary( - **asdict(rfsoc_pulse), i_values=shape.envelope_waveform_i, q_values=shape.envelope_waveform_q + **asdict(rfsoc_pulse), + i_values=shape.envelope_waveform_i, + q_values=shape.envelope_waveform_q, ) return new_pulse new_pulse_cls = getattr(rfsoc_pulses, shape.name) @@ -27,22 +31,30 @@ def replace_pulse_shape(rfsoc_pulse: rfsoc_pulses.Pulse, shape: PulseShape) -> r if shape.name == "Gaussian": return new_pulse_cls(**asdict(rfsoc_pulse), rel_sigma=shape.rel_sigma) if shape.name == "Drag": - return new_pulse_cls(**asdict(rfsoc_pulse), rel_sigma=shape.rel_sigma, beta=shape.beta) + return new_pulse_cls( + **asdict(rfsoc_pulse), rel_sigma=shape.rel_sigma, beta=shape.beta + ) if shape.name == "Exponential": - return new_pulse_cls(**asdict(rfsoc_pulse), tau=shape.tau, upsilon=shape.upsilon, weight=shape.g) + return new_pulse_cls( + **asdict(rfsoc_pulse), tau=shape.tau, upsilon=shape.upsilon, weight=shape.g + ) def pulse_lo_frequency(pulse: Pulse, qubits: dict[int, Qubit]) -> int: """Return local_oscillator frequency (HZ) of a pulse.""" pulse_type = pulse.type.name.lower() try: - lo_frequency = getattr(qubits[pulse.qubit], pulse_type).local_oscillator.frequency + lo_frequency = getattr( + qubits[pulse.qubit], pulse_type + ).local_oscillator.frequency except AttributeError: lo_frequency = 0 return lo_frequency -def convert_units_sweeper(sweeper: rfsoc.Sweeper, sequence: PulseSequence, qubits: dict[int, Qubit]): +def convert_units_sweeper( + sweeper: rfsoc.Sweeper, sequence: PulseSequence, qubits: dict[int, Qubit] +): """Convert units for `qibosoq.abstract.Sweeper` considering also LOs.""" for idx, jdx in enumerate(sweeper.indexes): parameter = sweeper.parameters[idx] @@ -67,7 +79,8 @@ def convert(*args): @convert.register def _(qubit: Qubit) -> rfsoc.Qubit: - """Convert `qibolab.platforms.abstract.Qubit` to `qibosoq.abstract.Qubit`.""" + """Convert `qibolab.platforms.abstract.Qubit` to + `qibosoq.abstract.Qubit`.""" if qubit.flux: return rfsoc.Qubit(qubit.flux.offset, qubit.flux.port.name) return rfsoc.Qubit(0.0, None) @@ -116,11 +129,14 @@ def _(par: Parameter) -> rfsoc.Parameter: @convert.register -def _(sweeper: Sweeper, sequence: PulseSequence, qubits: dict[int, Qubit]) -> rfsoc.Sweeper: +def _( + sweeper: Sweeper, sequence: PulseSequence, qubits: dict[int, Qubit] +) -> rfsoc.Sweeper: """Convert `qibolab.sweeper.Sweeper` to `qibosoq.abstract.Sweeper`. - Note that any unit conversion is not done in this function (to avoid to do it multiple times). - Conversion will be done in `convert_units_sweeper`. + Note that any unit conversion is not done in this function (to avoid + to do it multiple times). Conversion will be done in + `convert_units_sweeper`. """ parameters = [] starts = [] diff --git a/src/qibolab/instruments/rfsoc/driver.py b/src/qibolab/instruments/rfsoc/driver.py index 76fca6860d..c384aad129 100644 --- a/src/qibolab/instruments/rfsoc/driver.py +++ b/src/qibolab/instruments/rfsoc/driver.py @@ -74,7 +74,10 @@ def setup(self): """Empty deprecated method.""" def _execute_pulse_sequence( - self, sequence: PulseSequence, qubits: dict[int, Qubit], opcode: rfsoc.OperationCode + self, + sequence: PulseSequence, + qubits: dict[int, Qubit], + opcode: rfsoc.OperationCode, ) -> tuple[list, list]: """Prepare the commands dictionary to send to the qibosoq server. @@ -150,7 +153,9 @@ def play( if execution_parameters.acquisition_type is AcquisitionType.DISCRIMINATION: self.cfg.average = False else: - self.cfg.average = execution_parameters.averaging_mode is AveragingMode.CYCLIC + self.cfg.average = ( + execution_parameters.averaging_mode is AveragingMode.CYCLIC + ) if execution_parameters.acquisition_type is AcquisitionType.RAW: opcode = rfsoc.OperationCode.EXECUTE_PULSE_SEQUENCE_RAW @@ -166,8 +171,13 @@ def play( i_pulse = np.array(toti[j][i]) q_pulse = np.array(totq[j][i]) - if execution_parameters.acquisition_type is AcquisitionType.DISCRIMINATION: - discriminated_shots = self.classify_shots(i_pulse, q_pulse, qubits[ro_pulse.qubit]) + if ( + execution_parameters.acquisition_type + is AcquisitionType.DISCRIMINATION + ): + discriminated_shots = self.classify_shots( + i_pulse, q_pulse, qubits[ro_pulse.qubit] + ) if execution_parameters.averaging_mode is AveragingMode.CYCLIC: discriminated_shots = np.mean(discriminated_shots, axis=0) result = execution_parameters.results_type(discriminated_shots) @@ -178,13 +188,19 @@ def play( return results @staticmethod - def validate_input_command(sequence: PulseSequence, execution_parameters: ExecutionParameters, sweep: bool): + def validate_input_command( + sequence: PulseSequence, execution_parameters: ExecutionParameters, sweep: bool + ): """Check if sequence and execution_parameters are supported.""" if execution_parameters.acquisition_type is AcquisitionType.RAW: if sweep: - raise NotImplementedError("Raw data acquisition is not compatible with sweepers") + raise NotImplementedError( + "Raw data acquisition is not compatible with sweepers" + ) if len(sequence.ro_pulses) != 1: - raise NotImplementedError("Raw data acquisition is compatible only with a single readout") + raise NotImplementedError( + "Raw data acquisition is compatible only with a single readout" + ) if execution_parameters.averaging_mode is not AveragingMode.CYCLIC: raise NotImplementedError("Raw data acquisition can only be averaged") if execution_parameters.fast_reset: @@ -195,18 +211,26 @@ def update_cfg(self, execution_parameters: ExecutionParameters): if execution_parameters.nshots is not None: self.cfg.reps = execution_parameters.nshots if execution_parameters.relaxation_time is not None: - self.cfg.repetition_duration = execution_parameters.relaxation_time * NS_TO_US + self.cfg.repetition_duration = ( + execution_parameters.relaxation_time * NS_TO_US + ) def classify_shots( - self, i_values: npt.NDArray[np.float64], q_values: npt.NDArray[np.float64], qubit: Qubit + self, + i_values: npt.NDArray[np.float64], + q_values: npt.NDArray[np.float64], + qubit: Qubit, ) -> npt.NDArray[np.float64]: - """Classify IQ values using qubit threshold and rotation_angle if available in runcard.""" + """Classify IQ values using qubit threshold and rotation_angle if + available in runcard.""" if qubit.iq_angle is None or qubit.threshold is None: raise ValueError("Classification parameters were not provided") angle = qubit.iq_angle threshold = qubit.threshold - rotated = np.cos(angle) * np.array(i_values) - np.sin(angle) * np.array(q_values) + rotated = np.cos(angle) * np.array(i_values) - np.sin(angle) * np.array( + q_values + ) shots = np.heaviside(np.array(rotated) - threshold, 0) if isinstance(shots, float): return [shots] @@ -223,10 +247,10 @@ def play_sequence_in_sweep_recursion( """Last recursion layer, if no sweeps are present. After playing the sequence, the resulting dictionary keys need - to be converted to the correct values. - Even indexes correspond to qubit number and are not changed. - Odd indexes correspond to readout pulses serials and are convert - to match the original sequence (of the sweep) and not the one just executed. + to be converted to the correct values. Even indexes correspond + to qubit number and are not changed. Odd indexes correspond to + readout pulses serials and are convert to match the original + sequence (of the sweep) and not the one just executed. """ res = self.play(qubits, couplers, sequence, execution_parameters) newres = {} @@ -272,18 +296,24 @@ def recursive_python_sweep( # Last layer for recursion. if len(sweepers) == 0: - return self.play_sequence_in_sweep_recursion(qubits, couplers, sequence, or_sequence, execution_parameters) + return self.play_sequence_in_sweep_recursion( + qubits, couplers, sequence, or_sequence, execution_parameters + ) if not self.get_if_python_sweep(sequence, *sweepers): toti, totq = self._execute_sweeps(sequence, qubits, sweepers) - res = self.convert_sweep_results(or_sequence, qubits, toti, totq, execution_parameters) + res = self.convert_sweep_results( + or_sequence, qubits, toti, totq, execution_parameters + ) return res sweeper = sweepers[0] values = [] for idx, _ in enumerate(sweeper.indexes): val = np.linspace(sweeper.starts[idx], sweeper.stops[idx], sweeper.expts) - if sweeper.parameters[idx] in rfsoc.Parameter.variants({"duration", "delay"}): + if sweeper.parameters[idx] in rfsoc.Parameter.variants( + {"duration", "delay"} + ): val = val.astype(int) values.append(val) @@ -302,13 +332,20 @@ def recursive_python_sweep( "duration", } ): - setattr(sequence[kdx], sweeper_parameter.name.lower(), values[jdx][idx]) + setattr( + sequence[kdx], sweeper_parameter.name.lower(), values[jdx][idx] + ) elif sweeper is rfsoc.Parameter.DELAY: start_delay = values[jdx][idx] sequence[kdx].start_delay = values[jdx][idx] res = self.recursive_python_sweep( - qubits, couplers, sequence, or_sequence, *sweepers[1:], execution_parameters=execution_parameters + qubits, + couplers, + sequence, + or_sequence, + *sweepers[1:], + execution_parameters=execution_parameters, ) results = self.merge_sweep_results(results, res) return results # already in the right format @@ -336,7 +373,9 @@ def merge_sweep_results( dict_a[serial] = dict_b[serial] return dict_a - def get_if_python_sweep(self, sequence: PulseSequence, *sweepers: rfsoc.Sweeper) -> bool: + def get_if_python_sweep( + self, sequence: PulseSequence, *sweepers: rfsoc.Sweeper + ) -> bool: """Check if a sweeper must be run with python loop or on hardware. To be run on qick internal loop a sweep must: @@ -355,11 +394,18 @@ def get_if_python_sweep(self, sequence: PulseSequence, *sweepers: rfsoc.Sweeper) if any(pulse.type is PulseType.FLUX for pulse in sequence): return True for sweeper in sweepers: - if all(parameter is rfsoc.Parameter.BIAS for parameter in sweeper.parameters): + if all( + parameter is rfsoc.Parameter.BIAS for parameter in sweeper.parameters + ): continue - if all(parameter is rfsoc.Parameter.DELAY for parameter in sweeper.parameters): + if all( + parameter is rfsoc.Parameter.DELAY for parameter in sweeper.parameters + ): continue - if any(parameter is rfsoc.Parameter.DURATION for parameter in sweeper.parameters): + if any( + parameter is rfsoc.Parameter.DURATION + for parameter in sweeper.parameters + ): return True for sweep_idx, parameter in enumerate(sweeper.parameters): @@ -407,7 +453,11 @@ def convert_sweep_results( adcs = np.unique([qubits[p.qubit].feedback.port.name for p in original_ro]) for k, k_val in enumerate(adcs): - adc_ro = [pulse for pulse in original_ro if qubits[pulse.qubit].feedback.port.name == k_val] + adc_ro = [ + pulse + for pulse in original_ro + if qubits[pulse.qubit].feedback.port.name == k_val + ] for i, (ro_pulse, original_ro_pulse) in enumerate(zip(adc_ro, original_ro)): i_vals = np.array(toti[k][i]) q_vals = np.array(totq[k][i]) @@ -416,7 +466,10 @@ def convert_sweep_results( i_vals = np.reshape(i_vals, (self.cfg.reps, *i_vals.shape[:-1])) q_vals = np.reshape(q_vals, (self.cfg.reps, *q_vals.shape[:-1])) - if execution_parameters.acquisition_type is AcquisitionType.DISCRIMINATION: + if ( + execution_parameters.acquisition_type + is AcquisitionType.DISCRIMINATION + ): qubit = qubits[original_ro_pulse.qubit] discriminated_shots = self.classify_shots(i_vals, q_vals, qubit) if execution_parameters.averaging_mode is AveragingMode.CYCLIC: @@ -461,7 +514,9 @@ def sweep( if execution_parameters.acquisition_type is AcquisitionType.DISCRIMINATION: self.cfg.average = False else: - self.cfg.average = execution_parameters.averaging_mode is AveragingMode.CYCLIC + self.cfg.average = ( + execution_parameters.averaging_mode is AveragingMode.CYCLIC + ) rfsoc_sweepers = [convert(sweep, sequence, qubits) for sweep in sweepers] @@ -469,7 +524,10 @@ def sweep( bias_change = any(sweep.parameter is BIAS for sweep in sweepers) if bias_change: - initial_biases = [qubits[idx].flux.offset if qubits[idx].flux is not None else None for idx in qubits] + initial_biases = [ + qubits[idx].flux.offset if qubits[idx].flux is not None else None + for idx in qubits + ] results = self.recursive_python_sweep( qubits, diff --git a/src/qibolab/instruments/rohde_schwarz.py b/src/qibolab/instruments/rohde_schwarz.py index 3f8d2942f4..0f5996365a 100644 --- a/src/qibolab/instruments/rohde_schwarz.py +++ b/src/qibolab/instruments/rohde_schwarz.py @@ -52,11 +52,14 @@ def ref_osc_source(self, x): self.device.ref_osc_source = x def connect(self): - """Connects to the instrument using the IP address set in the runcard.""" + """Connects to the instrument using the IP address set in the + runcard.""" if not self.is_connected: for attempt in range(3): try: - self.device = LO_SGS100A.RohdeSchwarz_SGS100A(self.name, f"TCPIP0::{self.address}::5025::SOCKET") + self.device = LO_SGS100A.RohdeSchwarz_SGS100A( + self.name, f"TCPIP0::{self.address}::5025::SOCKET" + ) self.is_connected = True break except KeyError as exc: @@ -76,7 +79,8 @@ def connect(self): self.device.ref_osc_source = self._ref_osc_source def _set_device_parameter(self, parameter: str, value): - """Sets a parameter of the instrument, if it changed from the last stored in the cache. + """Sets a parameter of the instrument, if it changed from the last + stored in the cache. Args: parameter: str = The parameter to be cached and set. @@ -84,18 +88,25 @@ def _set_device_parameter(self, parameter: str, value): Raises: Exception = If attempting to set a parameter without a connection to the instrument. """ - if not (parameter in self._device_parameters and self._device_parameters[parameter] == value): + if not ( + parameter in self._device_parameters + and self._device_parameters[parameter] == value + ): if self.is_connected: if not parameter in self._device_parameters: if not hasattr(self.device, parameter): - raise ValueError(f"The instrument {self.name} does not have parameter {parameter}") + raise ValueError( + f"The instrument {self.name} does not have parameter {parameter}" + ) self.device.set(parameter, value) self._device_parameters[parameter] = value elif self._device_parameters[parameter] != value: self.device.set(parameter, value) self._device_parameters[parameter] = value else: - raise ConnectionError("There is no connection to the instrument {self.name}") + raise ConnectionError( + "There is no connection to the instrument {self.name}" + ) def _erase_device_parameters_cache(self): """Erases the cache of instrument parameters.""" diff --git a/src/qibolab/instruments/unrolling.py b/src/qibolab/instruments/unrolling.py index 7de0b20f2b..832c6a0cf0 100644 --- a/src/qibolab/instruments/unrolling.py +++ b/src/qibolab/instruments/unrolling.py @@ -6,7 +6,8 @@ def batch_max_sequences(sequences, max_size): - """Split a list of sequences to batches with a maximum number of sequences in each. + """Split a list of sequences to batches with a maximum number of sequences + in each. Args: sequences (list): List of :class:`qibolab.pulses.PulseSequence` objects. @@ -16,7 +17,8 @@ def batch_max_sequences(sequences, max_size): def batch_max_readout(sequences, max_measurements): - """Split a list of sequences to batches with a maximum number of readout pulses in each. + """Split a list of sequences to batches with a maximum number of readout + pulses in each. Useful for sequence unrolling on instruments that support a maximum number of readout pulses in a single sequence due to memory limitations. diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 276479798b..c46c8d1e1d 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -38,8 +38,7 @@ "HDAWG_MIN_PLAYWAVE_HINT": 64, "HDAWG_MIN_PLAYZERO_HINT": 64, } - -"""Translating to Zurich ExecutionParameters""" +"""Translating to Zurich ExecutionParameters.""" ACQUISITION_TYPE = { AcquisitionType.INTEGRATION: lo.AcquisitionType.INTEGRATION, AcquisitionType.RAW: lo.AcquisitionType.RAW, @@ -60,7 +59,7 @@ def select_pulse(pulse, pulse_type): - """Pulse translation""" + """Pulse translation.""" if "IIR" not in str(pulse.shape): if str(pulse.shape) == "Rectangular()": @@ -116,7 +115,8 @@ def select_pulse(pulse, pulse_type): # Test this when we have pulses that use it return sampled_pulse_complex( uid=(f"{pulse_type}_{pulse.qubit}_"), - samples=pulse.envelope_waveform_i.data + (1j * pulse.envelope_waveform_q.data), + samples=pulse.envelope_waveform_i.data + + (1j * pulse.envelope_waveform_q.data), can_compress=True, ) @@ -143,47 +143,44 @@ class ZhPulse: """Zurich pulse from qibolab pulse translation.""" def __init__(self, pulse): - """Zurich pulse from qibolab pulse""" + """Zurich pulse from qibolab pulse.""" self.pulse = pulse - """Qibolab pulse""" + """Qibolab pulse.""" self.signal = f"{pulse.type.name.lower()}{pulse.qubit}" - """Line associated with the pulse""" + """Line associated with the pulse.""" self.zhpulse = select_pulse(pulse, pulse.type.name.lower()) - """Zurich pulse""" + """Zurich pulse.""" class ZhSweeper: - """ - Zurich sweeper from qibolab sweeper for pulse parameters - Amplitude, Duration, Frequency (and maybe Phase) - - """ + """Zurich sweeper from qibolab sweeper for pulse parameters Amplitude, + Duration, Frequency (and maybe Phase)""" def __init__(self, pulse, sweeper, qubit): self.sweeper = sweeper - """Qibolab sweeper""" + """Qibolab sweeper.""" self.pulse = pulse - """Qibolab pulse associated to the sweeper""" + """Qibolab pulse associated to the sweeper.""" self.signal = f"{pulse.type.name.lower()}{pulse.qubit}" - """Line associated with the pulse""" + """Line associated with the pulse.""" self.zhpulse = ZhPulse(pulse).zhpulse - """Zurich pulse associated to the sweeper""" + """Zurich pulse associated to the sweeper.""" self.zhsweeper = self.select_sweeper(pulse.type, sweeper, qubit) - """Zurich sweeper""" + """Zurich sweeper.""" self.zhsweepers = [self.select_sweeper(pulse.type, sweeper, qubit)] - """ - Zurich sweepers, Need something better to store multiple sweeps on the same pulse + """Zurich sweepers, Need something better to store multiple sweeps on + the same pulse. - Not properly implemented as it was only used on Rabi amplitude vs lenght and it - was an unused routine. + Not properly implemented as it was only used on Rabi amplitude + vs lenght and it was an unused routine. """ @staticmethod # pylint: disable=R0903 def select_sweeper(ptype, sweeper, qubit): - """Sweeper translation""" + """Sweeper translation.""" if sweeper.parameter is Parameter.amplitude: return lo.SweepParameter( @@ -202,9 +199,13 @@ def select_sweeper(ptype, sweeper, qubit): ) if sweeper.parameter is Parameter.frequency: if ptype is PulseType.READOUT: - intermediate_frequency = qubit.readout_frequency - qubit.readout.local_oscillator.frequency + intermediate_frequency = ( + qubit.readout_frequency - qubit.readout.local_oscillator.frequency + ) elif ptype is PulseType.DRIVE: - intermediate_frequency = qubit.drive_frequency - qubit.drive.local_oscillator.frequency + intermediate_frequency = ( + qubit.drive_frequency - qubit.drive.local_oscillator.frequency + ) return lo.LinearSweepParameter( uid=sweeper.parameter.name, start=sweeper.values[0] + intermediate_frequency, @@ -213,23 +214,22 @@ def select_sweeper(ptype, sweeper, qubit): ) def add_sweeper(self, sweeper, qubit): - """Add sweeper to list of sweepers""" + """Add sweeper to list of sweepers.""" self.zhsweepers.append(self.select_sweeper(self.pulse.type, sweeper, qubit)) class ZhSweeperLine: - """ - Zurich sweeper from qibolab sweeper for non pulse parameters - Bias, Delay (, power_range, local_oscillator frequency, offset ???) + """Zurich sweeper from qibolab sweeper for non pulse parameters Bias, Delay + (, power_range, local_oscillator frequency, offset ???) - For now Parameter.bias sweepers are implemented as Parameter.Amplitude - on a flux pulse. We may want to keep this class separate for future - Near Time sweeps + For now Parameter.bias sweepers are implemented as + Parameter.Amplitude on a flux pulse. We may want to keep this class + separate for future Near Time sweeps """ def __init__(self, sweeper, qubit=None, sequence=None, pulse=None): self.sweeper = sweeper - """Qibolab sweeper""" + """Qibolab sweeper.""" # Do something with the pulse coming here if sweeper.parameter is Parameter.bias: @@ -274,7 +274,7 @@ def __init__(self, sweeper, qubit=None, sequence=None, pulse=None): @staticmethod # pylint: disable=R0903 def select_sweeper(sweeper): - """Sweeper translation""" + """Sweeper translation.""" if sweeper.parameter is Parameter.bias: return lo.SweepParameter( uid=sweeper.parameter.name, @@ -288,11 +288,13 @@ def select_sweeper(sweeper): class Zurich(Controller): - """Zurich driver main class""" + """Zurich driver main class.""" PortType = ZhPort - def __init__(self, name, device_setup, use_emulation=False, time_of_flight=0.0, smearing=0.0): + def __init__( + self, name, device_setup, use_emulation=False, time_of_flight=0.0, smearing=0.0 + ): self.name = name "Setup name (str)" @@ -364,8 +366,7 @@ def setup(self, *args, **kwargs): """Empty method to comply with Instrument interface.""" def calibration_step(self, qubits, couplers, options): - """ - Zurich general pre experiment calibration definitions + """Zurich general pre experiment calibration definitions. Change to get frequencies from sequence """ @@ -379,24 +380,28 @@ def calibration_step(self, qubits, couplers, options): if len(self.sequence[f"drive{qubit.name}"]) != 0: self.register_drive_line( qubit=qubit, - intermediate_frequency=qubit.drive_frequency - qubit.drive.local_oscillator.frequency, + intermediate_frequency=qubit.drive_frequency + - qubit.drive.local_oscillator.frequency, ) if len(self.sequence[f"readout{qubit.name}"]) != 0: self.register_readout_line( qubit=qubit, - intermediate_frequency=qubit.readout_frequency - qubit.readout.local_oscillator.frequency, + intermediate_frequency=qubit.readout_frequency + - qubit.readout.local_oscillator.frequency, options=options, ) if options.fast_reset is not False: if len(self.sequence[f"drive{qubit.name}"]) == 0: self.register_drive_line( qubit=qubit, - intermediate_frequency=qubit.drive_frequency - qubit.drive.local_oscillator.frequency, + intermediate_frequency=qubit.drive_frequency + - qubit.drive.local_oscillator.frequency, ) self.device_setup.set_calibration(self.calibration) def register_readout_line(self, qubit, intermediate_frequency, options): - """Registers qubit measure and acquire lines to calibration and signal map. + """Registers qubit measure and acquire lines to calibration and signal + map. Note ---- @@ -412,14 +417,15 @@ def register_readout_line(self, qubit, intermediate_frequency, options): port_mode=lo.PortMode.LF, ..., ) - """ q = qubit.name # pylint: disable=C0103 - self.signal_map[f"measure{q}"] = self.device_setup.logical_signal_groups[f"q{q}"].logical_signals[ - "measure_line" - ] - self.calibration[f"/logical_signal_groups/q{q}/measure_line"] = lo.SignalCalibration( + self.signal_map[f"measure{q}"] = self.device_setup.logical_signal_groups[ + f"q{q}" + ].logical_signals["measure_line"] + self.calibration[ + f"/logical_signal_groups/q{q}/measure_line" + ] = lo.SignalCalibration( oscillator=lo.Oscillator( frequency=intermediate_frequency, modulation_type=lo.ModulationType.SOFTWARE, @@ -433,9 +439,9 @@ def register_readout_line(self, qubit, intermediate_frequency, options): delay_signal=0, ) - self.signal_map[f"acquire{q}"] = self.device_setup.logical_signal_groups[f"q{q}"].logical_signals[ - "acquire_line" - ] + self.signal_map[f"acquire{q}"] = self.device_setup.logical_signal_groups[ + f"q{q}" + ].logical_signals["acquire_line"] if qubit.kernel_path: self.kernels[q] = qubit.kernel_path @@ -454,7 +460,9 @@ def register_readout_line(self, qubit, intermediate_frequency, options): # To keep compatibility with angle and threshold discrimination (Remove when possible) threshold = qubit.threshold - self.calibration[f"/logical_signal_groups/q{q}/acquire_line"] = lo.SignalCalibration( + self.calibration[ + f"/logical_signal_groups/q{q}/acquire_line" + ] = lo.SignalCalibration( oscillator=oscillator, range=qubit.feedback.power_range, port_delay=self.time_of_flight * NANO_TO_SECONDS, @@ -464,8 +472,12 @@ def register_readout_line(self, qubit, intermediate_frequency, options): def register_drive_line(self, qubit, intermediate_frequency): """Registers qubit drive line to calibration and signal map.""" q = qubit.name # pylint: disable=C0103 - self.signal_map[f"drive{q}"] = self.device_setup.logical_signal_groups[f"q{q}"].logical_signals["drive_line"] - self.calibration[f"/logical_signal_groups/q{q}/drive_line"] = lo.SignalCalibration( + self.signal_map[f"drive{q}"] = self.device_setup.logical_signal_groups[ + f"q{q}" + ].logical_signals["drive_line"] + self.calibration[ + f"/logical_signal_groups/q{q}/drive_line" + ] = lo.SignalCalibration( oscillator=lo.Oscillator( frequency=intermediate_frequency, modulation_type=lo.ModulationType.HARDWARE, @@ -482,8 +494,12 @@ def register_drive_line(self, qubit, intermediate_frequency): def register_flux_line(self, qubit): """Registers qubit flux line to calibration and signal map.""" q = qubit.name # pylint: disable=C0103 - self.signal_map[f"flux{q}"] = self.device_setup.logical_signal_groups[f"q{q}"].logical_signals["flux_line"] - self.calibration[f"/logical_signal_groups/q{q}/flux_line"] = lo.SignalCalibration( + self.signal_map[f"flux{q}"] = self.device_setup.logical_signal_groups[ + f"q{q}" + ].logical_signals["flux_line"] + self.calibration[ + f"/logical_signal_groups/q{q}/flux_line" + ] = lo.SignalCalibration( range=qubit.flux.power_range, port_delay=None, delay_signal=0, @@ -493,10 +509,12 @@ def register_flux_line(self, qubit): def register_couplerflux_line(self, coupler): """Registers qubit flux line to calibration and signal map.""" c = coupler.name # pylint: disable=C0103 - self.signal_map[f"couplerflux{c}"] = self.device_setup.logical_signal_groups[f"qc{c}"].logical_signals[ - "flux_line" - ] - self.calibration[f"/logical_signal_groups/qc{c}/flux_line"] = lo.SignalCalibration( + self.signal_map[f"couplerflux{c}"] = self.device_setup.logical_signal_groups[ + f"qc{c}" + ].logical_signals["flux_line"] + self.calibration[ + f"/logical_signal_groups/qc{c}/flux_line" + ] = lo.SignalCalibration( range=coupler.flux.power_range, port_delay=None, delay_signal=0, @@ -511,13 +529,15 @@ def run_exp(self): - Save a experiment compiled experiment (): self.exp.save("saved_exp") # saving compiled experiment """ - self.exp = self.session.compile(self.experiment, compiler_settings=COMPILER_SETTINGS) + self.exp = self.session.compile( + self.experiment, compiler_settings=COMPILER_SETTINGS + ) # self.exp.save_compiled_experiment("saved_exp") self.results = self.session.run(self.exp) @staticmethod def frequency_from_pulses(qubits, sequence): - """Gets the frequencies from the pulses to the qubits""" + """Gets the frequencies from the pulses to the qubits.""" # Implement Dual drive frequency experiments, we don't have any for now for pulse in sequence: qubit = qubits[pulse.qubit] @@ -526,9 +546,12 @@ def frequency_from_pulses(qubits, sequence): if pulse.type is PulseType.DRIVE: qubit.drive_frequency = pulse.frequency - def create_sub_sequence(self, line_name: str, quantum_elements: Union[Dict[str, Qubit], Dict[str, Coupler]]): - """ - Create a list of sequences for each measurement. + def create_sub_sequence( + self, + line_name: str, + quantum_elements: Union[Dict[str, Qubit], Dict[str, Coupler]], + ): + """Create a list of sequences for each measurement. Args: line_name (str): Name of the line from which extract the sequence. @@ -548,9 +571,10 @@ def create_sub_sequence(self, line_name: str, quantum_elements: Union[Dict[str, pulse_sequences[measurement_index].append(pulse) self.sub_sequences[f"{line_name}{q}"] = pulse_sequences - def create_sub_sequences(self, qubits: Dict[str, Qubit], couplers: Dict[str, Coupler]): - """ - Create subsequences for different lines (drive, flux, coupler flux). + def create_sub_sequences( + self, qubits: Dict[str, Qubit], couplers: Dict[str, Coupler] + ): + """Create subsequences for different lines (drive, flux, coupler flux). Args: qubits (dict[str, Qubit]): qubits for the platform. @@ -562,8 +586,9 @@ def create_sub_sequences(self, qubits: Dict[str, Qubit], couplers: Dict[str, Cou self.create_sub_sequence("couplerflux", couplers) def experiment_flow(self, qubits, couplers, sequence, options, sweepers=[]): - """ - Create the experiment object for the devices, following the steps separated one on each method: + """Create the experiment object for the devices, following the steps + separated one on each method: + Translation, Calibration, Experiment Definition. """ self.sequence_zh(sequence, qubits, couplers, sweepers) @@ -573,7 +598,7 @@ def experiment_flow(self, qubits, couplers, sequence, options, sweepers=[]): # pylint: disable=W0221 def play(self, qubits, couplers, sequence, options): - """Play pulse sequence""" + """Play pulse sequence.""" self.signal_map = {} self.frequency_from_pulses(qubits, sequence) @@ -590,7 +615,9 @@ def play(self, qubits, couplers, sequence, options): for i, ropulse in enumerate(self.sequence[f"readout{q}"]): data = np.array(self.results.get_data(f"sequence{q}_{i}")) if options.acquisition_type is AcquisitionType.DISCRIMINATION: - data = np.ones(data.shape) - data.real # Probability inversion patch + data = ( + np.ones(data.shape) - data.real + ) # Probability inversion patch serial = ropulse.pulse.serial qubit = ropulse.pulse.qubit results[serial] = results[qubit] = options.results_type(data) @@ -600,7 +627,7 @@ def play(self, qubits, couplers, sequence, options): return results def sequence_zh(self, sequence, qubits, couplers, sweepers): - """Qibo sequence to Zurich sequence""" + """Qibo sequence to Zurich sequence.""" # Define and assign the sequence zhsequence = defaultdict(list) @@ -624,31 +651,51 @@ def nt_loop(sweeper): if sweeper.parameter.name in SWEEPER_SET: for pulse in sweeper.pulses: aux_list = zhsequence[f"{pulse.type.name.lower()}{pulse.qubit}"] - if sweeper.parameter is Parameter.frequency and pulse.type is PulseType.READOUT: + if ( + sweeper.parameter is Parameter.frequency + and pulse.type is PulseType.READOUT + ): self.acquisition_type = lo.AcquisitionType.SPECTROSCOPY - if sweeper.parameter is Parameter.amplitude and pulse.type is PulseType.READOUT: + if ( + sweeper.parameter is Parameter.amplitude + and pulse.type is PulseType.READOUT + ): self.acquisition_type = lo.AcquisitionType.SPECTROSCOPY nt_loop(sweeper) for element in aux_list: if pulse == element.pulse: if isinstance(aux_list[aux_list.index(element)], ZhPulse): if isinstance(pulse, CouplerFluxPulse): - aux_list[aux_list.index(element)] = ZhSweeper(pulse, sweeper, couplers[pulse.qubit]) + aux_list[aux_list.index(element)] = ZhSweeper( + pulse, sweeper, couplers[pulse.qubit] + ) else: - aux_list[aux_list.index(element)] = ZhSweeper(pulse, sweeper, qubits[pulse.qubit]) - elif isinstance(aux_list[aux_list.index(element)], ZhSweeper): + aux_list[aux_list.index(element)] = ZhSweeper( + pulse, sweeper, qubits[pulse.qubit] + ) + elif isinstance( + aux_list[aux_list.index(element)], ZhSweeper + ): if isinstance(pulse, CouplerFluxPulse): - aux_list[aux_list.index(element)].add_sweeper(sweeper, couplers[pulse.qubit]) + aux_list[aux_list.index(element)].add_sweeper( + sweeper, couplers[pulse.qubit] + ) else: - aux_list[aux_list.index(element)].add_sweeper(sweeper, qubits[pulse.qubit]) + aux_list[aux_list.index(element)].add_sweeper( + sweeper, qubits[pulse.qubit] + ) if sweeper.parameter.name in SWEEPER_BIAS: if sweeper.qubits: for qubit in sweeper.qubits: - zhsequence[f"flux{qubit.name}"] = [ZhSweeperLine(sweeper, qubit, sequence)] + zhsequence[f"flux{qubit.name}"] = [ + ZhSweeperLine(sweeper, qubit, sequence) + ] if sweeper.couplers: for coupler in sweeper.couplers: - zhsequence[f"couplerflux{coupler.name}"] = [ZhSweeperLine(sweeper, coupler, sequence)] + zhsequence[f"couplerflux{coupler.name}"] = [ + ZhSweeperLine(sweeper, coupler, sequence) + ] # This may not place the Zhsweeper when the start occurs among different sections or lines if sweeper.parameter.name in SWEEPER_START: @@ -666,7 +713,7 @@ def nt_loop(sweeper): self.sequence = zhsequence def create_exp(self, qubits, couplers, options): - """Zurich experiment initialization using their Experiment class""" + """Zurich experiment initialization using their Experiment class.""" # Setting experiment signal lines signals = [] @@ -697,7 +744,9 @@ def create_exp(self, qubits, couplers, options): else: acquisition_type = ACQUISITION_TYPE[options.acquisition_type] averaging_mode = AVERAGING_MODE[options.averaging_mode] - exp_options = replace(options, acquisition_type=acquisition_type, averaging_mode=averaging_mode) + exp_options = replace( + options, acquisition_type=acquisition_type, averaging_mode=averaging_mode + ) exp_calib = lo.Calibration() # Near Time recursion loop or directly to Real Time recursion loop @@ -707,7 +756,7 @@ def create_exp(self, qubits, couplers, options): self.define_exp(qubits, couplers, exp_options, exp, exp_calib) def define_exp(self, qubits, couplers, exp_options, exp, exp_calib): - """Real time definition""" + """Real time definition.""" with exp.acquire_loop_rt( uid="shots", count=exp_options.nshots, @@ -724,7 +773,7 @@ def define_exp(self, qubits, couplers, exp_options, exp, exp_calib): self.experiment = exp def select_exp(self, exp, qubits, couplers, exp_options): - """Build Zurich Experiment selecting the relevant sections""" + """Build Zurich Experiment selecting the relevant sections.""" if "coupler" in str(self.sequence): self.couplerflux(exp, couplers) if "drive" in str(self.sequence): @@ -735,13 +784,19 @@ def select_exp(self, exp, qubits, couplers, exp_options): self.drive(exp, qubits) elif "flux" in str(self.sequence): self.flux(exp, qubits) - self.measure_relax(exp, qubits, couplers, exp_options.relaxation_time, exp_options.acquisition_type) + self.measure_relax( + exp, + qubits, + couplers, + exp_options.relaxation_time, + exp_options.acquisition_type, + ) if exp_options.fast_reset is not False: self.fast_reset(exp, qubits, exp_options.fast_reset) @staticmethod def play_sweep_select_single(exp, qubit, pulse, section, parameters, partial_sweep): - """Play Zurich pulse when a single sweeper is involved""" + """Play Zurich pulse when a single sweeper is involved.""" if any("amplitude" in param for param in parameters): pulse.zhpulse.amplitude *= max(pulse.zhsweeper.values) pulse.zhsweeper.values /= max(pulse.zhsweeper.values) @@ -775,7 +830,8 @@ def play_sweep_select_single(exp, qubit, pulse, section, parameters, partial_swe # Hardcoded for the flux pulse for 2q gates @staticmethod def play_sweep_select_dual(exp, qubit, pulse, section, parameters): - """Play Zurich pulse when two sweepers are involved on the same pulse""" + """Play Zurich pulse when two sweepers are involved on the same + pulse.""" if "amplitude" in parameters and "duration" in parameters: for sweeper in pulse.zhsweepers: if sweeper.uid == "amplitude": @@ -794,7 +850,8 @@ def play_sweep_select_dual(exp, qubit, pulse, section, parameters): ) def play_sweep(self, exp, qubit, pulse, section): - """Takes care of playing the sweepers and involved pulses for different options""" + """Takes care of playing the sweepers and involved pulses for different + options.""" if isinstance(pulse, ZhSweeperLine): if pulse.zhsweeper.uid == "bias": @@ -811,10 +868,12 @@ def play_sweep(self, exp, qubit, pulse, section): if len(parameters) == 2: self.play_sweep_select_dual(exp, qubit, pulse, section, parameters) else: - self.play_sweep_select_single(exp, qubit, pulse, section, parameters, partial_sweep) + self.play_sweep_select_single( + exp, qubit, pulse, section, parameters, partial_sweep + ) def couplerflux(self, exp, couplers): - """coupler flux for bias sweep or pulses""" + """Coupler flux for bias sweep or pulses.""" for coupler in couplers.values(): c = coupler.name # pylint: disable=C0103 with exp.section(uid=f"sequence_bias_coupler{c}"): @@ -838,7 +897,7 @@ def couplerflux(self, exp, couplers): i += 1 def flux(self, exp, qubits): - """qubit flux for bias sweep or pulses""" + """Qubit flux for bias sweep or pulses.""" for qubit in qubits.values(): q = qubit.name # pylint: disable=C0103 time = 0 @@ -846,17 +905,20 @@ def flux(self, exp, qubits): if len(self.sequence[f"flux{q}"]) != 0: play_after = None for j, sequence in enumerate(self.sub_sequences[f"flux{q}"]): - with exp.section(uid=f"sequence_bias{q}_{j}", play_after=play_after): + with exp.section( + uid=f"sequence_bias{q}_{j}", play_after=play_after + ): for pulse in sequence: if not isinstance(pulse, ZhSweeperLine): pulse.zhpulse.uid += str(i) exp.delay( signal=f"flux{q}", - time=round(pulse.pulse.start * NANO_TO_SECONDS, 9) - time, - ) - time = round(pulse.pulse.duration * NANO_TO_SECONDS, 9) + round( - pulse.pulse.start * NANO_TO_SECONDS, 9 + time=round(pulse.pulse.start * NANO_TO_SECONDS, 9) + - time, ) + time = round( + pulse.pulse.duration * NANO_TO_SECONDS, 9 + ) + round(pulse.pulse.start * NANO_TO_SECONDS, 9) if isinstance(pulse, ZhSweeperLine): self.play_sweep(exp, qubit, pulse, section="flux") elif isinstance(pulse, ZhSweeper): @@ -867,7 +929,7 @@ def flux(self, exp, qubits): play_after = f"sequence_bias{q}_{j}" def drive(self, exp, qubits): - """qubit driving pulses""" + """Qubit driving pulses.""" for qubit in qubits.values(): q = qubit.name # pylint: disable=C0103 time = 0 @@ -875,16 +937,19 @@ def drive(self, exp, qubits): if len(self.sequence[f"drive{q}"]) != 0: play_after = None for j, sequence in enumerate(self.sub_sequences[f"drive{q}"]): - with exp.section(uid=f"sequence_drive{q}_{j}", play_after=play_after): + with exp.section( + uid=f"sequence_drive{q}_{j}", play_after=play_after + ): for pulse in sequence: if not isinstance(pulse, ZhSweeperLine): exp.delay( signal=f"drive{q}", - time=round(pulse.pulse.start * NANO_TO_SECONDS, 9) - time, - ) - time = round(pulse.pulse.duration * NANO_TO_SECONDS, 9) + round( - pulse.pulse.start * NANO_TO_SECONDS, 9 + time=round(pulse.pulse.start * NANO_TO_SECONDS, 9) + - time, ) + time = round( + pulse.pulse.duration * NANO_TO_SECONDS, 9 + ) + round(pulse.pulse.start * NANO_TO_SECONDS, 9) pulse.zhpulse.uid += str(i) if isinstance(pulse, ZhSweeper): self.play_sweep(exp, qubit, pulse, section="drive") @@ -901,16 +966,23 @@ def drive(self, exp, qubits): if len(self.sequence[f"readout{q}"]) > 0 and isinstance( self.sequence[f"readout{q}"][0], ZhSweeperLine ): - exp.delay(signal=f"drive{q}", time=self.sequence[f"readout{q}"][0].zhsweeper) - self.sequence[f"readout{q}"].remove(self.sequence[f"readout{q}"][0]) + exp.delay( + signal=f"drive{q}", + time=self.sequence[f"readout{q}"][0].zhsweeper, + ) + self.sequence[f"readout{q}"].remove( + self.sequence[f"readout{q}"][0] + ) play_after = f"sequence_drive{q}_{j}" def find_subsequence_finish( - self, measurement_number: int, line: str, quantum_elements: Union[Dict[str, Qubit], Dict[str, Coupler]] + self, + measurement_number: int, + line: str, + quantum_elements: Union[Dict[str, Qubit], Dict[str, Coupler]], ) -> Tuple[int, str]: - """ - Find the finishing time and qubit for a given sequence. + """Find the finishing time and qubit for a given sequence. Args: measurement_number (int): number of the measure pulse. @@ -926,9 +998,14 @@ def find_subsequence_finish( time_finish = 0 sequence_finish = "None" for quantum_element in quantum_elements: - if len(self.sub_sequences[f"{line}{quantum_element}"]) <= measurement_number: + if ( + len(self.sub_sequences[f"{line}{quantum_element}"]) + <= measurement_number + ): continue - for pulse in self.sub_sequences[f"{line}{quantum_element}"][measurement_number]: + for pulse in self.sub_sequences[f"{line}{quantum_element}"][ + measurement_number + ]: if pulse.pulse.finish > time_finish: time_finish = pulse.pulse.finish sequence_finish = f"{line}{quantum_element}" @@ -938,7 +1015,7 @@ def find_subsequence_finish( # For CW spectroscopy, set only integration_length and do not specify the measure signal. # For all other measurements, set either length or pulse for both the measure pulse and integration kernel. def measure_relax(self, exp, qubits, couplers, relaxation_time, acquisition_type): - """qubit readout pulse, data acquisition and qubit relaxation""" + """Qubit readout pulse, data acquisition and qubit relaxation.""" readout_schedule = defaultdict(list) qubit_readout_schedule = defaultdict(list) iq_angle_readout_schedule = defaultdict(list) @@ -953,7 +1030,11 @@ def measure_relax(self, exp, qubits, couplers, relaxation_time, acquisition_type weights = {} for i, (pulses, qubits, iq_angles) in enumerate( - zip(readout_schedule.values(), qubit_readout_schedule.values(), iq_angle_readout_schedule.values()) + zip( + readout_schedule.values(), + qubit_readout_schedule.values(), + iq_angle_readout_schedule.values(), + ) ): qd_finish = self.find_subsequence_finish(i, "drive", qubits) qf_finish = self.find_subsequence_finish(i, "flux", qubits) @@ -981,7 +1062,10 @@ def measure_relax(self, exp, qubits, couplers, relaxation_time, acquisition_type time=self.smearing * NANO_TO_SECONDS, ) - if self.kernels[q].is_file() and acquisition_type == lo.AcquisitionType.DISCRIMINATION: + if ( + self.kernels[q].is_file() + and acquisition_type == lo.AcquisitionType.DISCRIMINATION + ): kernels = np.load(self.kernels[q]) weight = lo.pulse_library.sampled_pulse_complex( uid="weight" + str(q), @@ -993,7 +1077,12 @@ def measure_relax(self, exp, qubits, couplers, relaxation_time, acquisition_type if acquisition_type == lo.AcquisitionType.DISCRIMINATION: weight = lo.pulse_library.sampled_pulse_complex( samples=np.ones( - [int(pulse.pulse.duration * 2 - 3 * self.smearing * NANO_TO_SECONDS)] + [ + int( + pulse.pulse.duration * 2 + - 3 * self.smearing * NANO_TO_SECONDS + ) + ] ) * np.exp(1j * iq_angle), uid="weights" + str(q), @@ -1003,7 +1092,9 @@ def measure_relax(self, exp, qubits, couplers, relaxation_time, acquisition_type # TODO: Patch for multiple readouts: Remove different uids weight = lo.pulse_library.const( uid="weight" + str(q), - length=round(pulse.pulse.duration * NANO_TO_SECONDS, 9) + length=round( + pulse.pulse.duration * NANO_TO_SECONDS, 9 + ) - 1.5 * self.smearing * NANO_TO_SECONDS, amplitude=1, ) @@ -1028,7 +1119,9 @@ def measure_relax(self, exp, qubits, couplers, relaxation_time, acquisition_type integration_length=None, measure_signal=f"measure{q}", measure_pulse=pulse.zhpulse, - measure_pulse_length=round(pulse.pulse.duration * NANO_TO_SECONDS, 9), + measure_pulse_length=round( + pulse.pulse.duration * NANO_TO_SECONDS, 9 + ), measure_pulse_parameters=measure_pulse_parameters, measure_pulse_amplitude=None, acquire_delay=self.time_of_flight * NANO_TO_SECONDS, @@ -1056,7 +1149,8 @@ def fast_reset(self, exp, qubits, fast_reset): @staticmethod def rearrange_sweepers(sweepers): - """Rearranges sweepers from qibocal based on device hardware limitations""" + """Rearranges sweepers from qibocal based on device hardware + limitations.""" rearranging_axes = [[], []] if len(sweepers) == 2: if sweepers[1].parameter is Parameter.frequency: @@ -1080,13 +1174,13 @@ def rearrange_sweepers(sweepers): return rearranging_axes, sweepers def offsets_off(self): - """Sets the offsets from the HDAWGs to 0 after each experiment""" + """Sets the offsets from the HDAWGs to 0 after each experiment.""" for sigout in range(0, 8): self.session.devices["device_hdawg"].awgs[0].sigouts[sigout].offset = 0 self.session.devices["device_hdawg2"].awgs[0].sigouts[0].offset = 0 def sweep(self, qubits, couplers, sequence: PulseSequence, options, *sweepers): - """Play pulse and sweepers sequence""" + """Play pulse and sweepers sequence.""" self.signal_map = {} self.nt_sweeps = None @@ -1107,9 +1201,13 @@ def sweep(self, qubits, couplers, sequence: PulseSequence, options, *sweepers): for i, ropulse in enumerate(self.sequence[f"readout{q}"]): exp_res = self.results.get_data(f"sequence{q}_{i}") # Reorder dimensions - data = np.moveaxis(exp_res, rearranging_axes[0], rearranging_axes[1]) + data = np.moveaxis( + exp_res, rearranging_axes[0], rearranging_axes[1] + ) if options.acquisition_type is AcquisitionType.DISCRIMINATION: - data = np.ones(data.shape) - data.real # Probability inversion patch + data = ( + np.ones(data.shape) - data.real + ) # Probability inversion patch serial = ropulse.pulse.serial qubit = ropulse.pulse.qubit @@ -1121,7 +1219,7 @@ def sweep(self, qubits, couplers, sequence: PulseSequence, options, *sweepers): return results def sweep_recursion(self, qubits, couplers, exp, exp_calib, exp_options): - """Sweepers recursion for multiple nested Real Time sweepers""" + """Sweepers recursion for multiple nested Real Time sweepers.""" sweeper = self.sweepers[0] @@ -1132,7 +1230,9 @@ def sweep_recursion(self, qubits, couplers, exp, exp_calib, exp_options): if sweeper.parameter is Parameter.frequency: for pulse in sweeper.pulses: line = "drive" if pulse.type is PulseType.DRIVE else "measure" - zhsweeper = ZhSweeper(pulse, sweeper, qubits[sweeper.pulses[0].qubit]).zhsweeper + zhsweeper = ZhSweeper( + pulse, sweeper, qubits[sweeper.pulses[0].qubit] + ).zhsweeper zhsweeper.uid = "frequency" # Changing the name from "frequency" breaks it f"frequency_{i} exp_calib[f"{line}{pulse.qubit}"] = lo.SignalCalibration( oscillator=lo.Oscillator( @@ -1150,22 +1250,30 @@ def sweep_recursion(self, qubits, couplers, exp, exp_calib, exp_options): aux_max = max(abs(sweeper.values)) sweeper.values /= aux_max - parameter = ZhSweeper(pulse, sweeper, qubits[sweeper.pulses[0].qubit]).zhsweeper + parameter = ZhSweeper( + pulse, sweeper, qubits[sweeper.pulses[0].qubit] + ).zhsweeper sweeper.values *= aux_max if sweeper.parameter is Parameter.bias: if sweeper.qubits: for qubit in sweeper.qubits: - parameter = ZhSweeperLine(sweeper, qubit, self.sequence_qibo).zhsweeper + parameter = ZhSweeperLine( + sweeper, qubit, self.sequence_qibo + ).zhsweeper if sweeper.couplers: for qubit in sweeper.couplers: - parameter = ZhSweeperLine(sweeper, qubit, self.sequence_qibo).zhsweeper + parameter = ZhSweeperLine( + sweeper, qubit, self.sequence_qibo + ).zhsweeper elif sweeper.parameter is Parameter.start: parameter = ZhSweeperLine(sweeper).zhsweeper elif parameter is None: - parameter = ZhSweeper(sweeper.pulses[0], sweeper, qubits[sweeper.pulses[0].qubit]).zhsweeper + parameter = ZhSweeper( + sweeper.pulses[0], sweeper, qubits[sweeper.pulses[0].qubit] + ).zhsweeper with exp.sweep( uid=f"sweep_{sweeper.parameter.name.lower()}_{i}", # This uid trouble double freq ??? @@ -1178,11 +1286,12 @@ def sweep_recursion(self, qubits, couplers, exp, exp_calib, exp_options): self.select_exp(exp, qubits, couplers, exp_options) def sweep_recursion_nt(self, qubits, couplers, options, exp, exp_calib): - """ - Sweepers recursion for Near Time sweepers. Faster than regular software sweepers as - they are executed on the actual device by (software ? or slower hardware ones) + """Sweepers recursion for Near Time sweepers. Faster than regular + software sweepers as they are executed on the actual device by + (software ? or slower hardware ones) - You want to avoid them so for now they are implement for a specific sweep. + You want to avoid them so for now they are implement for a + specific sweep. """ log.info("nt Loop") @@ -1204,7 +1313,9 @@ def sweep_recursion_nt(self, qubits, couplers, options, exp, exp_calib): aux_max = max(abs(sweeper.values)) sweeper.values /= aux_max - zhsweeper = ZhSweeper(pulse, sweeper, qubits[sweeper.pulses[0].qubit]).zhsweeper + zhsweeper = ZhSweeper( + pulse, sweeper, qubits[sweeper.pulses[0].qubit] + ).zhsweeper sweeper.values *= aux_max zhsweeper.uid = "amplitude" # f"amplitude{i}" @@ -1212,7 +1323,9 @@ def sweep_recursion_nt(self, qubits, couplers, options, exp, exp_calib): parameter = zhsweeper elif parameter is None: - parameter = ZhSweeper(sweeper.pulses[0], sweeper, qubits[sweeper.pulses[0].qubit]).zhsweeper + parameter = ZhSweeper( + sweeper.pulses[0], sweeper, qubits[sweeper.pulses[0].qubit] + ).zhsweeper with exp.sweep( uid=f"sweep_{sweeper.parameter.name.lower()}_{i}", @@ -1232,13 +1345,13 @@ def split_batches(self, sequences): return batch_max_sequences(sequences, MAX_SEQUENCES) def play_sim(self, qubits, sequence, options, sim_time): - """Play pulse sequence""" + """Play pulse sequence.""" self.experiment_flow(qubits, sequence, options) self.run_sim(sim_time) def run_sim(self, sim_time): - """Run the simulation + """Run the simulation. Args: sim_time (float): Time[s] to simulate starting from 0 @@ -1247,7 +1360,9 @@ def run_sim(self, sim_time): self.sim_session = lo.Session(self.device_setup) # connect to session self.sim_device = self.sim_session.connect(do_emulation=True) - self.exp = self.sim_session.compile(self.experiment, compiler_settings=COMPILER_SETTINGS) + self.exp = self.sim_session.compile( + self.experiment, compiler_settings=COMPILER_SETTINGS + ) # Plot simulated output signals with helper function plot_simulation( diff --git a/src/qibolab/native.py b/src/qibolab/native.py index 0d8c9e4397..34c9b19847 100644 --- a/src/qibolab/native.py +++ b/src/qibolab/native.py @@ -15,7 +15,8 @@ @dataclass class NativePulse: - """Container with parameters required to generate a pulse implementing a native gate.""" + """Container with parameters required to generate a pulse implementing a + native gate.""" name: str """Name of the gate that the pulse implements.""" @@ -26,7 +27,8 @@ class NativePulse: qubit: "qubits.Qubit" frequency: int = 0 relative_start: int = 0 - """Relative start is relevant for two-qubit gate operations which correspond to a pulse sequence.""" + """Relative start is relevant for two-qubit gate operations which + correspond to a pulse sequence.""" # used for qblox if_frequency: Optional[int] = None @@ -52,7 +54,11 @@ def from_dict(cls, name, pulse, qubit): @property def raw(self): - data = {fld.name: getattr(self, fld.name) for fld in fields(self) if getattr(self, fld.name) is not None} + data = { + fld.name: getattr(self, fld.name) + for fld in fields(self) + if getattr(self, fld.name) is not None + } del data["name"] del data["start"] if self.pulse_type is PulseType.FLUX: @@ -63,7 +69,8 @@ def raw(self): return data def pulse(self, start, relative_phase=0.0): - """Construct the :class:`qibolab.pulses.Pulse` object implementing the gate. + """Construct the :class:`qibolab.pulses.Pulse` object implementing the + gate. Args: start (int): Start time of the pulse in the sequence. @@ -99,7 +106,8 @@ def pulse(self, start, relative_phase=0.0): @dataclass class VirtualZPulse: - """Container with parameters required to add a virtual Z phase in a pulse sequence.""" + """Container with parameters required to add a virtual Z phase in a pulse + sequence.""" phase: float qubit: "qubits.Qubit" @@ -111,7 +119,8 @@ def raw(self): @dataclass class CouplerPulse: - """Container with parameters required to add a coupler pulse in a pulse sequence.""" + """Container with parameters required to add a coupler pulse in a pulse + sequence.""" duration: int amplitude: float @@ -147,7 +156,8 @@ def raw(self): } def pulse(self, start): - """Construct the :class:`qibolab.pulses.Pulse` object implementing the gate. + """Construct the :class:`qibolab.pulses.Pulse` object implementing the + gate. Args: start (int): Start time of the pulse in the sequence. @@ -167,10 +177,12 @@ def pulse(self, start): @dataclass class NativeSequence: - """List of :class:`qibolab.platforms.native.NativePulse` objects implementing a gate. + """List of :class:`qibolab.platforms.native.NativePulse` objects + implementing a gate. - Relevant for two-qubit gates, which usually require a sequence of pulses to be implemented. - These pulses may act on qubits different than the qubits the gate is targeting. + Relevant for two-qubit gates, which usually require a sequence of + pulses to be implemented. These pulses may act on qubits different + than the qubits the gate is targeting. """ name: str @@ -179,7 +191,8 @@ class NativeSequence: @classmethod def from_dict(cls, name, sequence, qubits, couplers): - """Constructs the native sequence from the dictionaries provided in the runcard. + """Constructs the native sequence from the dictionaries provided in the + runcard. Args: name (str): Name of the gate the sequence is applying. @@ -210,7 +223,14 @@ def from_dict(cls, name, sequence, qubits, couplers): phase = pulse["phase"] pulses.append(VirtualZPulse(phase, qubit)) else: - pulses.append(NativePulse(f"{name}{i}", **pulse, pulse_type=PulseType(pulse_type), qubit=qubit)) + pulses.append( + NativePulse( + f"{name}{i}", + **pulse, + pulse_type=PulseType(pulse_type), + qubit=qubit, + ) + ) return cls(name, pulses, coupler_pulses) @property @@ -220,7 +240,8 @@ def raw(self): return pulses + coupler_pulses def sequence(self, start=0): - """Creates a :class:`qibolab.pulses.PulseSequence` object implementing the sequence.""" + """Creates a :class:`qibolab.pulses.PulseSequence` object implementing + the sequence.""" sequence = PulseSequence() virtual_z_phases = defaultdict(int) @@ -238,7 +259,8 @@ def sequence(self, start=0): @dataclass class SingleQubitNatives: - """Container with the native single-qubit gates acting on a specific qubit.""" + """Container with the native single-qubit gates acting on a specific + qubit.""" RX: Optional[NativePulse] = None """Pulse to drive the qubit from state 0 to state 1.""" @@ -262,12 +284,18 @@ def from_dict(cls, qubit, native_gates): native_gates (dict): Dictionary with native gate pulse parameters as loaded from the runcard. """ - pulses = {n: NativePulse.from_dict(n, pulse, qubit=qubit) for n, pulse in native_gates.items()} + pulses = { + n: NativePulse.from_dict(n, pulse, qubit=qubit) + for n, pulse in native_gates.items() + } return cls(**pulses) @property def raw(self): - """Serialize native gate pulses. ``None`` gates are not included.""" + """Serialize native gate pulses. + + ``None`` gates are not included. + """ data = {} for fld in fields(self): attr = getattr(self, fld.name) @@ -279,7 +307,8 @@ def raw(self): @dataclass class CouplerNatives: - """Container with the native single-qubit gates acting on a specific qubit.""" + """Container with the native single-qubit gates acting on a specific + qubit.""" CP: Optional[NativePulse] = None """Pulse to activate the coupler.""" @@ -294,12 +323,18 @@ def from_dict(cls, coupler, native_gates): native_gates (dict): Dictionary with native gate pulse parameters as loaded from the runcard [Reusing the dict from qubits]. """ - pulses = {n: CouplerPulse.from_dict(pulse, coupler=coupler) for n, pulse in native_gates.items()} + pulses = { + n: CouplerPulse.from_dict(pulse, coupler=coupler) + for n, pulse in native_gates.items() + } return cls(**pulses) @property def raw(self): - """Serialize native gate pulses. ``None`` gates are not included.""" + """Serialize native gate pulses. + + ``None`` gates are not included. + """ data = {} for fld in fields(self): attr = getattr(self, fld.name) @@ -310,14 +345,18 @@ def raw(self): @dataclass class TwoQubitNatives: - """Container with the native two-qubit gates acting on a specific pair of qubits.""" + """Container with the native two-qubit gates acting on a specific pair of + qubits.""" CZ: Optional[NativeSequence] = None iSWAP: Optional[NativeSequence] = None @classmethod def from_dict(cls, qubits, couplers, native_gates): - sequences = {n: NativeSequence.from_dict(n, seq, qubits, couplers) for n, seq in native_gates.items()} + sequences = { + n: NativeSequence.from_dict(n, seq, qubits, couplers) + for n, seq in native_gates.items() + } return cls(**sequences) @property diff --git a/src/qibolab/platform.py b/src/qibolab/platform.py index 770683c64e..9cbbdd8db8 100644 --- a/src/qibolab/platform.py +++ b/src/qibolab/platform.py @@ -23,8 +23,11 @@ NS_TO_SEC = 1e-9 -def unroll_sequences(sequences: List[PulseSequence], relaxation_time: int) -> Tuple[PulseSequence, Dict[str, str]]: - """Unrolls a list of pulse sequences to a single pulse sequence with multiple measurements. +def unroll_sequences( + sequences: List[PulseSequence], relaxation_time: int +) -> Tuple[PulseSequence, Dict[str, str]]: + """Unrolls a list of pulse sequences to a single pulse sequence with + multiple measurements. Args: sequences (list): List of pulse sequences to unroll. @@ -60,7 +63,8 @@ class Settings: sampling_rate: int = int(1e9) """Number of waveform samples supported by the instruments per second.""" relaxation_time: int = int(1e5) - """Time in ns to wait for the qubit to relax to its ground state between shots.""" + """Time in ns to wait for the qubit to relax to its ground state between + shots.""" def fill(self, options: ExecutionParameters): """Use default values for missing execution options.""" @@ -80,26 +84,34 @@ class Platform: name: str """Name of the platform.""" qubits: QubitMap - """Dictionary mapping qubit names to :class:`qibolab.qubits.Qubit` objects.""" + """Dictionary mapping qubit names to :class:`qibolab.qubits.Qubit` + objects.""" pairs: QubitPairMap - """Dictionary mapping sorted tuples of qubit names to :class:`qibolab.qubits.QubitPair` objects.""" + """Dictionary mapping sorted tuples of qubit names to + :class:`qibolab.qubits.QubitPair` objects.""" instruments: InstrumentMap - """Dictionary mapping instrument names to :class:`qibolab.instruments.abstract.Instrument` objects.""" + """Dictionary mapping instrument names to + :class:`qibolab.instruments.abstract.Instrument` objects.""" settings: Settings = field(default_factory=Settings) """Container with default execution settings.""" resonator_type: Optional[str] = None """Type of resonator (2D or 3D) in the used QPU. + Default is 3D for single-qubit chips and 2D for multi-qubit. """ couplers: CouplerMap = field(default_factory=dict) - """Dictionary mapping coupler names to :class:`qibolab.couplers.Coupler` objects.""" + """Dictionary mapping coupler names to :class:`qibolab.couplers.Coupler` + objects.""" is_connected: bool = False """Flag for whether we are connected to the physical instruments.""" two_qubit_native_types: NativeGates = field(default_factory=lambda: NativeGates(0)) - """Types of two qubit native gates. Used by the transpiler.""" + """Types of two qubit native gates. + + Used by the transpiler. + """ topology: nx.Graph = field(default_factory=nx.Graph) """Graph representing the qubit connectivity in the quantum chip.""" @@ -115,7 +127,9 @@ def __post_init__(self): self.two_qubit_native_types = NativeGates.CZ self.topology.add_nodes_from(self.qubits.keys()) - self.topology.add_edges_from([(pair.qubit1.name, pair.qubit2.name) for pair in self.pairs.values()]) + self.topology.add_edges_from( + [(pair.qubit1.name, pair.qubit2.name) for pair in self.pairs.values()] + ) def __str__(self): return self.name @@ -124,7 +138,13 @@ def __str__(self): def nqubits(self) -> int: """Total number of usable qubits in the QPU..""" # TODO: Seperate couplers from qubits (PR #508) - return len([qubit for qubit in self.qubits if not (isinstance(qubit, str) and "c" in qubit)]) + return len( + [ + qubit + for qubit in self.qubits + if not (isinstance(qubit, str) and "c" in qubit) + ] + ) def connect(self): """Connect to all instruments.""" @@ -179,7 +199,9 @@ def _execute(self, sequence, options, **kwargs): for instrument in self.instruments.values(): if isinstance(instrument, Controller): - new_result = instrument.play(self.qubits, self.couplers, sequence, options) + new_result = instrument.play( + self.qubits, self.couplers, sequence, options + ) if isinstance(new_result, dict): result.update(new_result) elif new_result is not None: @@ -188,7 +210,9 @@ def _execute(self, sequence, options, **kwargs): return result - def execute_pulse_sequence(self, sequence: PulseSequence, options: ExecutionParameters, **kwargs): + def execute_pulse_sequence( + self, sequence: PulseSequence, options: ExecutionParameters, **kwargs + ): """ Args: sequence (:class:`qibolab.pulses.PulseSequence`): Pulse sequences to execute. @@ -199,23 +223,32 @@ def execute_pulse_sequence(self, sequence: PulseSequence, options: ExecutionPara """ options = self.settings.fill(options) - time = (sequence.duration + options.relaxation_time) * options.nshots * NS_TO_SEC + time = ( + (sequence.duration + options.relaxation_time) * options.nshots * NS_TO_SEC + ) log.info(f"Minimal execution time (sequence): {time}") return self._execute(sequence, options, **kwargs) @property def _controller(self): - """Controller instrument used for splitting the unrolled sequences to batches. + """Controller instrument used for splitting the unrolled sequences to + batches. Used only by :meth:`qibolab.platform.Platform.execute_pulse_sequences` (unrolling). This method does not support platforms with more than one controller instruments. """ - controllers = [instr for instr in self.instruments.values() if isinstance(instr, Controller)] + controllers = [ + instr + for instr in self.instruments.values() + if isinstance(instr, Controller) + ] assert len(controllers) == 1 return controllers[0] - def execute_pulse_sequences(self, sequences: List[PulseSequence], options: ExecutionParameters, **kwargs): + def execute_pulse_sequences( + self, sequences: List[PulseSequence], options: ExecutionParameters, **kwargs + ): """ Args: sequence (List[:class:`qibolab.pulses.PulseSequence`]): Pulse sequences to execute. @@ -227,11 +260,19 @@ def execute_pulse_sequences(self, sequences: List[PulseSequence], options: Execu options = self.settings.fill(options) duration = sum(seq.duration for seq in sequences) - time = (duration + len(sequences) * options.relaxation_time) * options.nshots * NS_TO_SEC + time = ( + (duration + len(sequences) * options.relaxation_time) + * options.nshots + * NS_TO_SEC + ) log.info(f"Minimal execution time (unrolling): {time}") # find readout pulses - ro_pulses = {pulse.serial: pulse.qubit for sequence in sequences for pulse in sequence.ro_pulses} + ro_pulses = { + pulse.serial: pulse.qubit + for sequence in sequences + for pulse in sequence.ro_pulses + } results = defaultdict(list) for batch in self._controller.split_batches(sequences): @@ -245,8 +286,11 @@ def execute_pulse_sequences(self, sequences: List[PulseSequence], options: Execu return results - def sweep(self, sequence: PulseSequence, options: ExecutionParameters, *sweepers: Sweeper): - """Executes a pulse sequence for different values of sweeped parameters. + def sweep( + self, sequence: PulseSequence, options: ExecutionParameters, *sweepers: Sweeper + ): + """Executes a pulse sequence for different values of sweeped + parameters. Useful for performing chip characterization. @@ -278,7 +322,9 @@ def sweep(self, sequence: PulseSequence, options: ExecutionParameters, *sweepers if options.relaxation_time is None: options = replace(options, relaxation_time=self.settings.relaxation_time) - time = (sequence.duration + options.relaxation_time) * options.nshots * NS_TO_SEC + time = ( + (sequence.duration + options.relaxation_time) * options.nshots * NS_TO_SEC + ) for sweep in sweepers: time *= len(sweep.values) log.info(f"Minimal execution time (sweep): {time}") @@ -286,7 +332,9 @@ def sweep(self, sequence: PulseSequence, options: ExecutionParameters, *sweepers result = {} for instrument in self.instruments.values(): if isinstance(instrument, Controller): - new_result = instrument.sweep(self.qubits, self.couplers, sequence, options, *sweepers) + new_result = instrument.sweep( + self.qubits, self.couplers, sequence, options, *sweepers + ) if isinstance(new_result, dict): result.update(new_result) elif new_result is not None: @@ -299,10 +347,11 @@ def __call__(self, sequence, options): return self.execute_pulse_sequence(sequence, options) def get_qubit(self, qubit): - """Return the name of the physical qubit corresponding to a logical qubit. + """Return the name of the physical qubit corresponding to a logical + qubit. - Temporary fix for the compiler to work for platforms where the qubits - are not named as 0, 1, 2, ... + Temporary fix for the compiler to work for platforms where the + qubits are not named as 0, 1, 2, ... """ try: return self.qubits[qubit].name @@ -310,10 +359,11 @@ def get_qubit(self, qubit): return list(self.qubits.keys())[qubit] def get_coupler(self, coupler): - """Return the name of the physical coupler corresponding to a logical coupler. + """Return the name of the physical coupler corresponding to a logical + coupler. - Temporary fix for the compiler to work for platforms where the couplers - are not named as 0, 1, 2, ... + Temporary fix for the compiler to work for platforms where the + couplers are not named as 0, 1, 2, ... """ try: return self.couplers[coupler].name @@ -419,7 +469,8 @@ def get_lo_readout_frequency(self, qubit): return self.qubits[qubit].readout.lo_frequency def set_lo_twpa_frequency(self, qubit, freq): - """Set frequency of the local oscillator of the TWPA to which the qubit's feedline is connected to. + """Set frequency of the local oscillator of the TWPA to which the + qubit's feedline is connected to. Args: qubit (int): qubit whose local oscillator will be modified. @@ -428,11 +479,13 @@ def set_lo_twpa_frequency(self, qubit, freq): self.qubits[qubit].twpa.lo_frequency = freq def get_lo_twpa_frequency(self, qubit): - """Get frequency of the local oscillator of the TWPA to which the qubit's feedline is connected to in Hz.""" + """Get frequency of the local oscillator of the TWPA to which the + qubit's feedline is connected to in Hz.""" return self.qubits[qubit].twpa.lo_frequency def set_lo_twpa_power(self, qubit, power): - """Set power of the local oscillator of the TWPA to which the qubit's feedline is connected to. + """Set power of the local oscillator of the TWPA to which the qubit's + feedline is connected to. Args: qubit (int): qubit whose local oscillator will be modified. @@ -441,11 +494,13 @@ def set_lo_twpa_power(self, qubit, power): self.qubits[qubit].twpa.lo_power = power def get_lo_twpa_power(self, qubit): - """Get power of the local oscillator of the TWPA to which the qubit's feedline is connected to in dBm.""" + """Get power of the local oscillator of the TWPA to which the qubit's + feedline is connected to in dBm.""" return self.qubits[qubit].twpa.lo_power def set_attenuation(self, qubit, att): - """Set attenuation value. Usefeul for calibration routines such as punchout. + """Set attenuation value. Usefeul for calibration routines such as + punchout. Args: qubit (int): qubit whose attenuation will be modified. @@ -456,11 +511,15 @@ def set_attenuation(self, qubit, att): self.qubits[qubit].readout.attenuation = att def get_attenuation(self, qubit): - """Get attenuation value. Usefeul for calibration routines such as punchout.""" + """Get attenuation value. + + Usefeul for calibration routines such as punchout. + """ return self.qubits[qubit].readout.attenuation def set_gain(self, qubit, gain): - """Set gain value. Usefeul for calibration routines such as Rabi oscillations. + """Set gain value. Usefeul for calibration routines such as Rabi + oscillations. Args: qubit (int): qubit whose attenuation will be modified. @@ -471,7 +530,10 @@ def set_gain(self, qubit, gain): raise_error(NotImplementedError, f"{self.name} does not support gain.") def get_gain(self, qubit): - """Get gain value. Usefeul for calibration routines such as Rabi oscillations.""" + """Get gain value. + + Usefeul for calibration routines such as Rabi oscillations. + """ raise_error(NotImplementedError, f"{self.name} does not support gain.") def set_bias(self, qubit, bias): @@ -488,5 +550,8 @@ def set_bias(self, qubit, bias): self.qubits[qubit].flux.offset = bias def get_bias(self, qubit): - """Get bias value. Usefeul for calibration routines involving flux.""" + """Get bias value. + + Usefeul for calibration routines involving flux. + """ return self.qubits[qubit].flux.offset diff --git a/src/qibolab/pulses.py b/src/qibolab/pulses.py index 9e0ab82adf..962256270c 100644 --- a/src/qibolab/pulses.py +++ b/src/qibolab/pulses.py @@ -14,9 +14,9 @@ class PulseType(Enum): """An enumeration to distinguish different types of pulses. - READOUT pulses triger acquisitions. - DRIVE pulses are used to control qubit states. - FLUX pulses are used to shift the frequency of flux tunable qubits and with it implement two-qubit gates. + READOUT pulses triger acquisitions. DRIVE pulses are used to control + qubit states. FLUX pulses are used to shift the frequency of flux + tunable qubits and with it implement two-qubit gates. """ READOUT = "ro" @@ -53,14 +53,15 @@ def __len__(self): def __eq__(self, other): """Compares two waveforms. - Two waveforms are considered equal if their samples, rounded to `Waveform.DECIMALS` decimal places, - are all equal. + Two waveforms are considered equal if their samples, rounded to + `Waveform.DECIMALS` decimal places, are all equal. """ return self.__hash__() == other.__hash__() def __hash__(self): - """Returns a hash of the array of data, after rounding each sample to `Waveform.DECIMALS` decimal places.""" + """Returns a hash of the array of data, after rounding each sample to + `Waveform.DECIMALS` decimal places.""" return hash(str(np.around(self.data, Waveform.DECIMALS) + 0)) @@ -82,7 +83,9 @@ def plot(self, savefig_filename=None): plt.plot(self.data, c="C0", linestyle="dashed") plt.xlabel("Sample Number") plt.ylabel("Amplitude") - plt.grid(visible=True, which="both", axis="both", color="#888888", linestyle="-") + plt.grid( + visible=True, which="both", axis="both", color="#888888", linestyle="-" + ) plt.suptitle(self.serial) if savefig_filename: plt.savefig(savefig_filename) @@ -105,15 +108,19 @@ def __init__(self, msg=None, *args): class PulseShape(ABC): """Abstract class for pulse shapes. - A PulseShape object is responsible for generating envelope and modulated waveforms from a set - of pulse parameters, its type and a predefined SAMPLING_RATE. PulsShape generates both i (in-phase) - and q (quadrature) components. + A PulseShape object is responsible for generating envelope and + modulated waveforms from a set of pulse parameters, its type and a + predefined SAMPLING_RATE. PulsShape generates both i (in-phase) and + q (quadrature) components. """ SAMPLING_RATE = 1e9 # 1GSaPS """SAMPLING_RATE (int): sampling rate in samples per second (SaPS)""" pulse = None - """pulse (Pulse): the pulse associated with it. Its parameters are used to generate pulse waveforms.""" + """Pulse (Pulse): the pulse associated with it. + + Its parameters are used to generate pulse waveforms. + """ @property @abstractmethod @@ -133,19 +140,22 @@ def envelope_waveforms(self): # -> tuple[Waveform, Waveform]: # pragma: no co @property def modulated_waveform_i(self) -> Waveform: - """The waveform of the i component of the pulse, modulated with its frequency.""" + """The waveform of the i component of the pulse, modulated with its + frequency.""" return self.modulated_waveforms[0] @property def modulated_waveform_q(self) -> Waveform: - """The waveform of the q component of the pulse, modulated with its frequency.""" + """The waveform of the q component of the pulse, modulated with its + frequency.""" return self.modulated_waveforms[1] @property def modulated_waveforms(self): - """A tuple with the i and q waveforms of the pulse, modulated with its frequency.""" + """A tuple with the i and q waveforms of the pulse, modulated with its + frequency.""" if not self.pulse: raise ShapeInitError @@ -158,10 +168,16 @@ def modulated_waveforms(self): num_samples = int(np.rint(pulse.duration / 1e9 * PulseShape.SAMPLING_RATE)) time = np.arange(num_samples) / PulseShape.SAMPLING_RATE global_phase = pulse.global_phase - cosalpha = np.cos(2 * np.pi * pulse._if * time + global_phase + pulse.relative_phase) - sinalpha = np.sin(2 * np.pi * pulse._if * time + global_phase + pulse.relative_phase) + cosalpha = np.cos( + 2 * np.pi * pulse._if * time + global_phase + pulse.relative_phase + ) + sinalpha = np.sin( + 2 * np.pi * pulse._if * time + global_phase + pulse.relative_phase + ) - mod_matrix = np.array([[cosalpha, -sinalpha], [sinalpha, cosalpha]]) / np.sqrt(2) + mod_matrix = np.array([[cosalpha, -sinalpha], [sinalpha, cosalpha]]) / np.sqrt( + 2 + ) (envelope_waveform_i, envelope_waveform_q) = self.envelope_waveforms result = [] @@ -181,7 +197,7 @@ def modulated_waveforms(self): return (modulated_waveform_i, modulated_waveform_q) def __eq__(self, item) -> bool: - """Overloads == operator""" + """Overloads == operator.""" return isinstance(item, type(self)) @@ -197,7 +213,9 @@ def envelope_waveform_i(self) -> Waveform: """The envelope waveform of the i component of the pulse.""" if self.pulse: - num_samples = int(np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE)) + num_samples = int( + np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE) + ) waveform = Waveform(self.pulse.amplitude * np.ones(num_samples)) waveform.serial = f"Envelope_Waveform_I(num_samples = {num_samples}, amplitude = {format(self.pulse.amplitude, '.6f').rstrip('0').rstrip('.')}, shape = {repr(self)})" return waveform @@ -208,7 +226,9 @@ def envelope_waveform_q(self) -> Waveform: """The envelope waveform of the q component of the pulse.""" if self.pulse: - num_samples = int(np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE)) + num_samples = int( + np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE) + ) waveform = Waveform(np.zeros(num_samples)) waveform.serial = f"Envelope_Waveform_Q(num_samples = {num_samples}, amplitude = {format(self.pulse.amplitude, '.6f').rstrip('0').rstrip('.')}, shape = {repr(self)})" return waveform @@ -230,7 +250,6 @@ class Exponential(PulseShape): .. math:: A\frac{\exp\left(-\frac{x}{\text{upsilon}}\right) + g \exp\left(-\frac{x}{\text{tau}}\right)}{1 + g} - """ def __init__(self, tau: float, upsilon: float, g: float = 0.1): @@ -245,11 +264,16 @@ def envelope_waveform_i(self) -> Waveform: """The envelope waveform of the i component of the pulse.""" if self.pulse: - num_samples = int(np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE)) + num_samples = int( + np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE) + ) x = np.arange(0, num_samples, 1) waveform = Waveform( self.pulse.amplitude - * ((np.ones(num_samples) * np.exp(-x / self.upsilon)) + self.g * np.exp(-x / self.tau)) + * ( + (np.ones(num_samples) * np.exp(-x / self.upsilon)) + + self.g * np.exp(-x / self.tau) + ) / (1 + self.g) ) @@ -262,7 +286,9 @@ def envelope_waveform_q(self) -> Waveform: """The envelope waveform of the q component of the pulse.""" if self.pulse: - num_samples = int(np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE)) + num_samples = int( + np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE) + ) waveform = Waveform(np.zeros(num_samples)) waveform.serial = f"Envelope_Waveform_Q(num_samples = {num_samples}, amplitude = {format(self.pulse.amplitude, '.6f').rstrip('0').rstrip('.')}, shape = {repr(self)})" return waveform @@ -289,7 +315,7 @@ def __init__(self, rel_sigma: float): self.rel_sigma: float = float(rel_sigma) def __eq__(self, item) -> bool: - """Overloads == operator""" + """Overloads == operator.""" if super().__eq__(item): return self.rel_sigma == item.rel_sigma return False @@ -299,11 +325,19 @@ def envelope_waveform_i(self) -> Waveform: """The envelope waveform of the i component of the pulse.""" if self.pulse: - num_samples = int(np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE)) + num_samples = int( + np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE) + ) x = np.arange(0, num_samples, 1) waveform = Waveform( self.pulse.amplitude - * np.exp(-(1 / 2) * (((x - (num_samples - 1) / 2) ** 2) / (((num_samples) / self.rel_sigma) ** 2))) + * np.exp( + -(1 / 2) + * ( + ((x - (num_samples - 1) / 2) ** 2) + / (((num_samples) / self.rel_sigma) ** 2) + ) + ) ) waveform.serial = f"Envelope_Waveform_I(num_samples = {num_samples}, amplitude = {format(self.pulse.amplitude, '.6f').rstrip('0').rstrip('.')}, shape = {repr(self)})" return waveform @@ -314,7 +348,9 @@ def envelope_waveform_q(self) -> Waveform: """The envelope waveform of the q component of the pulse.""" if self.pulse: - num_samples = int(np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE)) + num_samples = int( + np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE) + ) waveform = Waveform(np.zeros(num_samples)) waveform.serial = f"Envelope_Waveform_Q(num_samples = {num_samples}, amplitude = {format(self.pulse.amplitude, '.6f').rstrip('0').rstrip('.')}, shape = {repr(self)})" return waveform @@ -325,15 +361,12 @@ def __repr__(self): class Drag(PulseShape): - """ - Derivative Removal by Adiabatic Gate (DRAG) pulse shape. + """Derivative Removal by Adiabatic Gate (DRAG) pulse shape. Args: rel_sigma (float): relative sigma so that the pulse standard deviation (sigma) = duration / rel_sigma beta (float): relative sigma so that the pulse standard deviation (sigma) = duration / rel_sigma .. math:: - - """ def __init__(self, rel_sigma, beta): @@ -343,7 +376,7 @@ def __init__(self, rel_sigma, beta): self.beta = float(beta) def __eq__(self, item) -> bool: - """Overloads == operator""" + """Overloads == operator.""" if super().__eq__(item): return self.rel_sigma == item.rel_sigma and self.beta == item.beta return False @@ -353,10 +386,16 @@ def envelope_waveform_i(self) -> Waveform: """The envelope waveform of the i component of the pulse.""" if self.pulse: - num_samples = int(np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE)) + num_samples = int( + np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE) + ) x = np.arange(0, num_samples, 1) i = self.pulse.amplitude * np.exp( - -(1 / 2) * (((x - (num_samples - 1) / 2) ** 2) / (((num_samples) / self.rel_sigma) ** 2)) + -(1 / 2) + * ( + ((x - (num_samples - 1) / 2) ** 2) + / (((num_samples) / self.rel_sigma) ** 2) + ) ) waveform = Waveform(i) waveform.serial = f"Envelope_Waveform_I(num_samples = {num_samples}, amplitude = {format(self.pulse.amplitude, '.6f').rstrip('0').rstrip('.')}, shape = {repr(self)})" @@ -368,10 +407,16 @@ def envelope_waveform_q(self) -> Waveform: """The envelope waveform of the q component of the pulse.""" if self.pulse: - num_samples = int(np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE)) + num_samples = int( + np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE) + ) x = np.arange(0, num_samples, 1) i = self.pulse.amplitude * np.exp( - -(1 / 2) * (((x - (num_samples - 1) / 2) ** 2) / (((num_samples) / self.rel_sigma) ** 2)) + -(1 / 2) + * ( + ((x - (num_samples - 1) / 2) ** 2) + / (((num_samples) / self.rel_sigma) ** 2) + ) ) q = ( self.beta @@ -406,9 +451,13 @@ def __init__(self, b, a, target: PulseShape): # Check len(a) = len(b) = 2 def __eq__(self, item) -> bool: - """Overloads == operator""" + """Overloads == operator.""" if super().__eq__(item): - return self.target == item.target and (self.a == item.a).all() and (self.b == item.b).all() + return ( + self.target == item.target + and (self.a == item.a).all() + and (self.b == item.b).all() + ) return False @property @@ -425,7 +474,9 @@ def envelope_waveform_i(self) -> Waveform: """The envelope waveform of the i component of the pulse.""" if self.pulse: - num_samples = int(np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE)) + num_samples = int( + np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE) + ) self.a = self.a / self.a[0] gain = np.sum(self.b) / np.sum(self.a) if not gain == 0: @@ -444,7 +495,9 @@ def envelope_waveform_q(self) -> Waveform: """The envelope waveform of the q component of the pulse.""" if self.pulse: - num_samples = int(np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE)) + num_samples = int( + np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE) + ) self.a = self.a / self.a[0] gain = np.sum(self.b) / np.sum(self.a) if not gain == 0: @@ -465,11 +518,10 @@ def __repr__(self): class SNZ(PulseShape): - """ - Sudden variant Net Zero. + """Sudden variant Net Zero. + https://arxiv.org/abs/2008.07411 (Supplementary materials: FIG. S1.) - """ def __init__(self, t_idling, b_amplitude=None): @@ -479,9 +531,11 @@ def __init__(self, t_idling, b_amplitude=None): self.b_amplitude = b_amplitude def __eq__(self, item) -> bool: - """Overloads == operator""" + """Overloads == operator.""" if super().__eq__(item): - return self.t_idling == item.t_idling and self.b_amplitude == item.b_amplitude + return ( + self.t_idling == item.t_idling and self.b_amplitude == item.b_amplitude + ) return False @property @@ -490,12 +544,18 @@ def envelope_waveform_i(self) -> Waveform: if self.pulse: if self.t_idling > self.pulse.duration: - raise ValueError(f"Cannot put idling time {self.t_idling} higher than duration {self.pulse.duration}.") + raise ValueError( + f"Cannot put idling time {self.t_idling} higher than duration {self.pulse.duration}." + ) if self.b_amplitude is None: self.b_amplitude = self.pulse.amplitude / 2 - num_samples = int(np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE)) + num_samples = int( + np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE) + ) half_pulse_duration = (self.pulse.duration - self.t_idling) / 2 - half_flux_pulse_samples = int(np.rint(num_samples * half_pulse_duration / self.pulse.duration)) + half_flux_pulse_samples = int( + np.rint(num_samples * half_pulse_duration / self.pulse.duration) + ) idling_samples = num_samples - 2 * half_flux_pulse_samples waveform = Waveform( np.concatenate( @@ -517,7 +577,9 @@ def envelope_waveform_q(self) -> Waveform: """The envelope waveform of the q component of the pulse.""" if self.pulse: - num_samples = int(np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE)) + num_samples = int( + np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE) + ) waveform = Waveform(np.zeros(num_samples)) waveform.serial = f"Envelope_Waveform_Q(num_samples = {num_samples}, amplitude = {format(self.pulse.amplitude, '.6f').rstrip('0').rstrip('.')}, shape = {repr(self)})" return waveform @@ -528,7 +590,7 @@ def __repr__(self): class eCap(PulseShape): - r"""eCap pulse shape. + r"""ECap pulse shape. Args: alpha (float): @@ -537,7 +599,6 @@ class eCap(PulseShape): e_{\cap(t,\alpha)} &=& A[1 + \tanh(\alpha t/t_\theta)][1 + \tanh(\alpha (1 - t/t_\theta))]\\ &\times& [1 + \tanh(\alpha/2)]^{-2} - """ def __init__(self, alpha: float): @@ -546,7 +607,7 @@ def __init__(self, alpha: float): self.alpha: float = float(alpha) def __eq__(self, item) -> bool: - """Overloads == operator""" + """Overloads == operator.""" if super().__eq__(item): return self.alpha == item.alpha return False @@ -598,7 +659,9 @@ def envelope_waveform_i(self) -> Waveform: if self.pulse: if self.pulse.duration != len(self.envelope_i): raise ValueError("Length of envelope_i must be equal to pulse duration") - num_samples = int(np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE)) + num_samples = int( + np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE) + ) waveform = Waveform(self.envelope_i * self.pulse.amplitude) waveform.serial = f"Envelope_Waveform_I(num_samples = {num_samples}, amplitude = {format(self.pulse.amplitude, '.6f').rstrip('0').rstrip('.')}, shape = {repr(self)})" @@ -612,7 +675,9 @@ def envelope_waveform_q(self) -> Waveform: if self.pulse: if self.pulse.duration != len(self.envelope_q): raise ValueError("Length of envelope_q must be equal to pulse duration") - num_samples = int(np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE)) + num_samples = int( + np.rint(self.pulse.duration / 1e9 * PulseShape.SAMPLING_RATE) + ) waveform = Waveform(self.envelope_q * self.pulse.amplitude) waveform.serial = f"Envelope_Waveform_Q(num_samples = {num_samples}, amplitude = {format(self.pulse.amplitude, '.6f').rstrip('0').rstrip('.')}, shape = {repr(self)})" @@ -736,7 +801,9 @@ def start(self, value): """ if not isinstance(value, (se_int, int, np.integer, float)): - raise TypeError(f"start argument type should be intSymbolicExpression or int, got {type(value).__name__}") + raise TypeError( + f"start argument type should be intSymbolicExpression or int, got {type(value).__name__}" + ) if not value >= 0: raise ValueError(f"start argument must be >= 0, got {value}") @@ -760,7 +827,9 @@ def start(self, value): or isinstance(self._duration, se_int) or isinstance(self._finish, se_int) ): - self._finish = se_int(self._start + self._duration)["_p" + str(self._id) + "_finish"] + self._finish = se_int(self._start + self._duration)[ + "_p" + str(self._id) + "_finish" + ] else: self._finish = self._start + self._duration @@ -805,7 +874,9 @@ def duration(self, value): or isinstance(self._duration, se_int) or isinstance(self._finish, se_int) ): - self._finish = se_int(self._start + self._duration)["_p" + str(self._id) + "_finish"] + self._finish = se_int(self._start + self._duration)[ + "_p" + str(self._id) + "_finish" + ] else: self._finish = self._start + self._duration @@ -863,7 +934,9 @@ def amplitude(self, value): if isinstance(value, int): value = float(value) if not isinstance(value, (float, np.floating)): - raise TypeError(f"amplitude argument type should be float, got {type(value).__name__}") + raise TypeError( + f"amplitude argument type should be float, got {type(value).__name__}" + ) if not ((value >= -1) & (value <= 1)): raise ValueError(f"amplitude argument must be >= -1 & <= 1, got {value}") if isinstance(value, np.floating): @@ -886,7 +959,9 @@ def frequency(self, value): """ if not isinstance(value, (int, float, np.integer, np.floating)): - raise TypeError(f"frequency argument type should be int, got {type(value).__name__}") + raise TypeError( + f"frequency argument type should be int, got {type(value).__name__}" + ) if isinstance(value, (float, np.integer, np.floating)): self._frequency = int(value) elif isinstance(value, int): @@ -896,7 +971,8 @@ def frequency(self, value): def global_phase(self): """Returns the global phase of the pulse, in radians. - This phase is calculated from the pulse start time and frequency as `2 * pi * frequency * start`. + This phase is calculated from the pulse start time and frequency + as `2 * pi * frequency * start`. """ # pulse start, duration and finish are in ns @@ -917,7 +993,9 @@ def relative_phase(self, value): """ if not isinstance(value, (int, float, np.integer, np.floating)): - raise TypeError(f"relative_phase argument type should be int or float, got {type(value).__name__}") + raise TypeError( + f"relative_phase argument type should be int or float, got {type(value).__name__}" + ) if isinstance(value, (int, np.integer, np.floating)): self._relative_phase = float(value) elif isinstance(value, float): @@ -927,7 +1005,8 @@ def relative_phase(self, value): def phase(self) -> float: """Returns the total phase of the pulse, in radians. - The total phase is computed as the sum of the global and relative phases. + The total phase is computed as the sum of the global and + relative phases. """ return 2 * np.pi * self._frequency * self.start / 1e9 + self._relative_phase @@ -946,7 +1025,9 @@ def shape(self, value): """ if not isinstance(value, (PulseShape, str)): - raise TypeError(f"shape argument type should be PulseShape or str, got {type(value).__name__}") + raise TypeError( + f"shape argument type should be PulseShape or str, got {type(value).__name__}" + ) if isinstance(value, PulseShape): self._shape = value elif isinstance(value, str): @@ -964,9 +1045,10 @@ def shape(self, value): def channel(self): """Returns the channel on which the pulse should be played. - When a sequence of pulses is sent to the platform for execution, each pulse is sent to the instrument - responsible for playing pulses the pulse channel. The connection of instruments with channels is defined - in the platform runcard. + When a sequence of pulses is sent to the platform for execution, + each pulse is sent to the instrument responsible for playing + pulses the pulse channel. The connection of instruments with + channels is defined in the platform runcard. """ # def channel(self) -> int | str: @@ -981,7 +1063,9 @@ def channel(self, value): """ if not isinstance(value, (int, str)): - raise TypeError(f"channel argument type should be int or str, got {type(value).__name__}") + raise TypeError( + f"channel argument type should be int or str, got {type(value).__name__}" + ) self._channel = value @property @@ -1003,7 +1087,9 @@ def type(self, value): elif isinstance(value, str): self._type = PulseType(value) else: - raise TypeError(f"type argument should be PulseType or str, got {type(value).__name__}") + raise TypeError( + f"type argument should be PulseType or str, got {type(value).__name__}" + ) @property def qubit(self): @@ -1021,7 +1107,9 @@ def qubit(self, value): """ if not isinstance(value, (int, str)): - raise TypeError(f"qubit argument type should be int or str, got {type(value).__name__}") + raise TypeError( + f"qubit argument type should be int or str, got {type(value).__name__}" + ) self._qubit = value @property @@ -1054,19 +1142,22 @@ def envelope_waveforms(self): # -> tuple[Waveform, Waveform]: @property def modulated_waveform_i(self) -> Waveform: - """The waveform of the i component of the pulse, modulated with its frequency.""" + """The waveform of the i component of the pulse, modulated with its + frequency.""" return self._shape.modulated_waveform_i @property def modulated_waveform_q(self) -> Waveform: - """The waveform of the q component of the pulse, modulated with its frequency.""" + """The waveform of the q component of the pulse, modulated with its + frequency.""" return self._shape.modulated_waveform_q @property def modulated_waveforms(self): # -> tuple[Waveform, Waveform]: - """A tuple with the i and q waveforms of the pulse, modulated with its frequency.""" + """A tuple with the i and q waveforms of the pulse, modulated with its + frequency.""" return self._shape.modulated_waveforms @@ -1161,7 +1252,7 @@ def shallow_copy(self): # -> Pulse: ) def is_equal_ignoring_start(self, item) -> bool: - """Check if two pulses are equal ignoring start time""" + """Check if two pulses are equal ignoring start time.""" return ( self.duration == item.duration and self.amplitude == item.amplitude @@ -1202,13 +1293,21 @@ def plot(self, savefig_filename=None): c="C1", linestyle="dashed", ) - ax1.plot(time, self.shape.modulated_waveform_i.data, label="modulated i", c="C0") - ax1.plot(time, self.shape.modulated_waveform_q.data, label="modulated q", c="C1") - ax1.plot(time, -self.shape.envelope_waveform_i.data, c="silver", linestyle="dashed") + ax1.plot( + time, self.shape.modulated_waveform_i.data, label="modulated i", c="C0" + ) + ax1.plot( + time, self.shape.modulated_waveform_q.data, label="modulated q", c="C1" + ) + ax1.plot( + time, -self.shape.envelope_waveform_i.data, c="silver", linestyle="dashed" + ) ax1.set_xlabel("Time [ns]") ax1.set_ylabel("Amplitude") - ax1.grid(visible=True, which="both", axis="both", color="#888888", linestyle="-") + ax1.grid( + visible=True, which="both", axis="both", color="#888888", linestyle="-" + ) ax1.axis([self.start, self.finish, -1, 1]) ax1.legend() @@ -1249,7 +1348,9 @@ def plot(self, savefig_filename=None): linestyle="dashed", ) - ax2.grid(visible=True, which="both", axis="both", color="#888888", linestyle="-") + ax2.grid( + visible=True, which="both", axis="both", color="#888888", linestyle="-" + ) ax2.legend() # ax2.axis([ -1, 1, -1, 1]) ax2.axis("equal") @@ -1264,7 +1365,8 @@ def plot(self, savefig_filename=None): class ReadoutPulse(Pulse): """Describes a readout pulse. - See :class:`qibolab.pulses.Pulse` for argument desciption. + See + :class: `qibolab.pulses.Pulse` for argument desciption. """ def __init__( @@ -1320,7 +1422,8 @@ def copy(self): # -> Pulse|ReadoutPulse|DrivePulse|FluxPulse: class DrivePulse(Pulse): """Describes a qubit drive pulse. - See :class:`qibolab.pulses.Pulse` for argument desciption. + See + :class: `qibolab.pulses.Pulse` for argument desciption. """ def __init__( @@ -1356,8 +1459,9 @@ def serial(self): class FluxPulse(Pulse): """Describes a qubit flux pulse. - Flux pulses have frequency and relative_phase equal to 0. Their i and q components are equal. - See :class:`qibolab.pulses.Pulse` for argument desciption. + Flux pulses have frequency and relative_phase equal to 0. Their i + and q components are equal. See + :class: `qibolab.pulses.Pulse` for argument desciption. """ PULSE_TYPE = PulseType.FLUX @@ -1408,7 +1512,9 @@ def serial(self): class CouplerFluxPulse(FluxPulse): """Describes a coupler flux pulse. - See :class:`qibolab.pulses.FluxPulse` for argument desciption. + + See + :class: `qibolab.pulses.FluxPulse` for argument desciption. """ PULSE_TYPE = PulseType.COUPLERFLUX @@ -1418,7 +1524,9 @@ class SplitPulse(Pulse): """A supporting class to represent sections or slices of a pulse.""" # TODO: Since this class is only required by qblox drivers, move to qblox.py - def __init__(self, pulse: Pulse, window_start: int = None, window_finish: int = None): + def __init__( + self, pulse: Pulse, window_start: int = None, window_finish: int = None + ): super().__init__( pulse.start, pulse.duration, @@ -1446,9 +1554,13 @@ def window_start(self): @window_start.setter def window_start(self, value: int): if not isinstance(value, int): - raise TypeError(f"window_start argument type should be int, got {type(value).__name__}") + raise TypeError( + f"window_start argument type should be int, got {type(value).__name__}" + ) if value < self.start: - raise ValueError("window_start should be >= pulse start ({self._start}), got {value}") + raise ValueError( + "window_start should be >= pulse start ({self._start}), got {value}" + ) self._window_start = value @property @@ -1458,9 +1570,13 @@ def window_finish(self): @window_finish.setter def window_finish(self, value: int): if not isinstance(value, int): - raise TypeError(f"window_start argument type should be int, got {type(value).__name__}") + raise TypeError( + f"window_start argument type should be int, got {type(value).__name__}" + ) if value > self.finish: - raise ValueError("window_finish should be <= pulse finish ({self._finish}), got {value}") + raise ValueError( + "window_finish should be <= pulse finish ({self._finish}), got {value}" + ) self._window_finish = value @property @@ -1474,7 +1590,9 @@ def serial(self): @property def envelope_waveform_i(self) -> Waveform: waveform = Waveform( - self._shape.envelope_waveform_i.data[self._window_start - self.start : self._window_finish - self.start] + self._shape.envelope_waveform_i.data[ + self._window_start - self.start : self._window_finish - self.start + ] ) waveform.serial = ( self._shape.envelope_waveform_i.serial @@ -1485,7 +1603,9 @@ def envelope_waveform_i(self) -> Waveform: @property def envelope_waveform_q(self) -> Waveform: waveform = Waveform( - self._shape.modulated_waveform_i.data[self._window_start - self.start : self._window_finish - self.start] + self._shape.modulated_waveform_i.data[ + self._window_start - self.start : self._window_finish - self.start + ] ) waveform.serial = ( self._shape.modulated_waveform_i.serial @@ -1500,7 +1620,9 @@ def envelope_waveforms(self): # -> tuple[Waveform, Waveform]: @property def modulated_waveform_i(self) -> Waveform: waveform = Waveform( - self._shape.envelope_waveform_q.data[self._window_start - self.start : self._window_finish - self.start] + self._shape.envelope_waveform_q.data[ + self._window_start - self.start : self._window_finish - self.start + ] ) waveform.serial = ( self._shape.envelope_waveform_q.serial @@ -1511,7 +1633,9 @@ def modulated_waveform_i(self) -> Waveform: @property def modulated_waveform_q(self) -> Waveform: waveform = Waveform( - self._shape.modulated_waveform_q.data[self._window_start - self.start : self._window_finish - self.start] + self._shape.modulated_waveform_q.data[ + self._window_start - self.start : self._window_finish - self.start + ] ) waveform.serial = ( self._shape.modulated_waveform_q.serial @@ -1529,7 +1653,9 @@ def plot(self, savefig_filename=None): idx = slice(self._window_start - self.start, self._window_finish - self.start) num_samples = len(self.shape.envelope_waveform_i.data[idx]) - time = self.window_start + np.arange(num_samples) / PulseShape.SAMPLING_RATE * 1e9 + time = ( + self.window_start + np.arange(num_samples) / PulseShape.SAMPLING_RATE * 1e9 + ) fig = plt.figure(figsize=(14, 5), dpi=200) gs = gridspec.GridSpec(ncols=2, nrows=1, width_ratios=[2, 1]) @@ -1569,7 +1695,9 @@ def plot(self, savefig_filename=None): ax1.set_xlabel("Time [ns]") ax1.set_ylabel("Amplitude") - ax1.grid(visible=True, which="both", axis="both", color="#888888", linestyle="-") + ax1.grid( + visible=True, which="both", axis="both", color="#888888", linestyle="-" + ) ax1.axis([self.window_start, self._window_finish, -1, 1]) ax1.legend() @@ -1593,7 +1721,9 @@ def plot(self, savefig_filename=None): linestyle="dashed", ) - ax2.grid(visible=True, which="both", axis="both", color="#888888", linestyle="-") + ax2.grid( + visible=True, which="both", axis="both", color="#888888", linestyle="-" + ) ax2.legend() # ax2.axis([ -1, 1, -1, 1]) ax2.axis("equal") @@ -1615,15 +1745,17 @@ class PulseConstructor(Enum): class PulseSequence: """A collection of scheduled pulses. - A quantum circuit can be translated into a set of scheduled pulses that implement the circuit gates. - This class contains many supporting fuctions to facilitate the creation and manipulation of - these collections of pulses. - None of the methods of PulseSequence modify any of the properties of its pulses. + A quantum circuit can be translated into a set of scheduled pulses + that implement the circuit gates. This class contains many + supporting fuctions to facilitate the creation and manipulation of + these collections of pulses. None of the methods of PulseSequence + modify any of the properties of its pulses. """ def __init__(self, *pulses): self.pulses = [] #: list[Pulse] = [] - """pulses (list): a list containing the pulses, ordered by their channel and start times.""" + """Pulses (list): a list containing the pulses, ordered by their + channel and start times.""" self.add(*pulses) def __len__(self): @@ -1686,7 +1818,9 @@ def __iadd__(self, other): elif isinstance(other, Pulse): self.add(other) else: - raise TypeError(f"Expected PulseSequence or Pulse; got {type(other).__name__}") + raise TypeError( + f"Expected PulseSequence or Pulse; got {type(other).__name__}" + ) return self def __mul__(self, n): @@ -1720,7 +1854,8 @@ def count(self): return len(self.pulses) def add(self, *items): - """Adds pulses to the sequence and sorts them by channel and start time.""" + """Adds pulses to the sequence and sorts them by channel and start + time.""" for item in items: if isinstance(item, Pulse): @@ -1738,7 +1873,8 @@ def index(self, pulse): return self.pulses.index(pulse) def pop(self, index=-1): - """Returns the pulse with the index provided and removes it from the sequence.""" + """Returns the pulse with the index provided and removes it from the + sequence.""" return self.pulses.pop(index) @@ -1756,7 +1892,8 @@ def clear(self): def shallow_copy(self): """Returns a shallow copy of the sequence. - It returns a new PulseSequence object with references to the same Pulse objects. + It returns a new PulseSequence object with references to the + same Pulse objects. """ return PulseSequence(*self.pulses) @@ -1764,7 +1901,8 @@ def shallow_copy(self): def copy(self): """Returns a deep copy of the sequence. - It returns a new PulseSequence with replicates of each of the pulses contained in the original sequence. + It returns a new PulseSequence with replicates of each of the + pulses contained in the original sequence. """ return PulseSequence(*[pulse.copy() for pulse in self.pulses]) @@ -1781,7 +1919,8 @@ def ro_pulses(self): @property def qd_pulses(self): - """Returns a new PulseSequence containing only its qubit drive pulses.""" + """Returns a new PulseSequence containing only its qubit drive + pulses.""" new_pc = PulseSequence() for pulse in self.pulses: @@ -1791,7 +1930,8 @@ def qd_pulses(self): @property def qf_pulses(self): - """Returns a new PulseSequence containing only its qubit flux pulses.""" + """Returns a new PulseSequence containing only its qubit flux + pulses.""" new_pc = PulseSequence() for pulse in self.pulses: @@ -1801,7 +1941,8 @@ def qf_pulses(self): @property def cf_pulses(self): - """Returns a new PulseSequence containing only its coupler flux pulses.""" + """Returns a new PulseSequence containing only its coupler flux + pulses.""" new_pc = PulseSequence() for pulse in self.pulses: @@ -1810,7 +1951,8 @@ def cf_pulses(self): return new_pc def get_channel_pulses(self, *channels): - """Returns a new PulseSequence containing only the pulses on a specific set of channels.""" + """Returns a new PulseSequence containing only the pulses on a specific + set of channels.""" new_pc = PulseSequence() for pulse in self.pulses: @@ -1819,7 +1961,8 @@ def get_channel_pulses(self, *channels): return new_pc def get_qubit_pulses(self, *qubits): - """Returns a new PulseSequence containing only the pulses on a specific set of qubits.""" + """Returns a new PulseSequence containing only the pulses on a specific + set of qubits.""" new_pc = PulseSequence() for pulse in self.pulses: @@ -1829,7 +1972,8 @@ def get_qubit_pulses(self, *qubits): return new_pc def coupler_pulses(self, *couplers): - """Returns a new PulseSequence containing only the pulses on a specific set of couplers.""" + """Returns a new PulseSequence containing only the pulses on a specific + set of couplers.""" new_pc = PulseSequence() for pulse in self.pulses: @@ -1872,7 +2016,8 @@ def duration(self) -> int: @property def channels(self) -> list: - """Returns list containing the channels used by the pulses in the sequence.""" + """Returns list containing the channels used by the pulses in the + sequence.""" channels = [] for pulse in self.pulses: @@ -1883,7 +2028,8 @@ def channels(self) -> list: @property def qubits(self) -> list: - """Returns list containing the qubits associated with the pulses in the sequence.""" + """Returns list containing the qubits associated with the pulses in the + sequence.""" qubits = [] for pulse in self.pulses: @@ -1893,7 +2039,8 @@ def qubits(self) -> list: return qubits def get_pulse_overlaps(self): # -> dict((int,int): PulseSequence): - """Returns a dictionary of slices of time (tuples with start and finish times) where pulses overlap.""" + """Returns a dictionary of slices of time (tuples with start and finish + times) where pulses overlap.""" times = [] for pulse in self.pulses: @@ -1912,7 +2059,8 @@ def get_pulse_overlaps(self): # -> dict((int,int): PulseSequence): return overlaps def separate_overlapping_pulses(self): # -> dict((int,int): PulseSequence): - """Separates a sequence of overlapping pulses into a list of non-overlapping sequences.""" + """Separates a sequence of overlapping pulses into a list of non- + overlapping sequences.""" # This routine separates the pulses of a sequence into non-overlapping sets # but it does not check if the frequencies of the pulses within a set have the same frequency @@ -1923,7 +2071,10 @@ def separate_overlapping_pulses(self): # -> dict((int,int): PulseSequence): for ps in separated_pulses: overlaps = False for existing_pulse in ps: - if new_pulse.start < existing_pulse.finish and new_pulse.finish > existing_pulse.start: + if ( + new_pulse.start < existing_pulse.finish + and new_pulse.finish > existing_pulse.start + ): overlaps = True break if not overlaps: @@ -1974,10 +2125,24 @@ def plot(self, savefig_filename=None): ax.axis([0, self.finish, -1, 1]) for pulse in channel_pulses: if isinstance(pulse, SplitPulse): - idx = slice(pulse.window_start - pulse.start, pulse.window_finish - pulse.start) - num_samples = len(pulse.shape.modulated_waveform_i.data[idx]) - time = pulse.window_start + np.arange(num_samples) / PulseShape.SAMPLING_RATE * 1e9 - ax.plot(time, pulse.shape.modulated_waveform_q.data[idx], c="lightgrey") + idx = slice( + pulse.window_start - pulse.start, + pulse.window_finish - pulse.start, + ) + num_samples = len( + pulse.shape.modulated_waveform_i.data[idx] + ) + time = ( + pulse.window_start + + np.arange(num_samples) + / PulseShape.SAMPLING_RATE + * 1e9 + ) + ax.plot( + time, + pulse.shape.modulated_waveform_q.data[idx], + c="lightgrey", + ) ax.plot( time, pulse.shape.modulated_waveform_i.data[idx], @@ -1995,7 +2160,12 @@ def plot(self, savefig_filename=None): ) else: num_samples = len(pulse.shape.modulated_waveform_i) - time = pulse.start + np.arange(num_samples) / PulseShape.SAMPLING_RATE * 1e9 + time = ( + pulse.start + + np.arange(num_samples) + / PulseShape.SAMPLING_RATE + * 1e9 + ) ax.plot( time, pulse.shape.modulated_waveform_q.data, diff --git a/src/qibolab/qubits.py b/src/qibolab/qubits.py index beb5a2b3f9..6dbf9945fd 100644 --- a/src/qibolab/qubits.py +++ b/src/qibolab/qubits.py @@ -11,6 +11,7 @@ CHANNEL_NAMES = ("readout", "feedback", "drive", "flux", "twpa") """Names of channels that belong to a qubit. + Not all channels are required to operate a qubit. """ EXCLUDED_FIELDS = CHANNEL_NAMES + ("name", "native_gates") @@ -41,22 +42,23 @@ class Qubit: bare_resonator_frequency: int = 0 readout_frequency: int = 0 - """ Readout dressed frequency""" + """Readout dressed frequency.""" drive_frequency: int = 0 anharmonicity: int = 0 sweetspot: float = 0.0 flux_to_bias: float = 0.0 asymmetry: float = 0.0 bare_resonator_frequency_sweetspot: float = 0.0 - """Bare resonator frequency at sweetspot""" + """Bare resonator frequency at sweetspot.""" ssf_brf: float = 0.0 - """Estimated sweetspot qubit frequency divided by the bare_resonator_frequency""" + """Estimated sweetspot qubit frequency divided by the + bare_resonator_frequency.""" Ec: float = 0.0 - """Readout Charge Energy""" + """Readout Charge Energy.""" Ej: float = 0.0 - """Readout Josephson Energy""" + """Readout Josephson Energy.""" g: float = 0.0 - """Readout coupling""" + """Readout coupling.""" assignment_fidelity: float = 0.0 """Assignment fidelity.""" readout_fidelity: float = 0.0 @@ -101,7 +103,11 @@ def channels(self): @property def characterization(self): """Dictionary containing characterization parameters.""" - return {fld.name: getattr(self, fld.name) for fld in fields(self) if fld.name not in EXCLUDED_FIELDS} + return { + fld.name: getattr(self, fld.name) + for fld in fields(self) + if fld.name not in EXCLUDED_FIELDS + } QubitPairId = Tuple[QubitId, QubitId] @@ -110,7 +116,8 @@ def characterization(self): @dataclass class QubitPair: - """Data structure for holding the native two-qubit gates acting on a pair of qubits. + """Data structure for holding the native two-qubit gates acting on a pair + of qubits. This is needed for symmetry to the single-qubit gates which are storred in the :class:`qibolab.platforms.abstract.Qubit`. diff --git a/src/qibolab/result.py b/src/qibolab/result.py index d9f378a092..1ee07539a6 100644 --- a/src/qibolab/result.py +++ b/src/qibolab/result.py @@ -6,12 +6,12 @@ class IntegratedResults: - """ - Data structure to deal with the output of - :func:`qibolab.platforms.abstract.AbstractPlatform.execute_pulse_sequence` + """Data structure to deal with the output of :func:`qibolab.platforms.abstr + act.AbstractPlatform.execute_pulse_sequence` :func:`qibolab.platforms.abstract.AbstractPlatform.sweep` - Associated with AcquisitionType.INTEGRATION and AveragingMode.SINGLESHOT + Associated with AcquisitionType.INTEGRATION and + AveragingMode.SINGLESHOT """ def __init__(self, data: np.ndarray): @@ -53,16 +53,15 @@ def serialize(self): @property def average(self): - """Perform average over i and q""" + """Perform average over i and q.""" average_data = np.mean(self.voltage, axis=0) std_data = np.std(self.voltage, axis=0, ddof=1) / np.sqrt(self.voltage.shape[0]) return AveragedIntegratedResults(average_data, std_data) class AveragedIntegratedResults(IntegratedResults): - """ - Data structure to deal with the output of - :func:`qibolab.platforms.abstract.AbstractPlatform.execute_pulse_sequence` + """Data structure to deal with the output of :func:`qibolab.platforms.abstr + act.AbstractPlatform.execute_pulse_sequence` :func:`qibolab.platforms.abstract.AbstractPlatform.sweep` Associated with AcquisitionType.INTEGRATION and AveragingMode.CYCLIC @@ -80,20 +79,18 @@ def __add__(self, data): class RawWaveformResults(IntegratedResults): - """ - Data structure to deal with the output of - :func:`qibolab.platforms.abstract.AbstractPlatform.execute_pulse_sequence` + """Data structure to deal with the output of :func:`qibolab.platforms.abstr + act.AbstractPlatform.execute_pulse_sequence` :func:`qibolab.platforms.abstract.AbstractPlatform.sweep` - Associated with AcquisitionType.RAW and AveragingMode.SINGLESHOT - may also be used to store the integration weights ? + Associated with AcquisitionType.RAW and AveragingMode.SINGLESHOT may + also be used to store the integration weights ? """ class AveragedRawWaveformResults(AveragedIntegratedResults): - """ - Data structure to deal with the output of - :func:`qibolab.platforms.abstract.AbstractPlatform.execute_pulse_sequence` + """Data structure to deal with the output of :func:`qibolab.platforms.abstr + act.AbstractPlatform.execute_pulse_sequence` :func:`qibolab.platforms.abstract.AbstractPlatform.sweep` Associated with AcquisitionType.RAW and AveragingMode.CYCLIC @@ -102,12 +99,12 @@ class AveragedRawWaveformResults(AveragedIntegratedResults): class SampleResults: - """ - Data structure to deal with the output of - :func:`qibolab.platforms.abstract.AbstractPlatform.execute_pulse_sequence` + """Data structure to deal with the output of :func:`qibolab.platforms.abstr + act.AbstractPlatform.execute_pulse_sequence` :func:`qibolab.platforms.abstract.AbstractPlatform.sweep` - Associated with AcquisitionType.DISCRIMINATION and AveragingMode.SINGLESHOT + Associated with AcquisitionType.DISCRIMINATION and + AveragingMode.SINGLESHOT """ def __init__(self, data: np.ndarray): @@ -118,7 +115,8 @@ def __add__(self, data): @lru_cache def probability(self, state=0): - """Returns the statistical frequency of the specified state (0 or 1).""" + """Returns the statistical frequency of the specified state (0 or + 1).""" return abs(1 - state - np.mean(self.samples, axis=0)) @property @@ -131,16 +129,15 @@ def serialize(self): @property def average(self): - """Perform samples average""" + """Perform samples average.""" average = self.probability(1) std = np.std(self.samples, axis=0, ddof=1) / np.sqrt(self.samples.shape[0]) return AveragedSampleResults(average, self.samples, std=std) class AveragedSampleResults(SampleResults): - """ - Data structure to deal with the output of - :func:`qibolab.platforms.abstract.AbstractPlatform.execute_pulse_sequence` + """Data structure to deal with the output of :func:`qibolab.platforms.abstr + act.AbstractPlatform.execute_pulse_sequence` :func:`qibolab.platforms.abstract.AbstractPlatform.sweep` Associated with AcquisitionType.DISCRIMINATION and AveragingMode.CYCLIC @@ -148,7 +145,10 @@ class AveragedSampleResults(SampleResults): """ def __init__( - self, statistical_frequency: np.ndarray, samples: np.ndarray = np.array([]), std: np.ndarray = np.array([]) + self, + statistical_frequency: np.ndarray, + samples: np.ndarray = np.array([]), + std: np.ndarray = np.array([]), ): super().__init__(samples) self.statistical_frequency: npt.NDArray[np.float64] = statistical_frequency @@ -156,11 +156,14 @@ def __init__( def __add__(self, data): new_res = super().__add__(data) - new_res.statistical_frequency = np.append(self.statistical_frequency, data.statistical_frequency) + new_res.statistical_frequency = np.append( + self.statistical_frequency, data.statistical_frequency + ) new_res.std = np.append(self.std, data.std) return new_res @lru_cache def probability(self, state=0): - """Returns the statistical frequency of the specified state (0 or 1).""" + """Returns the statistical frequency of the specified state (0 or + 1).""" return abs(1 - state - self.statistical_frequency) diff --git a/src/qibolab/serialize.py b/src/qibolab/serialize.py index 0d8b27f7db..7da9809148 100644 --- a/src/qibolab/serialize.py +++ b/src/qibolab/serialize.py @@ -33,14 +33,21 @@ def load_settings(runcard: dict) -> Settings: return Settings(**runcard["settings"]) -def load_qubits(runcard: dict, extras_folder: Path = None) -> Tuple[QubitMap, CouplerMap, QubitPairMap]: +def load_qubits( + runcard: dict, extras_folder: Path = None +) -> Tuple[QubitMap, CouplerMap, QubitPairMap]: """Load qubits and pairs from the runcard. - Uses the native gate and characterization sections of the runcard - to parse the :class:`qibolab.qubits.Qubit` and :class:`qibolab.qubits.QubitPair` + Uses the native gate and characterization sections of the runcard to + parse the + :class: `qibolab.qubits.Qubit` and + :class: `qibolab.qubits.QubitPair` objects. """ - qubits = {q: Qubit(q, **char) for q, char in runcard["characterization"]["single_qubit"].items()} + qubits = { + q: Qubit(q, **char) + for q, char in runcard["characterization"]["single_qubit"].items() + } if extras_folder is not None: single_qubit = runcard["characterization"]["single_qubit"] for qubit in qubits.values(): @@ -48,7 +55,10 @@ def load_qubits(runcard: dict, extras_folder: Path = None) -> Tuple[QubitMap, Co couplers = {} pairs = {} if "coupler" in runcard["characterization"]: - couplers = {c: Coupler(c, **char) for c, char in runcard["characterization"]["coupler"].items()} + couplers = { + c: Coupler(c, **char) + for c, char in runcard["characterization"]["coupler"].items() + } for c, pair in runcard["topology"].items(): pair = tuple(sorted(pair)) @@ -87,19 +97,28 @@ def register_gates( return qubits, pairs, couplers -def load_instrument_settings(runcard: dict, instruments: InstrumentMap) -> InstrumentMap: +def load_instrument_settings( + runcard: dict, instruments: InstrumentMap +) -> InstrumentMap: """Setup instruments according to the settings given in the runcard.""" for name, settings in runcard.get("instruments", {}).items(): instruments[name].setup(**settings) return instruments -def dump_qubits(qubits: QubitMap, pairs: QubitPairMap, couplers: CouplerMap = None) -> dict: - """Dump qubit and pair objects to a dictionary following the runcard format.""" +def dump_qubits( + qubits: QubitMap, pairs: QubitPairMap, couplers: CouplerMap = None +) -> dict: + """Dump qubit and pair objects to a dictionary following the runcard + format.""" - native_gates = {"single_qubit": {q: qubit.native_gates.raw for q, qubit in qubits.items()}} + native_gates = { + "single_qubit": {q: qubit.native_gates.raw for q, qubit in qubits.items()} + } if couplers: - native_gates["coupler"] = {c: coupler.native_pulse.raw for c, coupler in couplers.items()} + native_gates["coupler"] = { + c: coupler.native_pulse.raw for c, coupler in couplers.items() + } native_gates["two_qubit"] = {} # add two-qubit native gates @@ -118,7 +137,9 @@ def dump_qubits(qubits: QubitMap, pairs: QubitPairMap, couplers: CouplerMap = No qubit["kernel_path"] = kernel_path.name if couplers: - characterization["coupler"] = {c.name: {"sweetspot": c.sweetspot} for c in couplers.values()} + characterization["coupler"] = { + c.name: {"sweetspot": c.sweetspot} for c in couplers.values() + } return { "native_gates": native_gates, @@ -127,10 +148,13 @@ def dump_qubits(qubits: QubitMap, pairs: QubitPairMap, couplers: CouplerMap = No def dump_instruments(instruments: InstrumentMap) -> dict: - """Dump instrument settings to a dictionary following the runcard format.""" + """Dump instrument settings to a dictionary following the runcard + format.""" # Qblox modules settings are dictionaries and not dataclasses return { - name: instrument.settings if isinstance(instrument.settings, dict) else asdict(instrument.settings) + name: instrument.settings + if isinstance(instrument.settings, dict) + else asdict(instrument.settings) for name, instrument in instruments.items() if instrument.settings is not None } @@ -156,7 +180,12 @@ def dump_runcard(platform: Platform, path: Path): if platform.couplers: settings["couplers"] = list(platform.couplers) - settings["topology"] = {coupler: list(pair) for pair, coupler in zip(platform.pairs, platform.couplers)} + settings["topology"] = { + coupler: list(pair) + for pair, coupler in zip(platform.pairs, platform.couplers) + } settings.update(dump_qubits(platform.qubits, platform.pairs, platform.couplers)) - path.write_text(yaml.dump(settings, sort_keys=False, indent=4, default_flow_style=None)) + path.write_text( + yaml.dump(settings, sort_keys=False, indent=4, default_flow_style=None) + ) diff --git a/src/qibolab/sweeper.py b/src/qibolab/sweeper.py index cd16f7e426..c8539faca1 100644 --- a/src/qibolab/sweeper.py +++ b/src/qibolab/sweeper.py @@ -33,7 +33,7 @@ class Parameter(Enum): class SweeperType(Enum): - """Type of the Sweeper""" + """Type of the Sweeper.""" ABSOLUTE = partial(lambda x, y=None: x) FACTOR = operator.mul @@ -89,15 +89,27 @@ class Sweeper: type: Optional[SweeperType] = SweeperType.ABSOLUTE def __post_init__(self): - if self.pulses is not None and self.qubits is not None and self.couplers is not None: + if ( + self.pulses is not None + and self.qubits is not None + and self.couplers is not None + ): raise ValueError("Cannot use a sweeper on both pulses and qubits.") if self.pulses is not None and self.parameter in QubitParameter: - raise ValueError(f"Cannot sweep {self.parameter} without specifying qubits or couplers.") - if self.parameter not in QubitParameter and (self.qubits is not None or self.couplers is not None): - raise ValueError(f"Cannot sweep {self.parameter} without specifying pulses.") + raise ValueError( + f"Cannot sweep {self.parameter} without specifying qubits or couplers." + ) + if self.parameter not in QubitParameter and ( + self.qubits is not None or self.couplers is not None + ): + raise ValueError( + f"Cannot sweep {self.parameter} without specifying pulses." + ) if self.pulses is None and self.qubits is None and self.couplers is None: - raise ValueError("Cannot use a sweeper without specifying pulses, qubits or couplers.") + raise ValueError( + "Cannot use a sweeper without specifying pulses, qubits or couplers." + ) def get_values(self, base_value): - """Convert sweeper values depending on the sweeper type""" + """Convert sweeper values depending on the sweeper type.""" return self.type.value(self.values, base_value) diff --git a/src/qibolab/symbolic.py b/src/qibolab/symbolic.py index 3ca43bdfec..1145a13cbe 100644 --- a/src/qibolab/symbolic.py +++ b/src/qibolab/symbolic.py @@ -33,7 +33,10 @@ def collect_garbage(): internal_ref_count = 0 for se in list(SymbolicExpression.instances): - if re.search(rf"\b{re.escape(symbol)}\b", SymbolicExpression.instances[se].expression): + if re.search( + rf"\b{re.escape(symbol)}\b", + SymbolicExpression.instances[se].expression, + ): internal_ref_count += 1 if not internal_ref_count and external_ref_count < 4: @@ -64,7 +67,9 @@ def __init__( self, type: type, expression=0, symbol: str = "" ): # (self, expression: str|{self.type}|SymbolicExpression = 0, symbol: str = ''): if type not in self.supported_types: - raise TypeError(f"type argument should be int or float, got {type.__name__}") + raise TypeError( + f"type argument should be int or float, got {type.__name__}" + ) self.type = type self._symbol: str = "" @@ -101,7 +106,9 @@ def symbol(self) -> str: @symbol.setter def symbol(self, symbol: str): if not isinstance(symbol, str): - raise TypeError(f"symbol argument type should be str, got {type(symbol).__name__}") + raise TypeError( + f"symbol argument type should be str, got {type(symbol).__name__}" + ) if symbol in SymbolicExpression.instances.keys(): pass # Allows overwriting # raise KeyError(f"symbol should be unique, there is already a SymbolicExpression with symbol {symbol}: {SymbolicExpression.instances[symbol]}") @@ -110,16 +117,27 @@ def symbol(self, symbol: str): SymbolicExpression.instances[symbol] = self else: # Renaming - SymbolicExpression.instances[symbol] = self # Add a new reference with the new symbol + SymbolicExpression.instances[ + symbol + ] = self # Add a new reference with the new symbol if not self._symbol == symbol: - del SymbolicExpression.instances[self._symbol] # Remove the previous reference - - for se in SymbolicExpression.instances.values(): # Update all SymbolicExpressions with the symbol change + del SymbolicExpression.instances[ + self._symbol + ] # Remove the previous reference + + for ( + se + ) in ( + SymbolicExpression.instances.values() + ): # Update all SymbolicExpressions with the symbol change match_string = rf"\b{re.escape(self._symbol)}\b" replacement = symbol try: se.expression = re.sub(match_string, replacement, se.expression) - except (SymbolicExpression.InvalidExpressionError, SymbolicExpression.CircularReferenceError) as e: + except ( + SymbolicExpression.InvalidExpressionError, + SymbolicExpression.CircularReferenceError, + ) as e: pass self._symbol = symbol # test for CircularReferenceError @@ -134,7 +152,9 @@ def expression(self): return self._expression @expression.setter - def expression(self, expression): # (self, expression: str|{self.type}|SymbolicExpression): + def expression( + self, expression + ): # (self, expression: str|{self.type}|SymbolicExpression): if isinstance(expression, str): # evaluate the str expression to confirm it is valid before assigning it self.evaluate(expression) @@ -158,7 +178,9 @@ def value(self, value): # (self, value: {self.type}) if isinstance(value, self.type): self._expression = str(value) else: - raise TypeError(f"value argument type should be {self.type.__name__}, got {type(value).__name__}") + raise TypeError( + f"value argument type should be {self.type.__name__}, got {type(value).__name__}" + ) @property def is_constant(self) -> bool: @@ -200,7 +222,9 @@ def evaluate(self, expression: str, *previous_evaluations): # -> {self.type}: raise Exception() result = eval(expression) except: - raise SymbolicExpression.InvalidExpressionError(f"Unable to evaluate expression: {expression}") + raise SymbolicExpression.InvalidExpressionError( + f"Unable to evaluate expression: {expression}" + ) if not isinstance(result, self.type): raise SymbolicExpression.InvalidExpressionError( @@ -208,7 +232,9 @@ def evaluate(self, expression: str, *previous_evaluations): # -> {self.type}: ) return result - def _replace_internal_symbols(self, expression: str, *previous_evaluations): # -> {self.type}: + def _replace_internal_symbols( + self, expression: str, *previous_evaluations + ): # -> {self.type}: symbol: str for symbol in SymbolicExpression.instances.keys(): if symbol.startswith("_sym_") and symbol in expression: diff --git a/tests/conftest.py b/tests/conftest.py index 6043bdde45..9ca69576f4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -60,9 +60,9 @@ def find_instrument(platform, instrument_type): def get_instrument(platform, instrument_type): """Finds if an instrument of a given type exists in the given platform. - If the platform does not have such an instrument, the corresponding test - that asked for this instrument is skipped. - This ensures that QPU tests are executed only on the available instruments. + If the platform does not have such an instrument, the corresponding + test that asked for this instrument is skipped. This ensures that + QPU tests are executed only on the available instruments. """ instrument = find_instrument(platform, instrument_type) if instrument is None: diff --git a/tests/dummy_qrc/qm.py b/tests/dummy_qrc/qm.py index 0cd36e4104..bd6d8238c9 100644 --- a/tests/dummy_qrc/qm.py +++ b/tests/dummy_qrc/qm.py @@ -15,13 +15,16 @@ def create(runcard_path=RUNCARD): - """Dummy platform using Quantum Machines (QM) OPXs and Rohde Schwarz local oscillators. + """Dummy platform using Quantum Machines (QM) OPXs and Rohde Schwarz local + oscillators. Based on QuantWare 5-qubit device. Used in ``test_instruments_qm.py`` and ``test_instruments_qmsim.py`` """ - controller = QMSim("qmopx", "0.0.0.0:0", simulation_duration=1000, cloud=False, time_of_flight=280) + controller = QMSim( + "qmopx", "0.0.0.0:0", simulation_duration=1000, cloud=False, time_of_flight=280 + ) # Create channel objects and map controllers to channels channels = ChannelMap() @@ -32,10 +35,15 @@ def create(runcard_path=RUNCARD): channels |= Channel("L2-5_a", port=controller[(("con1", 2), ("con1", 1))]) channels |= Channel("L2-5_b", port=controller[(("con2", 2), ("con2", 1))]) # drive - channels |= (Channel(f"L3-1{i}", port=controller[(("con1", 2 * i), ("con1", 2 * i - 1))]) for i in range(1, 5)) + channels |= ( + Channel(f"L3-1{i}", port=controller[(("con1", 2 * i), ("con1", 2 * i - 1))]) + for i in range(1, 5) + ) channels |= Channel("L3-15", port=controller[(("con3", 2), ("con3", 1))]) # flux - channels |= (Channel(f"L4-{i}", port=controller[(("con2", i),)]) for i in range(1, 6)) + channels |= ( + Channel(f"L4-{i}", port=controller[(("con2", i),)]) for i in range(1, 6) + ) # TWPA channels |= "L4-26" @@ -77,7 +85,10 @@ def create(runcard_path=RUNCARD): qubits[q].flux = channels[f"L4-{q}"] # set filter for flux channel - qubits[2].flux.filters = {"feedforward": [1.0684635881381783, -1.0163217174522334], "feedback": [0.947858129314055]} + qubits[2].flux.filters = { + "feedforward": [1.0684635881381783, -1.0163217174522334], + "feedback": [0.947858129314055], + } # set maximum allowed bias values to protect amplifier # relevant only for qubits where an amplifier is used diff --git a/tests/dummy_qrc/zurich.py b/tests/dummy_qrc/zurich.py index dfb70700ab..a1c0b76b31 100644 --- a/tests/dummy_qrc/zurich.py +++ b/tests/dummy_qrc/zurich.py @@ -40,13 +40,31 @@ def create(runcard_path=RUNCARD): ) device_setup.add_connections( "device_shfqc", - *[create_connection(to_signal=f"q{i}/drive_line", ports=[f"SGCHANNELS/{i}/OUTPUT"]) for i in range(N_QUBITS)], - *[create_connection(to_signal=f"q{i}/measure_line", ports=["QACHANNELS/0/OUTPUT"]) for i in range(N_QUBITS)], - *[create_connection(to_signal=f"q{i}/acquire_line", ports=["QACHANNELS/0/INPUT"]) for i in range(N_QUBITS)], + *[ + create_connection( + to_signal=f"q{i}/drive_line", ports=[f"SGCHANNELS/{i}/OUTPUT"] + ) + for i in range(N_QUBITS) + ], + *[ + create_connection( + to_signal=f"q{i}/measure_line", ports=["QACHANNELS/0/OUTPUT"] + ) + for i in range(N_QUBITS) + ], + *[ + create_connection( + to_signal=f"q{i}/acquire_line", ports=["QACHANNELS/0/INPUT"] + ) + for i in range(N_QUBITS) + ], ) device_setup.add_connections( "device_hdawg", - *[create_connection(to_signal=f"q{i}/flux_line", ports=f"SIGOUTS/{i}") for i in range(N_QUBITS)], + *[ + create_connection(to_signal=f"q{i}/flux_line", ports=f"SIGOUTS/{i}") + for i in range(N_QUBITS) + ], *[ create_connection(to_signal=f"qc{c}/flux_line", ports=f"SIGOUTS/{i}") for c, i in zip(itertools.chain(range(0, 2), range(3, 4)), range(5, 8)) @@ -76,17 +94,30 @@ def create(runcard_path=RUNCARD): # Create channel objects and map controllers channels = ChannelMap() # feedback - channels |= Channel("L2-7", port=controller[("device_shfqc", "[QACHANNELS/0/INPUT]")]) + channels |= Channel( + "L2-7", port=controller[("device_shfqc", "[QACHANNELS/0/INPUT]")] + ) # readout - channels |= Channel("L3-31", port=controller[("device_shfqc", "[QACHANNELS/0/OUTPUT]")]) + channels |= Channel( + "L3-31", port=controller[("device_shfqc", "[QACHANNELS/0/OUTPUT]")] + ) # drive channels |= ( - Channel(f"L4-{i}", port=controller[("device_shfqc", f"SGCHANNELS/{i-5}/OUTPUT")]) for i in range(15, 20) + Channel( + f"L4-{i}", port=controller[("device_shfqc", f"SGCHANNELS/{i-5}/OUTPUT")] + ) + for i in range(15, 20) ) # flux qubits (CAREFUL WITH THIS !!!) - channels |= (Channel(f"L4-{i}", port=controller[("device_hdawg", f"SIGOUTS/{i-6}")]) for i in range(6, 11)) + channels |= ( + Channel(f"L4-{i}", port=controller[("device_hdawg", f"SIGOUTS/{i-6}")]) + for i in range(6, 11) + ) # flux couplers - channels |= (Channel(f"L4-{i}", port=controller[("device_hdawg", f"SIGOUTS/{i-11+5}")]) for i in range(11, 14)) + channels |= ( + Channel(f"L4-{i}", port=controller[("device_hdawg", f"SIGOUTS/{i-11+5}")]) + for i in range(11, 14) + ) channels |= Channel("L4-14", port=controller[("device_hdawg2", "SIGOUTS/0")]) # TWPA pump(EraSynth) channels |= Channel("L3-32") @@ -121,7 +152,10 @@ def create(runcard_path=RUNCARD): channels[f"L4-{i}"].power_range = 0.8 # Instantiate local oscillators - local_oscillators = [LocalOscillator(f"lo_{kind}", None) for kind in ["readout"] + [f"drive_{n}" for n in range(3)]] + local_oscillators = [ + LocalOscillator(f"lo_{kind}", None) + for kind in ["readout"] + [f"drive_{n}" for n in range(3)] + ] # Map LOs to channels ch_to_lo = { diff --git a/tests/test_backends.py b/tests/test_backends.py index 73559c052a..57d29dbe66 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -87,7 +87,9 @@ def test_execute_circuits(): @pytest.mark.qpu -@pytest.mark.xfail(raises=AssertionError, reason="Probabilities are not well calibrated") +@pytest.mark.xfail( + raises=AssertionError, reason="Probabilities are not well calibrated" +) def test_ground_state_probabilities_circuit(connected_backend): nshots = 5000 nqubits = connected_backend.platform.nqubits @@ -103,7 +105,9 @@ def test_ground_state_probabilities_circuit(connected_backend): @pytest.mark.qpu -@pytest.mark.xfail(raises=AssertionError, reason="Probabilities are not well calibrated") +@pytest.mark.xfail( + raises=AssertionError, reason="Probabilities are not well calibrated" +) def test_excited_state_probabilities_circuit(connected_backend): nshots = 5000 nqubits = connected_backend.platform.nqubits @@ -120,9 +124,12 @@ def test_excited_state_probabilities_circuit(connected_backend): @pytest.mark.qpu -@pytest.mark.xfail(raises=AssertionError, reason="Probabilities are not well calibrated") +@pytest.mark.xfail( + raises=AssertionError, reason="Probabilities are not well calibrated" +) def test_superposition_for_all_qubits(connected_backend): - """Applies an H gate to each qubit of the circuit and measures the probabilities.""" + """Applies an H gate to each qubit of the circuit and measures the + probabilities.""" nshots = 5000 nqubits = connected_backend.platform.nqubits probs = [] @@ -130,9 +137,13 @@ def test_superposition_for_all_qubits(connected_backend): circuit = Circuit(nqubits) circuit.add(gates.H(q=q)) circuit.add(gates.M(q)) - freqs = connected_backend.execute_circuit(circuit, nshots=nshots).frequencies(binary=False) + freqs = connected_backend.execute_circuit(circuit, nshots=nshots).frequencies( + binary=False + ) probs.append([freqs[i] / nshots for i in range(2)]) - warnings.warn(f"Probabilities after an Hadamard gate applied to qubit {q}: {probs[-1]}") + warnings.warn( + f"Probabilities after an Hadamard gate applied to qubit {q}: {probs[-1]}" + ) probs = np.asarray(probs) target_probs = np.repeat(a=0.5, repeats=nqubits) np.testing.assert_allclose(probs.T[0], target_probs, atol=0.05) diff --git a/tests/test_compilers_default.py b/tests/test_compilers_default.py index faf930d8ef..aae3c7125f 100644 --- a/tests/test_compilers_default.py +++ b/tests/test_compilers_default.py @@ -126,10 +126,14 @@ def test_u3_to_sequence(platform): assert len(sequence.qd_pulses) == 2 RX90_pulse1 = platform.create_RX90_pulse(0, start=0, relative_phase=0.3) - RX90_pulse2 = platform.create_RX90_pulse(0, start=RX90_pulse1.finish, relative_phase=0.4 - np.pi) + RX90_pulse2 = platform.create_RX90_pulse( + 0, start=RX90_pulse1.finish, relative_phase=0.4 - np.pi + ) s = PulseSequence(RX90_pulse1, RX90_pulse2) - np.testing.assert_allclose(sequence.duration, RX90_pulse1.duration + RX90_pulse2.duration) + np.testing.assert_allclose( + sequence.duration, RX90_pulse1.duration + RX90_pulse2.duration + ) assert sequence.serial == s.serial @@ -147,16 +151,24 @@ def test_two_u3_to_sequence(platform): np.testing.assert_allclose(sequence.duration, 2 * 2 * RX90_pulse.duration) RX90_pulse1 = platform.create_RX90_pulse(0, start=0, relative_phase=0.3) - RX90_pulse2 = platform.create_RX90_pulse(0, start=RX90_pulse1.finish, relative_phase=0.4 - np.pi) - RX90_pulse3 = platform.create_RX90_pulse(0, start=RX90_pulse2.finish, relative_phase=1.1) - RX90_pulse4 = platform.create_RX90_pulse(0, start=RX90_pulse3.finish, relative_phase=1.5 - np.pi) + RX90_pulse2 = platform.create_RX90_pulse( + 0, start=RX90_pulse1.finish, relative_phase=0.4 - np.pi + ) + RX90_pulse3 = platform.create_RX90_pulse( + 0, start=RX90_pulse2.finish, relative_phase=1.1 + ) + RX90_pulse4 = platform.create_RX90_pulse( + 0, start=RX90_pulse3.finish, relative_phase=1.5 - np.pi + ) s = PulseSequence(RX90_pulse1, RX90_pulse2, RX90_pulse3, RX90_pulse4) assert sequence.serial == s.serial def test_cz_to_sequence(platform): if (1, 2) not in platform.pairs: - pytest.skip(f"Skipping CZ test for {platform} because pair (1, 2) is not available.") + pytest.skip( + f"Skipping CZ test for {platform} because pair (1, 2) is not available." + ) circuit = Circuit(3) circuit.add(gates.X(0)) @@ -179,7 +191,9 @@ def test_add_measurement_to_sequence(platform): assert len(sequence.ro_pulses) == 1 RX90_pulse1 = platform.create_RX90_pulse(0, start=0, relative_phase=0.3) - RX90_pulse2 = platform.create_RX90_pulse(0, start=RX90_pulse1.finish, relative_phase=0.4 - np.pi) + RX90_pulse2 = platform.create_RX90_pulse( + 0, start=RX90_pulse1.finish, relative_phase=0.4 - np.pi + ) MZ_pulse = platform.create_MZ_pulse(0, start=RX90_pulse2.finish) s = PulseSequence(RX90_pulse1, RX90_pulse2, MZ_pulse) assert sequence.serial == s.serial diff --git a/tests/test_dummy.py b/tests/test_dummy.py index e764bcd1e6..a8604f169a 100644 --- a/tests/test_dummy.py +++ b/tests/test_dummy.py @@ -21,7 +21,9 @@ def test_dummy_initialization(name): @pytest.mark.parametrize("name", PLATFORM_NAMES) -@pytest.mark.parametrize("acquisition", [AcquisitionType.INTEGRATION, AcquisitionType.RAW]) +@pytest.mark.parametrize( + "acquisition", [AcquisitionType.INTEGRATION, AcquisitionType.RAW] +) def test_dummy_execute_pulse_sequence(name, acquisition): nshots = 100 platform = create_platform(name) @@ -54,7 +56,9 @@ def test_dummy_execute_coupler_pulse(): def test_dummy_execute_pulse_sequence_couplers(): platform = create_platform("dummy_couplers") - qubit_ordered_pair = QubitPair(platform.qubits[1], platform.qubits[2], platform.couplers[1]) + qubit_ordered_pair = QubitPair( + platform.qubits[1], platform.qubits[2], platform.couplers[1] + ) sequence = PulseSequence() cz, cz_phases = platform.create_CZ_pulse_sequence( @@ -86,7 +90,9 @@ def test_dummy_execute_pulse_sequence_fast_reset(name): @pytest.mark.parametrize("name", PLATFORM_NAMES) -@pytest.mark.parametrize("acquisition", [AcquisitionType.INTEGRATION, AcquisitionType.DISCRIMINATION]) +@pytest.mark.parametrize( + "acquisition", [AcquisitionType.INTEGRATION, AcquisitionType.DISCRIMINATION] +) @pytest.mark.parametrize("batch_size", [None, 3, 5]) def test_dummy_execute_pulse_sequence_unrolling(name, acquisition, batch_size): nshots = 100 @@ -131,16 +137,27 @@ def test_dummy_single_sweep_raw(name): @pytest.mark.parametrize("fast_reset", [True, False]) -@pytest.mark.parametrize("parameter", [Parameter.amplitude, Parameter.duration, Parameter.bias]) +@pytest.mark.parametrize( + "parameter", [Parameter.amplitude, Parameter.duration, Parameter.bias] +) @pytest.mark.parametrize("average", [AveragingMode.SINGLESHOT, AveragingMode.CYCLIC]) -@pytest.mark.parametrize("acquisition", [AcquisitionType.INTEGRATION, AcquisitionType.DISCRIMINATION]) +@pytest.mark.parametrize( + "acquisition", [AcquisitionType.INTEGRATION, AcquisitionType.DISCRIMINATION] +) @pytest.mark.parametrize("nshots", [10, 20]) -def test_dummy_single_sweep_coupler(fast_reset, parameter, average, acquisition, nshots): +def test_dummy_single_sweep_coupler( + fast_reset, parameter, average, acquisition, nshots +): platform = create_platform("dummy_couplers") sequence = PulseSequence() ro_pulse = platform.create_qubit_readout_pulse(qubit=0, start=0) coupler_pulse = CouplerFluxPulse( - start=0, duration=40, amplitude=0.5, shape="Rectangular()", channel="flux_coupler-0", qubit=0 + start=0, + duration=40, + amplitude=0.5, + shape="Rectangular()", + channel="flux_coupler-0", + qubit=0, ) if parameter is Parameter.amplitude: parameter_range = np.random.rand(SWEPT_POINTS) @@ -181,7 +198,9 @@ def test_dummy_single_sweep_coupler(fast_reset, parameter, average, acquisition, @pytest.mark.parametrize("fast_reset", [True, False]) @pytest.mark.parametrize("parameter", Parameter) @pytest.mark.parametrize("average", [AveragingMode.SINGLESHOT, AveragingMode.CYCLIC]) -@pytest.mark.parametrize("acquisition", [AcquisitionType.INTEGRATION, AcquisitionType.DISCRIMINATION]) +@pytest.mark.parametrize( + "acquisition", [AcquisitionType.INTEGRATION, AcquisitionType.DISCRIMINATION] +) @pytest.mark.parametrize("nshots", [10, 20]) def test_dummy_single_sweep(name, fast_reset, parameter, average, acquisition, nshots): platform = create_platform(name) @@ -225,7 +244,9 @@ def test_dummy_single_sweep(name, fast_reset, parameter, average, acquisition, n @pytest.mark.parametrize("parameter1", Parameter) @pytest.mark.parametrize("parameter2", Parameter) @pytest.mark.parametrize("average", [AveragingMode.SINGLESHOT, AveragingMode.CYCLIC]) -@pytest.mark.parametrize("acquisition", [AcquisitionType.INTEGRATION, AcquisitionType.DISCRIMINATION]) +@pytest.mark.parametrize( + "acquisition", [AcquisitionType.INTEGRATION, AcquisitionType.DISCRIMINATION] +) @pytest.mark.parametrize("nshots", [10, 20]) def test_dummy_double_sweep(name, parameter1, parameter2, average, acquisition, nshots): platform = create_platform(name) @@ -277,13 +298,19 @@ def test_dummy_double_sweep(name, parameter1, parameter2, average, acquisition, else results[pulse.qubit].samples.shape ) - assert results_shape == (SWEPT_POINTS, SWEPT_POINTS) if average else (nshots, SWEPT_POINTS, SWEPT_POINTS) + assert ( + results_shape == (SWEPT_POINTS, SWEPT_POINTS) + if average + else (nshots, SWEPT_POINTS, SWEPT_POINTS) + ) @pytest.mark.parametrize("name", PLATFORM_NAMES) @pytest.mark.parametrize("parameter", Parameter) @pytest.mark.parametrize("average", [AveragingMode.SINGLESHOT, AveragingMode.CYCLIC]) -@pytest.mark.parametrize("acquisition", [AcquisitionType.INTEGRATION, AcquisitionType.DISCRIMINATION]) +@pytest.mark.parametrize( + "acquisition", [AcquisitionType.INTEGRATION, AcquisitionType.DISCRIMINATION] +) @pytest.mark.parametrize("nshots", [10, 20]) def test_dummy_single_sweep_multiplex(name, parameter, average, acquisition, nshots): platform = create_platform(name) @@ -299,9 +326,17 @@ def test_dummy_single_sweep_multiplex(name, parameter, average, acquisition, nsh ) if parameter in QubitParameter: - sweeper1 = Sweeper(parameter, parameter_range, qubits=[platform.qubits[qubit] for qubit in platform.qubits]) + sweeper1 = Sweeper( + parameter, + parameter_range, + qubits=[platform.qubits[qubit] for qubit in platform.qubits], + ) else: - sweeper1 = Sweeper(parameter, parameter_range, pulses=[ro_pulses[qubit] for qubit in platform.qubits]) + sweeper1 = Sweeper( + parameter, + parameter_range, + pulses=[ro_pulses[qubit] for qubit in platform.qubits], + ) options = ExecutionParameters( nshots=nshots, diff --git a/tests/test_instruments_qblox.py b/tests/test_instruments_qblox.py index a7ea33a80b..661fde85ac 100644 --- a/tests/test_instruments_qblox.py +++ b/tests/test_instruments_qblox.py @@ -1,4 +1,4 @@ -""" Qblox instruments driver. +"""Qblox instruments driver. Supports the following Instruments: Cluster @@ -25,7 +25,6 @@ - waveforms cache, uses additional free sequencers if the memory of one sequencer (16384) is exhausted - instrument parameters cache - safe disconnection of offsets on termination - """ diff --git a/tests/test_instruments_qblox_cluster.py b/tests/test_instruments_qblox_cluster.py index de78a35c66..7b11b95df9 100644 --- a/tests/test_instruments_qblox_cluster.py +++ b/tests/test_instruments_qblox_cluster.py @@ -35,7 +35,14 @@ def test_instrument_interface(cluster: Cluster): for abstract_method in Instrument.__abstractmethods__: assert hasattr(cluster, abstract_method) - for attribute in ["name", "address", "is_connected", "signature", "tmp_folder", "data_folder"]: + for attribute in [ + "name", + "address", + "is_connected", + "signature", + "tmp_folder", + "data_folder", + ]: assert hasattr(cluster, attribute) diff --git a/tests/test_instruments_qblox_cluster_qcm_bb.py b/tests/test_instruments_qblox_cluster_qcm_bb.py index dfd7902d78..b9f2698582 100644 --- a/tests/test_instruments_qblox_cluster_qcm_bb.py +++ b/tests/test_instruments_qblox_cluster_qcm_bb.py @@ -65,7 +65,14 @@ def test_instrument_interface(qcm_bb: ClusterQCM_BB): for abstract_method in Instrument.__abstractmethods__: assert hasattr(qcm_bb, abstract_method) - for attribute in ["name", "address", "is_connected", "signature", "tmp_folder", "data_folder"]: + for attribute in [ + "name", + "address", + "is_connected", + "signature", + "tmp_folder", + "data_folder", + ]: assert hasattr(qcm_bb, attribute) @@ -206,7 +213,9 @@ def test_sweepers(connected_platform, connected_qcm_bb: ClusterQCM_BB): type=SweeperType.OFFSET, ) - connected_qcm_bb.process_pulse_sequence(qubits, ps, 1000, 1, 10000, sweepers=[sweeper]) + connected_qcm_bb.process_pulse_sequence( + qubits, ps, 1000, 1, 10000, sweepers=[sweeper] + ) connected_qcm_bb.upload() connected_qcm_bb.play_sequence() diff --git a/tests/test_instruments_qblox_cluster_qcm_rf.py b/tests/test_instruments_qblox_cluster_qcm_rf.py index f8959ec460..24f9807132 100644 --- a/tests/test_instruments_qblox_cluster_qcm_rf.py +++ b/tests/test_instruments_qblox_cluster_qcm_rf.py @@ -57,7 +57,14 @@ def test_instrument_interface(qcm_rf: ClusterQCM_RF): for abstract_method in Instrument.__abstractmethods__: assert hasattr(qcm_rf, abstract_method) - for attribute in ["name", "address", "is_connected", "signature", "tmp_folder", "data_folder"]: + for attribute in [ + "name", + "address", + "is_connected", + "signature", + "tmp_folder", + "data_folder", + ]: assert hasattr(qcm_rf, attribute) @@ -167,8 +174,28 @@ def test_connect(connected_cluster: Cluster, connected_qcm_rf: ClusterQCM_RF): @pytest.mark.qpu def test_pulse_sequence(connected_platform, connected_qcm_rf: ClusterQCM_RF): ps = PulseSequence() - ps.add(DrivePulse(0, 200, 1, O1_LO_FREQUENCY - 200e6, np.pi / 2, "Gaussian(5)", O1_OUTPUT_CHANNEL)) - ps.add(DrivePulse(0, 200, 1, O2_LO_FREQUENCY - 200e6, np.pi / 2, "Gaussian(5)", O2_OUTPUT_CHANNEL)) + ps.add( + DrivePulse( + 0, + 200, + 1, + O1_LO_FREQUENCY - 200e6, + np.pi / 2, + "Gaussian(5)", + O1_OUTPUT_CHANNEL, + ) + ) + ps.add( + DrivePulse( + 0, + 200, + 1, + O2_LO_FREQUENCY - 200e6, + np.pi / 2, + "Gaussian(5)", + O2_OUTPUT_CHANNEL, + ) + ) qubits = connected_platform.qubits connected_qcm_rf.ports["o2"].hardware_mod_en = True @@ -185,8 +212,28 @@ def test_pulse_sequence(connected_platform, connected_qcm_rf: ClusterQCM_RF): @pytest.mark.qpu def test_sweepers(connected_platform, connected_qcm_rf: ClusterQCM_RF): ps = PulseSequence() - ps.add(DrivePulse(0, 200, 1, O1_LO_FREQUENCY - 200e6, np.pi / 2, "Gaussian(5)", O1_OUTPUT_CHANNEL)) - ps.add(DrivePulse(0, 200, 1, O2_LO_FREQUENCY - 200e6, np.pi / 2, "Gaussian(5)", O2_OUTPUT_CHANNEL)) + ps.add( + DrivePulse( + 0, + 200, + 1, + O1_LO_FREQUENCY - 200e6, + np.pi / 2, + "Gaussian(5)", + O1_OUTPUT_CHANNEL, + ) + ) + ps.add( + DrivePulse( + 0, + 200, + 1, + O2_LO_FREQUENCY - 200e6, + np.pi / 2, + "Gaussian(5)", + O2_OUTPUT_CHANNEL, + ) + ) qubits = connected_platform.qubits @@ -201,7 +248,9 @@ def test_sweepers(connected_platform, connected_qcm_rf: ClusterQCM_RF): type=SweeperType.OFFSET, ) - connected_qcm_rf.process_pulse_sequence(qubits, ps, 1000, 1, 10000, sweepers=[sweeper]) + connected_qcm_rf.process_pulse_sequence( + qubits, ps, 1000, 1, 10000, sweepers=[sweeper] + ) connected_qcm_rf.upload() connected_qcm_rf.play_sequence() @@ -213,7 +262,9 @@ def test_sweepers(connected_platform, connected_qcm_rf: ClusterQCM_RF): type=SweeperType.ABSOLUTE, ) - connected_qcm_rf.process_pulse_sequence(qubits, ps, 1000, 1, 10000, sweepers=[sweeper]) + connected_qcm_rf.process_pulse_sequence( + qubits, ps, 1000, 1, 10000, sweepers=[sweeper] + ) connected_qcm_rf.upload() connected_qcm_rf.play_sequence() diff --git a/tests/test_instruments_qblox_cluster_qrm_rf.py b/tests/test_instruments_qblox_cluster_qrm_rf.py index f0b3a6b870..5004276d15 100644 --- a/tests/test_instruments_qblox_cluster_qrm_rf.py +++ b/tests/test_instruments_qblox_cluster_qrm_rf.py @@ -56,7 +56,14 @@ def test_instrument_interface(qrm_rf: ClusterQRM_RF): for abstract_method in Instrument.__abstractmethods__: assert hasattr(qrm_rf, abstract_method) - for attribute in ["name", "address", "is_connected", "signature", "tmp_folder", "data_folder"]: + for attribute in [ + "name", + "address", + "is_connected", + "signature", + "tmp_folder", + "data_folder", + ]: assert hasattr(qrm_rf, attribute) @@ -99,7 +106,10 @@ def test_connect(connected_qrm_rf: ClusterQRM_RF): assert qrm_rf.device.get("out0_offset_path1") == 0 assert qrm_rf.device.get("scope_acq_avg_mode_en_path0") == True assert qrm_rf.device.get("scope_acq_avg_mode_en_path1") == True - assert qrm_rf.device.get("scope_acq_sequencer_select") == qrm_rf.DEFAULT_SEQUENCERS["i1"] + assert ( + qrm_rf.device.get("scope_acq_sequencer_select") + == qrm_rf.DEFAULT_SEQUENCERS["i1"] + ) assert qrm_rf.device.get("scope_acq_trigger_level_path0") == 0 assert qrm_rf.device.get("scope_acq_trigger_level_path1") == 0 assert qrm_rf.device.get("scope_acq_trigger_mode_path0") == "sequencer" @@ -150,8 +160,16 @@ def test_pulse_sequence(connected_platform, connected_qrm_rf: ClusterQRM_RF): ps = PulseSequence() for channel in connected_qrm_rf.channel_map: ps.add(DrivePulse(0, 200, 1, 6.8e9, np.pi / 2, "Gaussian(5)", channel)) - ps.add(ReadoutPulse(200, 2000, 1, 7.1e9, np.pi / 2, "Rectangular()", channel, qubit=0)) - ps.add(ReadoutPulse(200, 2000, 1, 7.2e9, np.pi / 2, "Rectangular()", channel, qubit=1)) + ps.add( + ReadoutPulse( + 200, 2000, 1, 7.1e9, np.pi / 2, "Rectangular()", channel, qubit=0 + ) + ) + ps.add( + ReadoutPulse( + 200, 2000, 1, 7.2e9, np.pi / 2, "Rectangular()", channel, qubit=1 + ) + ) qubits = connected_platform.qubits connected_qrm_rf.ports["i1"].hardware_demod_en = True connected_qrm_rf.process_pulse_sequence(qubits, ps, 1000, 1, 10000) @@ -171,9 +189,15 @@ def test_sweepers(connected_platform, connected_qrm_rf: ClusterQRM_RF): qd_pulses = {} ro_pulses = {} for channel in connected_qrm_rf.channel_map: - qd_pulses[0] = DrivePulse(0, 200, 1, 7e9, np.pi / 2, "Gaussian(5)", channel, qubit=0) - ro_pulses[0] = ReadoutPulse(200, 2000, 1, 7.1e9, np.pi / 2, "Rectangular()", channel, qubit=0) - ro_pulses[1] = ReadoutPulse(200, 2000, 1, 7.2e9, np.pi / 2, "Rectangular()", channel, qubit=1) + qd_pulses[0] = DrivePulse( + 0, 200, 1, 7e9, np.pi / 2, "Gaussian(5)", channel, qubit=0 + ) + ro_pulses[0] = ReadoutPulse( + 200, 2000, 1, 7.1e9, np.pi / 2, "Rectangular()", channel, qubit=0 + ) + ro_pulses[1] = ReadoutPulse( + 200, 2000, 1, 7.2e9, np.pi / 2, "Rectangular()", channel, qubit=1 + ) ps.add(qd_pulses[0], ro_pulses[0], ro_pulses[1]) qubits = connected_platform.qubits @@ -189,7 +213,9 @@ def test_sweepers(connected_platform, connected_qrm_rf: ClusterQRM_RF): type=SweeperType.OFFSET, ) - connected_qrm_rf.process_pulse_sequence(qubits, ps, 1000, 1, 10000, sweepers=[sweeper]) + connected_qrm_rf.process_pulse_sequence( + qubits, ps, 1000, 1, 10000, sweepers=[sweeper] + ) connected_qrm_rf.upload() connected_qrm_rf.play_sequence() results = connected_qrm_rf.acquire() @@ -202,7 +228,9 @@ def test_sweepers(connected_platform, connected_qrm_rf: ClusterQRM_RF): type=SweeperType.ABSOLUTE, ) - connected_qrm_rf.process_pulse_sequence(qubits, ps, 1000, 1, 10000, sweepers=[sweeper]) + connected_qrm_rf.process_pulse_sequence( + qubits, ps, 1000, 1, 10000, sweepers=[sweeper] + ) connected_qrm_rf.upload() connected_qrm_rf.play_sequence() results = connected_qrm_rf.acquire() diff --git a/tests/test_instruments_qm.py b/tests/test_instruments_qm.py index 3c3da90def..4c1d98d6b6 100644 --- a/tests/test_instruments_qm.py +++ b/tests/test_instruments_qm.py @@ -67,11 +67,26 @@ def test_qmpulse_previous_and_next(): qd_qmpulses = [] ro_qmpulses = [] for qubit in range(nqubits): - qd_pulse = QMPulse(Pulse(0, 40, 0.05, int(3e9), 0.0, Rectangular(), f"drive{qubit}", qubit=qubit)) + qd_pulse = QMPulse( + Pulse( + 0, 40, 0.05, int(3e9), 0.0, Rectangular(), f"drive{qubit}", qubit=qubit + ) + ) qd_qmpulses.append(qd_pulse) qmsequence.add(qd_pulse) for qubit in range(nqubits): - ro_pulse = QMPulse(ReadoutPulse(40, 100, 0.05, int(3e9), 0.0, Rectangular(), f"readout{qubit}", qubit=qubit)) + ro_pulse = QMPulse( + ReadoutPulse( + 40, + 100, + 0.05, + int(3e9), + 0.0, + Rectangular(), + f"readout{qubit}", + qubit=qubit, + ) + ) ro_qmpulses.append(ro_pulse) qmsequence.add(ro_pulse) @@ -94,8 +109,12 @@ def test_qmpulse_previous_and_next_flux(): theta_pulse = Pulse(70, 40, 0.05, int(3e9), 0.0, Rectangular(), f"drive1", qubit=1) x_pulse_end = Pulse(70, 40, 0.05, int(3e9), 0.0, Rectangular(), f"drive2", qubit=2) - measure_lowfreq = ReadoutPulse(110, 100, 0.05, int(3e9), 0.0, Rectangular(), "readout1", qubit=1) - measure_highfreq = ReadoutPulse(110, 100, 0.05, int(3e9), 0.0, Rectangular(), "readout2", qubit=2) + measure_lowfreq = ReadoutPulse( + 110, 100, 0.05, int(3e9), 0.0, Rectangular(), "readout1", qubit=1 + ) + measure_highfreq = ReadoutPulse( + 110, 100, 0.05, int(3e9), 0.0, Rectangular(), "readout2", qubit=2 + ) drive11 = QMPulse(y90_pulse) drive21 = QMPulse(x_pulse_start) @@ -140,14 +159,18 @@ def test_qmopx_register_analog_output_controllers(): port = QMPort((("con1", 1), ("con1", 2))) opx.config.register_analog_output_controllers(port) controllers = opx.config.controllers - assert controllers == {"con1": {"analog_outputs": {1: {"offset": 0.0}, 2: {"offset": 0.0}}}} + assert controllers == { + "con1": {"analog_outputs": {1: {"offset": 0.0}, 2: {"offset": 0.0}}} + } opx = QMOPX(name, address) port = QMPort((("con1", 1), ("con1", 2))) port.offset = 0.005 opx.config.register_analog_output_controllers(port) controllers = opx.config.controllers - assert controllers == {"con1": {"analog_outputs": {1: {"offset": 0.005}, 2: {"offset": 0.005}}}} + assert controllers == { + "con1": {"analog_outputs": {1: {"offset": 0.005}, 2: {"offset": 0.005}}} + } opx = QMOPX(name, address) port = QMPort((("con2", 2),)) @@ -156,32 +179,59 @@ def test_qmopx_register_analog_output_controllers(): opx.config.register_analog_output_controllers(port) controllers = opx.config.controllers assert controllers == { - "con2": {"analog_outputs": {2: {"filter": {"feedback": [0.95], "feedforward": [1, -1]}, "offset": 0.005}}} + "con2": { + "analog_outputs": { + 2: { + "filter": {"feedback": [0.95], "feedforward": [1, -1]}, + "offset": 0.005, + } + } + } } def test_qmopx_register_drive_element(dummy_qrc): platform = create_platform("qm") opx = platform.instruments["qmopx"] - opx.config.register_drive_element(platform.qubits[0], intermediate_frequency=int(1e6)) + opx.config.register_drive_element( + platform.qubits[0], intermediate_frequency=int(1e6) + ) assert "drive0" in opx.config.elements target_element = { - "mixInputs": {"I": ("con3", 2), "Q": ("con3", 1), "lo_frequency": 4700000000, "mixer": "mixer_drive0"}, + "mixInputs": { + "I": ("con3", 2), + "Q": ("con3", 1), + "lo_frequency": 4700000000, + "mixer": "mixer_drive0", + }, "intermediate_frequency": 1000000, "operations": {}, } assert opx.config.elements["drive0"] == target_element - target_mixer = [{"intermediate_frequency": 1000000, "lo_frequency": 4700000000, "correction": [1.0, 0.0, 0.0, 1.0]}] + target_mixer = [ + { + "intermediate_frequency": 1000000, + "lo_frequency": 4700000000, + "correction": [1.0, 0.0, 0.0, 1.0], + } + ] assert opx.config.mixers["mixer_drive0"] == target_mixer def test_qmopx_register_readout_element(dummy_qrc): platform = create_platform("qm") opx = platform.instruments["qmopx"] - opx.config.register_readout_element(platform.qubits[2], int(1e6), opx.time_of_flight, opx.smearing) + opx.config.register_readout_element( + platform.qubits[2], int(1e6), opx.time_of_flight, opx.smearing + ) assert "readout2" in opx.config.elements target_element = { - "mixInputs": {"I": ("con2", 10), "Q": ("con2", 9), "lo_frequency": 7900000000, "mixer": "mixer_readout2"}, + "mixInputs": { + "I": ("con2", 10), + "Q": ("con2", 9), + "lo_frequency": 7900000000, + "mixer": "mixer_readout2", + }, "intermediate_frequency": 1000000, "operations": {}, "outputs": { @@ -192,7 +242,13 @@ def test_qmopx_register_readout_element(dummy_qrc): "smearing": 0, } assert opx.config.elements["readout2"] == target_element - target_mixer = [{"intermediate_frequency": 1000000, "lo_frequency": 7900000000, "correction": [1.0, 0.0, 0.0, 1.0]}] + target_mixer = [ + { + "intermediate_frequency": 1000000, + "lo_frequency": 7900000000, + "correction": [1.0, 0.0, 0.0, 1.0], + } + ] assert opx.config.mixers["mixer_readout2"] == target_mixer @@ -205,7 +261,10 @@ def test_qmopx_register_pulse(dummy_qrc, pulse_type, qubit): target_pulse = { "operation": "control", "length": pulse.duration, - "waveforms": {"I": pulse.envelope_waveform_i.serial, "Q": pulse.envelope_waveform_q.serial}, + "waveforms": { + "I": pulse.envelope_waveform_i.serial, + "Q": pulse.envelope_waveform_q.serial, + }, } else: @@ -222,19 +281,26 @@ def test_qmopx_register_pulse(dummy_qrc, pulse_type, qubit): }, } - opx.config.register_element(platform.qubits[qubit], pulse, opx.time_of_flight, opx.smearing) + opx.config.register_element( + platform.qubits[qubit], pulse, opx.time_of_flight, opx.smearing + ) opx.config.register_pulse(platform.qubits[qubit], pulse) assert opx.config.pulses[pulse.serial] == target_pulse assert target_pulse["waveforms"]["I"] in opx.config.waveforms assert target_pulse["waveforms"]["Q"] in opx.config.waveforms - assert opx.config.elements[f"{pulse_type}{qubit}"]["operations"][pulse.serial] == pulse.serial + assert ( + opx.config.elements[f"{pulse_type}{qubit}"]["operations"][pulse.serial] + == pulse.serial + ) def test_qmopx_register_flux_pulse(dummy_qrc): qubit = 2 platform = create_platform("qm") opx = platform.instruments["qmopx"] - pulse = FluxPulse(0, 30, 0.005, Rectangular(), platform.qubits[qubit].flux.name, qubit) + pulse = FluxPulse( + 0, 30, 0.005, Rectangular(), platform.qubits[qubit].flux.name, qubit + ) target_pulse = { "operation": "control", "length": pulse.duration, @@ -244,7 +310,9 @@ def test_qmopx_register_flux_pulse(dummy_qrc): opx.config.register_pulse(platform.qubits[qubit], pulse) assert opx.config.pulses[pulse.serial] == target_pulse assert target_pulse["waveforms"]["single"] in opx.config.waveforms - assert opx.config.elements[f"flux{qubit}"]["operations"][pulse.serial] == pulse.serial + assert ( + opx.config.elements[f"flux{qubit}"]["operations"][pulse.serial] == pulse.serial + ) @pytest.mark.parametrize("duration", [0, 30]) @@ -253,12 +321,16 @@ def test_qmopx_register_baked_pulse(dummy_qrc, duration): qubit = platform.qubits[3] opx = platform.instruments["qmopx"] opx.config.register_flux_element(qubit) - pulse = FluxPulse(3, duration, 0.05, Rectangular(), qubit.flux.name, qubit=qubit.name) + pulse = FluxPulse( + 3, duration, 0.05, Rectangular(), qubit.flux.name, qubit=qubit.name + ) qmpulse = BakedPulse(pulse) config = opx.config qmpulse.bake(config, [pulse.duration]) - assert config.elements["flux3"]["operations"] == {"baked_Op_0": "flux3_baked_pulse_0"} + assert config.elements["flux3"]["operations"] == { + "baked_Op_0": "flux3_baked_pulse_0" + } if duration == 0: assert config.pulses["flux3_baked_pulse_0"] == { "operation": "control", @@ -294,8 +366,12 @@ def test_qmopx_qubit_spectroscopy(mocker): qd_pulses = {} ro_pulses = {} for qubit in [1, 2, 3]: - qd_pulses[qubit] = platform.create_qubit_drive_pulse(qubit, start=0, duration=500) - ro_pulses[qubit] = platform.create_qubit_readout_pulse(qubit, start=qd_pulses[qubit].finish) + qd_pulses[qubit] = platform.create_qubit_drive_pulse( + qubit, start=0, duration=500 + ) + ro_pulses[qubit] = platform.create_qubit_readout_pulse( + qubit, start=qd_pulses[qubit].finish + ) sequence.add(qd_pulses[qubit]) sequence.add(ro_pulses[qubit]) options = ExecutionParameters(nshots=1024, relaxation_time=100000) diff --git a/tests/test_instruments_qmsim.py b/tests/test_instruments_qmsim.py index 902bbb198f..27940ba232 100644 --- a/tests/test_instruments_qmsim.py +++ b/tests/test_instruments_qmsim.py @@ -1,4 +1,5 @@ -"""Test compilation of different pulse sequences using the Quantum Machines simulator. +"""Test compilation of different pulse sequences using the Quantum Machines +simulator. In order to run these tests, provide the following options through the ``pytest`` parser: address (str): token for the QM simulator @@ -33,8 +34,8 @@ def simulator(request): """Platform using the QM cloud simulator. Requires the address for connecting to the simulator, which is - provided via command line. If an address is not provided these - tests are skipped. + provided via command line. If an address is not provided these tests + are skipped. """ set_platform_profile() address = request.config.getoption("--address") @@ -92,7 +93,10 @@ def plot(): np.testing.assert_allclose(waveform, target_waveform[:]) except AssertionError as exception: np.savetxt(os.path.join(folder, "waveform.txt"), waveform) - np.savetxt(os.path.join(folder, "target_waveform.txt"), target_waveform[:]) + np.savetxt( + os.path.join(folder, "target_waveform.txt"), + target_waveform[:], + ) plot() raise exception @@ -129,9 +133,13 @@ def test_qmsim_qubit_spectroscopy(simulator, folder): qd_pulses = {} ro_pulses = {} for qubit in qubits: - qd_pulses[qubit] = simulator.create_qubit_drive_pulse(qubit, start=0, duration=500) + qd_pulses[qubit] = simulator.create_qubit_drive_pulse( + qubit, start=0, duration=500 + ) qd_pulses[qubit].amplitude = 0.05 - ro_pulses[qubit] = simulator.create_qubit_readout_pulse(qubit, start=qd_pulses[qubit].finish) + ro_pulses[qubit] = simulator.create_qubit_readout_pulse( + qubit, start=qd_pulses[qubit].finish + ) sequence.add(qd_pulses[qubit]) sequence.add(ro_pulses[qubit]) options = ExecutionParameters(nshots=1) @@ -155,13 +163,18 @@ def test_qmsim_sweep(simulator, folder, parameter, values): ro_pulses = {} for qubit in qubits: qd_pulses[qubit] = simulator.create_RX_pulse(qubit, start=0) - ro_pulses[qubit] = simulator.create_MZ_pulse(qubit, start=qd_pulses[qubit].finish) + ro_pulses[qubit] = simulator.create_MZ_pulse( + qubit, start=qd_pulses[qubit].finish + ) sequence.add(qd_pulses[qubit]) sequence.add(ro_pulses[qubit]) pulses = [qd_pulses[qubit] for qubit in qubits] sweeper = Sweeper(parameter, values, pulses) options = ExecutionParameters( - nshots=1, relaxation_time=20, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC + nshots=1, + relaxation_time=20, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, ) result = simulator.sweep(sequence, options, sweeper) samples = result.get_simulated_samples() @@ -176,9 +189,14 @@ def test_qmsim_sweep_bias(simulator, folder): ro_pulses[qubit] = simulator.create_MZ_pulse(qubit, start=0) sequence.add(ro_pulses[qubit]) values = [0, 0.005] - sweeper = Sweeper(Parameter.bias, values, qubits=[simulator.qubits[q] for q in qubits]) + sweeper = Sweeper( + Parameter.bias, values, qubits=[simulator.qubits[q] for q in qubits] + ) options = ExecutionParameters( - nshots=1, relaxation_time=20, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC + nshots=1, + relaxation_time=20, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, ) result = simulator.sweep(sequence, options, sweeper) samples = result.get_simulated_samples() @@ -192,14 +210,19 @@ def test_qmsim_sweep_start(simulator, folder): ro_pulses = {} for qubit in qubits: qd_pulses[qubit] = simulator.create_RX_pulse(qubit, start=0) - ro_pulses[qubit] = simulator.create_MZ_pulse(qubit, start=qd_pulses[qubit].finish) + ro_pulses[qubit] = simulator.create_MZ_pulse( + qubit, start=qd_pulses[qubit].finish + ) sequence.add(qd_pulses[qubit]) sequence.add(ro_pulses[qubit]) values = [20, 40] pulses = [ro_pulses[qubit] for qubit in qubits] sweeper = Sweeper(Parameter.start, values, pulses=pulses) options = ExecutionParameters( - nshots=1, relaxation_time=0, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC + nshots=1, + relaxation_time=0, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, ) result = simulator.sweep(sequence, options, sweeper) samples = result.get_simulated_samples() @@ -214,8 +237,12 @@ def test_qmsim_sweep_start_two_pulses(simulator, folder): ro_pulses = {} for qubit in qubits: qd_pulses1[qubit] = simulator.create_RX_pulse(qubit, start=0) - qd_pulses2[qubit] = simulator.create_RX_pulse(qubit, start=qd_pulses1[qubit].finish) - ro_pulses[qubit] = simulator.create_MZ_pulse(qubit, start=qd_pulses2[qubit].finish) + qd_pulses2[qubit] = simulator.create_RX_pulse( + qubit, start=qd_pulses1[qubit].finish + ) + ro_pulses[qubit] = simulator.create_MZ_pulse( + qubit, start=qd_pulses2[qubit].finish + ) sequence.add(qd_pulses1[qubit]) sequence.add(qd_pulses2[qubit]) sequence.add(ro_pulses[qubit]) @@ -223,7 +250,10 @@ def test_qmsim_sweep_start_two_pulses(simulator, folder): pulses = [qd_pulses2[qubit] for qubit in qubits] sweeper = Sweeper(Parameter.start, values, pulses=pulses) options = ExecutionParameters( - nshots=1, relaxation_time=0, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC + nshots=1, + relaxation_time=0, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, ) result = simulator.sweep(sequence, options, sweeper) samples = result.get_simulated_samples() @@ -240,14 +270,19 @@ def test_qmsim_sweep_duration(simulator, folder): ro_pulses = {} for qubit in qubits: qd_pulses[qubit] = simulator.create_RX_pulse(qubit, start=0) - ro_pulses[qubit] = simulator.create_MZ_pulse(qubit, start=qd_pulses[qubit].finish) + ro_pulses[qubit] = simulator.create_MZ_pulse( + qubit, start=qd_pulses[qubit].finish + ) sequence.add(qd_pulses[qubit]) sequence.add(ro_pulses[qubit]) values = [20, 60] pulses = [qd_pulses[qubit] for qubit in qubits] sweeper = Sweeper(Parameter.duration, values, pulses=pulses) options = ExecutionParameters( - nshots=1, relaxation_time=0, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC + nshots=1, + relaxation_time=0, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, ) result = simulator.sweep(sequence, options, sweeper) samples = result.get_simulated_samples() @@ -266,8 +301,12 @@ def test_qmsim_sweep_duration_two_pulses(simulator, folder): ro_pulses = {} for qubit in qubits: qd_pulses1[qubit] = simulator.create_RX_pulse(qubit, start=0) - qd_pulses2[qubit] = simulator.create_RX_pulse(qubit, start=qd_pulses1[qubit].finish) - ro_pulses[qubit] = simulator.create_MZ_pulse(qubit, start=qd_pulses2[qubit].finish) + qd_pulses2[qubit] = simulator.create_RX_pulse( + qubit, start=qd_pulses1[qubit].finish + ) + ro_pulses[qubit] = simulator.create_MZ_pulse( + qubit, start=qd_pulses2[qubit].finish + ) sequence.add(qd_pulses1[qubit]) sequence.add(qd_pulses2[qubit]) sequence.add(ro_pulses[qubit]) @@ -275,7 +314,10 @@ def test_qmsim_sweep_duration_two_pulses(simulator, folder): pulses = [qd_pulses1[qubit] for qubit in qubits] sweeper = Sweeper(Parameter.duration, values, pulses=pulses) options = ExecutionParameters( - nshots=1, relaxation_time=0, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC + nshots=1, + relaxation_time=0, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, ) result = simulator.sweep(sequence, options, sweeper) samples = result.get_simulated_samples() @@ -314,9 +356,15 @@ def test_qmsim_allxy(simulator, folder, count, gate_pair): allxy_pulses = { "I": lambda qubit, start: None, "RX(pi)": lambda qubit, start: simulator.create_RX_pulse(qubit, start=start), - "RX(pi/2)": lambda qubit, start: simulator.create_RX90_pulse(qubit, start=start), - "RY(pi)": lambda qubit, start: simulator.create_RX_pulse(qubit, start=start, relative_phase=np.pi / 2), - "RY(pi/2)": lambda qubit, start: simulator.create_RX90_pulse(qubit, start=start, relative_phase=np.pi / 2), + "RX(pi/2)": lambda qubit, start: simulator.create_RX90_pulse( + qubit, start=start + ), + "RY(pi)": lambda qubit, start: simulator.create_RX_pulse( + qubit, start=start, relative_phase=np.pi / 2 + ), + "RY(pi/2)": lambda qubit, start: simulator.create_RX90_pulse( + qubit, start=start, relative_phase=np.pi / 2 + ), } sequence = PulseSequence() @@ -348,8 +396,12 @@ def test_qmsim_chevron(simulator, folder, sweep): channel=simulator.qubits[highfreq].flux.name, qubit=highfreq, ) - measure_lowfreq = simulator.create_qubit_readout_pulse(lowfreq, start=flux_pulse.finish) - measure_highfreq = simulator.create_qubit_readout_pulse(highfreq, start=flux_pulse.finish) + measure_lowfreq = simulator.create_qubit_readout_pulse( + lowfreq, start=flux_pulse.finish + ) + measure_highfreq = simulator.create_qubit_readout_pulse( + highfreq, start=flux_pulse.finish + ) sequence = PulseSequence() sequence.add(initialize_1) sequence.add(initialize_2) @@ -358,7 +410,10 @@ def test_qmsim_chevron(simulator, folder, sweep): sequence.add(measure_highfreq) options = ExecutionParameters( - nshots=1, relaxation_time=0, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC + nshots=1, + relaxation_time=0, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, ) if sweep is None: result = simulator.execute_pulse_sequence(sequence, options) @@ -392,14 +447,26 @@ def test_qmsim_tune_landscape(simulator, folder, qubits, use_flux_pulse): channel=simulator.qubits[highfreq].flux.name, qubit=highfreq, ) - theta_pulse = simulator.create_RX90_pulse(lowfreq, start=flux_pulse.se_finish, relative_phase=np.pi / 3) - x_pulse_end = simulator.create_RX_pulse(highfreq, start=flux_pulse.se_finish, relative_phase=0) + theta_pulse = simulator.create_RX90_pulse( + lowfreq, start=flux_pulse.se_finish, relative_phase=np.pi / 3 + ) + x_pulse_end = simulator.create_RX_pulse( + highfreq, start=flux_pulse.se_finish, relative_phase=0 + ) else: - theta_pulse = simulator.create_RX90_pulse(lowfreq, start=y90_pulse.se_finish, relative_phase=np.pi / 3) - x_pulse_end = simulator.create_RX_pulse(highfreq, start=x_pulse_start.se_finish, relative_phase=0) + theta_pulse = simulator.create_RX90_pulse( + lowfreq, start=y90_pulse.se_finish, relative_phase=np.pi / 3 + ) + x_pulse_end = simulator.create_RX_pulse( + highfreq, start=x_pulse_start.se_finish, relative_phase=0 + ) - measure_lowfreq = simulator.create_qubit_readout_pulse(lowfreq, start=theta_pulse.se_finish) - measure_highfreq = simulator.create_qubit_readout_pulse(highfreq, start=x_pulse_end.se_finish) + measure_lowfreq = simulator.create_qubit_readout_pulse( + lowfreq, start=theta_pulse.se_finish + ) + measure_highfreq = simulator.create_qubit_readout_pulse( + highfreq, start=x_pulse_end.se_finish + ) sequence = x_pulse_start + y90_pulse if use_flux_pulse: diff --git a/tests/test_instruments_qutech.py b/tests/test_instruments_qutech.py index 699f77caee..9f354b2353 100644 --- a/tests/test_instruments_qutech.py +++ b/tests/test_instruments_qutech.py @@ -15,7 +15,9 @@ def spi(connected_platform): def test_instruments_qutech_init(spi): assert spi.is_connected == True assert spi.device is None - assert spi.data_folder == INSTRUMENTS_DATA_FOLDER / spi.tmp_folder.name.split("/")[-1] + assert ( + spi.data_folder == INSTRUMENTS_DATA_FOLDER / spi.tmp_folder.name.split("/")[-1] + ) @pytest.mark.qpu diff --git a/tests/test_instruments_rfsoc.py b/tests/test_instruments_rfsoc.py index 7d6fcdeccc..a6a9ad3c56 100644 --- a/tests/test_instruments_rfsoc.py +++ b/tests/test_instruments_rfsoc.py @@ -1,4 +1,4 @@ -"""Tests for RFSoC driver""" +"""Tests for RFSoC driver.""" from dataclasses import asdict @@ -47,7 +47,8 @@ def test_convert_default(dummy_qrc): def test_convert_qubit(dummy_qrc): - """Tests conversion from `qibolab.platforms.abstract.Qubit` to `rfsoc.Qubit`. + """Tests conversion from `qibolab.platforms.abstract.Qubit` to + `rfsoc.Qubit`. Test conversion for flux qubit and for non-flux qubit. """ @@ -107,11 +108,15 @@ def test_convert_pulse(dummy_qrc): qubit.readout.local_oscillator.frequency = 1e6 pulse = Pulse(0, 40, 0.9, 50e6, 0, Drag(5, 2), 0, PulseType.DRIVE, 0) - targ = rfsoc_pulses.Drag(50, 0.9, 0, 0, 0.04, pulse.serial, "drive", 4, None, rel_sigma=5, beta=2) + targ = rfsoc_pulses.Drag( + 50, 0.9, 0, 0, 0.04, pulse.serial, "drive", 4, None, rel_sigma=5, beta=2 + ) assert convert(pulse, platform.qubits, 0) == targ pulse = Pulse(0, 40, 0.9, 50e6, 0, Gaussian(2), 0, PulseType.DRIVE, 0) - targ = rfsoc_pulses.Gaussian(50, 0.9, 0, 0, 0.04, pulse.serial, "drive", 4, None, rel_sigma=2) + targ = rfsoc_pulses.Gaussian( + 50, 0.9, 0, 0, 0.04, pulse.serial, "drive", 4, None, rel_sigma=2 + ) assert convert(pulse, platform.qubits, 0) == targ pulse = Pulse(0, 40, 0.9, 50e6, 0, Rectangular(), 0, PulseType.READOUT, 0) @@ -122,7 +127,8 @@ def test_convert_pulse(dummy_qrc): def test_convert_units_sweeper(dummy_qrc): """Tests units conversion for `rfsoc.Sweeper` objects. - Test frequency conversion (with and without LO), start and relative phase sweepers. + Test frequency conversion (with and without LO), start and relative + phase sweepers. """ platform = create_platform("rfsoc") qubit = platform.qubits[0] @@ -138,14 +144,26 @@ def test_convert_units_sweeper(dummy_qrc): seq.add(pulse1) # frequency sweeper - sweeper = rfsoc.Sweeper(parameters=[rfsoc.Parameter.FREQUENCY], indexes=[1], starts=[0], stops=[10e6], expts=100) + sweeper = rfsoc.Sweeper( + parameters=[rfsoc.Parameter.FREQUENCY], + indexes=[1], + starts=[0], + stops=[10e6], + expts=100, + ) convert_units_sweeper(sweeper, seq, platform.qubits) assert sweeper.starts == [-1] assert sweeper.stops == [9] qubit.readout.local_oscillator.frequency = 0 - sweeper = rfsoc.Sweeper(parameters=[rfsoc.Parameter.FREQUENCY], indexes=[1], starts=[0], stops=[10e6], expts=100) + sweeper = rfsoc.Sweeper( + parameters=[rfsoc.Parameter.FREQUENCY], + indexes=[1], + starts=[0], + stops=[10e6], + expts=100, + ) convert_units_sweeper(sweeper, seq, platform.qubits) assert sweeper.starts == [0] assert sweeper.stops == [10] @@ -164,7 +182,11 @@ def test_convert_units_sweeper(dummy_qrc): # phase sweeper sweeper = rfsoc.Sweeper( - parameters=[rfsoc.Parameter.RELATIVE_PHASE], indexes=[0], starts=[0], stops=[np.pi], expts=180 + parameters=[rfsoc.Parameter.RELATIVE_PHASE], + indexes=[0], + starts=[0], + stops=[np.pi], + expts=180, ) convert_units_sweeper(sweeper, seq, platform.qubits) assert sweeper.starts == [0] @@ -189,19 +211,36 @@ def test_convert_sweep(dummy_qrc): seq.add(pulse0) seq.add(pulse1) - sweeper = Sweeper(parameter=Parameter.bias, values=np.arange(-0.5, +0.5, 0.1), qubits=[qubit]) + sweeper = Sweeper( + parameter=Parameter.bias, values=np.arange(-0.5, +0.5, 0.1), qubits=[qubit] + ) rfsoc_sweeper = convert(sweeper, seq, platform.qubits) - targ = rfsoc.Sweeper(expts=10, parameters=[rfsoc.Parameter.BIAS], starts=[-0.5], stops=[0.4], indexes=[0]) + targ = rfsoc.Sweeper( + expts=10, + parameters=[rfsoc.Parameter.BIAS], + starts=[-0.5], + stops=[0.4], + indexes=[0], + ) assert targ.expts == rfsoc_sweeper.expts assert targ.parameters == rfsoc_sweeper.parameters assert targ.starts == rfsoc_sweeper.starts assert targ.stops == np.round(rfsoc_sweeper.stops, 2) assert targ.indexes == rfsoc_sweeper.indexes sweeper = Sweeper( - parameter=Parameter.bias, values=np.arange(-0.5, +0.5, 0.1), qubits=[qubit], type=SweeperType.OFFSET + parameter=Parameter.bias, + values=np.arange(-0.5, +0.5, 0.1), + qubits=[qubit], + type=SweeperType.OFFSET, ) rfsoc_sweeper = convert(sweeper, seq, platform.qubits) - targ = rfsoc.Sweeper(expts=10, parameters=[rfsoc.Parameter.BIAS], starts=[-0.45], stops=[0.45], indexes=[0]) + targ = rfsoc.Sweeper( + expts=10, + parameters=[rfsoc.Parameter.BIAS], + starts=[-0.45], + stops=[0.45], + indexes=[0], + ) assert targ.expts == rfsoc_sweeper.expts assert targ.parameters == rfsoc_sweeper.parameters assert targ.starts == rfsoc_sweeper.starts @@ -209,16 +248,31 @@ def test_convert_sweep(dummy_qrc): assert targ.indexes == rfsoc_sweeper.indexes qubit.flux.offset = 0.5 - sweeper = Sweeper(parameter=Parameter.bias, values=np.arange(0, +1, 0.1), qubits=[qubit], type=SweeperType.OFFSET) + sweeper = Sweeper( + parameter=Parameter.bias, + values=np.arange(0, +1, 0.1), + qubits=[qubit], + type=SweeperType.OFFSET, + ) with pytest.raises(ValueError): rfsoc_sweeper = convert(sweeper, seq, platform.qubits) - sweeper = Sweeper(parameter=Parameter.frequency, values=np.arange(0, 100, 1), pulses=[pulse0]) + sweeper = Sweeper( + parameter=Parameter.frequency, values=np.arange(0, 100, 1), pulses=[pulse0] + ) rfsoc_sweeper = convert(sweeper, seq, platform.qubits) - targ = rfsoc.Sweeper(expts=100, parameters=[rfsoc.Parameter.FREQUENCY], starts=[0], stops=[99], indexes=[0]) + targ = rfsoc.Sweeper( + expts=100, + parameters=[rfsoc.Parameter.FREQUENCY], + starts=[0], + stops=[99], + indexes=[0], + ) assert rfsoc_sweeper == targ - sweeper = Sweeper(parameter=Parameter.duration, values=np.arange(40, 100, 1), pulses=[pulse0]) + sweeper = Sweeper( + parameter=Parameter.duration, values=np.arange(40, 100, 1), pulses=[pulse0] + ) rfsoc_sweeper = convert(sweeper, seq, platform.qubits) targ = rfsoc.Sweeper( expts=60, @@ -233,7 +287,9 @@ def test_convert_sweep(dummy_qrc): assert rfsoc_sweeper.parameters == targ.parameters assert rfsoc_sweeper.indexes == targ.indexes - sweeper = Sweeper(parameter=Parameter.start, values=np.arange(0, 10, 1), pulses=[pulse0]) + sweeper = Sweeper( + parameter=Parameter.start, values=np.arange(0, 10, 1), pulses=[pulse0] + ) rfsoc_sweeper = convert(sweeper, seq, platform.qubits) targ = rfsoc.Sweeper( expts=10, @@ -246,7 +302,7 @@ def test_convert_sweep(dummy_qrc): def test_rfsoc_init(dummy_qrc): - """Tests instrument can initilize and its attribute are assigned""" + """Tests instrument can initilize and its attribute are assigned.""" platform = create_platform("rfsoc") instrument = platform.instruments["tii_rfsoc4x2"] @@ -269,19 +325,25 @@ def test_play(mocker, dummy_qrc): server_results = ([[np.random.rand(nshots)]], [[np.random.rand(nshots)]]) mocker.patch("qibosoq.client.connect", return_value=server_results) parameters = ExecutionParameters( - nshots=nshots, acquisition_type=AcquisitionType.DISCRIMINATION, averaging_mode=AveragingMode.SINGLESHOT + nshots=nshots, + acquisition_type=AcquisitionType.DISCRIMINATION, + averaging_mode=AveragingMode.SINGLESHOT, ) results = instrument.play(platform.qubits, platform.couplers, seq, parameters) assert pulse1.serial in results.keys() parameters = ExecutionParameters( - nshots=nshots, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.SINGLESHOT + nshots=nshots, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.SINGLESHOT, ) results = instrument.play(platform.qubits, platform.couplers, seq, parameters) assert pulse1.serial in results.keys() parameters = ExecutionParameters( - nshots=nshots, acquisition_type=AcquisitionType.DISCRIMINATION, averaging_mode=AveragingMode.CYCLIC + nshots=nshots, + acquisition_type=AcquisitionType.DISCRIMINATION, + averaging_mode=AveragingMode.CYCLIC, ) results = instrument.play(platform.qubits, platform.couplers, seq, parameters) assert pulse1.serial in results.keys() @@ -299,28 +361,44 @@ def test_sweep(mocker, dummy_qrc): pulse1 = Pulse(40, 40, 0.9, 50e6, 0, Rectangular(), 0, PulseType.READOUT, 0) seq.add(pulse0) seq.add(pulse1) - sweeper0 = Sweeper(parameter=Parameter.frequency, values=np.arange(0, 100, 1), pulses=[pulse0]) - sweeper1 = Sweeper(parameter=Parameter.bias, values=np.arange(0, 0.1, 0.01), qubits=[qubit]) + sweeper0 = Sweeper( + parameter=Parameter.frequency, values=np.arange(0, 100, 1), pulses=[pulse0] + ) + sweeper1 = Sweeper( + parameter=Parameter.bias, values=np.arange(0, 0.1, 0.01), qubits=[qubit] + ) nshots = 100 server_results = ([[np.random.rand(nshots)]], [[np.random.rand(nshots)]]) mocker.patch("qibosoq.client.connect", return_value=server_results) parameters = ExecutionParameters( - nshots=nshots, acquisition_type=AcquisitionType.DISCRIMINATION, averaging_mode=AveragingMode.SINGLESHOT + nshots=nshots, + acquisition_type=AcquisitionType.DISCRIMINATION, + averaging_mode=AveragingMode.SINGLESHOT, + ) + results = instrument.sweep( + platform.qubits, platform.couplers, seq, parameters, sweeper0, sweeper1 ) - results = instrument.sweep(platform.qubits, platform.couplers, seq, parameters, sweeper0, sweeper1) assert pulse1.serial in results.keys() parameters = ExecutionParameters( - nshots=nshots, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.SINGLESHOT + nshots=nshots, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.SINGLESHOT, + ) + results = instrument.sweep( + platform.qubits, platform.couplers, seq, parameters, sweeper0, sweeper1 ) - results = instrument.sweep(platform.qubits, platform.couplers, seq, parameters, sweeper0, sweeper1) assert pulse1.serial in results.keys() parameters = ExecutionParameters( - nshots=nshots, acquisition_type=AcquisitionType.DISCRIMINATION, averaging_mode=AveragingMode.CYCLIC + nshots=nshots, + acquisition_type=AcquisitionType.DISCRIMINATION, + averaging_mode=AveragingMode.CYCLIC, + ) + results = instrument.sweep( + platform.qubits, platform.couplers, seq, parameters, sweeper0, sweeper1 ) - results = instrument.sweep(platform.qubits, platform.couplers, seq, parameters, sweeper0, sweeper1) assert pulse1.serial in results.keys() @@ -370,7 +448,7 @@ def test_update_cfg(mocker, dummy_qrc): def test_classify_shots(dummy_qrc): - """Creates fake IQ values and check classification works as expected""" + """Creates fake IQ values and check classification works as expected.""" qubit0 = Qubit(name="q0", threshold=1, iq_angle=np.pi / 2) qubit1 = Qubit( name="q1", @@ -390,7 +468,8 @@ def test_classify_shots(dummy_qrc): def test_merge_sweep_results(dummy_qrc): - """Creates fake dictionary of results and check merging works as expected""" + """Creates fake dictionary of results and check merging works as + expected.""" dict_a = {"serial1": AveragedIntegratedResults(np.array([0 + 1j * 1]))} dict_b = { "serial1": AveragedIntegratedResults(np.array([4 + 1j * 4])), @@ -409,20 +488,33 @@ def test_merge_sweep_results(dummy_qrc): out_dict2 = instrument.merge_sweep_results(dict_c, dict_a) assert targ_dict.keys() == out_dict1.keys() - assert (out_dict1["serial1"].serialize["MSR[V]"] == targ_dict["serial1"].serialize["MSR[V]"]).all() - assert (out_dict1["serial1"].serialize["MSR[V]"] == targ_dict["serial1"].serialize["MSR[V]"]).all() + assert ( + out_dict1["serial1"].serialize["MSR[V]"] + == targ_dict["serial1"].serialize["MSR[V]"] + ).all() + assert ( + out_dict1["serial1"].serialize["MSR[V]"] + == targ_dict["serial1"].serialize["MSR[V]"] + ).all() assert dict_a.keys() == out_dict2.keys() - assert (out_dict2["serial1"].serialize["MSR[V]"] == dict_a["serial1"].serialize["MSR[V]"]).all() - assert (out_dict2["serial1"].serialize["MSR[V]"] == dict_a["serial1"].serialize["MSR[V]"]).all() + assert ( + out_dict2["serial1"].serialize["MSR[V]"] + == dict_a["serial1"].serialize["MSR[V]"] + ).all() + assert ( + out_dict2["serial1"].serialize["MSR[V]"] + == dict_a["serial1"].serialize["MSR[V]"] + ).all() def test_get_if_python_sweep(dummy_qrc): """Creates pulse sequences and check if they can be swept by the firmware. - Qibosoq does not support sweep on readout frequency, more than one sweep - at the same time, sweep on channels where multiple pulses are sent. - If Qibosoq does not support the sweep, the driver will use a python loop + Qibosoq does not support sweep on readout frequency, more than one + sweep at the same time, sweep on channels where multiple pulses are + sent. If Qibosoq does not support the sweep, the driver will use a + python loop """ platform = create_platform("rfsoc") @@ -432,9 +524,21 @@ def test_get_if_python_sweep(dummy_qrc): sequence_1.add(platform.create_RX_pulse(qubit=0, start=0)) sequence_1.add(platform.create_MZ_pulse(qubit=0, start=100)) - sweep1 = Sweeper(parameter=Parameter.frequency, values=np.arange(10, 100, 10), pulses=[sequence_1[0]]) - sweep2 = Sweeper(parameter=Parameter.frequency, values=np.arange(10, 100, 10), pulses=[sequence_1[1]]) - sweep3 = Sweeper(parameter=Parameter.amplitude, values=np.arange(0.01, 0.5, 0.1), pulses=[sequence_1[1]]) + sweep1 = Sweeper( + parameter=Parameter.frequency, + values=np.arange(10, 100, 10), + pulses=[sequence_1[0]], + ) + sweep2 = Sweeper( + parameter=Parameter.frequency, + values=np.arange(10, 100, 10), + pulses=[sequence_1[1]], + ) + sweep3 = Sweeper( + parameter=Parameter.amplitude, + values=np.arange(0.01, 0.5, 0.1), + pulses=[sequence_1[1]], + ) sweep1 = convert(sweep1, sequence_1, platform.qubits) sweep2 = convert(sweep2, sequence_1, platform.qubits) sweep3 = convert(sweep3, sequence_1, platform.qubits) @@ -446,8 +550,16 @@ def test_get_if_python_sweep(dummy_qrc): sequence_2 = PulseSequence() sequence_2.add(platform.create_RX_pulse(qubit=0, start=0)) - sweep1 = Sweeper(parameter=Parameter.frequency, values=np.arange(10, 100, 10), pulses=[sequence_2[0]]) - sweep2 = Sweeper(parameter=Parameter.amplitude, values=np.arange(0.01, 0.5, 0.1), pulses=[sequence_2[0]]) + sweep1 = Sweeper( + parameter=Parameter.frequency, + values=np.arange(10, 100, 10), + pulses=[sequence_2[0]], + ) + sweep2 = Sweeper( + parameter=Parameter.amplitude, + values=np.arange(0.01, 0.5, 0.1), + pulses=[sequence_2[0]], + ) sweep1 = convert(sweep1, sequence_2, platform.qubits) sweep2 = convert(sweep2, sequence_2, platform.qubits) @@ -460,9 +572,21 @@ def test_get_if_python_sweep(dummy_qrc): sequence_1 = PulseSequence() sequence_1.add(platform.create_RX_pulse(qubit=0, start=0)) - sweep1 = Sweeper(parameter=Parameter.frequency, values=np.arange(10, 100, 10), pulses=[sequence_1[0]]) - sweep2 = Sweeper(parameter=Parameter.relative_phase, values=np.arange(0, 1, 0.01), pulses=[sequence_1[0]]) - sweep3 = Sweeper(parameter=Parameter.bias, values=np.arange(-0.1, 0.1, 0.001), qubits=[platform.qubits[0]]) + sweep1 = Sweeper( + parameter=Parameter.frequency, + values=np.arange(10, 100, 10), + pulses=[sequence_1[0]], + ) + sweep2 = Sweeper( + parameter=Parameter.relative_phase, + values=np.arange(0, 1, 0.01), + pulses=[sequence_1[0]], + ) + sweep3 = Sweeper( + parameter=Parameter.bias, + values=np.arange(-0.1, 0.1, 0.001), + qubits=[platform.qubits[0]], + ) sweep1 = convert(sweep1, sequence_1, platform.qubits) sweep2 = convert(sweep2, sequence_1, platform.qubits) sweep3 = convert(sweep3, sequence_1, platform.qubits) @@ -475,9 +599,8 @@ def test_get_if_python_sweep(dummy_qrc): def test_convert_av_sweep_results(dummy_qrc): - """Qibosoq sends results using nested lists, check if the conversion - to dictionary of AveragedResults, for averaged sweep, works as expected - """ + """Qibosoq sends results using nested lists, check if the conversion to + dictionary of AveragedResults, for averaged sweep, works as expected.""" platform = create_platform("rfsoc") instrument = platform.instruments["tii_rfsoc4x2"] @@ -486,7 +609,11 @@ def test_convert_av_sweep_results(dummy_qrc): sequence.add(platform.create_RX_pulse(qubit=0, start=0)) sequence.add(platform.create_MZ_pulse(qubit=0, start=100)) sequence.add(platform.create_MZ_pulse(qubit=0, start=200)) - sweep1 = Sweeper(parameter=Parameter.frequency, values=np.arange(10, 35, 10), pulses=[sequence[0]]) + sweep1 = Sweeper( + parameter=Parameter.frequency, + values=np.arange(10, 35, 10), + pulses=[sequence[0]], + ) sweep1 = convert(sweep1, sequence, platform.qubits) serial1 = sequence[1].serial serial2 = sequence[2].serial @@ -495,24 +622,39 @@ def test_convert_av_sweep_results(dummy_qrc): avgq = [[[7, 8, 9], [-1, -2, -3]]] execution_parameters = ExecutionParameters( - acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, + ) + out_dict = instrument.convert_sweep_results( + sequence.ro_pulses, platform.qubits, avgi, avgq, execution_parameters ) - out_dict = instrument.convert_sweep_results(sequence.ro_pulses, platform.qubits, avgi, avgq, execution_parameters) targ_dict = { - serial1: AveragedIntegratedResults(np.array([1, 2, 3]) + 1j * np.array([7, 8, 9])), - serial2: AveragedIntegratedResults(np.array([4, 1, 2]) + 1j * np.array([-1, -2, -3])), + serial1: AveragedIntegratedResults( + np.array([1, 2, 3]) + 1j * np.array([7, 8, 9]) + ), + serial2: AveragedIntegratedResults( + np.array([4, 1, 2]) + 1j * np.array([-1, -2, -3]) + ), } - assert (out_dict[serial1].serialize["i[V]"] == targ_dict[serial1].serialize["i[V]"]).all() - assert (out_dict[serial1].serialize["q[V]"] == targ_dict[serial1].serialize["q[V]"]).all() - assert (out_dict[serial2].serialize["i[V]"] == targ_dict[serial2].serialize["i[V]"]).all() - assert (out_dict[serial2].serialize["q[V]"] == targ_dict[serial2].serialize["q[V]"]).all() + assert ( + out_dict[serial1].serialize["i[V]"] == targ_dict[serial1].serialize["i[V]"] + ).all() + assert ( + out_dict[serial1].serialize["q[V]"] == targ_dict[serial1].serialize["q[V]"] + ).all() + assert ( + out_dict[serial2].serialize["i[V]"] == targ_dict[serial2].serialize["i[V]"] + ).all() + assert ( + out_dict[serial2].serialize["q[V]"] == targ_dict[serial2].serialize["q[V]"] + ).all() def test_convert_nav_sweep_results(dummy_qrc): - """Qibosoq sends results using nested lists, check if the conversion - to dictionary of ExecutionResults, for not averaged sweep, works as expected - """ + """Qibosoq sends results using nested lists, check if the conversion to + dictionary of ExecutionResults, for not averaged sweep, works as + expected.""" platform = create_platform("rfsoc") instrument = platform.instruments["tii_rfsoc4x2"] @@ -520,7 +662,11 @@ def test_convert_nav_sweep_results(dummy_qrc): sequence.add(platform.create_RX_pulse(qubit=0, start=0)) sequence.add(platform.create_MZ_pulse(qubit=0, start=100)) sequence.add(platform.create_MZ_pulse(qubit=0, start=200)) - sweep1 = Sweeper(parameter=Parameter.frequency, values=np.arange(10, 35, 10), pulses=[sequence[0]]) + sweep1 = Sweeper( + parameter=Parameter.frequency, + values=np.arange(10, 35, 10), + pulses=[sequence[0]], + ) sweep1 = convert(sweep1, sequence, platform.qubits) serial1 = sequence[1].serial serial2 = sequence[2].serial @@ -529,18 +675,33 @@ def test_convert_nav_sweep_results(dummy_qrc): avgq = [[[[7, 7], [8, 8], [9, 9]], [[-1, -1], [-2, -2], [-3, -3]]]] execution_parameters = ExecutionParameters( - acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, + ) + out_dict = instrument.convert_sweep_results( + sequence.ro_pulses, platform.qubits, avgi, avgq, execution_parameters ) - out_dict = instrument.convert_sweep_results(sequence.ro_pulses, platform.qubits, avgi, avgq, execution_parameters) targ_dict = { - serial1: AveragedIntegratedResults(np.array([1, 1, 2, 2, 3, 3]) + 1j * np.array([7, 7, 8, 8, 9, 9])), - serial2: AveragedIntegratedResults(np.array([4, 4, 1, 1, 2, 2]) + 1j * np.array([-1, -1, -2, -2, -3, -3])), + serial1: AveragedIntegratedResults( + np.array([1, 1, 2, 2, 3, 3]) + 1j * np.array([7, 7, 8, 8, 9, 9]) + ), + serial2: AveragedIntegratedResults( + np.array([4, 4, 1, 1, 2, 2]) + 1j * np.array([-1, -1, -2, -2, -3, -3]) + ), } - assert (out_dict[serial1].serialize["i[V]"] == targ_dict[serial1].serialize["i[V]"]).all() - assert (out_dict[serial1].serialize["q[V]"] == targ_dict[serial1].serialize["q[V]"]).all() - assert (out_dict[serial2].serialize["i[V]"] == targ_dict[serial2].serialize["i[V]"]).all() - assert (out_dict[serial2].serialize["q[V]"] == targ_dict[serial2].serialize["q[V]"]).all() + assert ( + out_dict[serial1].serialize["i[V]"] == targ_dict[serial1].serialize["i[V]"] + ).all() + assert ( + out_dict[serial1].serialize["q[V]"] == targ_dict[serial1].serialize["q[V]"] + ).all() + assert ( + out_dict[serial2].serialize["i[V]"] == targ_dict[serial2].serialize["i[V]"] + ).all() + assert ( + out_dict[serial2].serialize["q[V]"] == targ_dict[serial2].serialize["q[V]"] + ).all() @pytest.fixture(scope="module") @@ -551,6 +712,7 @@ def instrument(connected_platform): @pytest.mark.qpu def test_call_executepulsesequence(connected_platform, instrument): """Executes a PulseSequence and check if result shape is as expected. + Both for averaged results and not averaged results. """ platform = connected_platform @@ -587,12 +749,18 @@ def test_call_execute_sweeps(connected_platform, instrument): sequence = PulseSequence() sequence.add(platform.create_RX_pulse(qubit=0, start=0)) sequence.add(platform.create_MZ_pulse(qubit=0, start=100)) - sweep = Sweeper(parameter=Parameter.frequency, values=np.arange(10, 35, 10), pulses=[sequence[0]]) + sweep = Sweeper( + parameter=Parameter.frequency, + values=np.arange(10, 35, 10), + pulses=[sequence[0]], + ) expts = len(sweep.values) sweep = [convert(sweep, sequence, platform.qubits)] instrument.cfg.average = False - i_vals_nav, q_vals_nav = instrument._execute_sweeps(sequence, platform.qubits, sweep) + i_vals_nav, q_vals_nav = instrument._execute_sweeps( + sequence, platform.qubits, sweep + ) instrument.cfg.average = True i_vals_av, q_vals_av = instrument._execute_sweeps(sequence, platform.qubits, sweep) @@ -604,7 +772,8 @@ def test_call_execute_sweeps(connected_platform, instrument): @pytest.mark.qpu def test_play_qpu(connected_platform, instrument): - """Sends a PulseSequence using `play` and check results are what expected""" + """Sends a PulseSequence using `play` and check results are what + expected.""" platform = connected_platform instrument = platform.instruments["tii_rfsoc4x2"] @@ -613,7 +782,9 @@ def test_play_qpu(connected_platform, instrument): sequence.add(platform.create_MZ_pulse(qubit=0, start=100)) out_dict = instrument.play( - platform.qubits, sequence, ExecutionParameters(acquisition_type=AcquisitionType.INTEGRATION) + platform.qubits, + sequence, + ExecutionParameters(acquisition_type=AcquisitionType.INTEGRATION), ) assert sequence[1].serial in out_dict @@ -623,20 +794,27 @@ def test_play_qpu(connected_platform, instrument): @pytest.mark.qpu def test_sweep_qpu(connected_platform, instrument): - """Sends a PulseSequence using `sweep` and check results are what expected""" + """Sends a PulseSequence using `sweep` and check results are what + expected.""" platform = connected_platform instrument = platform.instruments["tii_rfsoc4x2"] sequence = PulseSequence() sequence.add(platform.create_RX_pulse(qubit=0, start=0)) sequence.add(platform.create_MZ_pulse(qubit=0, start=100)) - sweep = Sweeper(parameter=Parameter.frequency, values=np.arange(10, 35, 10), pulses=[sequence[0]]) + sweep = Sweeper( + parameter=Parameter.frequency, + values=np.arange(10, 35, 10), + pulses=[sequence[0]], + ) out_dict1 = instrument.sweep( platform.qubits, platform.couplers, sequence, - ExecutionParameters(relaxation_time=100_000, averaging_mode=AveragingMode.CYCLIC), + ExecutionParameters( + relaxation_time=100_000, averaging_mode=AveragingMode.CYCLIC + ), sweep, ) out_dict2 = instrument.sweep( @@ -655,27 +833,43 @@ def test_sweep_qpu(connected_platform, instrument): assert sequence[1].serial in out_dict2 assert isinstance(out_dict1[sequence[1].serial], AveragedSampleResults) assert isinstance(out_dict2[sequence[1].serial], IntegratedResults) - assert np.shape(out_dict2[sequence[1].serial].voltage_i) == (1000, len(sweep.values)) - assert np.shape(out_dict1[sequence[1].serial].statistical_frequency) == (len(sweep.values),) + assert np.shape(out_dict2[sequence[1].serial].voltage_i) == ( + 1000, + len(sweep.values), + ) + assert np.shape(out_dict1[sequence[1].serial].statistical_frequency) == ( + len(sweep.values), + ) @pytest.mark.qpu def test_python_reqursive_sweep(connected_platform, instrument): - """Sends a PulseSequence directly to `python_reqursive_sweep` and check results are what expected""" + """Sends a PulseSequence directly to `python_reqursive_sweep` and check + results are what expected.""" platform = connected_platform instrument = platform.instruments["tii_rfsoc4x2"] sequence = PulseSequence() sequence.add(platform.create_RX_pulse(qubit=0, start=0)) sequence.add(platform.create_MZ_pulse(qubit=0, start=100)) - sweep1 = Sweeper(parameter=Parameter.amplitude, values=np.arange(0.01, 0.03, 10), pulses=[sequence[0]]) - sweep2 = Sweeper(parameter=Parameter.frequency, values=np.arange(10, 35, 10), pulses=[sequence[0]]) + sweep1 = Sweeper( + parameter=Parameter.amplitude, + values=np.arange(0.01, 0.03, 10), + pulses=[sequence[0]], + ) + sweep2 = Sweeper( + parameter=Parameter.frequency, + values=np.arange(10, 35, 10), + pulses=[sequence[0]], + ) out_dict = instrument.sweep( platform.qubits, platform.couplers, sequence, - ExecutionParameters(relaxation_time=100_000, averaging_mode=AveragingMode.CYCLIC), + ExecutionParameters( + relaxation_time=100_000, averaging_mode=AveragingMode.CYCLIC + ), sweep1, sweep2, ) diff --git a/tests/test_instruments_rohde_schwarz.py b/tests/test_instruments_rohde_schwarz.py index a6ff4166d8..06cef9c56a 100644 --- a/tests/test_instruments_rohde_schwarz.py +++ b/tests/test_instruments_rohde_schwarz.py @@ -42,7 +42,9 @@ def test_instruments_rohde_schwarz_set_device_paramter(instrument): set_and_test_parameter_values( instrument, f"power", np.arange(-120, 0, 10) ) # Max power is 25dBm but to be safe testing only until 0dBm - set_and_test_parameter_values(instrument, f"frequency", np.arange(1e6, 12750e6, 1e9)) + set_and_test_parameter_values( + instrument, f"frequency", np.arange(1e6, 12750e6, 1e9) + ) """ # TODO: add attitional paramter tests SGS100A: parameter value diff --git a/tests/test_instruments_zhinst.py b/tests/test_instruments_zhinst.py index 4e1cf3d670..990a0eb080 100644 --- a/tests/test_instruments_zhinst.py +++ b/tests/test_instruments_zhinst.py @@ -23,7 +23,9 @@ from .conftest import get_instrument -@pytest.mark.parametrize("shape", ["Rectangular", "Gaussian", "GaussianSquare", "Drag", "SNZ", "IIR"]) +@pytest.mark.parametrize( + "shape", ["Rectangular", "Gaussian", "GaussianSquare", "Drag", "SNZ", "IIR"] +) def test_zhpulse(shape): if shape == "Rectangular": pulse = Pulse(0, 40, 0.05, int(3e9), 0.0, Rectangular(), "ch0", qubit=0) @@ -36,12 +38,23 @@ def test_zhpulse(shape): if shape == "SNZ": pulse = Pulse(0, 40, 0.05, int(3e9), 0.0, SNZ(10, 0.01), "ch0", qubit=0) if shape == "IIR": - pulse = Pulse(0, 40, 0.05, int(3e9), 0.0, IIR([10, 1], [0.4, 1], target=Gaussian(5)), "ch0", qubit=0) + pulse = Pulse( + 0, + 40, + 0.05, + int(3e9), + 0.0, + IIR([10, 1], [0.4, 1], target=Gaussian(5)), + "ch0", + qubit=0, + ) zhpulse = ZhPulse(pulse) assert zhpulse.pulse.serial == pulse.serial if shape == "SNZ" or shape == "IIR": - assert len(zhpulse.zhpulse.samples) == 40 / 1e9 * 1e9 # * 2e9 When pulses stop hardcoding SamplingRate + assert ( + len(zhpulse.zhpulse.samples) == 40 / 1e9 * 1e9 + ) # * 2e9 When pulses stop hardcoding SamplingRate else: assert zhpulse.zhpulse.length == 40e-9 @@ -141,7 +154,9 @@ def test_zhsequence_couplers_sweeper(dummy_qrc): zhsequence = controller.sequence with pytest.raises(AttributeError): - controller.sequence_zh("sequence", IQM5q.qubits, IQM5q.couplers, sweepers=[sweeper]) + controller.sequence_zh( + "sequence", IQM5q.qubits, IQM5q.couplers, sweepers=[sweeper] + ) zhsequence = controller.sequence assert len(zhsequence) == 2 @@ -164,7 +179,9 @@ def test_zhsequence_multiple_ro(dummy_qrc): zhsequence = controller.sequence with pytest.raises(AttributeError): - controller.sequence_zh("sequence", platform.qubits, platform.couplers, sweepers=[]) + controller.sequence_zh( + "sequence", platform.qubits, platform.couplers, sweepers=[] + ) zhsequence = controller.sequence assert len(zhsequence) == 2 @@ -177,14 +194,20 @@ def test_zhinst_register_readout_line(dummy_qrc): IQM5q = platform.instruments["EL_ZURO"] options = ExecutionParameters( - relaxation_time=300e-6, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC + relaxation_time=300e-6, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, ) - IQM5q.register_readout_line(platform.qubits[0], intermediate_frequency=int(1e6), options=options) + IQM5q.register_readout_line( + platform.qubits[0], intermediate_frequency=int(1e6), options=options + ) assert "measure0" in IQM5q.signal_map assert "acquire0" in IQM5q.signal_map - assert "/logical_signal_groups/q0/measure_line" in IQM5q.calibration.calibration_items + assert ( + "/logical_signal_groups/q0/measure_line" in IQM5q.calibration.calibration_items + ) def test_zhinst_register_drive_line(dummy_qrc): @@ -234,7 +257,9 @@ def test_experiment_execute_pulse_sequence(dummy_qrc): sequence.add(ro_pulses[q]) options = ExecutionParameters( - relaxation_time=300e-6, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC + relaxation_time=300e-6, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, ) IQM5q.experiment_flow(qubits, couplers, sequence, options) @@ -285,7 +310,9 @@ def test_experiment_execute_pulse_sequence_coupler(dummy_qrc): sequence.add(cf_pulses[c]) options = ExecutionParameters( - relaxation_time=300e-6, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC + relaxation_time=300e-6, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, ) IQM5q.experiment_flow(qubits, couplers, sequence, options) @@ -345,7 +372,9 @@ def test_experiment_execute_pulse_sequence(dummy_qrc, fast_reset): fr_pulses[qubit] = platform.create_RX_pulse(qubit, start=0) qd_pulses[qubit] = platform.create_RX_pulse(qubit, start=0) sequence.add(qd_pulses[qubit]) - ro_pulses[qubit] = platform.create_qubit_readout_pulse(qubit, start=qd_pulses[qubit].finish) + ro_pulses[qubit] = platform.create_qubit_readout_pulse( + qubit, start=qd_pulses[qubit].finish + ) sequence.add(ro_pulses[qubit]) qf_pulses[qubit] = FluxPulse( start=0, @@ -392,7 +421,9 @@ def test_experiment_sweep_single(dummy_qrc, parameter1): for qubit in qubits: qd_pulses[qubit] = platform.create_RX_pulse(qubit, start=0) sequence.add(qd_pulses[qubit]) - ro_pulses[qubit] = platform.create_qubit_readout_pulse(qubit, start=qd_pulses[qubit].finish) + ro_pulses[qubit] = platform.create_qubit_readout_pulse( + qubit, start=qd_pulses[qubit].finish + ) sequence.add(ro_pulses[qubit]) parameter_range_1 = ( @@ -405,7 +436,9 @@ def test_experiment_sweep_single(dummy_qrc, parameter1): sweepers.append(Sweeper(parameter1, parameter_range_1, pulses=[qd_pulses[qubit]])) options = ExecutionParameters( - relaxation_time=300e-6, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC + relaxation_time=300e-6, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, ) IQM5q.sweepers = sweepers @@ -434,7 +467,9 @@ def test_experiment_sweep_single_coupler(dummy_qrc, parameter1): for qubit in qubits: qd_pulses[qubit] = platform.create_RX_pulse(qubit, start=0) sequence.add(qd_pulses[qubit]) - ro_pulses[qubit] = platform.create_qubit_readout_pulse(qubit, start=qd_pulses[qubit].finish) + ro_pulses[qubit] = platform.create_qubit_readout_pulse( + qubit, start=qd_pulses[qubit].finish + ) sequence.add(ro_pulses[qubit]) cf_pulses = {} @@ -460,7 +495,9 @@ def test_experiment_sweep_single_coupler(dummy_qrc, parameter1): sweepers.append(Sweeper(parameter1, parameter_range_1, pulses=[cf_pulses[c]])) options = ExecutionParameters( - relaxation_time=300e-6, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC + relaxation_time=300e-6, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, ) IQM5q.sweepers = sweepers @@ -500,7 +537,9 @@ def test_experiment_sweep_2d_general(dummy_qrc, parameter1, parameter2): for qubit in qubits: qd_pulses[qubit] = platform.create_RX_pulse(qubit, start=0) sequence.add(qd_pulses[qubit]) - ro_pulses[qubit] = platform.create_qubit_readout_pulse(qubit, start=qd_pulses[qubit].finish) + ro_pulses[qubit] = platform.create_qubit_readout_pulse( + qubit, start=qd_pulses[qubit].finish + ) sequence.add(ro_pulses[qubit]) parameter_range_1 = ( @@ -518,14 +557,20 @@ def test_experiment_sweep_2d_general(dummy_qrc, parameter1, parameter2): sweepers = [] if parameter1 in SweeperParameter: if parameter1 is not Parameter.start: - sweepers.append(Sweeper(parameter1, parameter_range_1, pulses=[ro_pulses[qubit]])) + sweepers.append( + Sweeper(parameter1, parameter_range_1, pulses=[ro_pulses[qubit]]) + ) if parameter2 in SweeperParameter: if parameter2 is Parameter.amplitude: if parameter1 is not Parameter.amplitude: - sweepers.append(Sweeper(parameter2, parameter_range_2, pulses=[qd_pulses[qubit]])) + sweepers.append( + Sweeper(parameter2, parameter_range_2, pulses=[qd_pulses[qubit]]) + ) options = ExecutionParameters( - relaxation_time=300e-6, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC + relaxation_time=300e-6, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, ) IQM5q.sweepers = sweepers @@ -553,7 +598,9 @@ def test_experiment_sweep_2d_specific(dummy_qrc): for qubit in qubits: qd_pulses[qubit] = platform.create_RX_pulse(qubit, start=0) sequence.add(qd_pulses[qubit]) - ro_pulses[qubit] = platform.create_qubit_readout_pulse(qubit, start=qd_pulses[qubit].finish) + ro_pulses[qubit] = platform.create_qubit_readout_pulse( + qubit, start=qd_pulses[qubit].finish + ) sequence.add(ro_pulses[qubit]) parameter1 = Parameter.relative_phase @@ -576,7 +623,9 @@ def test_experiment_sweep_2d_specific(dummy_qrc): sweepers.append(Sweeper(parameter2, parameter_range_2, pulses=[qd_pulses[qubit]])) options = ExecutionParameters( - relaxation_time=300e-6, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC + relaxation_time=300e-6, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, ) IQM5q.sweepers = sweepers @@ -589,7 +638,9 @@ def test_experiment_sweep_2d_specific(dummy_qrc): assert rearranging_axes != [[], []] -@pytest.mark.parametrize("parameter", [Parameter.frequency, Parameter.amplitude, Parameter.bias]) +@pytest.mark.parametrize( + "parameter", [Parameter.frequency, Parameter.amplitude, Parameter.bias] +) def test_experiment_sweep_punchouts(dummy_qrc, parameter): platform = create_platform("zurich") platform.setup() @@ -632,11 +683,15 @@ def test_experiment_sweep_punchouts(dummy_qrc, parameter): if parameter1 is Parameter.bias: sweepers.append(Sweeper(parameter1, parameter_range_1, qubits=[qubits[qubit]])) else: - sweepers.append(Sweeper(parameter1, parameter_range_1, pulses=[ro_pulses[qubit]])) + sweepers.append( + Sweeper(parameter1, parameter_range_1, pulses=[ro_pulses[qubit]]) + ) sweepers.append(Sweeper(parameter2, parameter_range_2, pulses=[ro_pulses[qubit]])) options = ExecutionParameters( - relaxation_time=300e-6, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC + relaxation_time=300e-6, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, ) IQM5q.sweepers = sweepers @@ -661,7 +716,9 @@ def test_sim(dummy_qrc): for qubit in qubits: qd_pulses[qubit] = platform.create_RX_pulse(qubit, start=0) sequence.add(qd_pulses[qubit]) - ro_pulses[qubit] = platform.create_qubit_readout_pulse(qubit, start=qd_pulses[qubit].finish) + ro_pulses[qubit] = platform.create_qubit_readout_pulse( + qubit, start=qd_pulses[qubit].finish + ) sequence.add(ro_pulses[qubit]) qf_pulses[qubit] = FluxPulse( start=0, @@ -733,7 +790,9 @@ def test_experiment_execute_pulse_sequence(connected_platform, instrument): sequence.add(ro_pulses[q]) options = ExecutionParameters( - relaxation_time=300e-6, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC + relaxation_time=300e-6, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, ) results = platform.execute_pulse_sequence( @@ -759,7 +818,9 @@ def test_experiment_sweep_2d_specific(connected_platform, instrument): for qubit in qubits: qd_pulses[qubit] = platform.create_RX_pulse(qubit, start=0) sequence.add(qd_pulses[qubit]) - ro_pulses[qubit] = platform.create_qubit_readout_pulse(qubit, start=qd_pulses[qubit].finish) + ro_pulses[qubit] = platform.create_qubit_readout_pulse( + qubit, start=qd_pulses[qubit].finish + ) sequence.add(ro_pulses[qubit]) parameter1 = Parameter.relative_phase @@ -782,7 +843,9 @@ def test_experiment_sweep_2d_specific(connected_platform, instrument): sweepers.append(Sweeper(parameter2, parameter_range_2, pulses=[qd_pulses[qubit]])) options = ExecutionParameters( - relaxation_time=300e-6, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC + relaxation_time=300e-6, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, ) results = platform.sweep( @@ -796,9 +859,7 @@ def test_experiment_sweep_2d_specific(connected_platform, instrument): def get_previous_subsequence_finish(instrument, name): - """ - Look recursively for sub_section finish times. - """ + """Look recursively for sub_section finish times.""" signal_name = re.sub("sequence_", "", name) signal_name = re.sub(r"_\d+$", "", signal_name) signal_name = re.sub(r"flux", "bias", signal_name) @@ -833,15 +894,21 @@ def test_experiment_measurement_sequence(dummy_qrc): readout_pulse_start = 40 for qubit in qubits: - qubit_drive_pulse_1 = platform.create_qubit_drive_pulse(qubit, start=0, duration=40) + qubit_drive_pulse_1 = platform.create_qubit_drive_pulse( + qubit, start=0, duration=40 + ) ro_pulse = platform.create_qubit_readout_pulse(qubit, start=readout_pulse_start) - qubit_drive_pulse_2 = platform.create_qubit_drive_pulse(qubit, start=readout_pulse_start + 50, duration=40) + qubit_drive_pulse_2 = platform.create_qubit_drive_pulse( + qubit, start=readout_pulse_start + 50, duration=40 + ) sequence.add(qubit_drive_pulse_1) sequence.add(ro_pulse) sequence.add(qubit_drive_pulse_2) options = ExecutionParameters( - relaxation_time=4, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC + relaxation_time=4, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, ) IQM5q.experiment_flow(qubits, couplers, sequence, options) diff --git a/tests/test_platform.py b/tests/test_platform.py index 8ac81d8aae..38d676b92c 100644 --- a/tests/test_platform.py +++ b/tests/test_platform.py @@ -1,6 +1,5 @@ """Tests :class:`qibolab.platforms.multiqubit.MultiqubitPlatform` and -:class:`qibolab.platforms.platform.DesignPlatform`. -""" +:class:`qibolab.platforms.platform.DesignPlatform`.""" import os import pathlib import pickle @@ -117,7 +116,9 @@ def test_platform_execute_one_coupler_pulse(qpu_platform): pytest.skip("The platform does not have couplers") coupler = next(iter(platform.couplers)) sequence = PulseSequence() - sequence.add(platform.create_coupler_pulse(coupler, start=0, duration=200, amplitude=1)) + sequence.add( + platform.create_coupler_pulse(coupler, start=0, duration=200, amplitude=1) + ) platform.execute_pulse_sequence(sequence, ExecutionParameters(nshots=nshots)) assert len(sequence.cf_pulses) > 0 @@ -134,7 +135,9 @@ def test_platform_execute_one_long_drive_pulse(qpu_platform): if find_instrument(platform, QbloxController) is not None: with pytest.raises(NotImplementedError): platform.execute_pulse_sequence(sequence, options) - elif find_instrument(platform, RFSoC) is not None and not isinstance(pulse.shape, Rectangular): + elif find_instrument(platform, RFSoC) is not None and not isinstance( + pulse.shape, Rectangular + ): with pytest.raises(RuntimeError): platform.execute_pulse_sequence(sequence, options) else: @@ -153,7 +156,9 @@ def test_platform_execute_one_extralong_drive_pulse(qpu_platform): if find_instrument(platform, QbloxController) is not None: with pytest.raises(NotImplementedError): platform.execute_pulse_sequence(sequence, options) - elif find_instrument(platform, RFSoC) is not None and not isinstance(pulse.shape, Rectangular): + elif find_instrument(platform, RFSoC) is not None and not isinstance( + pulse.shape, Rectangular + ): with pytest.raises(RuntimeError): platform.execute_pulse_sequence(sequence, options) else: @@ -222,8 +227,12 @@ def test_platform_execute_multiple_readout_pulses(qpu_platform): sequence = PulseSequence() qd_pulse1 = platform.create_qubit_drive_pulse(qubit, start=0, duration=200) ro_pulse1 = platform.create_qubit_readout_pulse(qubit, start=200) - qd_pulse2 = platform.create_qubit_drive_pulse(qubit, start=(ro_pulse1.start + ro_pulse1.duration), duration=400) - ro_pulse2 = platform.create_qubit_readout_pulse(qubit, start=(ro_pulse1.start + ro_pulse1.duration + 400)) + qd_pulse2 = platform.create_qubit_drive_pulse( + qubit, start=(ro_pulse1.start + ro_pulse1.duration), duration=400 + ) + ro_pulse2 = platform.create_qubit_readout_pulse( + qubit, start=(ro_pulse1.start + ro_pulse1.duration + 400) + ) sequence.add(qd_pulse1) sequence.add(ro_pulse1) sequence.add(qd_pulse2) @@ -233,7 +242,9 @@ def test_platform_execute_multiple_readout_pulses(qpu_platform): @pytest.mark.skip(reason="no way of currently testing this") @pytest.mark.qpu -@pytest.mark.xfail(raises=AssertionError, reason="Probabilities are not well calibrated") +@pytest.mark.xfail( + raises=AssertionError, reason="Probabilities are not well calibrated" +) def test_excited_state_probabilities_pulses(qpu_platform): platform = qpu_platform qubits = [q for q, qb in platform.qubits.items() if qb.drive is not None] @@ -248,7 +259,9 @@ def test_excited_state_probabilities_pulses(qpu_platform): nqubits = len(qubits) cr = CircuitResult(backend, Circuit(nqubits), result, nshots=5000) - probs = [backend.circuit_result_probabilities(cr, qubits=[qubit]) for qubit in qubits] + probs = [ + backend.circuit_result_probabilities(cr, qubits=[qubit]) for qubit in qubits + ] warnings.warn(f"Excited state probabilities: {probs}") target_probs = np.zeros((nqubits, 2)) target_probs[:, 1] = 1 @@ -258,7 +271,9 @@ def test_excited_state_probabilities_pulses(qpu_platform): @pytest.mark.skip(reason="no way of currently testing this") @pytest.mark.qpu @pytest.mark.parametrize("start_zero", [False, True]) -@pytest.mark.xfail(raises=AssertionError, reason="Probabilities are not well calibrated") +@pytest.mark.xfail( + raises=AssertionError, reason="Probabilities are not well calibrated" +) def test_ground_state_probabilities_pulses(qpu_platform, start_zero): platform = qpu_platform qubits = [q for q, qb in platform.qubits.items() if qb.drive is not None] @@ -275,7 +290,9 @@ def test_ground_state_probabilities_pulses(qpu_platform, start_zero): nqubits = len(qubits) cr = CircuitResult(backend, Circuit(nqubits), result, nshots=5000) - probs = [backend.circuit_result_probabilities(cr, qubits=[qubit]) for qubit in qubits] + probs = [ + backend.circuit_result_probabilities(cr, qubits=[qubit]) for qubit in qubits + ] warnings.warn(f"Ground state probabilities: {probs}") target_probs = np.zeros((nqubits, 2)) target_probs[:, 0] = 1 diff --git a/tests/test_pulses.py b/tests/test_pulses.py index 4c274e8093..fcd339c3cc 100644 --- a/tests/test_pulses.py +++ b/tests/test_pulses.py @@ -72,7 +72,10 @@ def test_pulses_pulse_init(): type=PulseType.READOUT, qubit=0, ) - assert repr(p0) == "Pulse(0, 50, 0.9, 20_000_000, 0, Rectangular(), 0, PulseType.READOUT, 0)" + assert ( + repr(p0) + == "Pulse(0, 50, 0.9, 20_000_000, 0, Rectangular(), 0, PulseType.READOUT, 0)" + ) # initialisation with Symbolic Expressions t1 = se_int(100, "t1") @@ -88,7 +91,10 @@ def test_pulses_pulse_init(): type=PulseType.READOUT, qubit=0, ) - assert repr(p1) == "Pulse(100, 50, 0.9, 20_000_000, 0, Rectangular(), 0, PulseType.READOUT, 0)" + assert ( + repr(p1) + == "Pulse(100, 50, 0.9, 20_000_000, 0, Rectangular(), 0, PulseType.READOUT, 0)" + ) # initialisation with non int (float) frequency p2 = Pulse( @@ -102,7 +108,10 @@ def test_pulses_pulse_init(): type=PulseType.READOUT, qubit=0, ) - assert repr(p2) == "Pulse(0, 50, 0.9, 20_000_000, 0, Rectangular(), 0, PulseType.READOUT, 0)" + assert ( + repr(p2) + == "Pulse(0, 50, 0.9, 20_000_000, 0, Rectangular(), 0, PulseType.READOUT, 0)" + ) assert type(p2.frequency) == int and p2.frequency == 20_000_000 # initialisation with non float (int) relative_phase @@ -117,7 +126,10 @@ def test_pulses_pulse_init(): type=PulseType.READOUT, qubit=0, ) - assert repr(p3) == "Pulse(0, 50, 0.9, 20_000_000, 1, Rectangular(), 0, PulseType.READOUT, 0)" + assert ( + repr(p3) + == "Pulse(0, 50, 0.9, 20_000_000, 1, Rectangular(), 0, PulseType.READOUT, 0)" + ) assert type(p3.relative_phase) == float and p3.relative_phase == 1.0 # initialisation with str shape @@ -132,7 +144,10 @@ def test_pulses_pulse_init(): type=PulseType.READOUT, qubit=0, ) - assert repr(p4) == "Pulse(0, 50, 0.9, 20_000_000, 0, Rectangular(), 0, PulseType.READOUT, 0)" + assert ( + repr(p4) + == "Pulse(0, 50, 0.9, 20_000_000, 0, Rectangular(), 0, PulseType.READOUT, 0)" + ) # initialisation with str channel and str qubit p5 = Pulse( @@ -146,7 +161,10 @@ def test_pulses_pulse_init(): type=PulseType.READOUT, qubit="qubit0", ) - assert repr(p5) == "Pulse(0, 50, 0.9, 20_000_000, 0, Rectangular(), channel0, PulseType.READOUT, qubit0)" + assert ( + repr(p5) + == "Pulse(0, 50, 0.9, 20_000_000, 0, Rectangular(), channel0, PulseType.READOUT, qubit0)" + ) assert p5.qubit == "qubit0" # initialisation with different frequencies, shapes and types @@ -170,7 +188,10 @@ def test_pulses_pulse_init(): type=PulseType.READOUT, qubit=0, ) - assert repr(p12) == "Pulse(5.5, 34.33, 0.9, 20_000_000, 1, Rectangular(), 0, PulseType.READOUT, 0)" + assert ( + repr(p12) + == "Pulse(5.5, 34.33, 0.9, 20_000_000, 1, Rectangular(), 0, PulseType.READOUT, 0)" + ) assert isinstance(p12.start, float) assert isinstance(p12.duration, float) assert p12.finish == 5.5 + 34.33 @@ -196,7 +217,9 @@ def test_pulses_pulse_attributes(): assert type(p10.duration) == int and p10.duration == 50 assert type(p10.amplitude) == float and p10.amplitude == 0.9 assert type(p10.frequency) == int and p10.frequency == 20_000_000 - assert type(p10.phase) == float and np.allclose(p10.phase, 2 * np.pi * p10.start * p10.frequency / 1e9) + assert type(p10.phase) == float and np.allclose( + p10.phase, 2 * np.pi * p10.start * p10.frequency / 1e9 + ) assert isinstance(p10.shape, PulseShape) and repr(p10.shape) == "Rectangular()" assert type(p10.channel) == type(channel) and p10.channel == channel assert type(p10.qubit) == type(qubit) and p10.qubit == qubit @@ -294,7 +317,7 @@ def test_pulses_pulse_attributes(): def test_pulses_is_equal_ignoring_start(): - """Checks if two pulses are equal, not looking at start time""" + """Checks if two pulses are equal, not looking at start time.""" p1 = Pulse(0, 40, 0.9, 0, 0, Rectangular(), 0, PulseType.FLUX, 0) p2 = Pulse(100, 40, 0.9, 0, 0, Rectangular(), 0, PulseType.FLUX, 0) @@ -315,7 +338,10 @@ def test_pulses_is_equal_ignoring_start(): def test_pulses_pulse_serial(): p11 = Pulse(0, 40, 0.9, 50_000_000, 0, Gaussian(5), 0, PulseType.DRIVE) - assert p11.serial == "Pulse(0, 40, 0.9, 50_000_000, 0, Gaussian(5), 0, PulseType.DRIVE, 0)" + assert ( + p11.serial + == "Pulse(0, 40, 0.9, 50_000_000, 0, Gaussian(5), 0, PulseType.DRIVE, 0)" + ) assert repr(p11) == p11.serial @@ -444,7 +470,9 @@ def test_pulses_pulse_aliases(): ) assert repr(dp) == "DrivePulse(0, 2000, 0.9, 200_000_000, 0, Gaussian(5), 0, 0)" - fp = FluxPulse(start=0, duration=300, amplitude=0.9, shape=Rectangular(), channel=0, qubit=0) + fp = FluxPulse( + start=0, duration=300, amplitude=0.9, shape=Rectangular(), channel=0, qubit=0 + ) assert repr(fp) == "FluxPulse(0, 300, 0.9, Rectangular(), 0, 0)" @@ -745,9 +773,15 @@ def test_pulses_pulseshape_rectangular(): assert isinstance(pulse.shape.modulated_waveform_q, Waveform) num_samples = int(pulse.duration / 1e9 * PulseShape.SAMPLING_RATE) - i, q = pulse.amplitude * np.ones(num_samples), pulse.amplitude * np.zeros(num_samples) - global_phase = 2 * np.pi * pulse._if * pulse.start / 1e9 # pulse start, duration and finish are in ns - mod_i, mod_q = modulate(i, q, num_samples, pulse._if, global_phase + pulse.relative_phase) + i, q = pulse.amplitude * np.ones(num_samples), pulse.amplitude * np.zeros( + num_samples + ) + global_phase = ( + 2 * np.pi * pulse._if * pulse.start / 1e9 + ) # pulse start, duration and finish are in ns + mod_i, mod_q = modulate( + i, q, num_samples, pulse._if, global_phase + pulse.relative_phase + ) np.testing.assert_allclose(pulse.shape.envelope_waveform_i.data, i) np.testing.assert_allclose(pulse.shape.envelope_waveform_q.data, q) @@ -797,11 +831,19 @@ def test_pulses_pulseshape_gaussian(): num_samples = int(pulse.duration / 1e9 * PulseShape.SAMPLING_RATE) x = np.arange(0, num_samples, 1) i = pulse.amplitude * np.exp( - -(1 / 2) * (((x - (num_samples - 1) / 2) ** 2) / (((num_samples) / pulse.shape.rel_sigma) ** 2)) + -(1 / 2) + * ( + ((x - (num_samples - 1) / 2) ** 2) + / (((num_samples) / pulse.shape.rel_sigma) ** 2) + ) ) q = pulse.amplitude * np.zeros(num_samples) - global_phase = 2 * np.pi * pulse.frequency * pulse.start / 1e9 # pulse start, duration and finish are in ns - mod_i, mod_q = modulate(i, q, num_samples, pulse._if, global_phase + pulse.relative_phase) + global_phase = ( + 2 * np.pi * pulse.frequency * pulse.start / 1e9 + ) # pulse start, duration and finish are in ns + mod_i, mod_q = modulate( + i, q, num_samples, pulse._if, global_phase + pulse.relative_phase + ) np.testing.assert_allclose(pulse.shape.envelope_waveform_i.data, i) np.testing.assert_allclose(pulse.shape.envelope_waveform_q.data, q) @@ -852,7 +894,11 @@ def test_pulses_pulseshape_drag(): num_samples = int(pulse.duration / 1e9 * PulseShape.SAMPLING_RATE) x = np.arange(0, num_samples, 1) i = pulse.amplitude * np.exp( - -(1 / 2) * (((x - (num_samples - 1) / 2) ** 2) / (((num_samples) / pulse.shape.rel_sigma) ** 2)) + -(1 / 2) + * ( + ((x - (num_samples - 1) / 2) ** 2) + / (((num_samples) / pulse.shape.rel_sigma) ** 2) + ) ) q = ( pulse.shape.beta @@ -861,8 +907,12 @@ def test_pulses_pulseshape_drag(): * PulseShape.SAMPLING_RATE / 1e9 ) - global_phase = 2 * np.pi * pulse._if * pulse.start / 1e9 # pulse start, duration and finish are in ns - mod_i, mod_q = modulate(i, q, num_samples, pulse._if, global_phase + pulse.relative_phase) + global_phase = ( + 2 * np.pi * pulse._if * pulse.start / 1e9 + ) # pulse start, duration and finish are in ns + mod_i, mod_q = modulate( + i, q, num_samples, pulse._if, global_phase + pulse.relative_phase + ) np.testing.assert_allclose(pulse.shape.envelope_waveform_i.data, i) np.testing.assert_allclose(pulse.shape.envelope_waveform_q.data, q) @@ -888,7 +938,7 @@ def test_pulses_pulseshape_drag(): def test_pulses_pulseshape_eq(): - """Checks == operator for pulse shapes""" + """Checks == operator for pulse shapes.""" shape1 = Rectangular() shape2 = Rectangular() @@ -1150,7 +1200,9 @@ def test_envelope_waveform_i_q(): @pytest.mark.parametrize("start", [0, 10, se_int(0, "t00"), se_int(10, "t10")]) -@pytest.mark.parametrize("duration", [100, 500, se_int(100, "d100"), se_int(500, "d500")]) +@pytest.mark.parametrize( + "duration", [100, 500, se_int(100, "d100"), se_int(500, "d500")] +) def test_pulse_properties(start, duration): def check_properties(pulse): assert isinstance(pulse.start, int) diff --git a/tests/test_result.py b/tests/test_result.py index bfe96a4f9c..70bb9c5d7f 100644 --- a/tests/test_result.py +++ b/tests/test_result.py @@ -1,4 +1,4 @@ -"""Testing result.py""" +"""Testing result.py.""" import numpy as np import pytest @@ -42,50 +42,56 @@ def generate_random_avg_state_result(length=5): def test_iq_constructor(): - """Testing ExecutionResults constructor""" + """Testing ExecutionResults constructor.""" test = np.array([(1, 2), (1, 2)]) IntegratedResults(test) def test_raw_constructor(): - """Testing ExecutionResults constructor""" + """Testing ExecutionResults constructor.""" test = np.array([(1, 2), (1, 2)]) RawWaveformResults(test) def test_state_constructor(): - """Testing ExecutionResults constructor""" + """Testing ExecutionResults constructor.""" test = np.array([1, 1, 0]) SampleResults(test) @pytest.mark.parametrize("result", ["iq", "raw"]) def test_integrated_result_properties(result): - """Testing IntegratedResults and RawWaveformResults properties""" + """Testing IntegratedResults and RawWaveformResults properties.""" if result == "iq": results = generate_random_iq_result(5) else: results = generate_random_raw_result(5) - np.testing.assert_equal(np.sqrt(results.voltage_i**2 + results.voltage_q**2), results.magnitude) - np.testing.assert_equal(np.angle(results.voltage_i + 1.0j * results.voltage_q), results.phase) + np.testing.assert_equal( + np.sqrt(results.voltage_i**2 + results.voltage_q**2), results.magnitude + ) + np.testing.assert_equal( + np.angle(results.voltage_i + 1.0j * results.voltage_q), results.phase + ) @pytest.mark.parametrize("state", [0, 1]) def test_state_probability(state): - """Testing raw_probability method""" + """Testing raw_probability method.""" results = generate_random_state_result(5) if state == 0: target_dict = {"probability": results.probability(0)} else: target_dict = {"probability": results.probability(1)} - assert np.allclose(target_dict["probability"], results.probability(state=state), atol=1e-08) + assert np.allclose( + target_dict["probability"], results.probability(state=state), atol=1e-08 + ) @pytest.mark.parametrize("average", [True, False]) @pytest.mark.parametrize("result", ["iq", "raw"]) def test_serialize(average, result): - """Testing to_dict method""" + """Testing to_dict method.""" if not average: if result == "iq": results = generate_random_iq_result(5) @@ -121,7 +127,7 @@ def test_serialize(average, result): @pytest.mark.parametrize("average", [True, False]) def test_serialize_state(average): - """Testing to_dict method""" + """Testing to_dict method.""" if not average: results = generate_random_state_result(5) output = results.serialize @@ -138,7 +144,7 @@ def test_serialize_state(average): @pytest.mark.parametrize("result", ["iq", "raw"]) def test_serialize_averaged_iq_results(result): - """Testing to_dict method""" + """Testing to_dict method.""" if result == "iq": results = generate_random_avg_iq_result(5) else: diff --git a/tests/test_result_shapes.py b/tests/test_result_shapes.py index baaaaae3e8..6498936bbc 100644 --- a/tests/test_result_shapes.py +++ b/tests/test_result_shapes.py @@ -25,7 +25,9 @@ def execute(platform, acquisition_type, averaging_mode, sweep=False): sequence.add(qd_pulse) sequence.add(ro_pulse) - options = ExecutionParameters(nshots=NSHOTS, acquisition_type=acquisition_type, averaging_mode=averaging_mode) + options = ExecutionParameters( + nshots=NSHOTS, acquisition_type=acquisition_type, averaging_mode=averaging_mode + ) if sweep: amp_values = np.linspace(0.01, 0.05, NSWEEP1) freq_values = np.linspace(-2e6, 2e6, NSWEEP2) @@ -41,7 +43,12 @@ def execute(platform, acquisition_type, averaging_mode, sweep=False): @pytest.mark.qpu @pytest.mark.parametrize("sweep", [False, True]) def test_discrimination_singleshot(connected_platform, sweep): - result = execute(connected_platform, AcquisitionType.DISCRIMINATION, AveragingMode.SINGLESHOT, sweep) + result = execute( + connected_platform, + AcquisitionType.DISCRIMINATION, + AveragingMode.SINGLESHOT, + sweep, + ) assert isinstance(result, SampleResults) if sweep: assert result.samples.shape == (NSHOTS, NSWEEP1, NSWEEP2) @@ -52,7 +59,9 @@ def test_discrimination_singleshot(connected_platform, sweep): @pytest.mark.qpu @pytest.mark.parametrize("sweep", [False, True]) def test_discrimination_cyclic(connected_platform, sweep): - result = execute(connected_platform, AcquisitionType.DISCRIMINATION, AveragingMode.CYCLIC, sweep) + result = execute( + connected_platform, AcquisitionType.DISCRIMINATION, AveragingMode.CYCLIC, sweep + ) assert isinstance(result, AveragedSampleResults) if sweep: assert result.statistical_frequency.shape == (NSWEEP1, NSWEEP2) @@ -63,7 +72,9 @@ def test_discrimination_cyclic(connected_platform, sweep): @pytest.mark.qpu @pytest.mark.parametrize("sweep", [False, True]) def test_integration_singleshot(connected_platform, sweep): - result = execute(connected_platform, AcquisitionType.INTEGRATION, AveragingMode.SINGLESHOT, sweep) + result = execute( + connected_platform, AcquisitionType.INTEGRATION, AveragingMode.SINGLESHOT, sweep + ) assert isinstance(result, IntegratedResults) if sweep: assert result.voltage.shape == (NSHOTS, NSWEEP1, NSWEEP2) @@ -74,7 +85,9 @@ def test_integration_singleshot(connected_platform, sweep): @pytest.mark.qpu @pytest.mark.parametrize("sweep", [False, True]) def test_integration_cyclic(connected_platform, sweep): - result = execute(connected_platform, AcquisitionType.INTEGRATION, AveragingMode.CYCLIC, sweep) + result = execute( + connected_platform, AcquisitionType.INTEGRATION, AveragingMode.CYCLIC, sweep + ) assert isinstance(result, AveragedIntegratedResults) if sweep: assert result.voltage.shape == (NSWEEP1, NSWEEP2) diff --git a/tests/test_symbolic.py b/tests/test_symbolic.py index 227f15348e..9451356488 100644 --- a/tests/test_symbolic.py +++ b/tests/test_symbolic.py @@ -15,12 +15,16 @@ def test_symbolic_init(): assert repr(se) == "se: 0 = 0" se0 = intSymbolicExpression(0) # Initialisation only with int expression. - assert repr(se0) == "_sym_int0: 0 = 0" # A unique symbol _sym_int is generated by the class + assert ( + repr(se0) == "_sym_int0: 0 = 0" + ) # A unique symbol _sym_int is generated by the class se1 = intSymbolicExpression(1) assert repr(se1) == "_sym_int1: 1 = 1" - se2 = intSymbolicExpression("8 // 4") # Initialisation with a string expression (when evaluated must return an int) + se2 = intSymbolicExpression( + "8 // 4" + ) # Initialisation with a string expression (when evaluated must return an int) assert repr(se2) == "_sym_int2: 8 // 4 = 2" se3 = intSymbolicExpression( @@ -28,7 +32,9 @@ def test_symbolic_init(): ) # Initialisation with a string expression using other intSymbolicExpressions assert repr(se3) == "_sym_int3: (se + 3) = 3" - se4 = intSymbolicExpression(se3) # Initialisation with another intSymbolicExpression + se4 = intSymbolicExpression( + se3 + ) # Initialisation with another intSymbolicExpression assert ( repr(se4) == "_sym_int4: (se + 3) = 3" ) # doing so copies the expression of the intSymbolicExpression argument @@ -44,7 +50,10 @@ def test_symbolic_init(): assert repr(SymbolicExpression.instances["_sym_int2"]) == "_sym_int2: 8 // 4 = 2" assert repr(SymbolicExpression.instances["_sym_int3"]) == "_sym_int3: (se + 3) = 3" assert repr(SymbolicExpression.instances["_sym_int4"]) == "_sym_int4: (se + 3) = 3" - assert repr(SymbolicExpression.instances["_sym_int5"]) == "_sym_int5: ((se + 3) * 3) = 9" + assert ( + repr(SymbolicExpression.instances["_sym_int5"]) + == "_sym_int5: ((se + 3) * 3) = 9" + ) assert repr(SymbolicExpression.instances["se5"]) == "se5: ((se + 3) * 3) = 9" se0 = intSymbolicExpression( @@ -66,14 +75,20 @@ def test_symbolic_symbol(): t0.symbol = "modified_t0" assert t0.symbol == "modified_t0" - assert repr(intSymbolicExpression.instances) == "{'modified_t0': modified_t0: 0 = 0}" + assert ( + repr(intSymbolicExpression.instances) == "{'modified_t0': modified_t0: 0 = 0}" + ) # def __getitem__(self, symbol): - t1 = intSymbolicExpression(1)["t1"] # Defining a new instance and renaming it to 't1' afterwards + t1 = intSymbolicExpression(1)[ + "t1" + ] # Defining a new instance and renaming it to 't1' afterwards assert t1.symbol == "t1" # This abuse of the __getitem__ method, in conjunction with class operators allows this: - t2 = (t1 + 1)["t2"] # operations between a intSymbolicExpression and another intSymbolicExpression or int + t2 = (t1 + 1)[ + "t2" + ] # operations between a intSymbolicExpression and another intSymbolicExpression or int # return a intSymbolicExpression assert t2.symbol == "t2" assert repr(t2) == "t2: (t1 + 1) = 2" @@ -96,11 +111,15 @@ def test_symbolic_expression(): assert t1.expression == "7" assert t1.value == 7 - t1.expression = "3 + 3" # setting intSymbolicExpression expression using a str expression + t1.expression = ( + "3 + 3" # setting intSymbolicExpression expression using a str expression + ) assert t1.expression == "3 + 3" assert t1.value == 6 - t1.expression = t0 + 1 # setting intSymbolicExpression expression using another intSymbolicExpression + t1.expression = ( + t0 + 1 + ) # setting intSymbolicExpression expression using another intSymbolicExpression assert t1.expression == "(t0 + 1)" assert ( @@ -140,7 +159,9 @@ def test_symbolic_is_constant(): t1.value = 15 assert t1.is_constant - assert repr(intSymbolicExpression.instances) == "{'t0': t0: 0 = 0, 't1': t1: 15 = 15}" + assert ( + repr(intSymbolicExpression.instances) == "{'t0': t0: 0 = 0, 't1': t1: 15 = 15}" + ) def test_symbolic_circular_reference_error(): @@ -164,9 +185,18 @@ def test_symbolic_circular_reference_error(): assert not ValueError_raised assert repr(t1) == "t1: (t0 + 1) = CircularReferenceError" - assert repr(SymbolicExpression.instances["t0"]) == "t0: (t1 + 1) = CircularReferenceError" - assert repr(SymbolicExpression.instances["t1"]) == "t1: (t0 + 1) = CircularReferenceError" - assert repr(SymbolicExpression.instances["_sym_int1"]) == "_sym_int1: (t1 + 1) = CircularReferenceError" + assert ( + repr(SymbolicExpression.instances["t0"]) + == "t0: (t1 + 1) = CircularReferenceError" + ) + assert ( + repr(SymbolicExpression.instances["t1"]) + == "t1: (t0 + 1) = CircularReferenceError" + ) + assert ( + repr(SymbolicExpression.instances["_sym_int1"]) + == "_sym_int1: (t1 + 1) = CircularReferenceError" + ) def test_symbolic_evaluate(): @@ -277,9 +307,17 @@ def test_symbolic_mixed_type_operators(): se4 += 2 assert repr(SymbolicExpression.instances["se1"]) == "se1: 7 = 7" - assert repr(SymbolicExpression.instances["_sym_int1"]) == "_sym_int1: (se1 + 5) = 12" - assert repr(SymbolicExpression.instances["se3"]) == "se3: ((3 * (se1 - 1)) + (se1 + (se1 + 5))) = 37" - assert repr(SymbolicExpression.instances["_sym_int6"]) == "_sym_int6: (((se1 + 5) - se3) + 2) = -23" + assert ( + repr(SymbolicExpression.instances["_sym_int1"]) == "_sym_int1: (se1 + 5) = 12" + ) + assert ( + repr(SymbolicExpression.instances["se3"]) + == "se3: ((3 * (se1 - 1)) + (se1 + (se1 + 5))) = 37" + ) + assert ( + repr(SymbolicExpression.instances["_sym_int6"]) + == "_sym_int6: (((se1 + 5) - se3) + 2) = -23" + ) SymbolicExpression.clear_instances() @@ -290,6 +328,15 @@ def test_symbolic_mixed_type_operators(): se4 = se2 - se3 assert repr(SymbolicExpression.instances["se1"]) == "se1: 3.5 = 3.5" - assert repr(SymbolicExpression.instances["_sym_float1"]) == "_sym_float1: (se1 + 5) = 8.5" - assert repr(SymbolicExpression.instances["se3"]) == "se3: ((3 * (se1 - 1)) + (se1 + (se1 + 5))) = 19.5" - assert repr(SymbolicExpression.instances["_sym_float6"]) == "_sym_float6: ((se1 + 5) - se3) = -11.0" + assert ( + repr(SymbolicExpression.instances["_sym_float1"]) + == "_sym_float1: (se1 + 5) = 8.5" + ) + assert ( + repr(SymbolicExpression.instances["se3"]) + == "se3: ((3 * (se1 - 1)) + (se1 + (se1 + 5))) = 19.5" + ) + assert ( + repr(SymbolicExpression.instances["_sym_float6"]) + == "_sym_float6: ((se1 + 5) - se3) = -11.0" + ) From 2482f3e5c44def36f9a870cc11f58c2cb2183197 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 06:24:31 +0000 Subject: [PATCH 66/66] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibolab/instruments/zhinst.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index 228e254c50..fdd201624a 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -1150,8 +1150,8 @@ def fast_reset(self, exp, qubits, fast_reset): @staticmethod def rearrange_sweepers(sweepers: List[Sweeper]) -> Tuple[np.ndarray, List[Sweeper]]: - """ - Rearranges sweepers from qibocal based on device hardware limitations. + """Rearranges sweepers from qibocal based on device hardware + limitations. Frequency sweepers must be applied before bias or amplitude sweepers.