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 1/7] 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 2/7] 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 3/7] 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 4/7] 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 5/7] 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 6/7] 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 7/7] 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] = {}