From c6704ad8d3e034c25d8aaa5d71f0a8a4c4d7d6eb Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Wed, 21 Aug 2024 15:30:00 +0400 Subject: [PATCH 01/70] fix kernel based weights being generated multiple times --- src/qibolab/instruments/zhinst/executor.py | 74 ++++++++++------------ 1 file changed, 33 insertions(+), 41 deletions(-) diff --git a/src/qibolab/instruments/zhinst/executor.py b/src/qibolab/instruments/zhinst/executor.py index bfba7a6d98..995863bf94 100644 --- a/src/qibolab/instruments/zhinst/executor.py +++ b/src/qibolab/instruments/zhinst/executor.py @@ -559,6 +559,34 @@ def get_channel_node_path(self, channel_name: str) -> str: f"Could not find instrument node corresponding to channel {channel_name}" ) + def _calculate_weight(self, pulse, qubit, exp_options): + if ( + qubit.kernel is not None + and exp_options.acquisition_type == lo.AcquisitionType.DISCRIMINATION + ): + return lo.pulse_library.sampled_pulse_complex( + samples=qubit.kernel * np.exp(1j * qubit.iq_angle), + ) + + elif exp_options.acquisition_type == lo.AcquisitionType.DISCRIMINATION: + return lo.pulse_library.sampled_pulse_complex( + samples=np.ones( + [ + int( + pulse.pulse.duration * 2 + - 3 * self.smearing * NANO_TO_SECONDS + ) + ] + ) + * np.exp(1j * qubit.iq_angle), + ) + else: + return lo.pulse_library.const( + length=round(pulse.pulse.duration * NANO_TO_SECONDS, 9) + - 1.5 * self.smearing * NANO_TO_SECONDS, + amplitude=1, + ) + def select_exp(self, exp, qubits, exp_options): """Build Zurich Experiment selecting the relevant sections.""" # channels that were not split are just applied in parallel to the rest of the experiment @@ -607,53 +635,17 @@ def select_exp(self, exp, qubits, exp_options): with exp.section(uid=section_uid, play_after=previous_section): for ch, pulse in seq.measurements: qubit = qubits[pulse.pulse.qubit] - q = qubit.name exp.delay( signal=acquire_channel_name(qubit), time=self.smearing * NANO_TO_SECONDS, ) - if ( - qubit.kernel is not None - and exp_options.acquisition_type - == lo.AcquisitionType.DISCRIMINATION - ): - weight = lo.pulse_library.sampled_pulse_complex( - samples=qubit.kernel * np.exp(1j * qubit.iq_angle), + if qubit.name not in weights: + weights[qubit.name] = self._calculate_weight( + pulse, qubit, exp_options ) - else: - if i == 0: - if ( - exp_options.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 - ) - ] - ) - * np.exp(1j * qubit.iq_angle), - ) - weights[q] = weight - else: - weight = lo.pulse_library.const( - length=round( - pulse.pulse.duration * NANO_TO_SECONDS, 9 - ) - - 1.5 * self.smearing * NANO_TO_SECONDS, - amplitude=1, - ) - - weights[q] = weight - elif i != 0: - weight = weights[q] - measure_pulse_parameters = {"phase": 0} if i == len(self.sequence[measure_channel_name(qubit)]) - 1: @@ -663,8 +655,8 @@ def select_exp(self, exp, qubits, exp_options): exp.measure( acquire_signal=acquire_channel_name(qubit), - handle=f"sequence{q}_{i}", - integration_kernel=weight, + handle=f"sequence{qubit.name}_{i}", + integration_kernel=weights[qubit.name], integration_kernel_parameters=None, integration_length=None, measure_signal=measure_channel_name(qubit), From cb040762e572642da732a2557e39bb477b4bc146 Mon Sep 17 00:00:00 2001 From: Andrea Date: Sun, 15 Sep 2024 11:47:07 +0400 Subject: [PATCH 02/70] fix: Add minus sign to RZ rule --- src/qibolab/compilers/default.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibolab/compilers/default.py b/src/qibolab/compilers/default.py index a9bc0a8d3c..b178eefe92 100644 --- a/src/qibolab/compilers/default.py +++ b/src/qibolab/compilers/default.py @@ -22,7 +22,7 @@ def z_rule(gate, platform): def rz_rule(gate, platform): """RZ gate applied virtually.""" qubit = list(platform.qubits)[gate.target_qubits[0]] - return PulseSequence(), {qubit: gate.parameters[0]} + return PulseSequence(), {qubit: -gate.parameters[0]} def gpi2_rule(gate, platform): From 15dce6acc7c283363f9fc67ecde5b9972925d9ed Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Tue, 8 Oct 2024 15:20:48 +0400 Subject: [PATCH 03/70] fix: change Rust array type to u32 --- crate/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crate/src/lib.rs b/crate/src/lib.rs index 3238e6acb6..be73f64552 100644 --- a/crate/src/lib.rs +++ b/crate/src/lib.rs @@ -3,7 +3,7 @@ use numpy::PyArray2; use pyo3::prelude::*; use pyo3::types::PyDict; -pub fn execute_qasm(circuit: String, platform: String, nshots: u32) -> PyResult> { +pub fn execute_qasm(circuit: String, platform: String, nshots: u32) -> PyResult> { Python::with_gil(|py| { let kwargs = PyDict::new(py); kwargs.set_item("circuit", circuit)?; @@ -11,7 +11,7 @@ pub fn execute_qasm(circuit: String, platform: String, nshots: u32) -> PyResult< kwargs.set_item("nshots", nshots)?; let qibolab = PyModule::import(py, "qibolab")?; - let pyarray: &PyArray2 = qibolab + let pyarray: &PyArray2 = qibolab .getattr("execute_qasm")? .call((), Some(kwargs))? .call_method0("samples")? From edf8b2574e92e72f7d8ae7ceecf662bb07190498 Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Mon, 16 Sep 2024 16:26:12 +0400 Subject: [PATCH 04/70] feat: introduce OPX1000 concepts --- src/qibolab/instruments/qm/config.py | 18 +++++++- src/qibolab/instruments/qm/devices.py | 62 +++++++++++++++++++++++++-- src/qibolab/instruments/qm/ports.py | 30 ++++++++++++- 3 files changed, 103 insertions(+), 7 deletions(-) diff --git a/src/qibolab/instruments/qm/config.py b/src/qibolab/instruments/qm/config.py index 41c1f3e4af..3f528048e4 100644 --- a/src/qibolab/instruments/qm/config.py +++ b/src/qibolab/instruments/qm/config.py @@ -6,7 +6,7 @@ from qibolab.pulses import PulseType, Rectangular -from .ports import OPXIQ, OctaveInput, OctaveOutput, OPXOutput +from .ports import OPXIQ, FEMInput, FEMOutput, OctaveInput, OctaveOutput, OPXOutput SAMPLING_RATE = 1 """Sampling rate of Quantum Machines OPX in GSps.""" @@ -48,17 +48,31 @@ def register_port(self, port): self.register_port(port.q) else: is_octave = isinstance(port, (OctaveOutput, OctaveInput)) + is_fem = isinstance(port, (FEMOutput, FEMInput)) controllers = self.octaves if is_octave else self.controllers if port.device not in controllers: if is_octave: controllers[port.device] = {} + elif is_fem: + controllers[port.device] = {"type": "opx1000", "fems": {}} else: controllers[port.device] = { "analog_inputs": DEFAULT_INPUTS, "digital_outputs": {}, } - device = controllers[port.device] + if is_fem: + fems = controllers[port.device]["fems"] + if port.fem_number not in fems: + fems[port.fem_number] = { + "type": port.fem_type, + "analog_inputs": DEFAULT_INPUTS, + "digital_outputs": {}, + } + device = fems[port.fem_number] + else: + device = controllers[port.device] + if port.key in device: device[port.key].update(port.config) else: diff --git a/src/qibolab/instruments/qm/devices.py b/src/qibolab/instruments/qm/devices.py index 6ec0674d88..f314529ac4 100644 --- a/src/qibolab/instruments/qm/devices.py +++ b/src/qibolab/instruments/qm/devices.py @@ -1,12 +1,14 @@ from collections import defaultdict from dataclasses import dataclass, field from itertools import chain -from typing import Dict +from typing import Dict, Literal, Union from qibolab.instruments.abstract import Instrument from .ports import ( OPXIQ, + FEMInput, + FEMOutput, OctaveInput, OctaveOutput, OPXInput, @@ -94,13 +96,60 @@ def __post_init__(self): self.inputs = PortsDefaultdict(lambda n: OPXInput(self.name, n)) +@dataclass +class FEM: + """Device handling OPX1000 FEMs.""" + + name: int + type: Literal["LF", "MF"] = "LF" + + +@dataclass +class OPX1000(QMDevice): + """Device handling OPX1000 controllers.""" + + fems: Dict[int, FEM] = field(default_factory=dict) + + def __post_init__(self): + def kwargs(fem): + return {"fem_number": fem, "fem_type": self.fems[fem].type} + + self.outputs = PortsDefaultdict( + lambda fem, n: FEMOutput(self.name, n, **kwargs(fem)) + ) + self.inputs = PortsDefaultdict( + lambda fem, n: FEMInput(self.name, n, **kwargs(fem)) + ) + + def ports(self, fem_number: int, number: int, output: bool = True): + ports_ = self.outputs if output else self.inputs + return ports_[(fem_number, number)] + + def connectivity(self, fem_number: int) -> tuple["OPX1000", int]: + return (self, fem_number) + + def setup(self, **kwargs): + for name, settings in kwargs.items(): + fem, port = name.split("/") + fem = int(fem) + number = int(port[1:]) + if port[0] == "o": + self.outputs[(fem, number)].setup(**settings) + elif port[0] == "i": + self.inputs[(fem, number)].setup(**settings) + else: + raise ValueError( + f"Invalid port name {name} in instrument settings for {self.name}." + ) + + @dataclass class Octave(QMDevice): """Device handling Octaves.""" port: int """Network port of the Octave in the cluster configuration.""" - connectivity: OPXplus + connectivity: Union[OPXplus, tuple[OPX1000, int]] """OPXplus that acts as the waveform generator for the Octave.""" def __post_init__(self): @@ -115,7 +164,12 @@ def ports(self, number, output=True): """ port = super().ports(number, output) if port.opx_port is None: - iport = self.connectivity.ports(2 * number - 1, output) - qport = self.connectivity.ports(2 * number, output) + if isinstance(self.connectivity, OPXplus): + iport = self.connectivity.ports(2 * number - 1, output) + qport = self.connectivity.ports(2 * number, output) + else: + opx, fem_number = self.connectivity + iport = opx.ports(fem_number, 2 * number - 1, output) + qport = opx.ports(fem_number, 2 * number, output) port.opx_port = OPXIQ(iport, qport) return port diff --git a/src/qibolab/instruments/qm/ports.py b/src/qibolab/instruments/qm/ports.py index 1d6ce2d444..2d3e54b4b8 100644 --- a/src/qibolab/instruments/qm/ports.py +++ b/src/qibolab/instruments/qm/ports.py @@ -1,5 +1,5 @@ from dataclasses import dataclass, field, fields -from typing import ClassVar, Dict, Optional, Union +from typing import ClassVar, Dict, Literal, Optional, Union DIGITAL_DELAY = 57 DIGITAL_BUFFER = 18 @@ -131,6 +131,34 @@ class OPXIQ: """Port implementing the Q-component of the signal.""" +@dataclass +class FEMOutput(OPXOutput): + fem_number: int = 0 + fem_type: Literal["LF", "MF"] = "LF" + + @property + def name(self): + return f"{self.fem_number}/o{self.number}" + + @property + def pair(self): + return (self.device, self.fem_number, self.number) + + +@dataclass +class FEMInput(OPXInput): + fem_number: int = 0 + fem_type: Literal["LF", "MF"] = "LF" + + @property + def name(self): + return f"{self.fem_number}/i{self.number}" + + @property + def pair(self): + return (self.device, self.fem_number, self.number) + + @dataclass class OctaveOutput(QMOutput): key: ClassVar[str] = "RF_outputs" From 450624f31523ffa88612858d08f1a099c49df2da Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:27:52 +0400 Subject: [PATCH 05/70] fix: octave connectivity --- src/qibolab/instruments/qm/__init__.py | 2 +- src/qibolab/instruments/qm/config.py | 32 ++++++++++++------------ src/qibolab/instruments/qm/controller.py | 4 ++- src/qibolab/instruments/qm/devices.py | 7 ++++-- src/qibolab/instruments/qm/ports.py | 9 ++++--- 5 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/qibolab/instruments/qm/__init__.py b/src/qibolab/instruments/qm/__init__.py index e053aa970a..6689872786 100644 --- a/src/qibolab/instruments/qm/__init__.py +++ b/src/qibolab/instruments/qm/__init__.py @@ -1,2 +1,2 @@ from .controller import QMController -from .devices import Octave, OPXplus +from .devices import FEM, OPX1000, Octave, OPXplus diff --git a/src/qibolab/instruments/qm/config.py b/src/qibolab/instruments/qm/config.py index 3f528048e4..74e69d1084 100644 --- a/src/qibolab/instruments/qm/config.py +++ b/src/qibolab/instruments/qm/config.py @@ -79,11 +79,19 @@ def register_port(self, port): device[port.key] = port.config if is_octave: - con = port.opx_port.i.device - number = port.opx_port.i.number - device["connectivity"] = con self.register_port(port.opx_port) - self.controllers[con]["digital_outputs"][number] = {} + subport = port.opx_port.i + if isinstance(subport, (FEMOutput, FEMInput)): + con = subport.device + fem = subport.fem_number + number = subport.number + device["connectivity"] = (con, fem) + self.controllers[con]["fems"][fem]["digital_outputs"][number] = {} + else: + con = subport.device + number = subport.number + device["connectivity"] = con + self.controllers[con]["digital_outputs"][number] = {} @staticmethod def iq_imbalance(g, phi): @@ -289,10 +297,6 @@ def register_pulse(self, qubit, qmpulse): "waveforms": {"I": serial_i, "Q": serial_q}, "digital_marker": "ON", } - # register drive pulse in elements - self.elements[qmpulse.element]["operations"][ - qmpulse.operation - ] = qmpulse.operation elif pulse.type is PulseType.FLUX: serial = self.register_waveform(pulse) @@ -303,10 +307,6 @@ def register_pulse(self, qubit, qmpulse): "single": serial, }, } - # register flux pulse in elements - self.elements[qmpulse.element]["operations"][ - qmpulse.operation - ] = qmpulse.operation elif pulse.type is PulseType.READOUT: serial_i = self.register_waveform(pulse, "i") @@ -326,14 +326,14 @@ def register_pulse(self, qubit, qmpulse): }, "digital_marker": "ON", } - # register readout pulse in elements - self.elements[qmpulse.element]["operations"][ - qmpulse.operation - ] = qmpulse.operation else: raise_error(TypeError, f"Unknown pulse type {pulse.type.name}.") + self.elements[qmpulse.element]["operations"][ + qmpulse.operation + ] = qmpulse.operation + def register_waveform(self, pulse, mode="i"): """Registers waveforms in QM config. diff --git a/src/qibolab/instruments/qm/controller.py b/src/qibolab/instruments/qm/controller.py index 08d3f23f6e..a8032eea03 100644 --- a/src/qibolab/instruments/qm/controller.py +++ b/src/qibolab/instruments/qm/controller.py @@ -189,6 +189,9 @@ def __post_init__(self): # convert simulation duration from ns to clock cycles self.simulation_duration //= 4 + def __str__(self): + return self.name + def ports(self, name, output=True): """Provides instrument ports to the user. @@ -255,7 +258,6 @@ def disconnect(self): self._reset_temporary_calibration() if self.manager is not None: self.manager.close_all_quantum_machines() - self.manager.close() self.is_connected = False def calibrate_mixers(self, qubits): diff --git a/src/qibolab/instruments/qm/devices.py b/src/qibolab/instruments/qm/devices.py index f314529ac4..e8b3a547bd 100644 --- a/src/qibolab/instruments/qm/devices.py +++ b/src/qibolab/instruments/qm/devices.py @@ -45,6 +45,9 @@ class QMDevice(Instrument): inputs: Dict[int, QMInput] = field(init=False) """Dictionary containing the instrument's input ports.""" + def __str__(self): + return self.name + def ports(self, number, output=True): """Provides instrument's ports to the user. @@ -115,10 +118,10 @@ def kwargs(fem): return {"fem_number": fem, "fem_type": self.fems[fem].type} self.outputs = PortsDefaultdict( - lambda fem, n: FEMOutput(self.name, n, **kwargs(fem)) + lambda pair: FEMOutput(self.name, pair[1], **kwargs(pair[0])) ) self.inputs = PortsDefaultdict( - lambda fem, n: FEMInput(self.name, n, **kwargs(fem)) + lambda pair: FEMInput(self.name, pair[1], **kwargs(pair[0])) ) def ports(self, fem_number: int, number: int, output: bool = True): diff --git a/src/qibolab/instruments/qm/ports.py b/src/qibolab/instruments/qm/ports.py index 2d3e54b4b8..598f72406f 100644 --- a/src/qibolab/instruments/qm/ports.py +++ b/src/qibolab/instruments/qm/ports.py @@ -193,11 +193,14 @@ def digital_inputs(self): Digital markers are used to switch LOs on in triggered mode. """ - opx = self.opx_port.i.device - number = self.opx_port.i.number + opx_port = self.opx_port.i + if isinstance(opx_port, (FEMOutput, FEMInput)): + port = (opx_port.device, opx_port.fem_number, opx_port.number) + else: + port = (opx_port.device, opx_port.number) return { "output_switch": { - "port": (opx, number), + "port": port, "delay": self.digital_delay, "buffer": self.digital_buffer, } From e4bf6e4bd5146c4d70787c5e0c32dbb485a900fa Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Thu, 26 Sep 2024 16:08:19 +0400 Subject: [PATCH 06/70] feat: amplified output mode --- src/qibolab/instruments/qm/ports.py | 3 +++ src/qibolab/instruments/qm/sweepers.py | 10 ++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/qibolab/instruments/qm/ports.py b/src/qibolab/instruments/qm/ports.py index 598f72406f..fcb50f67d3 100644 --- a/src/qibolab/instruments/qm/ports.py +++ b/src/qibolab/instruments/qm/ports.py @@ -135,6 +135,9 @@ class OPXIQ: class FEMOutput(OPXOutput): fem_number: int = 0 fem_type: Literal["LF", "MF"] = "LF" + output_mode: Literal["direct", "amplified"] = field( + default="direct", metadata={"config": "output_mode"} + ) @property def name(self): diff --git a/src/qibolab/instruments/qm/sweepers.py b/src/qibolab/instruments/qm/sweepers.py index 3110e1f3d8..081f682649 100644 --- a/src/qibolab/instruments/qm/sweepers.py +++ b/src/qibolab/instruments/qm/sweepers.py @@ -141,16 +141,18 @@ def _sweep_bias(sweepers, qubits, qmsequence, relaxation_time): for qubit in sweeper.qubits: b0 = qubit.flux.offset max_offset = qubit.flux.max_offset + if max_offset is None: + max_offset = 0.5 max_value = maximum_sweep_value(sweeper.values, b0) check_max_offset(max_value, max_offset) offset0.append(declare(fixed, value=b0)) b = declare(fixed) with for_(*from_array(b, sweeper.values)): for qubit, b0 in zip(sweeper.qubits, offset0): - with qua.if_((b + b0) >= 0.49): - qua.set_dc_offset(f"flux{qubit.name}", "single", 0.49) - with qua.elif_((b + b0) <= -0.49): - qua.set_dc_offset(f"flux{qubit.name}", "single", -0.49) + with qua.if_((b + b0) >= max_offset): + qua.set_dc_offset(f"flux{qubit.name}", "single", max_offset) + with qua.elif_((b + b0) <= -max_offset): + qua.set_dc_offset(f"flux{qubit.name}", "single", -max_offset) with qua.else_(): qua.set_dc_offset(f"flux{qubit.name}", "single", (b + b0)) From cb4ed576ce68645babb9697adec771fcb0ab4a09 Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Wed, 2 Oct 2024 13:21:25 +0400 Subject: [PATCH 07/70] chore: Change LO output mode to always_on --- src/qibolab/instruments/qm/ports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibolab/instruments/qm/ports.py b/src/qibolab/instruments/qm/ports.py index fcb50f67d3..bfdce18c00 100644 --- a/src/qibolab/instruments/qm/ports.py +++ b/src/qibolab/instruments/qm/ports.py @@ -180,7 +180,7 @@ class OctaveOutput(QMOutput): Can be external or internal. """ - output_mode: str = field(default="triggered", metadata={"config": "output_mode"}) + output_mode: str = field(default="always_on", metadata={"config": "output_mode"}) """Can be: "always_on" / "always_off"/ "triggered" / "triggered_reversed".""" digital_delay: int = DIGITAL_DELAY """Delay for digital output channel.""" From 91a9c5bde401f3bc3a5c2a261ee955461843bdee Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Mon, 7 Oct 2024 17:01:10 +0400 Subject: [PATCH 08/70] fix: force offset setting --- src/qibolab/instruments/qm/controller.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/qibolab/instruments/qm/controller.py b/src/qibolab/instruments/qm/controller.py index a8032eea03..e9017bb4b0 100644 --- a/src/qibolab/instruments/qm/controller.py +++ b/src/qibolab/instruments/qm/controller.py @@ -375,6 +375,10 @@ def sweep(self, qubits, couplers, sequence, options, *sweepers): # play pulses using QUA with qua.program() as experiment: n = declare(int) + for qubit in qubits.values(): + if qubit.flux: + qua.set_dc_offset(qubit.flux.name, "single", qubit.sweetspot) + acquisitions = declare_acquisitions(ro_pulses, qubits, options) with for_(n, 0, n < options.nshots, n + 1): sweep( From a2ee33f9a3a58f4569085a6794faf2a7ea081ddc Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Mon, 7 Oct 2024 17:01:29 +0400 Subject: [PATCH 09/70] fix: output_mode dump --- src/qibolab/instruments/qm/ports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibolab/instruments/qm/ports.py b/src/qibolab/instruments/qm/ports.py index bfdce18c00..5a593746dc 100644 --- a/src/qibolab/instruments/qm/ports.py +++ b/src/qibolab/instruments/qm/ports.py @@ -136,7 +136,7 @@ class FEMOutput(OPXOutput): fem_number: int = 0 fem_type: Literal["LF", "MF"] = "LF" output_mode: Literal["direct", "amplified"] = field( - default="direct", metadata={"config": "output_mode"} + default="direct", metadata={"config": "output_mode", "settings": True} ) @property From 721e127a4496f982f6419924c30cea3cb539d83f Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Tue, 8 Oct 2024 13:08:31 +0400 Subject: [PATCH 10/70] chore: variable definition repeated in if branches --- src/qibolab/instruments/qm/config.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/qibolab/instruments/qm/config.py b/src/qibolab/instruments/qm/config.py index 74e69d1084..578efc24a5 100644 --- a/src/qibolab/instruments/qm/config.py +++ b/src/qibolab/instruments/qm/config.py @@ -81,15 +81,13 @@ def register_port(self, port): if is_octave: self.register_port(port.opx_port) subport = port.opx_port.i + con = subport.device + number = subport.number if isinstance(subport, (FEMOutput, FEMInput)): - con = subport.device fem = subport.fem_number - number = subport.number device["connectivity"] = (con, fem) self.controllers[con]["fems"][fem]["digital_outputs"][number] = {} else: - con = subport.device - number = subport.number device["connectivity"] = con self.controllers[con]["digital_outputs"][number] = {} From 9786545de79fcbdc40fc1d3341468eaec6e4d1ee Mon Sep 17 00:00:00 2001 From: Alvaro Orgaz Date: Wed, 15 May 2024 23:41:32 +0400 Subject: [PATCH 11/70] qblox support for couplers bias --- .../instruments/qblox/cluster_qcm_bb.py | 42 +++++++++++++++---- .../instruments/qblox/cluster_qcm_rf.py | 1 + .../instruments/qblox/cluster_qrm_rf.py | 1 + src/qibolab/instruments/qblox/controller.py | 16 +++++-- 4 files changed, 49 insertions(+), 11 deletions(-) diff --git a/src/qibolab/instruments/qblox/cluster_qcm_bb.py b/src/qibolab/instruments/qblox/cluster_qcm_bb.py index e95cf69ef9..8aca5e382a 100644 --- a/src/qibolab/instruments/qblox/cluster_qcm_bb.py +++ b/src/qibolab/instruments/qblox/cluster_qcm_bb.py @@ -201,7 +201,7 @@ def setup(self, **settings): """ pass - def _get_next_sequencer(self, port, frequency, qubits: dict): + def _get_next_sequencer(self, port, frequency, qubits: dict, couplers: dict = {}): """Retrieves and configures the next avaliable sequencer. The parameters of the new sequencer are copied from those of the default sequencer, except for the @@ -209,17 +209,33 @@ def _get_next_sequencer(self, port, frequency, qubits: dict): Args: port (str): frequency (): - qubit (): + qubits (): + couplers (): Raises: Exception = If attempting to set a parameter without a connection to the instrument. """ - # select the qubit with flux line, if present, connected to the specific port + # check if this port is responsible for the flux of any qubit or coupler qubit = None for _qubit in qubits.values(): - name = _qubit.flux.port.name - module = _qubit.flux.port.module - if _qubit.flux is not None and (name, module) == (port, self): - qubit = _qubit + 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") + + coupler = None + for _coupler in couplers.values(): + if _coupler.flux.port is not None: + if ( + _coupler.flux.port.name == port + and _coupler.flux.port.module.name == self.name + ): + coupler = _coupler + else: + log.warning(f"Coupler {_coupler.name} has no flux line connected") # select a new sequencer and configure it as required next_sequencer_number = self._free_sequencers_numbers.pop(0) @@ -245,6 +261,13 @@ def _get_next_sequencer(self, port, frequency, qubits: dict): # TODO: Throw error in that event or implement for non_overlapping_same_frequency_pulses # Even better, set the frequency before each pulse is played (would work with hardware modulation only) + if qubit: + self._ports[port].offset = qubit.sweetspot + elif coupler: + self._ports[port].offset = coupler.sweetspot + else: + self._ports[port].offset = 0 + # create sequencer wrapper sequencer = Sequencer(next_sequencer_number) sequencer.qubit = qubit.name if qubit else None @@ -267,6 +290,7 @@ def get_if(self, pulse): def process_pulse_sequence( self, qubits: dict, + couplers: dict, instrument_pulses: PulseSequence, navgs: int, nshots: int, @@ -349,6 +373,7 @@ def process_pulse_sequence( port=port, frequency=self.get_if(non_overlapping_pulses[0]), qubits=qubits, + couplers=couplers, ) # add the sequencer to the list of sequencers required by the port self._sequencers[port].append(sequencer) @@ -383,12 +408,13 @@ def process_pulse_sequence( port=port, frequency=self.get_if(non_overlapping_pulses[0]), qubits=qubits, + couplers=couplers, ) # 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 + port=port, frequency=0, qubits=qubits, couplers=couplers ) # add the sequencer to the list of sequencers required by the port self._sequencers[port].append(sequencer) diff --git a/src/qibolab/instruments/qblox/cluster_qcm_rf.py b/src/qibolab/instruments/qblox/cluster_qcm_rf.py index cd91832577..0d5aa64abc 100644 --- a/src/qibolab/instruments/qblox/cluster_qcm_rf.py +++ b/src/qibolab/instruments/qblox/cluster_qcm_rf.py @@ -289,6 +289,7 @@ def get_if(self, pulse): def process_pulse_sequence( self, qubits: dict, + couplers: dict, instrument_pulses: PulseSequence, navgs: int, nshots: int, diff --git a/src/qibolab/instruments/qblox/cluster_qrm_rf.py b/src/qibolab/instruments/qblox/cluster_qrm_rf.py index a70d26c428..7c93242faf 100644 --- a/src/qibolab/instruments/qblox/cluster_qrm_rf.py +++ b/src/qibolab/instruments/qblox/cluster_qrm_rf.py @@ -337,6 +337,7 @@ def get_if(self, pulse: Pulse): def process_pulse_sequence( self, qubits: dict, + couplers: dict, instrument_pulses: PulseSequence, navgs: int, nshots: int, diff --git a/src/qibolab/instruments/qblox/controller.py b/src/qibolab/instruments/qblox/controller.py index f125677583..b85719c066 100644 --- a/src/qibolab/instruments/qblox/controller.py +++ b/src/qibolab/instruments/qblox/controller.py @@ -120,6 +120,7 @@ def _set_module_channel_map(self, module: QrmRf, qubits: dict): def _execute_pulse_sequence( self, qubits: dict, + couplers: dict, sequence: PulseSequence, options: ExecutionParameters, sweepers: list() = [], # list(Sweeper) = [] @@ -178,6 +179,7 @@ def _execute_pulse_sequence( # ask each module to generate waveforms & program and upload them to the device module.process_pulse_sequence( qubits, + couplers, module_pulses[name], navgs, nshots, @@ -228,7 +230,7 @@ def _execute_pulse_sequence( return data def play(self, qubits, couplers, sequence, options): - return self._execute_pulse_sequence(qubits, sequence, options) + return self._execute_pulse_sequence(qubits, couplers, sequence, options) def sweep( self, @@ -292,6 +294,7 @@ def sweep( # execute the each sweeper recursively self._sweep_recursion( qubits, + couplers, sequence_copy, options, *tuple(sweepers_copy), @@ -308,6 +311,7 @@ def sweep( def _sweep_recursion( self, qubits, + couplers, sequence, options: ExecutionParameters, *sweepers, @@ -386,6 +390,7 @@ def _sweep_recursion( if len(sweepers) > 1: self._sweep_recursion( qubits, + couplers, sequence, options, *sweepers[1:], @@ -393,7 +398,10 @@ def _sweep_recursion( ) else: result = self._execute_pulse_sequence( - qubits=qubits, sequence=sequence, options=options + qubits=qubits, + couplers=couplers, + sequence=sequence, + options=options, ) for pulse in sequence.ro_pulses: if results[pulse.id]: @@ -440,6 +448,7 @@ def _sweep_recursion( ) self._sweep_recursion( qubits, + couplers, sequence, options, *((split_sweeper,) + sweepers[1:]), @@ -486,7 +495,7 @@ def _sweep_recursion( # qubits[pulse.qubit].drive.gain = 1 result = self._execute_pulse_sequence( - qubits, sequence, options, sweepers + qubits, couplers, sequence, options, sweepers ) self._add_to_results(sequence, results, result) else: @@ -509,6 +518,7 @@ def _sweep_recursion( res = self._execute_pulse_sequence( qubits, + couplers, sequence, replace(options, nshots=_nshots), sweepers, From db433330b5a4e17839bd96aad10dedf8a1ae3d3b Mon Sep 17 00:00:00 2001 From: Alvaro Orgaz Date: Sat, 18 May 2024 11:22:54 +0400 Subject: [PATCH 12/70] coupler support for qblox drivers --- src/qibolab/instruments/qblox/cluster_qcm_bb.py | 10 +++++++++- src/qibolab/instruments/qblox/controller.py | 10 ++++++++-- src/qibolab/instruments/qblox/sequencer.py | 1 + src/qibolab/pulses.py | 10 ++++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/qibolab/instruments/qblox/cluster_qcm_bb.py b/src/qibolab/instruments/qblox/cluster_qcm_bb.py index 8aca5e382a..5156e4d9df 100644 --- a/src/qibolab/instruments/qblox/cluster_qcm_bb.py +++ b/src/qibolab/instruments/qblox/cluster_qcm_bb.py @@ -271,6 +271,7 @@ def _get_next_sequencer(self, port, frequency, qubits: dict, couplers: dict = {} # create sequencer wrapper sequencer = Sequencer(next_sequencer_number) sequencer.qubit = qubit.name if qubit else None + sequencer.coupler = coupler.name if coupler else None return sequencer def get_if(self, pulse): @@ -519,7 +520,14 @@ def process_pulse_sequence( ) else: # qubit_sweeper_parameters - if sequencer.qubit in [qubit.name for qubit in sweeper.qubits]: + + if ( + sweeper.qubits + and sequencer.qubit in [q.name for q in sweeper.qubits] + ) or ( + sweeper.couplers + and sequencer.coupler in [c.name for c in sweeper.couplers] + ): # plays an active role if sweeper.parameter == Parameter.bias: reference_value = self._ports[port].offset diff --git a/src/qibolab/instruments/qblox/controller.py b/src/qibolab/instruments/qblox/controller.py index b85719c066..05604d821c 100644 --- a/src/qibolab/instruments/qblox/controller.py +++ b/src/qibolab/instruments/qblox/controller.py @@ -102,7 +102,7 @@ def _termination_handler(self, signum, frame): log.warning("QbloxController: all modules are disconnected.") exit(0) - def _set_module_channel_map(self, module: QrmRf, qubits: dict): + def _set_module_channel_map(self, module: QrmRf, qubits: dict, couplers: dict): """Retrieve all the channels connected to a specific Qblox module. This method updates the `channel_port_map` attribute of the @@ -115,6 +115,10 @@ def _set_module_channel_map(self, module: QrmRf, qubits: dict): for channel in qubit.channels: if channel.port and channel.port.module.name == module.name: module.channel_map[channel.name] = channel + for coupler in couplers.values(): + for channel in coupler.channels: + if channel.port and channel.port.module.name == module.name: + module.channel_map[channel.name] = channel return list(module.channel_map) def _execute_pulse_sequence( @@ -173,7 +177,7 @@ def _execute_pulse_sequence( data = {} for name, module in self.modules.items(): # from the pulse sequence, select those pulses to be synthesised by the module - module_channels = self._set_module_channel_map(module, qubits) + module_channels = self._set_module_channel_map(module, qubits, couplers) module_pulses[name] = sequence.get_channel_pulses(*module_channels) # ask each module to generate waveforms & program and upload them to the device @@ -271,6 +275,7 @@ def sweep( values=sweeper.values, pulses=ps, qubits=sweeper.qubits, + couplers=sweeper.couplers, type=sweeper.type, ) ) @@ -445,6 +450,7 @@ def _sweep_recursion( values=_values, pulses=sweeper.pulses, qubits=sweeper.qubits, + couplers=sweeper.couplers, ) self._sweep_recursion( qubits, diff --git a/src/qibolab/instruments/qblox/sequencer.py b/src/qibolab/instruments/qblox/sequencer.py index 185375185d..c55a7df904 100644 --- a/src/qibolab/instruments/qblox/sequencer.py +++ b/src/qibolab/instruments/qblox/sequencer.py @@ -232,3 +232,4 @@ def __init__(self, number: int): self.weights: dict = {} self.program: Program = Program() self.qubit = None # self.qubit: int | str = None + self.coupler = None # self.coupler: int | str = None diff --git a/src/qibolab/pulses.py b/src/qibolab/pulses.py index 8da469bb9e..001c963934 100644 --- a/src/qibolab/pulses.py +++ b/src/qibolab/pulses.py @@ -929,6 +929,15 @@ def copy(self): # -> Pulse|ReadoutPulse|DrivePulse|FluxPulse: self.channel, self.qubit, ) + elif type(self) == CouplerFluxPulse: + return CouplerFluxPulse( + self.start, + self.duration, + self.amplitude, + self._shape, + self.channel, + self.qubit, + ) else: # return eval(self.serial) return Pulse( @@ -1221,6 +1230,7 @@ class PulseConstructor(Enum): READOUT = ReadoutPulse DRIVE = DrivePulse FLUX = FluxPulse + COUPLERFLUX = CouplerFluxPulse class PulseSequence: From a751d725a63976053f62d5da689be93f0a6ea6c9 Mon Sep 17 00:00:00 2001 From: Alvaro Orgaz Date: Sat, 18 May 2024 11:22:54 +0400 Subject: [PATCH 13/70] coupler support for qblox drivers --- src/qibolab/instruments/qblox/cluster_qcm_bb.py | 10 ++++++---- src/qibolab/instruments/qblox/cluster_qcm_rf.py | 5 ++++- src/qibolab/instruments/qblox/cluster_qrm_rf.py | 10 ++++++++-- src/qibolab/instruments/qblox/sequencer.py | 2 +- src/qibolab/pulses.py | 9 +++++++++ 5 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/qibolab/instruments/qblox/cluster_qcm_bb.py b/src/qibolab/instruments/qblox/cluster_qcm_bb.py index 5156e4d9df..9e65262319 100644 --- a/src/qibolab/instruments/qblox/cluster_qcm_bb.py +++ b/src/qibolab/instruments/qblox/cluster_qcm_bb.py @@ -520,13 +520,12 @@ def process_pulse_sequence( ) else: # qubit_sweeper_parameters - if ( sweeper.qubits - and sequencer.qubit in [q.name for q in sweeper.qubits] + and sequencer.qubit in [_.name for _ in sweeper.qubits] ) or ( sweeper.couplers - and sequencer.coupler in [c.name for c in sweeper.couplers] + and sequencer.coupler in [_.name for _ in sweeper.couplers] ): # plays an active role if sweeper.parameter == Parameter.bias: @@ -650,7 +649,10 @@ def process_pulse_sequence( and pulses[n].sweeper.type == QbloxSweeperType.duration ): RI = pulses[n].sweeper.register - if pulses[n].type == PulseType.FLUX: + if ( + pulses[n].type == PulseType.FLUX + or pulses[n].type == PulseType.COUPLERFLUX + ): RQ = pulses[n].sweeper.register else: RQ = pulses[n].sweeper.aux_register diff --git a/src/qibolab/instruments/qblox/cluster_qcm_rf.py b/src/qibolab/instruments/qblox/cluster_qcm_rf.py index 0d5aa64abc..f587e609b0 100644 --- a/src/qibolab/instruments/qblox/cluster_qcm_rf.py +++ b/src/qibolab/instruments/qblox/cluster_qcm_rf.py @@ -611,7 +611,10 @@ def process_pulse_sequence( and pulses[n].sweeper.type == QbloxSweeperType.duration ): RI = pulses[n].sweeper.register - if pulses[n].type == PulseType.FLUX: + if ( + pulses[n].type == PulseType.FLUX + or pulses[n].type == PulseType.COUPLERFLUX + ): RQ = pulses[n].sweeper.register else: RQ = pulses[n].sweeper.aux_register diff --git a/src/qibolab/instruments/qblox/cluster_qrm_rf.py b/src/qibolab/instruments/qblox/cluster_qrm_rf.py index 7c93242faf..4705821d51 100644 --- a/src/qibolab/instruments/qblox/cluster_qrm_rf.py +++ b/src/qibolab/instruments/qblox/cluster_qrm_rf.py @@ -741,7 +741,10 @@ def process_pulse_sequence( and pulses[n].sweeper.type == QbloxSweeperType.duration ): RI = pulses[n].sweeper.register - if pulses[n].type == PulseType.FLUX: + if ( + pulses[n].type == PulseType.FLUX + or pulses[n].type == PulseType.COUPLERFLUX + ): RQ = pulses[n].sweeper.register else: RQ = pulses[n].sweeper.aux_register @@ -788,7 +791,10 @@ def process_pulse_sequence( and pulses[n].sweeper.type == QbloxSweeperType.duration ): RI = pulses[n].sweeper.register - if pulses[n].type == PulseType.FLUX: + if ( + pulses[n].type == PulseType.FLUX + or pulses[n].type == PulseType.COUPLERFLUX + ): RQ = pulses[n].sweeper.register else: RQ = pulses[n].sweeper.aux_register diff --git a/src/qibolab/instruments/qblox/sequencer.py b/src/qibolab/instruments/qblox/sequencer.py index c55a7df904..922e33f9be 100644 --- a/src/qibolab/instruments/qblox/sequencer.py +++ b/src/qibolab/instruments/qblox/sequencer.py @@ -127,7 +127,7 @@ def bake_pulse_waveforms( # there may be other waveforms stored already, set first index as the next available first_idx = len(self.unique_waveforms) - if pulse.type == PulseType.FLUX: + if pulses[n].type == PulseType.FLUX or pulses[n].type == PulseType.COUPLERFLUX: # for flux pulses, store i waveforms idx_range = np.arange(first_idx, first_idx + len(values), 1) diff --git a/src/qibolab/pulses.py b/src/qibolab/pulses.py index 001c963934..3468d856dd 100644 --- a/src/qibolab/pulses.py +++ b/src/qibolab/pulses.py @@ -938,6 +938,15 @@ def copy(self): # -> Pulse|ReadoutPulse|DrivePulse|FluxPulse: self.channel, self.qubit, ) + elif type(self) == CouplerFluxPulse: + return CouplerFluxPulse( + self.start, + self.duration, + self.amplitude, + self._shape, + self.channel, + self.qubit, + ) else: # return eval(self.serial) return Pulse( From 40c53fc2a1cfc89eb3d353181c9fdc6b353aecf1 Mon Sep 17 00:00:00 2001 From: Alvaro Orgaz Date: Sat, 18 May 2024 19:47:56 +0400 Subject: [PATCH 14/70] coupler support for qblox drivers --- src/qibolab/instruments/qblox/sequencer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibolab/instruments/qblox/sequencer.py b/src/qibolab/instruments/qblox/sequencer.py index 922e33f9be..304a6b716c 100644 --- a/src/qibolab/instruments/qblox/sequencer.py +++ b/src/qibolab/instruments/qblox/sequencer.py @@ -127,7 +127,7 @@ def bake_pulse_waveforms( # there may be other waveforms stored already, set first index as the next available first_idx = len(self.unique_waveforms) - if pulses[n].type == PulseType.FLUX or pulses[n].type == PulseType.COUPLERFLUX: + if pulse.type == PulseType.FLUX or pulse.type == PulseType.COUPLERFLUX: # for flux pulses, store i waveforms idx_range = np.arange(first_idx, first_idx + len(values), 1) From e6e7d2f1e81331bc9322cd25a12caac2502a3dff Mon Sep 17 00:00:00 2001 From: Alvaro Orgaz Date: Mon, 20 May 2024 10:05:36 +0400 Subject: [PATCH 15/70] support for baking Custom pulses and enhancements in their evaluation from a string --- src/qibolab/pulses.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/qibolab/pulses.py b/src/qibolab/pulses.py index 3468d856dd..0598495b79 100644 --- a/src/qibolab/pulses.py +++ b/src/qibolab/pulses.py @@ -705,11 +705,13 @@ class Custom(PulseShape): """Arbitrary shape.""" def __init__(self, envelope_i, envelope_q=None): + from ast import literal_eval + self.name = "Custom" self.pulse: Pulse = None - self.envelope_i: np.ndarray = np.array(envelope_i) + self.envelope_i: np.ndarray = np.array(literal_eval(envelope_i)) if envelope_q is not None: - self.envelope_q: np.ndarray = np.array(envelope_q) + self.envelope_q: np.ndarray = np.array(literal_eval(envelope_q)) else: self.envelope_q = self.envelope_i @@ -717,11 +719,13 @@ def envelope_waveform_i(self, sampling_rate=SAMPLING_RATE) -> Waveform: """The envelope waveform of the i component of the pulse.""" if self.pulse: - if self.pulse.duration != len(self.envelope_i): + 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 * sampling_rate)) - waveform = Waveform(self.envelope_i * self.pulse.amplitude) + waveform = Waveform( + np.clip(self.envelope_i[:num_samples] * self.pulse.amplitude, -1, 1) + ) waveform.serial = f"Envelope_Waveform_I(num_samples = {num_samples}, amplitude = {format(self.pulse.amplitude, '.6f').rstrip('0').rstrip('.')}, shape = {repr(self)})" return waveform raise ShapeInitError @@ -730,11 +734,13 @@ def envelope_waveform_q(self, sampling_rate=SAMPLING_RATE) -> Waveform: """The envelope waveform of the q component of the pulse.""" if self.pulse: - if self.pulse.duration != len(self.envelope_q): + 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 * sampling_rate)) - waveform = Waveform(self.envelope_q * self.pulse.amplitude) + waveform = Waveform( + np.clip(self.envelope_q[:num_samples] * self.pulse.amplitude, -1, 1) + ) waveform.serial = f"Envelope_Waveform_Q(num_samples = {num_samples}, amplitude = {format(self.pulse.amplitude, '.6f').rstrip('0').rstrip('.')}, shape = {repr(self)})" return waveform raise ShapeInitError From 1d97650e72f262737b453785d3e2dc57c98b6a18 Mon Sep 17 00:00:00 2001 From: Alvaro Orgaz Date: Mon, 20 May 2024 23:08:24 +0400 Subject: [PATCH 16/70] add possibility to define shape in create_qubit_flux_pulse --- src/qibolab/platform/platform.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/qibolab/platform/platform.py b/src/qibolab/platform/platform.py index 7477405caf..94f2b06440 100644 --- a/src/qibolab/platform/platform.py +++ b/src/qibolab/platform/platform.py @@ -385,13 +385,15 @@ def create_qubit_readout_pulse(self, qubit, start): qubit = self.get_qubit(qubit) return self.create_MZ_pulse(qubit, start) - def create_qubit_flux_pulse(self, qubit, start, duration, amplitude=1): + def create_qubit_flux_pulse(self, qubit, start, duration, amplitude=1, shape=None): qubit = self.get_qubit(qubit) + if shape is None: + shape = "Rectangular()" pulse = FluxPulse( start=start, duration=duration, amplitude=amplitude, - shape="Rectangular", + shape=shape, channel=self.qubits[qubit].flux.name, qubit=qubit, ) From f174b6f115b115c398ff5fd3080984b73f06e614 Mon Sep 17 00:00:00 2001 From: Alvaro Orgaz Date: Fri, 31 May 2024 06:12:06 +0400 Subject: [PATCH 17/70] support the creation of coupler flux pulses with a particular shape --- src/qibolab/platform/platform.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/qibolab/platform/platform.py b/src/qibolab/platform/platform.py index 94f2b06440..269ec67fa4 100644 --- a/src/qibolab/platform/platform.py +++ b/src/qibolab/platform/platform.py @@ -400,8 +400,10 @@ def create_qubit_flux_pulse(self, qubit, start, duration, amplitude=1, shape=Non pulse.duration = duration return pulse - def create_coupler_pulse(self, coupler, start, duration=None, amplitude=None): + def create_coupler_pulse(self, coupler, start, duration=None, amplitude=None, shape=None): coupler = self.get_coupler(coupler) + if shape is None: + shape = "Rectangular()" pulse = self.couplers[coupler].native_pulse.CP.pulse(start) if duration is not None: pulse.duration = duration From 9ac640084b1b5ff66e1e6d0e478f9c1d3a062db0 Mon Sep 17 00:00:00 2001 From: Alvaro Orgaz Date: Fri, 31 May 2024 06:12:47 +0400 Subject: [PATCH 18/70] fix custom pulse initialisation so that it works both with strings and np arrays --- src/qibolab/pulses.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/qibolab/pulses.py b/src/qibolab/pulses.py index 0598495b79..f70dec8bd9 100644 --- a/src/qibolab/pulses.py +++ b/src/qibolab/pulses.py @@ -709,11 +709,24 @@ def __init__(self, envelope_i, envelope_q=None): self.name = "Custom" self.pulse: Pulse = None - self.envelope_i: np.ndarray = np.array(literal_eval(envelope_i)) - if envelope_q is not None: - self.envelope_q: np.ndarray = np.array(literal_eval(envelope_q)) - else: - self.envelope_q = self.envelope_i + if isinstance(envelope_i, str): + self.envelope_i: np.ndarray = np.array(literal_eval(envelope_i)) + if envelope_q is not None: + self.envelope_q: np.ndarray = np.array(literal_eval(envelope_q)) + else: + self.envelope_q = self.envelope_i + elif isinstance(envelope_i, list): + self.envelope_i: np.ndarray = np.array(envelope_i) + if envelope_q is not None: + self.envelope_q: np.ndarray = np.array(envelope_q) + else: + self.envelope_q = self.envelope_i + elif isinstance(envelope_i, np.ndarray): + self.envelope_i: np.ndarray = envelope_i + if envelope_q is not None: + self.envelope_q: np.ndarray = envelope_q + else: + self.envelope_q = self.envelope_i def envelope_waveform_i(self, sampling_rate=SAMPLING_RATE) -> Waveform: """The envelope waveform of the i component of the pulse.""" From 93559ad5e1ace00a0c787e49580daddc40359e0c Mon Sep 17 00:00:00 2001 From: Alvaro Orgaz Date: Mon, 3 Jun 2024 09:27:23 +0400 Subject: [PATCH 19/70] expose i & q offsets properties in qblox port, for mixer calibration --- .../instruments/qblox/cluster_qcm_rf.py | 4 ++ .../instruments/qblox/cluster_qrm_rf.py | 4 ++ src/qibolab/instruments/qblox/port.py | 41 +++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/src/qibolab/instruments/qblox/cluster_qcm_rf.py b/src/qibolab/instruments/qblox/cluster_qcm_rf.py index f587e609b0..507ac78693 100644 --- a/src/qibolab/instruments/qblox/cluster_qcm_rf.py +++ b/src/qibolab/instruments/qblox/cluster_qcm_rf.py @@ -197,6 +197,10 @@ def connect(self, cluster: Cluster = None): self._ports[port].lo_frequency = self.settings[port][ "lo_frequency" ] + if "mixer_calibration" in self.settings[port]: + self._ports[port].mixer_calibration = self.settings[port][ + "mixer_calibration" + ] self._ports[port].attenuation = self.settings[port]["attenuation"] self._ports[port].hardware_mod_en = True self._ports[port].nco_freq = 0 diff --git a/src/qibolab/instruments/qblox/cluster_qrm_rf.py b/src/qibolab/instruments/qblox/cluster_qrm_rf.py index 4705821d51..98c0f8fe26 100644 --- a/src/qibolab/instruments/qblox/cluster_qrm_rf.py +++ b/src/qibolab/instruments/qblox/cluster_qrm_rf.py @@ -213,6 +213,10 @@ def connect(self, cluster: Cluster = None): self._ports["o1"].lo_frequency = self.settings["o1"][ "lo_frequency" ] + if "mixer_calibration" in self.settings["o1"]: + self._ports["o1"].mixer_calibration = self.settings["o1"][ + "mixer_calibration" + ] self._ports["o1"].hardware_mod_en = True self._ports["o1"].nco_freq = 0 self._ports["o1"].nco_phase_offs = 0 diff --git a/src/qibolab/instruments/qblox/port.py b/src/qibolab/instruments/qblox/port.py index c83cf391e3..b087b0d345 100644 --- a/src/qibolab/instruments/qblox/port.py +++ b/src/qibolab/instruments/qblox/port.py @@ -19,6 +19,8 @@ class QbloxOutputPort_Settings: nco_phase_offs: float = 0 lo_enabled: bool = True lo_frequency: int = 2_000_000_000 + i_offset: float = 0 + q_offset: float = 0 @dataclass @@ -221,6 +223,45 @@ def lo_frequency(self, value): elif self.module.device.is_qcm_type: self.module.device.set(f"out{self.port_number}_lo_freq", value=value) + @property + def mixer_calibration(self): + """Parameters for calibrating mixer output. + + i and q offsets are supported. + """ + if self.module.device: + self._settings.i_offset = self.module.device.get( + f"out{self.port_number}_offset_path0" + ) + self._settings.q_offset = self.module.device.get( + f"out{self.port_number}_offset_path1" + ) + return [self._settings.i_offset, self._settings.q_offset] + + @mixer_calibration.setter + def mixer_calibration(self, value): + if not isinstance(value, list) or len(value) != 2: + raise_error( + ValueError, + f"Invalid mixer calibration parameters {value}. A list [i_offset, q_offset] is required.", + ) + self._settings.i_offset, self._settings.q_offset = value + + if self.module.device: + self.module._set_device_parameter( + self.module.device, + f"out{self.port_number}_offset_path0", + value=self._settings.i_offset, + ) + self.module._set_device_parameter( + self.module.device, + f"out{self.port_number}_offset_path1", + value=self._settings.q_offset, + ) + else: + pass + # TODO: This case regards a connection error of the module + class QbloxInputPort: def __init__(self, module, port_number: int, port_name: str = None): From 84c61ccf229de1b127f9c21725bc106ec119f838 Mon Sep 17 00:00:00 2001 From: Alvaro Orgaz Date: Tue, 16 Jul 2024 11:03:11 +0400 Subject: [PATCH 20/70] cz_sweep & standard_rb --- src/qibolab/instruments/qblox/sequencer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/qibolab/instruments/qblox/sequencer.py b/src/qibolab/instruments/qblox/sequencer.py index 304a6b716c..d061d38ebc 100644 --- a/src/qibolab/instruments/qblox/sequencer.py +++ b/src/qibolab/instruments/qblox/sequencer.py @@ -121,6 +121,7 @@ def bake_pulse_waveforms( NotEnoughMemory: If the memory needed to store the waveforms in more than the memory avalible. """ # In order to generate waveforms for each duration value, the pulse will need to be modified. + values = np.round(values).astype(int) # To avoid any conflicts, make a copy of the pulse first. pulse_copy = pulse.copy() From a18a6f1fa5b53b169f14302bbf30fdd92e19d6ea Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Tue, 13 Aug 2024 12:14:57 +0400 Subject: [PATCH 21/70] cleanup the cherry-picked commits --- .../instruments/qblox/cluster_qcm_bb.py | 41 ++++++++++--------- .../instruments/qblox/cluster_qcm_rf.py | 5 +-- .../instruments/qblox/cluster_qrm_rf.py | 12 +++--- src/qibolab/instruments/qblox/port.py | 19 +++------ src/qibolab/instruments/qblox/sequencer.py | 10 +++-- src/qibolab/platform/platform.py | 36 ++++++++++++---- src/qibolab/pulses.py | 20 ++++----- 7 files changed, 80 insertions(+), 63 deletions(-) diff --git a/src/qibolab/instruments/qblox/cluster_qcm_bb.py b/src/qibolab/instruments/qblox/cluster_qcm_bb.py index 9e65262319..4693777022 100644 --- a/src/qibolab/instruments/qblox/cluster_qcm_bb.py +++ b/src/qibolab/instruments/qblox/cluster_qcm_bb.py @@ -201,16 +201,18 @@ def setup(self, **settings): """ pass - def _get_next_sequencer(self, port, frequency, qubits: dict, couplers: dict = {}): - """Retrieves and configures the next avaliable sequencer. + def _get_next_sequencer( + self, port: str, frequency: float, qubits: dict, couplers: dict + ): + """Retrieves and configures the next available sequencer. The parameters of the new sequencer are copied from those of the default sequencer, except for the intermediate frequency and classification parameters. Args: - port (str): - frequency (): - qubits (): - couplers (): + port: name of the output port + frequency: NCO frequency + qubits: qubits associated with this sequencer + couplers: couplers associated with this sequencer Raises: Exception = If attempting to set a parameter without a connection to the instrument. """ @@ -237,6 +239,17 @@ def _get_next_sequencer(self, port, frequency, qubits: dict, couplers: dict = {} else: log.warning(f"Coupler {_coupler.name} has no flux line connected") + if qubit and coupler: + raise ValueError( + f"Port {port} of device {self.name} is configured for more than one line (flux lines of qubit {qubit.name} and coupler {coupler.name}" + ) + if qubit: + self._ports[port].offset = qubit.sweetspot + elif coupler: + self._ports[port].offset = coupler.sweetspot + else: + self._ports[port].offset = 0 + # 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]: @@ -261,13 +274,6 @@ def _get_next_sequencer(self, port, frequency, qubits: dict, couplers: dict = {} # TODO: Throw error in that event or implement for non_overlapping_same_frequency_pulses # Even better, set the frequency before each pulse is played (would work with hardware modulation only) - if qubit: - self._ports[port].offset = qubit.sweetspot - elif coupler: - self._ports[port].offset = coupler.sweetspot - else: - self._ports[port].offset = 0 - # create sequencer wrapper sequencer = Sequencer(next_sequencer_number) sequencer.qubit = qubit.name if qubit else None @@ -522,10 +528,10 @@ def process_pulse_sequence( else: # qubit_sweeper_parameters if ( sweeper.qubits - and sequencer.qubit in [_.name for _ in sweeper.qubits] + and sequencer.qubit in [q.name for q in sweeper.qubits] ) or ( sweeper.couplers - and sequencer.coupler in [_.name for _ in sweeper.couplers] + and sequencer.coupler in [c.name for c in sweeper.couplers] ): # plays an active role if sweeper.parameter == Parameter.bias: @@ -649,10 +655,7 @@ def process_pulse_sequence( and pulses[n].sweeper.type == QbloxSweeperType.duration ): RI = pulses[n].sweeper.register - if ( - pulses[n].type == PulseType.FLUX - or pulses[n].type == PulseType.COUPLERFLUX - ): + if pulses[n].type in (PulseType.FLUX, PulseType.COUPLERFLUX): RQ = pulses[n].sweeper.register else: RQ = pulses[n].sweeper.aux_register diff --git a/src/qibolab/instruments/qblox/cluster_qcm_rf.py b/src/qibolab/instruments/qblox/cluster_qcm_rf.py index 507ac78693..a8f2c62bd1 100644 --- a/src/qibolab/instruments/qblox/cluster_qcm_rf.py +++ b/src/qibolab/instruments/qblox/cluster_qcm_rf.py @@ -615,10 +615,7 @@ def process_pulse_sequence( and pulses[n].sweeper.type == QbloxSweeperType.duration ): RI = pulses[n].sweeper.register - if ( - pulses[n].type == PulseType.FLUX - or pulses[n].type == PulseType.COUPLERFLUX - ): + if pulses[n].type in (PulseType.FLUX, PulseType.COUPLERFLUX): RQ = pulses[n].sweeper.register else: RQ = pulses[n].sweeper.aux_register diff --git a/src/qibolab/instruments/qblox/cluster_qrm_rf.py b/src/qibolab/instruments/qblox/cluster_qrm_rf.py index 98c0f8fe26..7d930a4e92 100644 --- a/src/qibolab/instruments/qblox/cluster_qrm_rf.py +++ b/src/qibolab/instruments/qblox/cluster_qrm_rf.py @@ -745,9 +745,9 @@ def process_pulse_sequence( and pulses[n].sweeper.type == QbloxSweeperType.duration ): RI = pulses[n].sweeper.register - if ( - pulses[n].type == PulseType.FLUX - or pulses[n].type == PulseType.COUPLERFLUX + if pulses[n].type in ( + PulseType.FLUX, + PulseType.COUPLERFLUX, ): RQ = pulses[n].sweeper.register else: @@ -795,9 +795,9 @@ def process_pulse_sequence( and pulses[n].sweeper.type == QbloxSweeperType.duration ): RI = pulses[n].sweeper.register - if ( - pulses[n].type == PulseType.FLUX - or pulses[n].type == PulseType.COUPLERFLUX + if pulses[n].type in ( + PulseType.FLUX, + PulseType.COUPLERFLUX, ): RQ = pulses[n].sweeper.register else: diff --git a/src/qibolab/instruments/qblox/port.py b/src/qibolab/instruments/qblox/port.py index b087b0d345..9efb4e160f 100644 --- a/src/qibolab/instruments/qblox/port.py +++ b/src/qibolab/instruments/qblox/port.py @@ -248,19 +248,12 @@ def mixer_calibration(self, value): self._settings.i_offset, self._settings.q_offset = value if self.module.device: - self.module._set_device_parameter( - self.module.device, - f"out{self.port_number}_offset_path0", - value=self._settings.i_offset, - ) - self.module._set_device_parameter( - self.module.device, - f"out{self.port_number}_offset_path1", - value=self._settings.q_offset, - ) - else: - pass - # TODO: This case regards a connection error of the module + self.module.device.set( + f"out{self.port_number}_offset_path0", self._settings.i_offset + ), + self.module.device.set( + f"out{self.port_number}_offset_path1", self._settings.q_offset + ), class QbloxInputPort: diff --git a/src/qibolab/instruments/qblox/sequencer.py b/src/qibolab/instruments/qblox/sequencer.py index d061d38ebc..60a03fd33c 100644 --- a/src/qibolab/instruments/qblox/sequencer.py +++ b/src/qibolab/instruments/qblox/sequencer.py @@ -1,3 +1,5 @@ +from typing import Optional, Union + import numpy as np from qblox_instruments.qcodes_drivers.sequencer import Sequencer as QbloxSequencer @@ -120,15 +122,15 @@ def bake_pulse_waveforms( Raises: NotEnoughMemory: If the memory needed to store the waveforms in more than the memory avalible. """ - # In order to generate waveforms for each duration value, the pulse will need to be modified. values = np.round(values).astype(int) + # In order to generate waveforms for each duration value, the pulse will need to be modified. # To avoid any conflicts, make a copy of the pulse first. pulse_copy = pulse.copy() # there may be other waveforms stored already, set first index as the next available first_idx = len(self.unique_waveforms) - if pulse.type == PulseType.FLUX or pulse.type == PulseType.COUPLERFLUX: + if pulse.type in (PulseType.FLUX, PulseType.COUPLERFLUX): # for flux pulses, store i waveforms idx_range = np.arange(first_idx, first_idx + len(values), 1) @@ -232,5 +234,5 @@ def __init__(self, number: int): self.acquisitions: dict = {} self.weights: dict = {} self.program: Program = Program() - self.qubit = None # self.qubit: int | str = None - self.coupler = None # self.coupler: int | str = None + self.qubit: Optional[Union[int, str]] = None + self.coupler: Optional[Union[int, str]] = None diff --git a/src/qibolab/platform/platform.py b/src/qibolab/platform/platform.py index 269ec67fa4..5178978cbc 100644 --- a/src/qibolab/platform/platform.py +++ b/src/qibolab/platform/platform.py @@ -10,7 +10,14 @@ from qibolab.couplers import Coupler from qibolab.execution_parameters import ExecutionParameters from qibolab.instruments.abstract import Controller, Instrument, InstrumentId -from qibolab.pulses import Drag, FluxPulse, PulseSequence, ReadoutPulse +from qibolab.pulses import ( + CouplerFluxPulse, + Drag, + FluxPulse, + PulseSequence, + ReadoutPulse, + Rectangular, +) from qibolab.qubits import Qubit, QubitId, QubitPair, QubitPairId from qibolab.sweeper import Sweeper from qibolab.unrolling import batch @@ -388,7 +395,7 @@ def create_qubit_readout_pulse(self, qubit, start): def create_qubit_flux_pulse(self, qubit, start, duration, amplitude=1, shape=None): qubit = self.get_qubit(qubit) if shape is None: - shape = "Rectangular()" + shape = Rectangular() pulse = FluxPulse( start=start, duration=duration, @@ -400,15 +407,30 @@ def create_qubit_flux_pulse(self, qubit, start, duration, amplitude=1, shape=Non pulse.duration = duration return pulse - def create_coupler_pulse(self, coupler, start, duration=None, amplitude=None, shape=None): + def create_coupler_pulse( + self, coupler, start, duration=None, amplitude=None, shape=None + ): coupler = self.get_coupler(coupler) + native_pulse = self.couplers[coupler].native_pulse.CP.pulse(start) + + if duration is None: + duration = native_pulse.duration + if amplitude is None: + amplitude = native_pulse.amplitude + if shape is None: - shape = "Rectangular()" - pulse = self.couplers[coupler].native_pulse.CP.pulse(start) - if duration is not None: + pulse = native_pulse pulse.duration = duration - if amplitude is not None: pulse.amplitude = amplitude + else: + pulse = CouplerFluxPulse( + start=start, + duration=duration, + amplitude=amplitude, + shape=shape, + channel=self.qubits[coupler].flux.name, + qubit=coupler, + ) return pulse # TODO Remove RX90_drag_pulse and RX_drag_pulse, replace them with create_qubit_drive_pulse diff --git a/src/qibolab/pulses.py b/src/qibolab/pulses.py index f70dec8bd9..6e8213e33c 100644 --- a/src/qibolab/pulses.py +++ b/src/qibolab/pulses.py @@ -732,13 +732,13 @@ def envelope_waveform_i(self, sampling_rate=SAMPLING_RATE) -> Waveform: """The envelope waveform of the i component of the pulse.""" 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 * sampling_rate)) + if len(self.envelope_i) != num_samples: + raise ValueError( + "Length of envelope_i must be equal to pulse duration in samples" + ) - waveform = Waveform( - np.clip(self.envelope_i[:num_samples] * self.pulse.amplitude, -1, 1) - ) + 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)})" return waveform raise ShapeInitError @@ -747,13 +747,13 @@ def envelope_waveform_q(self, sampling_rate=SAMPLING_RATE) -> Waveform: """The envelope waveform of the q component of the pulse.""" 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 * sampling_rate)) + if len(self.envelope_q) != num_samples: + raise ValueError( + "Length of envelope_q must be equal to pulse duration in samples" + ) - waveform = Waveform( - np.clip(self.envelope_q[:num_samples] * self.pulse.amplitude, -1, 1) - ) + 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)})" return waveform raise ShapeInitError From 48e8d2683490b7b8097cb91ad49ba99f12b9c39b Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Tue, 13 Aug 2024 12:58:34 +0400 Subject: [PATCH 22/70] revert a5013cbb9c0f9145b1889943b245e2b928e4989c --- src/qibolab/pulses.py | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/src/qibolab/pulses.py b/src/qibolab/pulses.py index 6e8213e33c..27f5b573f8 100644 --- a/src/qibolab/pulses.py +++ b/src/qibolab/pulses.py @@ -705,28 +705,14 @@ class Custom(PulseShape): """Arbitrary shape.""" def __init__(self, envelope_i, envelope_q=None): - from ast import literal_eval self.name = "Custom" self.pulse: Pulse = None - if isinstance(envelope_i, str): - self.envelope_i: np.ndarray = np.array(literal_eval(envelope_i)) - if envelope_q is not None: - self.envelope_q: np.ndarray = np.array(literal_eval(envelope_q)) - else: - self.envelope_q = self.envelope_i - elif isinstance(envelope_i, list): - self.envelope_i: np.ndarray = np.array(envelope_i) - if envelope_q is not None: - self.envelope_q: np.ndarray = np.array(envelope_q) - else: - self.envelope_q = self.envelope_i - elif isinstance(envelope_i, np.ndarray): - self.envelope_i: np.ndarray = envelope_i - if envelope_q is not None: - self.envelope_q: np.ndarray = envelope_q - else: - self.envelope_q = self.envelope_i + self.envelope_i: np.ndarray = np.array(envelope_i) + if envelope_q is not None: + self.envelope_q: np.ndarray = np.array(envelope_q) + else: + self.envelope_q = self.envelope_i def envelope_waveform_i(self, sampling_rate=SAMPLING_RATE) -> Waveform: """The envelope waveform of the i component of the pulse.""" From 39f8fc1cfe36d363fe10c21300205717e43327ce Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Tue, 13 Aug 2024 13:01:56 +0400 Subject: [PATCH 23/70] remove duplicate block --- src/qibolab/pulses.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/qibolab/pulses.py b/src/qibolab/pulses.py index 27f5b573f8..b28fc1869e 100644 --- a/src/qibolab/pulses.py +++ b/src/qibolab/pulses.py @@ -943,15 +943,6 @@ def copy(self): # -> Pulse|ReadoutPulse|DrivePulse|FluxPulse: self.channel, self.qubit, ) - elif type(self) == CouplerFluxPulse: - return CouplerFluxPulse( - self.start, - self.duration, - self.amplitude, - self._shape, - self.channel, - self.qubit, - ) else: # return eval(self.serial) return Pulse( From 169f39751a3179c7f6805e6480c19d53c72d6e41 Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Wed, 14 Aug 2024 09:45:29 +0400 Subject: [PATCH 24/70] fix reference to shape --- src/qibolab/pulses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibolab/pulses.py b/src/qibolab/pulses.py index b28fc1869e..17c2917148 100644 --- a/src/qibolab/pulses.py +++ b/src/qibolab/pulses.py @@ -939,7 +939,7 @@ def copy(self): # -> Pulse|ReadoutPulse|DrivePulse|FluxPulse: self.start, self.duration, self.amplitude, - self._shape, + self.shape, self.channel, self.qubit, ) From 6110d392b7a1444fcec769f8641300d71268faef Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Thu, 15 Aug 2024 12:24:58 +0400 Subject: [PATCH 25/70] fix pulse shape deserialization not working for Custom, IIR, and SNZ --- src/qibolab/pulses.py | 59 +++++++++++++++++++++++++++---------- tests/test_pulses.py | 67 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 109 insertions(+), 17 deletions(-) diff --git a/src/qibolab/pulses.py b/src/qibolab/pulses.py index 17c2917148..62b58614f2 100644 --- a/src/qibolab/pulses.py +++ b/src/qibolab/pulses.py @@ -19,6 +19,22 @@ """ +def _np_array_from_string_or_array_like(x) -> np.ndarray: + """Convert input into numpy array. + + The input can be in one of these forms: + 1. string in form '[1, 2, 3, ...]' + 2. list, e.g. [1, 2, 3, ...] + 3. numpy array. This will be identity conversion. + """ + if isinstance(x, str): + return np.fromstring(x[1:-1], sep=",") + elif isinstance(x, (list, np.ndarray)): + return np.array(x) + else: + raise ValueError(f"Data in unrecognized format: {x}") + + class PulseType(Enum): """An enumeration to distinguish different types of pulses. @@ -215,12 +231,19 @@ def eval(value: str) -> "PulseShape": To be replaced by proper serialization. """ - shape_name = re.findall(r"(\w+)", value)[0] - if shape_name not in globals(): - raise ValueError(f"shape {value} not found") - shape_parameters = re.findall(r"[-\w+\d\.\d]+", value)[1:] - # TODO: create multiple tests to prove regex working correctly - return globals()[shape_name](*shape_parameters) + match = re.fullmatch(r"(\w+)\((.*)\)", value) + shape_name, params = None, None + if match is not None: + shape_name, params = match.groups() + if match is None or shape_name not in globals(): + raise ValueError(f"shape {value} not recognized") + + single_item_pattern = r"[^,\s\[\]\(\)]+" + csv_items_pattern = rf"(?:{single_item_pattern}(?:,\s*)?)*" + param_pattern = ( + rf"\[{csv_items_pattern}\]|\w+\({csv_items_pattern}\)|{single_item_pattern}" + ) + return globals()[shape_name](*re.findall(param_pattern, params)) class Rectangular(PulseShape): @@ -509,12 +532,14 @@ class IIR(PulseShape): # p = [b0 = 1−k +k ·α, b1 = −(1−k)·(1−α),a0 = 1 and a1 = −(1−α)] # p = [b0, b1, a0, a1] - def __init__(self, b, a, target: PulseShape): + def __init__(self, b, a, target): self.name = "IIR" - self.target: PulseShape = target + self.target: PulseShape = ( + PulseShape.eval(target) if isinstance(target, str) else target + ) self._pulse: Pulse = None - self.a: np.ndarray = np.array(a) - self.b: np.ndarray = np.array(b) + self.a: np.ndarray = _np_array_from_string_or_array_like(a) + self.b: np.ndarray = _np_array_from_string_or_array_like(b) # Check len(a) = len(b) = 2 def __eq__(self, item) -> bool: @@ -596,8 +621,10 @@ class SNZ(PulseShape): def __init__(self, t_idling, b_amplitude=None): self.name = "SNZ" self.pulse: Pulse = None - self.t_idling: float = t_idling - self.b_amplitude = b_amplitude + self.t_idling: float = float(t_idling) + self.b_amplitude = ( + float(b_amplitude) if b_amplitude is not None else b_amplitude + ) def __eq__(self, item) -> bool: """Overloads == operator.""" @@ -708,9 +735,11 @@ def __init__(self, envelope_i, envelope_q=None): self.name = "Custom" self.pulse: Pulse = None - self.envelope_i: np.ndarray = np.array(envelope_i) + self.envelope_i: np.ndarray = _np_array_from_string_or_array_like(envelope_i) if envelope_q is not None: - self.envelope_q: np.ndarray = np.array(envelope_q) + self.envelope_q: np.ndarray = _np_array_from_string_or_array_like( + envelope_q + ) else: self.envelope_q = self.envelope_i @@ -745,7 +774,7 @@ def envelope_waveform_q(self, sampling_rate=SAMPLING_RATE) -> Waveform: raise ShapeInitError def __repr__(self): - return f"{self.name}({self.envelope_i[:3]}, ..., {self.envelope_q[:3]}, ...)" + return f"{self.name}({self.envelope_i[:3]}, {self.envelope_q[:3]})" @dataclass diff --git a/tests/test_pulses.py b/tests/test_pulses.py index 9939b8faec..62f7a64b63 100644 --- a/tests/test_pulses.py +++ b/tests/test_pulses.py @@ -12,6 +12,7 @@ Custom, Drag, DrivePulse, + Exponential, FluxPulse, Gaussian, GaussianSquare, @@ -275,8 +276,70 @@ def test_pulses_pulseshape_sampling_rate(shape): def test_pulseshape_eval(): shape = PulseShape.eval("Rectangular()") assert isinstance(shape, Rectangular) - with pytest.raises(ValueError): - shape = PulseShape.eval("Ciao()") + + shape = PulseShape.eval("Exponential(1, 2)") + assert isinstance(shape, Exponential) + assert shape.tau == 1 + assert shape.upsilon == 2 + + shape = PulseShape.eval("Exponential(4, 5, 6)") + assert isinstance(shape, Exponential) + assert shape.tau == 4 + assert shape.upsilon == 5 + assert shape.g == 6 + + shape = PulseShape.eval("Gaussian(3.1)") + assert isinstance(shape, Gaussian) + assert shape.rel_sigma == 3.1 + + shape = PulseShape.eval("GaussianSquare(5, 78)") + assert isinstance(shape, GaussianSquare) + assert shape.rel_sigma == 5 + assert shape.width == 78 + + shape = PulseShape.eval("Drag(4, 0.1)") + assert isinstance(shape, Drag) + assert shape.rel_sigma == 4 + assert shape.beta == 0.1 + + shape = PulseShape.eval("IIR([1, 2, 3], [5], Drag(3, 0.2))") + assert isinstance(shape, IIR) + assert np.array_equal(shape.b, np.array([1, 2, 3])) + assert np.array_equal(shape.a, np.array([5])) + assert isinstance(shape.target, Drag) + assert shape.target.rel_sigma == 3 + assert shape.target.beta == 0.2 + + shape = PulseShape.eval("SNZ(10, 20)") + assert isinstance(shape, SNZ) + assert shape.t_idling == 10 + assert shape.b_amplitude == 20 + + shape = PulseShape.eval("eCap(3.14)") + assert isinstance(shape, eCap) + assert shape.alpha == 3.14 + + shape = PulseShape.eval("Custom([1, 2, 3], [4, 5, 6])") + assert isinstance(shape, Custom) + assert np.array_equal(shape.envelope_i, np.array([1, 2, 3])) + assert np.array_equal(shape.envelope_q, np.array([4, 5, 6])) + + with pytest.raises(ValueError, match="shape .* not recognized"): + _ = PulseShape.eval("Ciao()") + + +@pytest.mark.parametrize( + "value_str", + ["-0.1", "+0.1", "1.", "-3.", "+1.", "-0.1e2", "1e-2", "+1e3", "-3e-1", "-.4"], +) +def test_pulse_shape_eval_numeric_varieties(value_str): + shape = PulseShape.eval(f"Drag(1, {value_str})") + assert isinstance(shape, Drag) + assert shape.beta == float(value_str) + + shape = PulseShape.eval(f"Custom([0.1, {value_str}])") + assert isinstance(shape, Custom) + assert np.array_equal(shape.envelope_i, np.array([0.1, float(value_str)])) @pytest.mark.parametrize("rel_sigma,beta", [(5, 1), (5, -1), (3, -0.03), (4, 0.02)]) From f34b3fb537dc295ecc2ea6436e4383bd3c3db60b Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Tue, 20 Aug 2024 11:42:21 +0400 Subject: [PATCH 26/70] update qblox test setup --- tests/dummy_qrc/qblox/parameters.json | 88 ++++++++++++++++------ tests/dummy_qrc/qblox/platform.py | 22 +++++- tests/test_instruments_qblox_controller.py | 19 +++++ 3 files changed, 106 insertions(+), 23 deletions(-) diff --git a/tests/dummy_qrc/qblox/parameters.json b/tests/dummy_qrc/qblox/parameters.json index 7a5099b5fe..e8797d7266 100644 --- a/tests/dummy_qrc/qblox/parameters.json +++ b/tests/dummy_qrc/qblox/parameters.json @@ -11,24 +11,30 @@ 3, 4 ], - "topology": [ - [ + "couplers": [ + 0, + 1, + 3, + 4 + ], + "topology": { + "0": [ 0, 2 ], - [ + "1": [ 1, 2 ], - [ + "3": [ 2, 3 ], - [ + "4": [ 2, 4 ] - ], + }, "instruments": { "qblox_controller": { "bounds": { @@ -76,7 +82,8 @@ "o1": { "attenuation": 36, "lo_frequency": 7300000000, - "gain": 0.6 + "gain": 0.6, + "mixer_calibration": [-3.2, 1.4] }, "i1": { "acquisition_hold_off": 500, @@ -87,7 +94,8 @@ "o1": { "attenuation": 36, "lo_frequency": 7850000000, - "gain": 0.6 + "gain": 0.6, + "mixer_calibration": [0.3, -3.8] }, "i1": { "acquisition_hold_off": 500, @@ -243,6 +251,48 @@ } } }, + "coupler": { + "0": { + "CP": { + "type": "coupler", + "duration": 0, + "amplitude": 0, + "shape": "Rectangular()", + "coupler": 0, + "relative_start": 0 + } + }, + "1": { + "CP": { + "type": "coupler", + "duration": 0, + "amplitude": 0, + "shape": "Rectangular()", + "coupler": 1, + "relative_start": 0 + } + }, + "3": { + "CP": { + "type": "coupler", + "duration": 0, + "amplitude": 0, + "shape": "Rectangular()", + "coupler": 3, + "relative_start": 0 + } + }, + "4": { + "CP": { + "type": "coupler", + "duration": 0, + "amplitude": 0, + "shape": "Rectangular()", + "coupler": 4, + "relative_start": 0 + } + } + }, "two_qubit": { "2-3": { "CZ": [ @@ -401,22 +451,18 @@ "threshold": 0.002323 } }, - "two_qubit":{ - "0-2": { - "gate_fidelity": [0.0, 0.0], - "cz_fidelity": [0.0, 0.0] + "coupler": { + "0": { + "sweetspot": 0.1 }, - "1-2": { - "gate_fidelity": [0.0, 0.0], - "cz_fidelity": [0.0, 0.0] + "1": { + "sweetspot": 0.2 }, - "2-3": { - "gate_fidelity": [0.0, 0.0], - "cz_fidelity": [0.0, 0.0] + "3": { + "sweetspot": -0.3 }, - "2-4": { - "gate_fidelity": [0.0, 0.0], - "cz_fidelity": [0.0, 0.0] + "4": { + "sweetspot": 0.0 } } } diff --git a/tests/dummy_qrc/qblox/platform.py b/tests/dummy_qrc/qblox/platform.py index a60a600d8f..6129c716d9 100644 --- a/tests/dummy_qrc/qblox/platform.py +++ b/tests/dummy_qrc/qblox/platform.py @@ -30,6 +30,7 @@ def create(): modules = { "qcm_bb0": QcmBb("qcm_bb0", f"{ADDRESS}:2"), "qcm_bb1": QcmBb("qcm_bb1", f"{ADDRESS}:4"), + "qcm_bb2": QcmBb("qcm_bb2", f"{ADDRESS}:3"), "qcm_rf0": QcmRf("qcm_rf0", f"{ADDRESS}:6"), "qcm_rf1": QcmRf("qcm_rf1", f"{ADDRESS}:8"), "qcm_rf2": QcmRf("qcm_rf2", f"{ADDRESS}:10"), @@ -61,12 +62,17 @@ def create(): channels |= Channel(name="L3-12", port=modules["qcm_rf1"].ports("o1")) channels |= Channel(name="L3-13", port=modules["qcm_rf1"].ports("o2")) channels |= Channel(name="L3-14", port=modules["qcm_rf2"].ports("o1")) - # Flux + # Qubit flux channels |= Channel(name="L4-5", port=modules["qcm_bb0"].ports("o1")) channels |= Channel(name="L4-1", port=modules["qcm_bb0"].ports("o2")) channels |= Channel(name="L4-2", port=modules["qcm_bb0"].ports("o3")) channels |= Channel(name="L4-3", port=modules["qcm_bb0"].ports("o4")) channels |= Channel(name="L4-4", port=modules["qcm_bb1"].ports("o1")) + # Coupler flux + channels |= Channel(name="L4-12", port=modules["qcm_bb1"].ports("o2")) + channels |= Channel(name="L4-13", port=modules["qcm_bb1"].ports("o3")) + channels |= Channel(name="L4-14", port=modules["qcm_bb1"].ports("o4")) + channels |= Channel(name="L4-5", port=modules["qcm_bb2"].ports("o1")) # TWPA channels |= Channel(name="L3-28", port=None) channels["L3-28"].local_oscillator = twpa_pump @@ -98,8 +104,20 @@ def create(): for q in range(5): qubits[q].flux.max_bias = 2.5 + for i, coupler in enumerate(couplers): + couplers[coupler].flux = ( + channels[f"L4-{11 + i}"] if i > 0 else channels[f"L4-5"] + ) + couplers[coupler].flux.max_bias = 2.5 + settings = load_settings(runcard) return Platform( - str(FOLDER), qubits, pairs, instruments, settings, resonator_type="2D" + str(FOLDER), + qubits, + pairs, + instruments, + settings, + resonator_type="2D", + couplers=couplers, ) diff --git a/tests/test_instruments_qblox_controller.py b/tests/test_instruments_qblox_controller.py index fde3682f07..c5c5fba003 100644 --- a/tests/test_instruments_qblox_controller.py +++ b/tests/test_instruments_qblox_controller.py @@ -61,6 +61,25 @@ def test_sweep_too_many_sweep_points(platform, controller): controller.sweep({0: qubit}, {}, PulseSequence(pulse), params, sweep) +def test_sweep_coupler(platform, controller): + """Test that coupler related sweep is accepted.""" + ro_pulse = platform.create_MZ_pulse(qubit=0, start=0) + sequence = PulseSequence(ro_pulse) + + sweeper = Sweeper(Parameter.bias, np.random.rand(4), couplers=[0]) + params = ExecutionParameters( + nshots=10, relaxation_time=1000, averaging_mode=AveragingMode.CYCLIC + ) + mock_data = np.array([1, 2, 3, 4]) + controller._execute_pulse_sequence = Mock( + return_value={ro_pulse.serial: IntegratedResults(mock_data)} + ) + res = controller.sweep( + platform.qubits, platform.couplers, sequence, params, sweeper + ) + assert np.array_equal(res[ro_pulse.serial].voltage, mock_data) + + @pytest.mark.qpu def connect(connected_controller: QbloxController): connected_controller.connect() From 4074c9d2f84573e849f6e591b18490f4faa39d03 Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Wed, 18 Sep 2024 15:07:25 +0400 Subject: [PATCH 27/70] workaround to make experiments with start sweeper on non RO lines move RO according to the sweep --- src/qibolab/instruments/qblox/controller.py | 41 ++++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/qibolab/instruments/qblox/controller.py b/src/qibolab/instruments/qblox/controller.py index 05604d821c..d4623036f0 100644 --- a/src/qibolab/instruments/qblox/controller.py +++ b/src/qibolab/instruments/qblox/controller.py @@ -11,7 +11,7 @@ from qibolab.instruments.qblox.cluster_qcm_rf import QcmRf from qibolab.instruments.qblox.cluster_qrm_rf import QrmRf from qibolab.instruments.qblox.sequencer import SAMPLING_RATE -from qibolab.pulses import PulseSequence, PulseType +from qibolab.pulses import Custom, PulseSequence, PulseType, ReadoutPulse from qibolab.result import SampleResults from qibolab.sweeper import Parameter, Sweeper, SweeperType from qibolab.unrolling import Bounds @@ -280,6 +280,43 @@ def sweep( ) ) + serial_map = {p.serial: p.serial for p in sequence_copy.ro_pulses} + for sweeper in sweepers_copy: + if sweeper.parameter is Parameter.start and not any( + pulse.type is PulseType.READOUT for pulse in sweeper.pulses + ): + for pulse in sequence_copy: + if pulse.type is PulseType.READOUT: + idx = sequence_copy.index(pulse) + padded_pulse = ReadoutPulse( + start=0, + duration=pulse.start + pulse.duration, + amplitude=pulse.amplitude, + frequency=pulse.frequency, + relative_phase=pulse.relative_phase, + shape=Custom( + envelope_i=np.concatenate( + ( + np.zeros(pulse.start), + pulse.envelope_waveform_i().data + / pulse.amplitude, + ) + ), + envelope_q=np.concatenate( + ( + np.zeros(pulse.start), + pulse.envelope_waveform_q().data + / pulse.amplitude, + ) + ), + ), + channel=pulse.channel, + qubit=pulse.qubit, + ) + serial_map[padded_pulse.serial] = pulse.serial + sequence_copy[idx] = padded_pulse + sweeper.pulses.append(padded_pulse) + # reverse sweepers exept for res punchout att contains_attenuation_frequency = any( sweepers_copy[i].parameter == Parameter.attenuation @@ -309,7 +346,7 @@ def sweep( # return the results using the original serials serial_results = {} for pulse in sequence_copy.ro_pulses: - serial_results[map_id_serial[pulse.id]] = id_results[pulse.id] + serial_results[serial_map[map_id_serial[pulse.id]]] = id_results[pulse.id] serial_results[pulse.qubit] = id_results[pulse.id] return serial_results From 108c3c2ffaa208ee1ec959bff7b799e2430b35a3 Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Fri, 20 Sep 2024 11:30:28 +0400 Subject: [PATCH 28/70] trim waveforms of pulses with custom shapes for shorter pulse durations --- src/qibolab/pulses.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/qibolab/pulses.py b/src/qibolab/pulses.py index 62b58614f2..63fd1b314a 100644 --- a/src/qibolab/pulses.py +++ b/src/qibolab/pulses.py @@ -748,12 +748,12 @@ def envelope_waveform_i(self, sampling_rate=SAMPLING_RATE) -> Waveform: if self.pulse: num_samples = int(np.rint(self.pulse.duration * sampling_rate)) - if len(self.envelope_i) != num_samples: + if len(self.envelope_i) < num_samples: raise ValueError( - "Length of envelope_i must be equal to pulse duration in samples" + "Length of envelope_i must not be shorter than pulse duration in samples" ) - waveform = Waveform(self.envelope_i * self.pulse.amplitude) + waveform = Waveform(self.envelope_i[:num_samples] * 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)})" return waveform raise ShapeInitError @@ -763,12 +763,12 @@ def envelope_waveform_q(self, sampling_rate=SAMPLING_RATE) -> Waveform: if self.pulse: num_samples = int(np.rint(self.pulse.duration * sampling_rate)) - if len(self.envelope_q) != num_samples: + if len(self.envelope_q) < num_samples: raise ValueError( - "Length of envelope_q must be equal to pulse duration in samples" + "Length of envelope_q must not be shorter than pulse duration in samples" ) - waveform = Waveform(self.envelope_q * self.pulse.amplitude) + waveform = Waveform(self.envelope_q[:num_samples] * 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)})" return waveform raise ShapeInitError From 9242c2afe31277b65d7c0693b2a3296261ecff38 Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Fri, 20 Sep 2024 14:06:36 +0400 Subject: [PATCH 29/70] workaround to make sure RO pulse does not overlap with duration-swept pulse --- src/qibolab/instruments/qblox/controller.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/qibolab/instruments/qblox/controller.py b/src/qibolab/instruments/qblox/controller.py index d4623036f0..a82ca6635f 100644 --- a/src/qibolab/instruments/qblox/controller.py +++ b/src/qibolab/instruments/qblox/controller.py @@ -282,11 +282,19 @@ def sweep( serial_map = {p.serial: p.serial for p in sequence_copy.ro_pulses} for sweeper in sweepers_copy: - if sweeper.parameter is Parameter.start and not any( + if sweeper.parameter in (Parameter.duration, Parameter.start) and not any( pulse.type is PulseType.READOUT for pulse in sweeper.pulses ): - for pulse in sequence_copy: - if pulse.type is PulseType.READOUT: + for pulse in sequence_copy.ro_pulses: + current_serial = pulse.serial + if sweeper.parameter is Parameter.duration: + sweep_values = sweeper.get_values(sweeper.pulses[0].duration) + if ( + max_finish := sweeper.pulses[0].start + np.max(sweep_values) + ) > pulse.start: + pulse.start = max_finish + serial_map[pulse.serial] = current_serial + if sweeper.parameter is Parameter.start: idx = sequence_copy.index(pulse) padded_pulse = ReadoutPulse( start=0, @@ -313,7 +321,7 @@ def sweep( channel=pulse.channel, qubit=pulse.qubit, ) - serial_map[padded_pulse.serial] = pulse.serial + serial_map[padded_pulse.serial] = current_serial sequence_copy[idx] = padded_pulse sweeper.pulses.append(padded_pulse) From 730e291af4c5c54a137417970e5f930dc920b232 Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Fri, 20 Sep 2024 15:04:01 +0400 Subject: [PATCH 30/70] change order of arctan2 arguments --- src/qibolab/result.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qibolab/result.py b/src/qibolab/result.py index 837d7fba10..19ecb0c7fd 100644 --- a/src/qibolab/result.py +++ b/src/qibolab/result.py @@ -38,7 +38,7 @@ def magnitude(self): @cached_property def phase(self): """Signal phase in radians.""" - return np.unwrap(np.arctan2(self.voltage_i, self.voltage_q)) + return np.unwrap(np.arctan2(self.voltage_q, self.voltage_i)) @cached_property def phase_std(self): @@ -95,7 +95,7 @@ def phase_std(self): @cached_property def phase(self): """Phase not unwrapped because it is a single value.""" - return np.arctan2(self.voltage_i, self.voltage_q) + return np.arctan2(self.voltage_q, self.voltage_i) class RawWaveformResults(IntegratedResults): From 1e56825290f21d721e39c12a435135ced2ed92c1 Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Thu, 26 Sep 2024 14:48:48 +0400 Subject: [PATCH 31/70] take sequencer number from defaults --- src/qibolab/instruments/qblox/port.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibolab/instruments/qblox/port.py b/src/qibolab/instruments/qblox/port.py index 9efb4e160f..8827709d99 100644 --- a/src/qibolab/instruments/qblox/port.py +++ b/src/qibolab/instruments/qblox/port.py @@ -38,7 +38,7 @@ class QbloxOutputPort(Port): def __init__(self, module, port_number: int, port_name: str = None): self.name = port_name self.module = module - self.sequencer_number: int = port_number + self.sequencer_number: int = module.DEFAULT_SEQUENCERS[port_name] self.port_number: int = port_number self._settings = QbloxOutputPort_Settings() From 035b325929ebc1f0d1d503e1fdb9cd34f28f1196 Mon Sep 17 00:00:00 2001 From: jevillegasdatTII Date: Wed, 9 Oct 2024 11:45:11 +0400 Subject: [PATCH 32/70] Fix for frequencies not overlapping. With @aorgazf. --- src/qibolab/pulses.py | 55 +++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/src/qibolab/pulses.py b/src/qibolab/pulses.py index 63fd1b314a..45eae38c28 100644 --- a/src/qibolab/pulses.py +++ b/src/qibolab/pulses.py @@ -1587,30 +1587,39 @@ def separate_overlapping_pulses(self): # -> dict((int,int): PulseSequence): """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 - - separated_pulses = [] - for new_pulse in self.pulses: - stored = False - 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 - ): - overlaps = True + # This routine separates the pulses of a sequence into sets of different frequecy, non-overlapping + # pulses + + freqs = set() + for pulse in self.pulses: + freqs |= {pulse.frequency} + PS_freq = {} + separated_pulses = {} + for freq in freqs: + PS_freq[freq] = PulseSequence() + separated_pulses[freq] = [] + for pulse in self.pulses: + if pulse.frequency == freq: + PS_freq[freq].add(pulse) + + for new_pulse in PS_freq[freq]: + stored = False + for ps in separated_pulses[freq]: + overlaps = False + for existing_pulse in ps: + if ( + new_pulse.start < existing_pulse.finish + and new_pulse.finish > existing_pulse.start + ): + overlaps = True + break + if not overlaps: + ps.add(new_pulse) + stored = True break - if not overlaps: - ps.add(new_pulse) - stored = True - break - if not stored: - separated_pulses.append(PulseSequence(new_pulse)) - return separated_pulses - - # TODO: Implement separate_different_frequency_pulses() + if not stored: + separated_pulses[freq].append(PulseSequence(new_pulse)) + return [ps for freq in freqs for ps in separated_pulses[freq]] @property def pulses_overlap(self) -> bool: From f85447aa9ff231432842e4e26fa1ab0069085e14 Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Wed, 23 Oct 2024 11:27:26 +0400 Subject: [PATCH 33/70] fix result tests --- tests/test_result.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_result.py b/tests/test_result.py index e861f56796..7ba339bbae 100644 --- a/tests/test_result.py +++ b/tests/test_result.py @@ -71,7 +71,7 @@ def test_integrated_result_properties(result): np.sqrt(results.voltage_i**2 + results.voltage_q**2), results.magnitude ) np.testing.assert_equal( - np.unwrap(np.arctan2(results.voltage_i, results.voltage_q)), results.phase + np.unwrap(np.arctan2(results.voltage_q, results.voltage_i)), results.phase ) @@ -119,7 +119,7 @@ def test_serialize(average, result): "MSR[V]": np.sqrt(avg.voltage_i**2 + avg.voltage_q**2), "i[V]": avg.voltage_i, "q[V]": avg.voltage_q, - "phase[rad]": np.unwrap(np.arctan2(avg.voltage_i, avg.voltage_q)), + "phase[rad]": np.unwrap(np.arctan2(avg.voltage_q, avg.voltage_i)), } assert avg.serialize.keys() == target_dict.keys() for key in output: @@ -155,7 +155,7 @@ def test_serialize_averaged_iq_results(result): "MSR[V]": np.sqrt(results.voltage_i**2 + results.voltage_q**2), "i[V]": results.voltage_i, "q[V]": results.voltage_q, - "phase[rad]": np.unwrap(np.arctan2(results.voltage_i, results.voltage_q)), + "phase[rad]": np.unwrap(np.arctan2(results.voltage_q, results.voltage_i)), } assert output.keys() == target_dict.keys() for key in output: From d7e480684820bca0a0be4c57679f9a1b4b837548 Mon Sep 17 00:00:00 2001 From: Alessandro Candido Date: Mon, 28 Oct 2024 13:41:52 +0100 Subject: [PATCH 34/70] chore: Switch docformatter hook to master Waiting for a proper release which will fix the pre-commit 4 incompatibility --- .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 8896d83bfa..2e3810a89b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: - id: isort args: ["--profile", "black"] - repo: https://github.com/PyCQA/docformatter - rev: v1.7.5 + rev: master hooks: - id: docformatter additional_dependencies: [tomli] From 761047c18da0ad49e9e0c4d76481a59b2c77c84e Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Fri, 18 Oct 2024 14:30:29 +0400 Subject: [PATCH 35/70] feat: add properties in QibolabBackend --- src/qibolab/backends.py | 19 +++++++++++++++++++ tests/test_backends.py | 14 ++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/qibolab/backends.py b/src/qibolab/backends.py index f17ce97f82..fc6aa4d9e7 100644 --- a/src/qibolab/backends.py +++ b/src/qibolab/backends.py @@ -50,6 +50,25 @@ def __init__(self, platform): } self.compiler = Compiler.default() + @property + def qubits(self) -> list[str | int]: + return list(self.platform.qubits.keys()) + + @property + def connectivity(self) -> list[tuple[str | int, str | int]]: + return list(self.platform.pairs) + + @property + def natives(self) -> list[str]: + native_gates = set() + for _, q in self.platform.qubits.items(): + native_gates |= {k for k, v in q.native_gates.__dict__.items() if v is not None} + + for _, p in self.platform.pairs.items(): + native_gates |= {k for k, v in p.native_gates.__dict__.items() if v is not None} + + return list(native_gates) + def apply_gate(self, gate, state, nqubits): # pragma: no cover raise_error(NotImplementedError, "Qibolab cannot apply gates directly.") diff --git a/tests/test_backends.py b/tests/test_backends.py index 886d1a5bdc..4c05a55601 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -22,6 +22,20 @@ def generate_circuit_with_gate(nqubits, gate, **kwargs): def connected_backend(connected_platform): yield QibolabBackend(connected_platform) +def test_qubits(): + backend = QibolabBackend("dummy") + assert isinstance(backend.qubits, list) + assert set(backend.qubits) == set([0, 1, 2, 3, 4]) + +def test_connectivity(): + backend = QibolabBackend("dummy") + assert isinstance(backend.connectivity, list) + assert set(backend.connectivity) == set([(0, 2), (2, 0), (1, 2), (2, 1), (2, 3), (3, 2), (2, 4), (4, 2)]) + +def test_natives(): + backend = QibolabBackend("dummy") + assert isinstance(backend.natives, list) + assert set(backend.natives) == set(['RX12', 'RX', 'CZ', 'iSWAP', 'MZ', 'CNOT']) def test_execute_circuit_initial_state(): backend = QibolabBackend("dummy") From b7e993923741a999d62a78e0d03b068b1ba6d1fa Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Fri, 18 Oct 2024 14:34:34 +0400 Subject: [PATCH 36/70] fix: type hint for python 3.9 --- src/qibolab/backends.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/qibolab/backends.py b/src/qibolab/backends.py index fc6aa4d9e7..5baaa39210 100644 --- a/src/qibolab/backends.py +++ b/src/qibolab/backends.py @@ -1,4 +1,5 @@ from collections import deque +from typing import Union import numpy as np from qibo import __version__ as qibo_version @@ -51,11 +52,11 @@ def __init__(self, platform): self.compiler = Compiler.default() @property - def qubits(self) -> list[str | int]: + def qubits(self) -> list[Union[str, int]]: return list(self.platform.qubits.keys()) @property - def connectivity(self) -> list[tuple[str | int, str | int]]: + def connectivity(self) -> list[tuple[Union[str, int], Union[str, int]]]: return list(self.platform.pairs) @property From c5381ca6638ac3e2a9d50cd4a35061caa52167d5 Mon Sep 17 00:00:00 2001 From: Changsoo Kim <57739683+csookim@users.noreply.github.com> Date: Sun, 20 Oct 2024 11:05:23 +0400 Subject: [PATCH 37/70] Update src/qibolab/backends.py Co-authored-by: Alessandro Candido --- src/qibolab/backends.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibolab/backends.py b/src/qibolab/backends.py index 5baaa39210..7f1cec8ad4 100644 --- a/src/qibolab/backends.py +++ b/src/qibolab/backends.py @@ -60,7 +60,7 @@ def connectivity(self) -> list[tuple[Union[str, int], Union[str, int]]]: return list(self.platform.pairs) @property - def natives(self) -> list[str]: + def natives(self) -> list[str]: native_gates = set() for _, q in self.platform.qubits.items(): native_gates |= {k for k, v in q.native_gates.__dict__.items() if v is not None} From 6da1b133f234d482208b6a03b741e7e69cd2cfcf Mon Sep 17 00:00:00 2001 From: Changsoo Kim <57739683+csookim@users.noreply.github.com> Date: Sun, 20 Oct 2024 11:05:48 +0400 Subject: [PATCH 38/70] Update src/qibolab/backends.py Co-authored-by: Alessandro Candido --- src/qibolab/backends.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibolab/backends.py b/src/qibolab/backends.py index 7f1cec8ad4..c3703b21fd 100644 --- a/src/qibolab/backends.py +++ b/src/qibolab/backends.py @@ -53,7 +53,7 @@ def __init__(self, platform): @property def qubits(self) -> list[Union[str, int]]: - return list(self.platform.qubits.keys()) + return list(self.platform.qubits) @property def connectivity(self) -> list[tuple[Union[str, int], Union[str, int]]]: From fd6ff6e13c5a4c435524c5edf8caca61c2c0a66b Mon Sep 17 00:00:00 2001 From: Changsoo Kim <57739683+csookim@users.noreply.github.com> Date: Sun, 20 Oct 2024 11:06:26 +0400 Subject: [PATCH 39/70] Update src/qibolab/backends.py Co-authored-by: Alessandro Candido --- src/qibolab/backends.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibolab/backends.py b/src/qibolab/backends.py index c3703b21fd..f662b26f0b 100644 --- a/src/qibolab/backends.py +++ b/src/qibolab/backends.py @@ -63,7 +63,7 @@ def connectivity(self) -> list[tuple[Union[str, int], Union[str, int]]]: def natives(self) -> list[str]: native_gates = set() for _, q in self.platform.qubits.items(): - native_gates |= {k for k, v in q.native_gates.__dict__.items() if v is not None} + native_gates |= {k for k, v in asdict(q.native_gates).items() if v is not None} for _, p in self.platform.pairs.items(): native_gates |= {k for k, v in p.native_gates.__dict__.items() if v is not None} From cbc5943214d18a94a7bd181794c64cd58651e495 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Sun, 20 Oct 2024 17:01:43 +0400 Subject: [PATCH 40/70] fix: replace __dict__ with flelds() --- src/qibolab/backends.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/qibolab/backends.py b/src/qibolab/backends.py index f662b26f0b..dfe30ad80b 100644 --- a/src/qibolab/backends.py +++ b/src/qibolab/backends.py @@ -1,4 +1,5 @@ from collections import deque +from dataclasses import fields from typing import Union import numpy as np @@ -63,11 +64,18 @@ def connectivity(self) -> list[tuple[Union[str, int], Union[str, int]]]: def natives(self) -> list[str]: native_gates = set() for _, q in self.platform.qubits.items(): - native_gates |= {k for k, v in asdict(q.native_gates).items() if v is not None} - - for _, p in self.platform.pairs.items(): - native_gates |= {k for k, v in p.native_gates.__dict__.items() if v is not None} + native_gates |= { + fld.name + for fld in fields(q.native_gates) + if getattr(q.native_gates, fld.name) is not None + } + for _, p in self.platform.pairs.items(): + native_gates |= { + fld.name + for fld in fields(p.native_gates) + if getattr(p.native_gates, fld.name) is not None + } return list(native_gates) def apply_gate(self, gate, state, nqubits): # pragma: no cover From 86e836e240e3f08a60d7326adba73aa443e6e89b Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Tue, 22 Oct 2024 09:32:03 +0400 Subject: [PATCH 41/70] feat: add _rename_gates --- src/qibolab/backends.py | 20 +++++++++++++++++++- tests/test_backends.py | 30 +++++++++++++++++++++++++++--- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/qibolab/backends.py b/src/qibolab/backends.py index dfe30ad80b..1d3231c5e1 100644 --- a/src/qibolab/backends.py +++ b/src/qibolab/backends.py @@ -76,7 +76,25 @@ def natives(self) -> list[str]: for fld in fields(p.native_gates) if getattr(p.native_gates, fld.name) is not None } - return list(native_gates) + + return self._rename_gates(list(native_gates)) + + def _rename_gates(self, natives: list[str]): + gates = [] + for native in natives: + if native == "RX12": + pass + elif native == "RX": + gates.append("RX") + elif native == "CZ": + gates.append("CZ") + elif native == "iSWAP": + gates.append("iSWAP") + elif native == "MZ": + gates.append("M") + elif native == "CNOT": + gates.append("CNOT") + return gates def apply_gate(self, gate, state, nqubits): # pragma: no cover raise_error(NotImplementedError, "Qibolab cannot apply gates directly.") diff --git a/tests/test_backends.py b/tests/test_backends.py index 4c05a55601..9f09771c48 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -22,20 +22,44 @@ def generate_circuit_with_gate(nqubits, gate, **kwargs): def connected_backend(connected_platform): yield QibolabBackend(connected_platform) + def test_qubits(): backend = QibolabBackend("dummy") assert isinstance(backend.qubits, list) - assert set(backend.qubits) == set([0, 1, 2, 3, 4]) + assert set(backend.qubits) == {0, 1, 2, 3, 4} + def test_connectivity(): backend = QibolabBackend("dummy") assert isinstance(backend.connectivity, list) - assert set(backend.connectivity) == set([(0, 2), (2, 0), (1, 2), (2, 1), (2, 3), (3, 2), (2, 4), (4, 2)]) + assert set(backend.connectivity) == { + (0, 2), + (2, 0), + (1, 2), + (2, 1), + (2, 3), + (3, 2), + (2, 4), + (4, 2), + } + def test_natives(): backend = QibolabBackend("dummy") assert isinstance(backend.natives, list) - assert set(backend.natives) == set(['RX12', 'RX', 'CZ', 'iSWAP', 'MZ', 'CNOT']) + assert set(backend.natives) == {"RX", "CZ", "iSWAP", "M", "CNOT"} + + +def test_rename_gates(): + backend = QibolabBackend("dummy") + assert backend._rename_gates(["RX12", "RX", "CZ", "iSWAP", "MZ", "CNOT"]) == [ + "RX", + "CZ", + "iSWAP", + "M", + "CNOT", + ] + def test_execute_circuit_initial_state(): backend = QibolabBackend("dummy") From 8166c419ff1de4b555a6cc827f251316f212967c Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Wed, 23 Oct 2024 11:17:29 +0400 Subject: [PATCH 42/70] fix: update natives() and add two-qubit gate filter --- src/qibolab/backends.py | 63 +++++++++++++++++++---------------------- tests/test_backends.py | 18 ++++++------ 2 files changed, 37 insertions(+), 44 deletions(-) diff --git a/src/qibolab/backends.py b/src/qibolab/backends.py index 1d3231c5e1..e67b137722 100644 --- a/src/qibolab/backends.py +++ b/src/qibolab/backends.py @@ -1,9 +1,9 @@ from collections import deque -from dataclasses import fields from typing import Union import numpy as np from qibo import __version__ as qibo_version +from qibo import gates from qibo.backends import NumpyBackend from qibo.config import raise_error from qibo.models import Circuit @@ -54,47 +54,42 @@ def __init__(self, platform): @property def qubits(self) -> list[Union[str, int]]: + """Returns the qubits in the platform.""" return list(self.platform.qubits) @property def connectivity(self) -> list[tuple[Union[str, int], Union[str, int]]]: + """Returns the list of connected qubits.""" return list(self.platform.pairs) @property def natives(self) -> list[str]: - native_gates = set() - for _, q in self.platform.qubits.items(): - native_gates |= { - fld.name - for fld in fields(q.native_gates) - if getattr(q.native_gates, fld.name) is not None - } - - for _, p in self.platform.pairs.items(): - native_gates |= { - fld.name - for fld in fields(p.native_gates) - if getattr(p.native_gates, fld.name) is not None - } - - return self._rename_gates(list(native_gates)) - - def _rename_gates(self, natives: list[str]): - gates = [] - for native in natives: - if native == "RX12": - pass - elif native == "RX": - gates.append("RX") - elif native == "CZ": - gates.append("CZ") - elif native == "iSWAP": - gates.append("iSWAP") - elif native == "MZ": - gates.append("M") - elif native == "CNOT": - gates.append("CNOT") - return gates + """Returns the list of native gates supported by the platform.""" + compiler = Compiler.default() + natives = [g.__name__ for g in list(compiler.rules)] + + if "CZ" in natives and "CNOT" in natives: + for pair in self.connectivity: + cz = gates.CZ(*pair) + cnot = gates.CNOT(*pair) + + if not self._is_gate_calibrated(cz, compiler): + natives.remove("CZ") + break + + if not self._is_gate_calibrated(cnot, compiler): + natives.remove("CNOT") + break + + return natives + + def _is_gate_calibrated(self, gate, compiler) -> bool: + """Helper method to check if a gate is calibrated.""" + try: + compiler[gate.__class__](gate, self.platform) + return True + except ValueError: + return False def apply_gate(self, gate, state, nqubits): # pragma: no cover raise_error(NotImplementedError, "Qibolab cannot apply gates directly.") diff --git a/tests/test_backends.py b/tests/test_backends.py index 9f09771c48..94a01887a3 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -47,18 +47,16 @@ def test_connectivity(): def test_natives(): backend = QibolabBackend("dummy") assert isinstance(backend.natives, list) - assert set(backend.natives) == {"RX", "CZ", "iSWAP", "M", "CNOT"} - - -def test_rename_gates(): - backend = QibolabBackend("dummy") - assert backend._rename_gates(["RX12", "RX", "CZ", "iSWAP", "MZ", "CNOT"]) == [ - "RX", + assert set(backend.natives) == { + "I", + "Z", + "RZ", + "U3", "CZ", - "iSWAP", + "GPI2", + "GPI", "M", - "CNOT", - ] + } def test_execute_circuit_initial_state(): From 1a1899c8aa96a7c87c1bf2aaf113295f3b034e78 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Wed, 23 Oct 2024 11:25:47 +0400 Subject: [PATCH 43/70] fix: add pragma and remove commented pre commit --- src/qibolab/backends.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibolab/backends.py b/src/qibolab/backends.py index e67b137722..81d6ce1c52 100644 --- a/src/qibolab/backends.py +++ b/src/qibolab/backends.py @@ -73,7 +73,7 @@ def natives(self) -> list[str]: cz = gates.CZ(*pair) cnot = gates.CNOT(*pair) - if not self._is_gate_calibrated(cz, compiler): + if not self._is_gate_calibrated(cz, compiler): # pragma: no cover natives.remove("CZ") break From 030a7b96960a61e2ffe6dd3c8aa0ba7f80aa820b Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Thu, 24 Oct 2024 09:41:46 +0400 Subject: [PATCH 44/70] fix: natives() find uniformly supported gates --- src/qibolab/backends.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/qibolab/backends.py b/src/qibolab/backends.py index 81d6ce1c52..09e9847465 100644 --- a/src/qibolab/backends.py +++ b/src/qibolab/backends.py @@ -68,25 +68,19 @@ def natives(self) -> list[str]: compiler = Compiler.default() natives = [g.__name__ for g in list(compiler.rules)] - if "CZ" in natives and "CNOT" in natives: - for pair in self.connectivity: - cz = gates.CZ(*pair) - cnot = gates.CNOT(*pair) - - if not self._is_gate_calibrated(cz, compiler): # pragma: no cover - natives.remove("CZ") - break - - if not self._is_gate_calibrated(cnot, compiler): - natives.remove("CNOT") - break - + check_2q = ["CZ", "CNOT"] + for gate in check_2q: + if gate in natives and any( + not self._is_gate_calibrated(getattr(gates, gate)(*pair), compiler) + for pair in self.connectivity + ): + natives.remove(gate) return natives def _is_gate_calibrated(self, gate, compiler) -> bool: """Helper method to check if a gate is calibrated.""" try: - compiler[gate.__class__](gate, self.platform) + compiler[type(gate)](gate, self.platform) return True except ValueError: return False From cdf87052c7c37cb3840795c268803b9a8bcc34dd Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Thu, 24 Oct 2024 11:28:40 +0400 Subject: [PATCH 45/70] fix: natives() return any available gate --- src/qibolab/backends.py | 28 +++++++++++++++++++++++----- tests/test_backends.py | 1 + 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/qibolab/backends.py b/src/qibolab/backends.py index 09e9847465..823e22b737 100644 --- a/src/qibolab/backends.py +++ b/src/qibolab/backends.py @@ -62,6 +62,20 @@ def connectivity(self) -> list[tuple[Union[str, int], Union[str, int]]]: """Returns the list of connected qubits.""" return list(self.platform.pairs) + # @property + # def natives(self) -> list[str]: + # """Returns the list of native gates supported by the platform.""" + # compiler = Compiler.default() + # natives = [g.__name__ for g in list(compiler.rules)] + + # check_2q = ["CZ", "CNOT"] + # for gate in check_2q: + # if gate in natives and any( + # not self._is_gate_calibrated(getattr(gates, gate)(*pair), compiler) + # for pair in self.connectivity + # ): + # natives.remove(gate) + # return natives @property def natives(self) -> list[str]: """Returns the list of native gates supported by the platform.""" @@ -70,11 +84,15 @@ def natives(self) -> list[str]: check_2q = ["CZ", "CNOT"] for gate in check_2q: - if gate in natives and any( - not self._is_gate_calibrated(getattr(gates, gate)(*pair), compiler) - for pair in self.connectivity - ): - natives.remove(gate) + if gate in natives: + for pair in self.connectivity: + logical_pair = [list(self.platform.qubits).index(q) for q in pair] + if self._is_gate_calibrated( + getattr(gates, gate)(*logical_pair), compiler + ): + break + else: + natives.remove(gate) return natives def _is_gate_calibrated(self, gate, compiler) -> bool: diff --git a/tests/test_backends.py b/tests/test_backends.py index 94a01887a3..d3effec884 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -53,6 +53,7 @@ def test_natives(): "RZ", "U3", "CZ", + "CNOT", "GPI2", "GPI", "M", From 92022c410df591051282cfa85c6969a6ca7258f9 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Thu, 24 Oct 2024 11:29:42 +0400 Subject: [PATCH 46/70] fix: remove dead code --- src/qibolab/backends.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/qibolab/backends.py b/src/qibolab/backends.py index 823e22b737..ca595242cb 100644 --- a/src/qibolab/backends.py +++ b/src/qibolab/backends.py @@ -62,20 +62,6 @@ def connectivity(self) -> list[tuple[Union[str, int], Union[str, int]]]: """Returns the list of connected qubits.""" return list(self.platform.pairs) - # @property - # def natives(self) -> list[str]: - # """Returns the list of native gates supported by the platform.""" - # compiler = Compiler.default() - # natives = [g.__name__ for g in list(compiler.rules)] - - # check_2q = ["CZ", "CNOT"] - # for gate in check_2q: - # if gate in natives and any( - # not self._is_gate_calibrated(getattr(gates, gate)(*pair), compiler) - # for pair in self.connectivity - # ): - # natives.remove(gate) - # return natives @property def natives(self) -> list[str]: """Returns the list of native gates supported by the platform.""" From a15cd83eb20ca51ab5bb82c360feec0767b4c2a9 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Fri, 25 Oct 2024 10:21:05 +0400 Subject: [PATCH 47/70] fix: natives() use QubitPair.native_gates --- src/qibolab/backends.py | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/src/qibolab/backends.py b/src/qibolab/backends.py index ca595242cb..d752c5cb4a 100644 --- a/src/qibolab/backends.py +++ b/src/qibolab/backends.py @@ -3,7 +3,6 @@ import numpy as np from qibo import __version__ as qibo_version -from qibo import gates from qibo.backends import NumpyBackend from qibo.config import raise_error from qibo.models import Circuit @@ -67,27 +66,17 @@ def natives(self) -> list[str]: """Returns the list of native gates supported by the platform.""" compiler = Compiler.default() natives = [g.__name__ for g in list(compiler.rules)] + calibrated = self.platform.pairs check_2q = ["CZ", "CNOT"] for gate in check_2q: - if gate in natives: - for pair in self.connectivity: - logical_pair = [list(self.platform.qubits).index(q) for q in pair] - if self._is_gate_calibrated( - getattr(gates, gate)(*logical_pair), compiler - ): - break - else: - natives.remove(gate) - return natives + if gate in natives and all( + getattr(calibrated[p].native_gates, gate) is None + for p in self.connectivity + ): + natives.remove(gate) - def _is_gate_calibrated(self, gate, compiler) -> bool: - """Helper method to check if a gate is calibrated.""" - try: - compiler[type(gate)](gate, self.platform) - return True - except ValueError: - return False + return natives def apply_gate(self, gate, state, nqubits): # pragma: no cover raise_error(NotImplementedError, "Qibolab cannot apply gates directly.") From 2a19f003fd66f7fe4a7514b7cc200a27dcd60d3f Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Fri, 25 Oct 2024 11:53:39 +0400 Subject: [PATCH 48/70] fix: add test_natives_no_cz_cnot --- tests/test_backends.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_backends.py b/tests/test_backends.py index d3effec884..da0676269e 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -60,6 +60,24 @@ def test_natives(): } +def test_natives_no_cz_cnot(): + platform = create_platform("dummy") + for p in platform.pairs: + platform.pairs[p].native_gates.CZ = None + platform.pairs[p].native_gates.CNOT = None + + backend = QibolabBackend(platform) + assert set(backend.natives) == { + "I", + "Z", + "RZ", + "U3", + "GPI2", + "GPI", + "M", + } + + def test_execute_circuit_initial_state(): backend = QibolabBackend("dummy") circuit = Circuit(1) From 82526b70391913cee719c8b6b25e8ad295685036 Mon Sep 17 00:00:00 2001 From: Changsoo Kim <57739683+csookim@users.noreply.github.com> Date: Fri, 25 Oct 2024 13:45:48 +0400 Subject: [PATCH 49/70] Update tests/test_backends.py Co-authored-by: Alessandro Candido --- tests/test_backends.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/test_backends.py b/tests/test_backends.py index da0676269e..3ce064e8fb 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -62,10 +62,6 @@ def test_natives(): def test_natives_no_cz_cnot(): platform = create_platform("dummy") - for p in platform.pairs: - platform.pairs[p].native_gates.CZ = None - platform.pairs[p].native_gates.CNOT = None - backend = QibolabBackend(platform) assert set(backend.natives) == { "I", @@ -75,7 +71,14 @@ def test_natives_no_cz_cnot(): "GPI2", "GPI", "M", + "CZ", + "CNOT" } + + for gate in ["CZ", "CNOT"]: + for p in platform.pairs: + setattr(platform.pairs[p].native_gates, gate, None) + assert gate not in set(backend.natives) def test_execute_circuit_initial_state(): From b96e3b483c337c7ea755bacfc6a4c12f447f51b9 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Mon, 28 Oct 2024 16:28:46 +0400 Subject: [PATCH 50/70] fix: use QubitId, QubitPairId --- src/qibolab/backends.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/qibolab/backends.py b/src/qibolab/backends.py index d752c5cb4a..44cdb06c00 100644 --- a/src/qibolab/backends.py +++ b/src/qibolab/backends.py @@ -13,6 +13,7 @@ from qibolab.platform import Platform, create_platform from qibolab.platform.load import available_platforms from qibolab.version import __version__ as qibolab_version +from qibolab.qubits import QubitId, QubitPairId def execute_qasm(circuit: str, platform, initial_state=None, nshots=1000): @@ -52,12 +53,12 @@ def __init__(self, platform): self.compiler = Compiler.default() @property - def qubits(self) -> list[Union[str, int]]: + def qubits(self) -> list[QubitId]: """Returns the qubits in the platform.""" return list(self.platform.qubits) @property - def connectivity(self) -> list[tuple[Union[str, int], Union[str, int]]]: + def connectivity(self) -> list[QubitPairId]: """Returns the list of connected qubits.""" return list(self.platform.pairs) From a30c55264e0d0348aa2763debe9605d134a01340 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 13:50:21 +0000 Subject: [PATCH 51/70] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibolab/backends.py | 3 +-- tests/test_backends.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/qibolab/backends.py b/src/qibolab/backends.py index 44cdb06c00..53f2bcaa9f 100644 --- a/src/qibolab/backends.py +++ b/src/qibolab/backends.py @@ -1,5 +1,4 @@ from collections import deque -from typing import Union import numpy as np from qibo import __version__ as qibo_version @@ -12,8 +11,8 @@ from qibolab.execution_parameters import ExecutionParameters from qibolab.platform import Platform, create_platform from qibolab.platform.load import available_platforms -from qibolab.version import __version__ as qibolab_version from qibolab.qubits import QubitId, QubitPairId +from qibolab.version import __version__ as qibolab_version def execute_qasm(circuit: str, platform, initial_state=None, nshots=1000): diff --git a/tests/test_backends.py b/tests/test_backends.py index 3ce064e8fb..5b189bfbb3 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -72,9 +72,9 @@ def test_natives_no_cz_cnot(): "GPI", "M", "CZ", - "CNOT" + "CNOT", } - + for gate in ["CZ", "CNOT"]: for p in platform.pairs: setattr(platform.pairs[p].native_gates, gate, None) From 54dca9e5bb6cc59fd6016704a383bf73ddfe397e Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Thu, 24 Oct 2024 16:56:33 +0400 Subject: [PATCH 52/70] feat: use qibo.Circuit.wire_names to specify hw qubit --- src/qibolab/compilers/default.py | 17 ++++++++++------- tests/test_backends.py | 15 +++++++++++++-- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/qibolab/compilers/default.py b/src/qibolab/compilers/default.py index b178eefe92..e69f1779a2 100644 --- a/src/qibolab/compilers/default.py +++ b/src/qibolab/compilers/default.py @@ -15,19 +15,19 @@ def identity_rule(gate, platform): def z_rule(gate, platform): """Z gate applied virtually.""" - qubit = list(platform.qubits)[gate.target_qubits[0]] + qubit = platform.get_qubit(gate.target_qubits[0]) return PulseSequence(), {qubit: math.pi} def rz_rule(gate, platform): """RZ gate applied virtually.""" - qubit = list(platform.qubits)[gate.target_qubits[0]] + qubit = platform.get_qubit(gate.target_qubits[0]) return PulseSequence(), {qubit: -gate.parameters[0]} def gpi2_rule(gate, platform): """Rule for GPI2.""" - qubit = list(platform.qubits)[gate.target_qubits[0]] + qubit = platform.get_qubit(gate.target_qubits[0]) theta = gate.parameters[0] sequence = PulseSequence() pulse = platform.create_RX90_pulse(qubit, start=0, relative_phase=theta) @@ -37,7 +37,7 @@ def gpi2_rule(gate, platform): def gpi_rule(gate, platform): """Rule for GPI.""" - qubit = list(platform.qubits)[gate.target_qubits[0]] + qubit = platform.get_qubit(gate.target_qubits[0]) theta = gate.parameters[0] sequence = PulseSequence() # the following definition has a global phase difference compare to @@ -51,7 +51,7 @@ def gpi_rule(gate, platform): def u3_rule(gate, platform): """U3 applied as RZ-RX90-RZ-RX90-RZ.""" - qubit = list(platform.qubits)[gate.target_qubits[0]] + qubit = platform.get_qubit(gate.target_qubits[0]) # Transform gate to U3 and add pi/2-pulses theta, phi, lam = gate.parameters # apply RZ(lam) @@ -85,18 +85,21 @@ def cz_rule(gate, platform): 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) + qubits = tuple(platform.get_qubit(q) for q in gate.qubits) + return platform.create_CZ_pulse_sequence(qubits) def cnot_rule(gate, platform): """CNOT applied as defined in the platform runcard.""" - return platform.create_CNOT_pulse_sequence(gate.qubits) + qubits = tuple(platform.get_qubit(q) for q in gate.qubits) + return platform.create_CNOT_pulse_sequence(qubits) def measurement_rule(gate, platform): """Measurement gate applied using the platform readout pulse.""" sequence = PulseSequence() for qubit in gate.target_qubits: + qubit = platform.get_qubit(qubit) MZ_pulse = platform.create_MZ_pulse(qubit, start=0) sequence.add(MZ_pulse) return sequence, {} diff --git a/tests/test_backends.py b/tests/test_backends.py index 5b189bfbb3..bf5065723e 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -11,10 +11,11 @@ from qibolab.backends import QibolabBackend -def generate_circuit_with_gate(nqubits, gate, **kwargs): +def generate_circuit_with_gate(nqubits, gate, names, **kwargs): circuit = Circuit(nqubits) circuit.add(gate(qubit, **kwargs) for qubit in range(nqubits)) circuit.add(gates.M(*range(nqubits))) + circuit._wire_names = names return circuit @@ -86,6 +87,7 @@ def test_execute_circuit_initial_state(): circuit = Circuit(1) circuit.add(gates.GPI2(0, phi=0)) circuit.add(gates.M(0)) + circuit._wire_names = [0] with pytest.raises(ValueError): backend.execute_circuit(circuit, initial_state=np.ones(2)) @@ -107,7 +109,7 @@ def test_execute_circuit_initial_state(): def test_execute_circuit(gate, kwargs): backend = QibolabBackend("dummy") nqubits = backend.platform.nqubits - circuit = generate_circuit_with_gate(nqubits, gate, **kwargs) + circuit = generate_circuit_with_gate(nqubits, gate, list(range(nqubits)), **kwargs) result = backend.execute_circuit(circuit, nshots=100) @@ -117,12 +119,14 @@ def test_measurement_samples(): circuit = Circuit(nqubits) circuit.add(gates.M(*range(nqubits))) + circuit._wire_names = list(range(nqubits)) result = backend.execute_circuit(circuit, nshots=100) assert result.samples().shape == (100, nqubits) assert sum(result.frequencies().values()) == 100 circuit = Circuit(nqubits) circuit.add(gates.M(0, 2)) + circuit._wire_names = list(range(nqubits)) result = backend.execute_circuit(circuit, nshots=100) assert result.samples().shape == (100, 2) assert sum(result.frequencies().values()) == 100 @@ -135,6 +139,7 @@ def test_execute_circuits(): circuit = Circuit(3) circuit.add(gates.GPI2(i, phi=np.pi / 2) for i in range(3)) circuit.add(gates.M(0, 1, 2)) + circuit._wire_names = list(range(3)) results = backend.execute_circuits( 5 * [circuit], initial_states=initial_state_circuit, nshots=100 @@ -151,6 +156,7 @@ def test_multiple_measurements(): circuit = Circuit(4) circuit.add(gates.GPI2(i, phi=np.pi / 2) for i in range(2)) circuit.add(gates.CZ(1, 2)) + circuit._wire_names = list(range(4)) res0 = circuit.add(gates.M(0)) res1 = circuit.add(gates.M(3)) res2 = circuit.add(gates.M(1)) @@ -171,6 +177,7 @@ def dummy_string_qubit_names(): platform.pairs = { (f"A{q0}", f"A{q1}"): pair for (q0, q1), pair in platform.pairs.items() } + platform.wire_names = [f"A{q}" for q in range(platform.nqubits)] return platform @@ -182,6 +189,7 @@ def test_execute_circuit_str_qubit_names(): circuit.add(gates.GPI2(i, phi=np.pi / 2) for i in range(2)) circuit.add(gates.CZ(1, 2)) circuit.add(gates.M(0, 1)) + circuit._wire_names = ["A0", "A1", "A2"] result = backend.execute_circuit(circuit, nshots=20) assert result.samples().shape == (20, 2) @@ -195,6 +203,7 @@ def test_ground_state_probabilities_circuit(connected_backend): nqubits = connected_backend.platform.nqubits circuit = Circuit(nqubits) circuit.add(gates.M(*range(nqubits))) + circuit._wire_names = list(range(nqubits)) result = connected_backend.execute_circuit(circuit, nshots=nshots) freqs = result.frequencies(binary=False) probs = [freqs[i] / nshots for i in range(2**nqubits)] @@ -214,6 +223,7 @@ def test_excited_state_probabilities_circuit(connected_backend): circuit = Circuit(nqubits) circuit.add(gates.X(q) for q in range(nqubits)) circuit.add(gates.M(*range(nqubits))) + circuit._wire_names = list(range(nqubits)) result = connected_backend.execute_circuit(circuit, nshots=nshots) freqs = result.frequencies(binary=False) probs = [freqs[i] / nshots for i in range(2**nqubits)] @@ -237,6 +247,7 @@ def test_superposition_for_all_qubits(connected_backend): circuit = Circuit(nqubits) circuit.add(gates.GPI2(q=q, phi=np.pi / 2)) circuit.add(gates.M(q)) + circuit._wire_names = list(range(nqubits)) freqs = connected_backend.execute_circuit(circuit, nshots=nshots).frequencies( binary=False ) From 5945732e8a7b85ca22dcf03e3f2d95e19c61d0d6 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Fri, 25 Oct 2024 13:14:40 +0400 Subject: [PATCH 53/70] fix: revert cz/cnot/measurement_rule --- src/qibolab/compilers/default.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/qibolab/compilers/default.py b/src/qibolab/compilers/default.py index e69f1779a2..2c91b211c1 100644 --- a/src/qibolab/compilers/default.py +++ b/src/qibolab/compilers/default.py @@ -85,21 +85,18 @@ def cz_rule(gate, platform): Applying the CZ gate may involve sending pulses on qubits that the gate is not directly acting on. """ - qubits = tuple(platform.get_qubit(q) for q in gate.qubits) - return platform.create_CZ_pulse_sequence(qubits) + return platform.create_CZ_pulse_sequence(gate.qubits) def cnot_rule(gate, platform): """CNOT applied as defined in the platform runcard.""" - qubits = tuple(platform.get_qubit(q) for q in gate.qubits) - return platform.create_CNOT_pulse_sequence(qubits) + return platform.create_CNOT_pulse_sequence(gate.qubits) def measurement_rule(gate, platform): """Measurement gate applied using the platform readout pulse.""" sequence = PulseSequence() for qubit in gate.target_qubits: - qubit = platform.get_qubit(qubit) MZ_pulse = platform.create_MZ_pulse(qubit, start=0) sequence.add(MZ_pulse) return sequence, {} From 801dced7c51ae42085987f8b251b5e553eb82e37 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Mon, 28 Oct 2024 17:25:32 +0400 Subject: [PATCH 54/70] fix: remove platform.wire_names --- src/qibolab/backends.py | 12 ++++++++++++ tests/test_backends.py | 11 ++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/qibolab/backends.py b/src/qibolab/backends.py index 53f2bcaa9f..ea80aebffb 100644 --- a/src/qibolab/backends.py +++ b/src/qibolab/backends.py @@ -125,6 +125,18 @@ def execute_circuit(self, circuit, initial_state=None, nshots=1000): "Hardware backend only supports circuits as initial states.", ) + if not all(q in circuit.wire_names for q in self.platform.qubits): + # This should be done in qibo side + # raise_error( + # ValueError, + # "Circuit qubits do not match the platform qubits.", + # ) + + # Temporary fix: overwrite the wire names + circuit._wire_names = self.qubits + + self.platform.wire_names = circuit.wire_names + sequence, measurement_map = self.compiler.compile(circuit, self.platform) if not self.platform.is_connected: diff --git a/tests/test_backends.py b/tests/test_backends.py index bf5065723e..232106764c 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -63,6 +63,10 @@ def test_natives(): def test_natives_no_cz_cnot(): platform = create_platform("dummy") + for p in platform.pairs: + platform.pairs[p].native_gates.CZ = None + platform.pairs[p].native_gates.CNOT = None + backend = QibolabBackend(platform) assert set(backend.natives) == { "I", @@ -72,15 +76,8 @@ def test_natives_no_cz_cnot(): "GPI2", "GPI", "M", - "CZ", - "CNOT", } - for gate in ["CZ", "CNOT"]: - for p in platform.pairs: - setattr(platform.pairs[p].native_gates, gate, None) - assert gate not in set(backend.natives) - def test_execute_circuit_initial_state(): backend = QibolabBackend("dummy") From 17a778040bdebdc9e6caf9d622254ace1cd28dd3 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Mon, 28 Oct 2024 18:44:53 +0400 Subject: [PATCH 55/70] fix: update compiler/rules to use circuit.wire_names --- src/qibolab/backends.py | 15 +++++----- src/qibolab/compilers/compiler.py | 24 ++++++++++++++-- src/qibolab/compilers/default.py | 46 +++++++++++++++---------------- tests/test_compilers_default.py | 2 ++ 4 files changed, 53 insertions(+), 34 deletions(-) diff --git a/src/qibolab/backends.py b/src/qibolab/backends.py index ea80aebffb..4c1c31a3a8 100644 --- a/src/qibolab/backends.py +++ b/src/qibolab/backends.py @@ -125,14 +125,9 @@ def execute_circuit(self, circuit, initial_state=None, nshots=1000): "Hardware backend only supports circuits as initial states.", ) + # This should be done in qibo side + # Temporary fix: overwrite the wire names if not all(q in circuit.wire_names for q in self.platform.qubits): - # This should be done in qibo side - # raise_error( - # ValueError, - # "Circuit qubits do not match the platform qubits.", - # ) - - # Temporary fix: overwrite the wire names circuit._wire_names = self.qubits self.platform.wire_names = circuit.wire_names @@ -179,6 +174,12 @@ def execute_circuits(self, circuits, initial_states=None, nshots=1000): "Hardware backend only supports circuits as initial states.", ) + # This should be done in qibo side + # Temporary fix: overwrite the wire names + for circuit in circuits: + if not all(q in circuit.wire_names for q in self.platform.qubits): + circuit._wire_names = self.qubits + # TODO: Maybe these loops can be parallelized sequences, measurement_maps = zip( *(self.compiler.compile(circuit, self.platform) for circuit in circuits) diff --git a/src/qibolab/compilers/compiler.py b/src/qibolab/compilers/compiler.py index 316fde2586..38fb9c7628 100644 --- a/src/qibolab/compilers/compiler.py +++ b/src/qibolab/compilers/compiler.py @@ -99,12 +99,24 @@ def inner(func): return inner def _compile_gate( - self, gate, platform, sequence, virtual_z_phases, moment_start, delays + self, + gate, + platform, + sequence, + virtual_z_phases, + moment_start, + delays, + wire_names, ): """Adds a single gate to the pulse sequence.""" rule = self[gate.__class__] + # get local sequence and phases for the current gate - gate_sequence, gate_phases = rule(gate, platform) + qubits_ids = ( + [wire_names[qubit] for qubit in gate.control_qubits], + [wire_names[qubit] for qubit in gate.target_qubits], + ) + gate_sequence, gate_phases = rule(qubits_ids, platform, gate.parameters) # update global pulse sequence # determine the right start time based on the availability of the qubits involved @@ -154,7 +166,13 @@ def compile(self, circuit, platform): delays[qubit] += gate.delay continue gate_sequence, gate_phases = self._compile_gate( - gate, platform, sequence, virtual_z_phases, moment_start, delays + gate, + platform, + sequence, + virtual_z_phases, + moment_start, + delays, + circuit.wire_names, ) for qubit in gate.qubits: delays[qubit] = 0 diff --git a/src/qibolab/compilers/default.py b/src/qibolab/compilers/default.py index 2c91b211c1..317a22ac32 100644 --- a/src/qibolab/compilers/default.py +++ b/src/qibolab/compilers/default.py @@ -8,52 +8,48 @@ from qibolab.pulses import PulseSequence -def identity_rule(gate, platform): +def identity_rule(qubits_ids, platform, parameters=None): """Identity gate skipped.""" return PulseSequence(), {} -def z_rule(gate, platform): +def z_rule(qubits_ids, platform, parameters=None): """Z gate applied virtually.""" - qubit = platform.get_qubit(gate.target_qubits[0]) - return PulseSequence(), {qubit: math.pi} + return PulseSequence(), {qubits_ids[1][0]: math.pi} -def rz_rule(gate, platform): +def rz_rule(qubits_ids, platform, parameters=None): """RZ gate applied virtually.""" - qubit = platform.get_qubit(gate.target_qubits[0]) - return PulseSequence(), {qubit: -gate.parameters[0]} + return PulseSequence(), {qubits_ids[1][0]: -parameters[0]} -def gpi2_rule(gate, platform): +def gpi2_rule(qubits_ids, platform, parameters=None): """Rule for GPI2.""" - qubit = platform.get_qubit(gate.target_qubits[0]) - theta = gate.parameters[0] + theta = parameters[0] sequence = PulseSequence() - pulse = platform.create_RX90_pulse(qubit, start=0, relative_phase=theta) + pulse = platform.create_RX90_pulse(qubits_ids[1][0], start=0, relative_phase=theta) sequence.add(pulse) return sequence, {} -def gpi_rule(gate, platform): +def gpi_rule(qubits_ids, platform, parameters=None): """Rule for GPI.""" - qubit = platform.get_qubit(gate.target_qubits[0]) - theta = gate.parameters[0] + theta = parameters[0] sequence = PulseSequence() # the following definition has a global phase difference compare to # to the matrix representation. See # https://github.com/qiboteam/qibolab/pull/804#pullrequestreview-1890205509 # for more detail. - pulse = platform.create_RX_pulse(qubit, start=0, relative_phase=theta) + pulse = platform.create_RX_pulse(qubits_ids[1][0], start=0, relative_phase=theta) sequence.add(pulse) return sequence, {} -def u3_rule(gate, platform): +def u3_rule(qubits_ids, platform, parameters=None): """U3 applied as RZ-RX90-RZ-RX90-RZ.""" - qubit = platform.get_qubit(gate.target_qubits[0]) + qubit = qubits_ids[1][0] # Transform gate to U3 and add pi/2-pulses - theta, phi, lam = gate.parameters + theta, phi, lam = parameters # apply RZ(lam) virtual_z_phases = {qubit: lam} sequence = PulseSequence() @@ -79,24 +75,26 @@ def u3_rule(gate, platform): return sequence, virtual_z_phases -def cz_rule(gate, platform): +def cz_rule(qubits_ids, platform, parameters=None): """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. """ - return platform.create_CZ_pulse_sequence(gate.qubits) + qubits = qubits_ids[0] + qubits_ids[1] + return platform.create_CZ_pulse_sequence(qubits) -def cnot_rule(gate, platform): +def cnot_rule(qubits_ids, platform, parameters=None): """CNOT applied as defined in the platform runcard.""" - return platform.create_CNOT_pulse_sequence(gate.qubits) + qubits = qubits_ids[0] + qubits_ids[1] + return platform.create_CNOT_pulse_sequence(qubits) -def measurement_rule(gate, platform): +def measurement_rule(qubits_ids, platform, parameters=None): """Measurement gate applied using the platform readout pulse.""" sequence = PulseSequence() - for qubit in gate.target_qubits: + for qubit in qubits_ids[1]: MZ_pulse = platform.create_MZ_pulse(qubit, start=0) sequence.add(MZ_pulse) return sequence, {} diff --git a/tests/test_compilers_default.py b/tests/test_compilers_default.py index a1a85c3667..fdadbef49c 100644 --- a/tests/test_compilers_default.py +++ b/tests/test_compilers_default.py @@ -32,6 +32,8 @@ def test_u3_sim_agreement(): def compile_circuit(circuit, platform): """Compile a circuit to a pulse sequence.""" compiler = Compiler.default() + # Temporary fix: overwrite the wire names + circuit._wire_names = list(platform.qubits) sequence, _ = compiler.compile(circuit, platform) return sequence From fd1a9f603b8d8c2ad60ae904e2593a4962159739 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Mon, 28 Oct 2024 18:56:43 +0400 Subject: [PATCH 56/70] fix: update documentation compiler.rst --- doc/source/tutorials/compiler.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/source/tutorials/compiler.rst b/doc/source/tutorials/compiler.rst index a445e8e7f1..b6ae145d0a 100644 --- a/doc/source/tutorials/compiler.rst +++ b/doc/source/tutorials/compiler.rst @@ -80,11 +80,10 @@ The following example shows how to modify the compiler in order to execute a cir # define a compiler rule that translates X to the pi-pulse - def x_rule(gate, platform): + def x_rule(qubits_ids, platform, parameters=None): """X gate applied with a single pi-pulse.""" - qubit = gate.target_qubits[0] sequence = PulseSequence() - sequence.add(platform.create_RX_pulse(qubit, start=0)) + sequence.add(platform.create_RX_pulse(qubits_ids[1][0], start=0)) return sequence, {} From 1b3f5f8b2ebf39494d75ace88bd402d4837ead9b Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Mon, 28 Oct 2024 19:41:34 +0400 Subject: [PATCH 57/70] fix: remove dead code --- src/qibolab/backends.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/qibolab/backends.py b/src/qibolab/backends.py index 4c1c31a3a8..42eeeb03da 100644 --- a/src/qibolab/backends.py +++ b/src/qibolab/backends.py @@ -130,8 +130,6 @@ def execute_circuit(self, circuit, initial_state=None, nshots=1000): if not all(q in circuit.wire_names for q in self.platform.qubits): circuit._wire_names = self.qubits - self.platform.wire_names = circuit.wire_names - sequence, measurement_map = self.compiler.compile(circuit, self.platform) if not self.platform.is_connected: From eac4e141eaa9d9bdb8338283a0cd286417b08e5a Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Tue, 29 Oct 2024 09:37:05 +0400 Subject: [PATCH 58/70] fix: resolve diff --- tests/test_backends.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/test_backends.py b/tests/test_backends.py index 232106764c..bf5065723e 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -63,10 +63,6 @@ def test_natives(): def test_natives_no_cz_cnot(): platform = create_platform("dummy") - for p in platform.pairs: - platform.pairs[p].native_gates.CZ = None - platform.pairs[p].native_gates.CNOT = None - backend = QibolabBackend(platform) assert set(backend.natives) == { "I", @@ -76,8 +72,15 @@ def test_natives_no_cz_cnot(): "GPI2", "GPI", "M", + "CZ", + "CNOT", } + for gate in ["CZ", "CNOT"]: + for p in platform.pairs: + setattr(platform.pairs[p].native_gates, gate, None) + assert gate not in set(backend.natives) + def test_execute_circuit_initial_state(): backend = QibolabBackend("dummy") From 9e9d625c2fefbc8409c4e200898812884bbba803 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Tue, 29 Oct 2024 10:53:45 +0400 Subject: [PATCH 59/70] fix: temp fix minor update --- src/qibolab/backends.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qibolab/backends.py b/src/qibolab/backends.py index 42eeeb03da..229b2c015c 100644 --- a/src/qibolab/backends.py +++ b/src/qibolab/backends.py @@ -127,7 +127,7 @@ def execute_circuit(self, circuit, initial_state=None, nshots=1000): # This should be done in qibo side # Temporary fix: overwrite the wire names - if not all(q in circuit.wire_names for q in self.platform.qubits): + if not all(q in circuit.wire_names for q in self.qubits): circuit._wire_names = self.qubits sequence, measurement_map = self.compiler.compile(circuit, self.platform) @@ -175,7 +175,7 @@ def execute_circuits(self, circuits, initial_states=None, nshots=1000): # This should be done in qibo side # Temporary fix: overwrite the wire names for circuit in circuits: - if not all(q in circuit.wire_names for q in self.platform.qubits): + if not all(q in circuit.wire_names for q in self.qubits): circuit._wire_names = self.qubits # TODO: Maybe these loops can be parallelized From 923fe93c209f3263d918bc586955ae6d99f7ffea Mon Sep 17 00:00:00 2001 From: Edoardo-Pedicillo Date: Wed, 30 Oct 2024 20:14:00 +0400 Subject: [PATCH 60/70] fix: change qubit targets in dummy virtual z corrections --- src/qibolab/dummy/parameters.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/qibolab/dummy/parameters.json b/src/qibolab/dummy/parameters.json index 498ab97d58..1ea6198ea0 100644 --- a/src/qibolab/dummy/parameters.json +++ b/src/qibolab/dummy/parameters.json @@ -280,7 +280,7 @@ { "type": "virtual_z", "phase": 0.0, - "qubit": 1 + "qubit": 0 }, { "type": "virtual_z", @@ -396,7 +396,7 @@ { "type": "virtual_z", "phase": 0.0, - "qubit": 1 + "qubit": 3 }, { "type": "virtual_z", @@ -426,7 +426,7 @@ { "type": "virtual_z", "phase": 0.0, - "qubit": 1 + "qubit": 3 }, { "type": "virtual_z", @@ -476,7 +476,7 @@ { "type": "virtual_z", "phase": 0.0, - "qubit": 1 + "qubit": 4 }, { "type": "virtual_z", From 134efd252649a5af93bc8a3a2cd75f7d55ce021a Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Thu, 31 Oct 2024 14:20:04 +0400 Subject: [PATCH 61/70] fix: update condition in temp fix --- src/qibolab/backends.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/qibolab/backends.py b/src/qibolab/backends.py index 229b2c015c..b2207505c3 100644 --- a/src/qibolab/backends.py +++ b/src/qibolab/backends.py @@ -127,8 +127,8 @@ def execute_circuit(self, circuit, initial_state=None, nshots=1000): # This should be done in qibo side # Temporary fix: overwrite the wire names - if not all(q in circuit.wire_names for q in self.qubits): - circuit._wire_names = self.qubits + if not all(q in self.qubits for q in circuit.wire_names): + circuit._wire_names = self.qubits[: circuit.nqubits] sequence, measurement_map = self.compiler.compile(circuit, self.platform) @@ -175,8 +175,8 @@ def execute_circuits(self, circuits, initial_states=None, nshots=1000): # This should be done in qibo side # Temporary fix: overwrite the wire names for circuit in circuits: - if not all(q in circuit.wire_names for q in self.qubits): - circuit._wire_names = self.qubits + if not all(q in self.qubits for q in circuit.wire_names): + circuit._wire_names = self.qubits[: circuit.nqubits] # TODO: Maybe these loops can be parallelized sequences, measurement_maps = zip( From 49b6572aee42f7ab38961e964042246ecac389fc Mon Sep 17 00:00:00 2001 From: Stefano Carrazza Date: Fri, 1 Nov 2024 21:43:12 +0400 Subject: [PATCH 62/70] Update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ff0656b367..06fa87e18c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "qibolab" -version = "0.1.10" +version = "0.1.11" description = "Quantum hardware module and drivers for Qibo" authors = ["The Qibo team"] license = "Apache License 2.0" From a6784132e8e00a38bfc3f4d6cc5172452c6426a5 Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Wed, 13 Nov 2024 18:20:01 +0400 Subject: [PATCH 63/70] fix: drop compiler phase from flux pulses --- src/qibolab/compilers/compiler.py | 4 ++-- src/qibolab/dummy/parameters.json | 4 ++-- tests/test_compilers_default.py | 15 +++++++++++++++ tests/test_dummy.py | 2 +- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/qibolab/compilers/compiler.py b/src/qibolab/compilers/compiler.py index 38fb9c7628..dcc7c2a784 100644 --- a/src/qibolab/compilers/compiler.py +++ b/src/qibolab/compilers/compiler.py @@ -15,7 +15,7 @@ u3_rule, z_rule, ) -from qibolab.pulses import PulseSequence, ReadoutPulse +from qibolab.pulses import DrivePulse, PulseSequence @dataclass @@ -131,7 +131,7 @@ def _compile_gate( # shift start time and phase according to the global sequence for pulse in gate_sequence: pulse.start += start - if not isinstance(pulse, ReadoutPulse): + if isinstance(pulse, DrivePulse): pulse.relative_phase += virtual_z_phases[pulse.qubit] sequence.add(pulse) diff --git a/src/qibolab/dummy/parameters.json b/src/qibolab/dummy/parameters.json index 1ea6198ea0..fb4632ff47 100644 --- a/src/qibolab/dummy/parameters.json +++ b/src/qibolab/dummy/parameters.json @@ -309,12 +309,12 @@ }, { "type": "virtual_z", - "phase": 0.0, + "phase": 0.1, "qubit": 1 }, { "type": "virtual_z", - "phase": 0.0, + "phase": 0.2, "qubit": 2 }, { diff --git a/tests/test_compilers_default.py b/tests/test_compilers_default.py index fdadbef49c..5ba3d95f18 100644 --- a/tests/test_compilers_default.py +++ b/tests/test_compilers_default.py @@ -185,6 +185,21 @@ def test_cz_to_sequence(platform): assert sequence == test_sequence +def test_twocz_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." + ) + + circuit = Circuit(3) + circuit.add(gates.CZ(1, 2)) + circuit.add(gates.CZ(1, 2)) + + sequence = compile_circuit(circuit, platform) + assert sequence[0].relative_phase == 0 + assert sequence[1].relative_phase == 0 + + def test_cnot_to_sequence(): platform = create_platform("dummy") circuit = Circuit(4) diff --git a/tests/test_dummy.py b/tests/test_dummy.py index 42f454f252..145e9a74d6 100644 --- a/tests/test_dummy.py +++ b/tests/test_dummy.py @@ -88,7 +88,7 @@ def test_dummy_execute_pulse_sequence_couplers(): result = platform.execute_pulse_sequence(sequence, options) test_pulses = "PulseSequence\nFluxPulse(0, 30, 0.05, GaussianSquare(5, 0.75), flux-2, 2)\nCouplerFluxPulse(0, 30, 0.05, GaussianSquare(5, 0.75), flux_coupler-1, 1)" - test_phases = {1: 0.0, 2: 0.0} + test_phases = {1: 0.1, 2: 0.2} assert test_pulses == cz.serial assert test_phases == cz_phases From 97595fbc4b91ae8986670c3f18f8d6e0a80068ee Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Sat, 23 Nov 2024 18:00:01 +0400 Subject: [PATCH 64/70] build: update qm-qua version to 1.2.1 --- poetry.lock | 101 +++++++++++++++++++------------------------------ pyproject.toml | 2 +- 2 files changed, 40 insertions(+), 63 deletions(-) diff --git a/poetry.lock b/poetry.lock index 44aadec062..33d033bb9e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "alabaster" @@ -193,39 +193,23 @@ lxml = ["lxml"] [[package]] name = "betterproto" -version = "2.0.0b5" +version = "2.0.0b7" description = "A better Protobuf / gRPC generator & library" optional = false -python-versions = ">=3.6.2,<4.0" +python-versions = "<4.0,>=3.7" files = [ - {file = "betterproto-2.0.0b5-py3-none-any.whl", hash = "sha256:d3e6115c7d5136f1d5974e565b7560273f66b43065e74218e472321ee1258f4c"}, - {file = "betterproto-2.0.0b5.tar.gz", hash = "sha256:00a301c70a2db4d3cdd2b261522ae1d34972fb04b655a154d67daaaf4131102e"}, + {file = "betterproto-2.0.0b7-py3-none-any.whl", hash = "sha256:401ab8055e2f814e77b9c88a74d0e1ae3d1e8a969cced6aeb1b59f71ad63fbd2"}, + {file = "betterproto-2.0.0b7.tar.gz", hash = "sha256:1b1458ca5278d519bcd62556a4c236f998a91d503f0f71c67b0b954747052af2"}, ] [package.dependencies] grpclib = ">=0.4.1,<0.5.0" python-dateutil = ">=2.8,<3.0" +typing-extensions = ">=4.7.1,<5.0.0" [package.extras] -compiler = ["black (>=19.3b0)", "isort (>=5.10.1,<6.0.0)", "jinja2 (>=3.0.3)"] - -[[package]] -name = "betterproto" -version = "2.0.0b6" -description = "A better Protobuf / gRPC generator & library" -optional = false -python-versions = ">=3.7,<4.0" -files = [ - {file = "betterproto-2.0.0b6-py3-none-any.whl", hash = "sha256:a0839ec165d110a69d0d116f4d0e2bec8d186af4db826257931f0831dab73fcf"}, - {file = "betterproto-2.0.0b6.tar.gz", hash = "sha256:720ae92697000f6fcf049c69267d957f0871654c8b0d7458906607685daee784"}, -] - -[package.dependencies] -grpclib = ">=0.4.1,<0.5.0" -python-dateutil = ">=2.8,<3.0" - -[package.extras] -compiler = ["black (>=19.3b0)", "isort (>=5.11.5,<6.0.0)", "jinja2 (>=3.0.3)"] +compiler = ["black (>=23.1.0)", "isort (>=5.11.5,<6.0.0)", "jinja2 (>=3.0.3)"] +rust-codec = ["betterproto-rust-codec (==0.1.1)"] [[package]] name = "bleach" @@ -2199,20 +2183,22 @@ files = [ [[package]] name = "marshmallow" -version = "3.0.0" +version = "3.23.1" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false -python-versions = ">=3.5" +python-versions = ">=3.9" files = [ - {file = "marshmallow-3.0.0-py2.py3-none-any.whl", hash = "sha256:e5e9fd0c2e919b4ece915eb30808206349a49a45df72e99ed20e27a9053d574b"}, - {file = "marshmallow-3.0.0.tar.gz", hash = "sha256:fa2d8a4b61d09b0e161a14acc5ad8ab7aaaf1477f3dd52819ddd6c6c8275733a"}, + {file = "marshmallow-3.23.1-py3-none-any.whl", hash = "sha256:fece2eb2c941180ea1b7fcbd4a83c51bfdd50093fdd3ad2585ee5e1df2508491"}, + {file = "marshmallow-3.23.1.tar.gz", hash = "sha256:3a8dfda6edd8dcdbf216c0ede1d1e78d230a6dc9c5a088f58c4083b974a0d468"}, ] +[package.dependencies] +packaging = ">=17.0" + [package.extras] -dev = ["flake8 (==3.7.8)", "flake8-bugbear (==19.8.0)", "pre-commit (>=1.17,<2.0)", "pytest", "pytz", "simplejson", "tox"] -docs = ["alabaster (==0.7.12)", "sphinx (==2.1.2)", "sphinx-issues (==1.2.0)", "sphinx-version-warning (==1.1.2)"] -lint = ["flake8 (==3.7.8)", "flake8-bugbear (==19.8.0)", "pre-commit (>=1.17,<2.0)"] -tests = ["pytest", "pytz", "simplejson"] +dev = ["marshmallow[tests]", "pre-commit (>=3.5,<5.0)", "tox"] +docs = ["alabaster (==1.0.0)", "autodocsumm (==0.2.14)", "sphinx (==8.1.3)", "sphinx-issues (==5.0.0)", "sphinx-version-warning (==1.1.2)"] +tests = ["pytest", "simplejson"] [[package]] name = "marshmallow-polyfield" @@ -2825,6 +2811,7 @@ optional = false python-versions = ">=3.9" files = [ {file = "pandas-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce"}, + {file = "pandas-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238"}, {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08"}, {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0"}, {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51"}, @@ -2838,12 +2825,14 @@ files = [ {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1"}, {file = "pandas-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24"}, {file = "pandas-2.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef"}, + {file = "pandas-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce"}, {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad"}, {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad"}, {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76"}, {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32"}, {file = "pandas-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23"}, {file = "pandas-2.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2"}, + {file = "pandas-2.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd"}, {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863"}, {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921"}, {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a"}, @@ -4313,64 +4302,52 @@ full = ["pyro4"] [[package]] name = "qm-octave" -version = "2.0.1" +version = "2.1.3" description = "SDK to control an Octave with QUA" optional = false -python-versions = ">=3.7,<3.12" +python-versions = "<3.13,>=3.8" files = [ - {file = "qm_octave-2.0.1-py3-none-any.whl", hash = "sha256:ef2607101b978beb306f30bfc56656f087fe7764133ba22e3ce5739a409c4edb"}, - {file = "qm_octave-2.0.1.tar.gz", hash = "sha256:f20844d98e493a253bedb72f86fcb665bc7461a46d502de3cc7c91f3d54416d6"}, + {file = "qm_octave-2.1.3-py3-none-any.whl", hash = "sha256:1957eadaa6d8e3150c57bb8b378d3d9fbcd9dc66163ace3a317ecb94685e606a"}, + {file = "qm_octave-2.1.3.tar.gz", hash = "sha256:f7981b73dc51276f306e553f9906e40119024067ce7cab79c19f07b53778cc7b"}, ] [package.dependencies] -betterproto = [ - {version = "2.0.0b5", markers = "python_version >= \"3.7\" and python_version < \"3.11\""}, - {version = "2.0.0b6", markers = "python_version >= \"3.11\" and python_version < \"4.0\""}, -] +betterproto = "2.0.0b7" grpcio = ">=1.59.2,<2.0.0" grpclib = {version = ">=0.4.3rc3,<0.5.0", markers = "python_version >= \"3.10\" and python_version < \"4.0\""} protobuf = [ - {version = ">=3.17.3,<4.0.0", markers = "python_version >= \"3.7\" and python_version < \"3.11\""}, + {version = ">=3.17.3,<4.0.0", markers = "python_version >= \"3.8\" and python_version < \"3.11\""}, {version = ">=4.24,<5.0", markers = "python_version >= \"3.11\""}, ] [[package]] name = "qm-qua" -version = "1.1.6" +version = "1.2.1" description = "QUA language SDK to control a Quantum Computer" optional = false -python-versions = ">=3.7,<3.12" +python-versions = "<3.13,>=3.8" files = [ - {file = "qm_qua-1.1.6-py3-none-any.whl", hash = "sha256:69f8159805889fe9389b1acb4e94afacc37d68e1455017ec0e58a4fcc238bd8c"}, - {file = "qm_qua-1.1.6.tar.gz", hash = "sha256:9e09240bf1d9623c0f5b15fb2bc955f7387da15280d0b02c266e42a40818a2b1"}, + {file = "qm_qua-1.2.1-py3-none-any.whl", hash = "sha256:3315379d16929468b058adbe489f6ac3979d1a5fcae102d4fc498bb7ac2feded"}, + {file = "qm_qua-1.2.1.tar.gz", hash = "sha256:9a6af3cbab527bd95948c78a640d84d4ec8487ab1e146afd9e38f0ce9f939701"}, ] [package.dependencies] -betterproto = [ - {version = "2.0.0b5", markers = "python_version >= \"3.7\" and python_version < \"3.11\""}, - {version = "2.0.0b6", markers = "python_version == \"3.11\""}, -] +betterproto = "2.0.0b7" datadog-api-client = ">=2.6.0,<3.0.0" dependency_injector = ">=4.41.0,<5.0.0" deprecation = ">=2.1.0,<3.0.0" grpcio = [ - {version = ">=1.39.0,<2.0.0", markers = "python_version >= \"3.7\" and python_version < \"3.11\""}, + {version = ">=1.39.0,<2.0.0", markers = "python_version >= \"3.8\" and python_version < \"3.11\""}, {version = ">=1.57,<2.0", markers = "python_version >= \"3.11\""}, ] grpclib = {version = ">=0.4.5,<0.5.0", markers = "python_version >= \"3.10\""} -httpx = {version = ">=0.23.3,<0.24.0", extras = ["http2"]} -marshmallow = "3" +httpx = {version = ">=0.23.3,<1", extras = ["http2"]} +marshmallow = ">=3.20.1,<4.0.0" marshmallow-polyfield = ">=5.7,<6.0" -numpy = [ - {version = ">=1.17.0,<2.0.0", markers = "python_version >= \"3.7\" and python_version < \"3.11\""}, - {version = ">=1.24,<2.0", markers = "python_version >= \"3.11\""}, -] +numpy = {version = ">=1.17.0,<2", markers = "python_version >= \"3.8\" and python_version < \"3.12\""} plotly = ">=5.13.0,<6.0.0" -protobuf = [ - {version = ">=3.17.3,<4.0.0", markers = "python_version >= \"3.7\" and python_version < \"3.11\""}, - {version = ">=4.24,<5.0", markers = "python_version >= \"3.11\""}, -] -qm-octave = ">=2.0.1,<2.1.0" +protobuf = ">=3.17.3,<5" +qm-octave = "2.1.3" tinydb = ">=4.6.1,<5.0.0" typing-extensions = ">=4.5,<5.0" @@ -5798,4 +5775,4 @@ zh = ["laboneq"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.12" -content-hash = "2e9555b32f971566f63c0505cb15a651f0dabd88ce630a265e96bc27cf250f6e" +content-hash = "593881ae2ecdab7ce87f556217d8be30ae8cbb0e5e7d61a43f0f839b2060b45e" diff --git a/pyproject.toml b/pyproject.toml index 06fa87e18c..7258638f67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ qblox-instruments = { version = "0.12.0", optional = true } qcodes = { version = "^0.37.0", optional = true } qcodes_contrib_drivers = { version = "0.18.0", optional = true } pyvisa-py = { version = "0.5.3", optional = true } -qm-qua = { version = "==1.1.6", optional = true } +qm-qua = { version = "==1.2.1", optional = true } qualang-tools = { version = "^0.15.0", optional = true } setuptools = { version = ">67.0.0", optional = true } laboneq = { version = "==2.25.0", optional = true } From e11e0d04e60ef22f26221a28bf3457b5bdbda466 Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Wed, 27 Nov 2024 17:16:00 +0400 Subject: [PATCH 65/70] fix: remove QM manager when disconnecting --- src/qibolab/instruments/qm/controller.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/qibolab/instruments/qm/controller.py b/src/qibolab/instruments/qm/controller.py index e9017bb4b0..abbb6f5d35 100644 --- a/src/qibolab/instruments/qm/controller.py +++ b/src/qibolab/instruments/qm/controller.py @@ -258,6 +258,7 @@ def disconnect(self): self._reset_temporary_calibration() if self.manager is not None: self.manager.close_all_quantum_machines() + self.manager = None self.is_connected = False def calibrate_mixers(self, qubits): From e46b12c0a57c593a7932d8a3ae8fe7101f6280ca Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Wed, 27 Nov 2024 17:22:00 +0400 Subject: [PATCH 66/70] feat: raise error when not connected --- src/qibolab/instruments/qm/controller.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/qibolab/instruments/qm/controller.py b/src/qibolab/instruments/qm/controller.py index abbb6f5d35..c7d7871dea 100644 --- a/src/qibolab/instruments/qm/controller.py +++ b/src/qibolab/instruments/qm/controller.py @@ -288,6 +288,10 @@ def execute_program(self, program): Args: program: QUA program. """ + if self.manager is None: + raise RuntimeError( + "Quantum Machines are not connected. Please use ``platform.connect()``." + ) machine = self.manager.open_qm(self.config.__dict__) return machine.execute(program) From 1ee377827db1b732f2ec85c78438122fc551762f Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Sun, 1 Dec 2024 17:48:24 +0400 Subject: [PATCH 67/70] feat: disconnect platform when job is interrupted --- src/qibolab/platform/platform.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/qibolab/platform/platform.py b/src/qibolab/platform/platform.py index 5178978cbc..706585f3e3 100644 --- a/src/qibolab/platform/platform.py +++ b/src/qibolab/platform/platform.py @@ -1,5 +1,6 @@ """A platform for executing quantum algorithms.""" +import signal from collections import defaultdict from dataclasses import dataclass, field, replace from typing import Dict, List, Optional, Tuple @@ -117,6 +118,10 @@ class Platform: """Graph representing the qubit connectivity in the quantum chip.""" def __post_init__(self): + signal.signal(signal.SIGTERM, self.termination_handler) + signal.signal(signal.SIGINT, self.termination_handler) + log.info("signals registered") + log.info("Loading platform %s", self.name) if self.resonator_type is None: self.resonator_type = "3D" if self.nqubits == 1 else "2D" @@ -168,6 +173,12 @@ def disconnect(self): instrument.disconnect() self.is_connected = False + def termination_handler(self, signum, frame): + self.disconnect() + raise RuntimeError( + f"Platform {self.name} disconnected because job was cancelled. Signal type: {signum}." + ) + def _execute(self, sequence, options, **kwargs): """Executes sequence on the controllers.""" result = {} From 3025a0abe21f958bac533190ab83482d2dbde2cf Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Sun, 1 Dec 2024 17:55:54 +0400 Subject: [PATCH 68/70] chore: drop log message --- src/qibolab/platform/platform.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/qibolab/platform/platform.py b/src/qibolab/platform/platform.py index 706585f3e3..88f3a310f9 100644 --- a/src/qibolab/platform/platform.py +++ b/src/qibolab/platform/platform.py @@ -120,7 +120,6 @@ class Platform: def __post_init__(self): signal.signal(signal.SIGTERM, self.termination_handler) signal.signal(signal.SIGINT, self.termination_handler) - log.info("signals registered") log.info("Loading platform %s", self.name) if self.resonator_type is None: From 74b86173e93daa53ef87a535fe897773bcc7827b Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Sat, 28 Dec 2024 00:22:23 +0400 Subject: [PATCH 69/70] fix: compilation error when running mixer calibration --- src/qibolab/instruments/qm/config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/qibolab/instruments/qm/config.py b/src/qibolab/instruments/qm/config.py index 578efc24a5..7915f1b917 100644 --- a/src/qibolab/instruments/qm/config.py +++ b/src/qibolab/instruments/qm/config.py @@ -11,7 +11,7 @@ SAMPLING_RATE = 1 """Sampling rate of Quantum Machines OPX in GSps.""" -DEFAULT_INPUTS = {1: {}, 2: {}} +DEFAULT_INPUTS = {1: {"offset": 0}, 2: {"offset": 0}} """Default controller config section. Inputs are always registered to avoid issues with automatic mixer @@ -57,7 +57,7 @@ def register_port(self, port): controllers[port.device] = {"type": "opx1000", "fems": {}} else: controllers[port.device] = { - "analog_inputs": DEFAULT_INPUTS, + "analog_inputs": DEFAULT_INPUTS.copy(), "digital_outputs": {}, } @@ -66,7 +66,7 @@ def register_port(self, port): if port.fem_number not in fems: fems[port.fem_number] = { "type": port.fem_type, - "analog_inputs": DEFAULT_INPUTS, + "analog_inputs": DEFAULT_INPUTS.copy(), "digital_outputs": {}, } device = fems[port.fem_number] From 4d9895aca0adc7e2479aa321e86238d195b0f518 Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Sat, 28 Dec 2024 00:35:15 +0400 Subject: [PATCH 70/70] fix: tests --- tests/test_instruments_qm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_instruments_qm.py b/tests/test_instruments_qm.py index fee8633709..f018e554e5 100644 --- a/tests/test_instruments_qm.py +++ b/tests/test_instruments_qm.py @@ -158,7 +158,7 @@ def test_qm_register_port(qmcontroller, offset): controllers = qmcontroller.config.controllers assert controllers == { "con1": { - "analog_inputs": {1: {}, 2: {}}, + "analog_inputs": {1: {"offset": 0}, 2: {"offset": 0}}, "analog_outputs": {1: {"offset": offset, "filter": {}}}, "digital_outputs": {}, } @@ -173,7 +173,7 @@ def test_qm_register_port_filter(qmcontroller): controllers = qmcontroller.config.controllers assert controllers == { "con1": { - "analog_inputs": {1: {}, 2: {}}, + "analog_inputs": {1: {"offset": 0}, 2: {"offset": 0}}, "analog_outputs": { 2: { "filter": {"feedback": [0.95], "feedforward": [1, -1]},