From 71cf3347087a8522b09957e956b835cd969d657f Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Sat, 24 Aug 2024 13:07:35 +1000 Subject: [PATCH 01/40] Migrate from qua-libs branch. --- qualang_tools/wirer/__init__.py | 0 qualang_tools/wirer/connectivity/__init__.py | 1 + .../wirer/connectivity/connectivity.py | 119 ++++++++++++++ qualang_tools/wirer/connectivity/element.py | 31 ++++ qualang_tools/wirer/connectivity/types.py | 7 + .../wirer/connectivity/wiring_spec.py | 25 +++ .../wirer/connectivity/wiring_spec_enums.py | 55 +++++++ qualang_tools/wirer/instruments/__init__.py | 1 + qualang_tools/wirer/instruments/constants.py | 8 + .../wirer/instruments/instrument_channels.py | 133 ++++++++++++++++ .../wirer/instruments/instruments.py | 66 ++++++++ qualang_tools/wirer/visualizer/__init__.py | 0 qualang_tools/wirer/visualizer/visualizer.py | 148 ++++++++++++++++++ qualang_tools/wirer/wirer/__init__.py | 1 + qualang_tools/wirer/wirer/wire_to_spec.py | 52 ++++++ qualang_tools/wirer/wirer/wirer.py | 35 +++++ .../wirer/wirer_assign_channels_to_spec.py | 101 ++++++++++++ .../wirer/wirer/wirer_channel_map.py | 45 ++++++ qualang_tools/wirer/wirer/wirer_exception.py | 11 ++ tests/wirer/conftest.py | 41 +++++ tests/wirer/test_instrument_validation.py | 60 +++++++ tests/wirer/test_wirer_lf_and_mw.py | 59 +++++++ tests/wirer/test_wirer_lf_and_octave.py | 80 ++++++++++ 23 files changed, 1079 insertions(+) create mode 100644 qualang_tools/wirer/__init__.py create mode 100644 qualang_tools/wirer/connectivity/__init__.py create mode 100644 qualang_tools/wirer/connectivity/connectivity.py create mode 100644 qualang_tools/wirer/connectivity/element.py create mode 100644 qualang_tools/wirer/connectivity/types.py create mode 100644 qualang_tools/wirer/connectivity/wiring_spec.py create mode 100644 qualang_tools/wirer/connectivity/wiring_spec_enums.py create mode 100644 qualang_tools/wirer/instruments/__init__.py create mode 100644 qualang_tools/wirer/instruments/constants.py create mode 100644 qualang_tools/wirer/instruments/instrument_channels.py create mode 100644 qualang_tools/wirer/instruments/instruments.py create mode 100644 qualang_tools/wirer/visualizer/__init__.py create mode 100644 qualang_tools/wirer/visualizer/visualizer.py create mode 100644 qualang_tools/wirer/wirer/__init__.py create mode 100644 qualang_tools/wirer/wirer/wire_to_spec.py create mode 100644 qualang_tools/wirer/wirer/wirer.py create mode 100644 qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py create mode 100644 qualang_tools/wirer/wirer/wirer_channel_map.py create mode 100644 qualang_tools/wirer/wirer/wirer_exception.py create mode 100644 tests/wirer/conftest.py create mode 100644 tests/wirer/test_instrument_validation.py create mode 100644 tests/wirer/test_wirer_lf_and_mw.py create mode 100644 tests/wirer/test_wirer_lf_and_octave.py diff --git a/qualang_tools/wirer/__init__.py b/qualang_tools/wirer/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/qualang_tools/wirer/connectivity/__init__.py b/qualang_tools/wirer/connectivity/__init__.py new file mode 100644 index 00000000..9daef322 --- /dev/null +++ b/qualang_tools/wirer/connectivity/__init__.py @@ -0,0 +1 @@ +from .connectivity import Connectivity diff --git a/qualang_tools/wirer/connectivity/connectivity.py b/qualang_tools/wirer/connectivity/connectivity.py new file mode 100644 index 00000000..5505b8ab --- /dev/null +++ b/qualang_tools/wirer/connectivity/connectivity.py @@ -0,0 +1,119 @@ +import copy +from typing import Dict, List + +from .element import Element, ElementId, QubitReference, QubitPairReference +from .types import QubitsType, QubitPairsType +from .wiring_spec import WiringSpec +from .wiring_spec_enums import ( + WiringFrequency, + WiringIOType, + WiringLineType, + WiringIOSpec, +) + + +class Connectivity: + """ + This class stores placeholders for the quantum elements which will be used + in a setup, as well as the wiring specification for each of those elements. + This includes at what frequency the element will be driven, whether it + requires input/output or both lines, if it is required on a particular + module or FEM slot, and what high-level component will be manipulated. + """ + + def __init__(self): + self.elements: Dict[ElementId, Element] = {} + self.specs: List[WiringSpec] = [] + + def add_resonator_line( + self, qubits: QubitsType, con: int = None, slot: int = None, port: int = None + ): + elements = self._make_qubit_elements(qubits) + io_spec = WiringIOSpec( + type=WiringIOType.INPUT_AND_OUTPUT, con=con, slot=slot, port=port + ) + return self.add_wiring_spec( + elements, + WiringFrequency.RF, + io_spec, + WiringLineType.RESONATOR, + shared_line=True, + ) + + def add_qubit_drive_lines( + self, qubits: QubitsType, con: int = None, slot: int = None, port: int = None + ): + elements = self._make_qubit_elements(qubits) + io_spec = WiringIOSpec(type=WiringIOType.OUTPUT, con=con, slot=slot, port=port) + return self.add_wiring_spec( + elements, WiringFrequency.RF, io_spec, WiringLineType.DRIVE + ) + + def add_qubit_flux_lines( + self, qubits: QubitsType, con: int = None, slot: int = None, port: int = None + ): + elements = self._make_qubit_elements(qubits) + io_spec = WiringIOSpec(type=WiringIOType.OUTPUT, con=con, slot=slot, port=port) + return self.add_wiring_spec( + elements, WiringFrequency.DC, io_spec, WiringLineType.FLUX + ) + + def add_qubit_pair_flux_lines( + self, + qubit_pairs: QubitPairsType, + con: int = None, + slot: int = None, + port: int = None, + ): + elements = self._make_qubit_pair_elements(qubit_pairs) + io_spec = WiringIOSpec(type=WiringIOType.OUTPUT, con=con, slot=slot, port=port) + return self.add_wiring_spec( + elements, WiringFrequency.DC, io_spec, WiringLineType.COUPLER + ) + + def add_wiring_spec( + self, + elements: List[Element], + frequency: WiringFrequency, + io_spec: WiringIOSpec, + line_type: WiringLineType, + shared_line: bool = False, + ): + specs = [] + if shared_line: + spec = WiringSpec(frequency, io_spec, line_type, elements) + specs.append(spec) + else: + for element in elements: + io_spec = copy.deepcopy(io_spec) + spec = WiringSpec(frequency, io_spec, line_type, element) + specs.append(spec) + self.specs.extend(specs) + + return specs + + def _make_qubit_elements(self, qubits: QubitsType): + if not isinstance(qubits, list): + qubits = [qubits] + + elements = [] + for qubit in qubits: + id = QubitReference(qubit) + if id not in self.elements: + self.elements[id] = Element(id) + elements.append(self.elements[id]) + + return elements + + def _make_qubit_pair_elements(self, qubit_pairs: QubitPairsType): + if not isinstance(qubit_pairs, list): + qubit_pairs = [qubit_pairs] + + elements = [] + for qubit_pair in qubit_pairs: + id = QubitPairReference(*qubit_pair) + if id not in self.elements: + self.elements[id] = Element(id) + elements.append(self.elements[id]) + + return elements diff --git a/qualang_tools/wirer/connectivity/element.py b/qualang_tools/wirer/connectivity/element.py new file mode 100644 index 00000000..7aba5863 --- /dev/null +++ b/qualang_tools/wirer/connectivity/element.py @@ -0,0 +1,31 @@ +from dataclasses import dataclass, field +from typing import List, Dict, Any, Union + +from .wiring_spec_enums import WiringLineType +from ..instruments.instrument_channels import InstrumentChannel + + +@dataclass(frozen=True) +class QubitReference: + index: int + + +@dataclass(frozen=True) +class QubitPairReference: + control_index: int + target_index: int + + +ElementId = Union[QubitReference, QubitPairReference] + + +@dataclass +class Element: + id: ElementId + channels: Dict[WiringLineType, List[InstrumentChannel]] = field(default_factory=dict) + + def __str__(self): + return str(self.channels) + + def __eq__(self, other): + return self.id == other.id diff --git a/qualang_tools/wirer/connectivity/types.py b/qualang_tools/wirer/connectivity/types.py new file mode 100644 index 00000000..f785aa51 --- /dev/null +++ b/qualang_tools/wirer/connectivity/types.py @@ -0,0 +1,7 @@ +from typing import Union, List, Tuple + +QubitType = int +QubitPairType = Tuple[int, int] + +QubitsType = Union[QubitType, List[QubitType]] +QubitPairsType = Union[QubitPairType, List[QubitPairType]] diff --git a/qualang_tools/wirer/connectivity/wiring_spec.py b/qualang_tools/wirer/connectivity/wiring_spec.py new file mode 100644 index 00000000..beaa6322 --- /dev/null +++ b/qualang_tools/wirer/connectivity/wiring_spec.py @@ -0,0 +1,25 @@ +from typing import Union, List + +from .element import Element +from .wiring_spec_enums import * + + +class WiringSpec: + """ + A technical specification for the wiring that will be required to + manipulate the given quantum elements. + """ + + def __init__( + self, + frequency: WiringFrequency, + io_spec: WiringIOSpec, + line_type: WiringLineType, + elements: Union[Element, List[Element]], + ): + self.frequency = frequency + self.io_spec = io_spec + self.line_type = line_type + if not isinstance(elements, list): + elements = [elements] + self.elements: List[Element] = elements diff --git a/qualang_tools/wirer/connectivity/wiring_spec_enums.py b/qualang_tools/wirer/connectivity/wiring_spec_enums.py new file mode 100644 index 00000000..045d3d3e --- /dev/null +++ b/qualang_tools/wirer/connectivity/wiring_spec_enums.py @@ -0,0 +1,55 @@ +from enum import Enum +from dataclasses import dataclass +from typing import Optional, Callable + + +class WiringFrequency(Enum): + DC = "DC" + RF = "RF" + + +class WiringIOType(Enum): + INPUT = "input" + OUTPUT = "output" + INPUT_AND_OUTPUT = "input_output" + + +class WiringLineType(Enum): + RESONATOR = "rr" + DRIVE = "xy" + FLUX = "z" + COUPLER = "c" + + +@dataclass +class WiringIOSpec: + """ + A technical specification for what IO is required in a WiringSpecifcation. + The wiring type indicates whether the required channels are input, output + or both. The con/slot/port attributes allow the hardcoding of more + specific channels to satisfy the wiring. + """ + + type: WiringIOType + con: Optional[int] = None + slot: Optional[int] = None + port: Optional[int] = None + + def make_channel_filter(self) -> Callable[["InstrumentChannel"], bool]: + # todo: + return lambda channel: ( + (self.con is None or self.con == channel.con) + and (self.slot is None or self.slot == channel.slot) + and (self.port is None or self.port == channel.port) + ) + + def __str__(self): + return ( + f"IO type={self.type.value}" + f", con=con{self.con}" + if self.con is not None + else "" + f", slot={self.slot}" + if self.slot is not None + else "" + f", port={self.port}" + if self.port is not None + else "" + ) diff --git a/qualang_tools/wirer/instruments/__init__.py b/qualang_tools/wirer/instruments/__init__.py new file mode 100644 index 00000000..7576b8ab --- /dev/null +++ b/qualang_tools/wirer/instruments/__init__.py @@ -0,0 +1 @@ +from .instruments import Instruments diff --git a/qualang_tools/wirer/instruments/constants.py b/qualang_tools/wirer/instruments/constants.py new file mode 100644 index 00000000..4d5629e4 --- /dev/null +++ b/qualang_tools/wirer/instruments/constants.py @@ -0,0 +1,8 @@ +NUM_OCTAVE_INPUT_PORTS = 2 +NUM_OCTAVE_OUTPUT_PORTS = 5 +NUM_OPX_PLUS_INPUT_PORTS = 2 +NUM_OPX_PLUS_OUTPUT_PORTS = 10 +NUM_LF_FEM_INPUT_PORTS = 2 +NUM_LF_FEM_OUTPUT_PORTS = 8 +NUM_MW_FEM_INPUT_PORTS = 2 +NUM_MW_FEM_OUTPUT_PORTS = 8 diff --git a/qualang_tools/wirer/instruments/instrument_channels.py b/qualang_tools/wirer/instruments/instrument_channels.py new file mode 100644 index 00000000..7e0b83cb --- /dev/null +++ b/qualang_tools/wirer/instruments/instrument_channels.py @@ -0,0 +1,133 @@ +from dataclasses import dataclass +from typing import Union, Literal, Type + + +@dataclass +class InstrumentChannel: + con: int + port: int + slot: Union[None, int] = None + io_type: Literal["input", "output"] = None + + def __str__(self): + return f'({", ".join([f"con{self.con}", f"{self.slot}" if self.slot else "", str(self.port)])})' + + +@dataclass +class InstrumentChannelInput(InstrumentChannel): + io_type: Literal["input", "output"] = "input" + + +@dataclass +class InstrumentChannelOutput(InstrumentChannel): + io_type: Literal["input", "output"] = "output" + + +class InstrumentChannelLfFemInput(InstrumentChannelInput): + pass + + +class InstrumentChannelLfFemOutput(InstrumentChannelOutput): + pass + + +class InstrumentChannelMwFemInput(InstrumentChannelInput): + pass + + +class InstrumentChannelMwFemOutput(InstrumentChannelOutput): + pass + + +class InstrumentChannelOpxPlusInput(InstrumentChannelInput): + pass + + +class InstrumentChannelOpxPlusOutput(InstrumentChannelOutput): + pass + + +class InstrumentChannelOctaveInput(InstrumentChannelInput): + pass + + +class InstrumentChannelOctaveOutput(InstrumentChannelOutput): + pass + + +CHANNELS_OPX_PLUS = [InstrumentChannelOpxPlusInput, InstrumentChannelOpxPlusOutput] + +CHANNELS_OPX_1000 = [ + InstrumentChannelLfFemInput, + InstrumentChannelLfFemOutput, + InstrumentChannelMwFemInput, + InstrumentChannelMwFemOutput, +] + + +class InstrumentChannels: + """ + Collection of "stack" data-structures organized by channel type. + Each entry contains a list of instrument channels. This wrapper + can be interacted with as if it were the underlying stack dictionary. + """ + + def __init__(self): + self.stack = {} + + def check_if_already_occupied(self, channel: InstrumentChannel): + for channel_type in self.stack: + for existing_channel in self.stack[channel_type]: + if ( + channel.con == existing_channel.con + and channel.slot == existing_channel.slot + and channel.port == existing_channel.port + and channel.io_type == existing_channel.io_type + ): + if channel.slot is None: + if type(channel) != type(existing_channel): + pass + else: + raise ValueError( + f"{channel.io_type} channel on con{channel.con}, " + f"port {channel.port} is already occupied." + ) + else: + raise ValueError( + f"{channel.io_type} channel on con{channel.con}, " + f"slot {channel.slot}, port {channel.port} is already occupied." + ) + + def check_if_mixing_opx_1000_and_opx_plus(self, channel: InstrumentChannel): + if type(channel) in CHANNELS_OPX_1000: + for channel_type in CHANNELS_OPX_PLUS: + if channel_type in self.stack: + raise ValueError(f"Can't add an FEM to a setup with an OPX+.") + elif type(channel) in CHANNELS_OPX_PLUS: + for channel_type in CHANNELS_OPX_1000: + if channel_type in self.stack: + raise ValueError( + f"Can't add an OPX+ to a setup with an OPX1000 FEM." + ) + + def add(self, channel: InstrumentChannel): + self.check_if_already_occupied(channel) + self.check_if_mixing_opx_1000_and_opx_plus(channel) + + channel_type = type(channel) + if channel_type not in self.stack: + self.stack[channel_type] = [] + + self.stack[channel_type].append(channel) + + def pop(self, channel: Type[InstrumentChannel]): + return self.stack[channel].pop(0) + + def get(self, key: InstrumentChannel, fallback=None): + return self.stack.get(key, fallback) + + def __getitem__(self, item): + return self.stack[item] + + def __iter__(self): + return iter(self.stack) diff --git a/qualang_tools/wirer/instruments/instruments.py b/qualang_tools/wirer/instruments/instruments.py new file mode 100644 index 00000000..c067db63 --- /dev/null +++ b/qualang_tools/wirer/instruments/instruments.py @@ -0,0 +1,66 @@ +from typing import List, Union +from .instrument_channels import * +from .constants import * + + +class Instruments: + """ + Class to add the static information about which QM instruments will be used + in an experimental setup. Upon adding an instrument, its available channels + will be enumerated and added individually to a stack of free channels. + """ + + def __init__(self): + self.available_channels = InstrumentChannels() + + def add_octave(self, indices: Union[List[int], int]): + if isinstance(indices, int): + indices = [indices] + + for index in indices: + for port in range(1, NUM_OCTAVE_INPUT_PORTS + 1): + channel = InstrumentChannelOctaveInput(con=index, port=port) + self.available_channels.add(channel) + + for port in range(1, NUM_OCTAVE_OUTPUT_PORTS + 1): + channel = InstrumentChannelOctaveOutput(con=index, port=port) + self.available_channels.add(channel) + + def add_lf_fem(self, con: int, slots: Union[List[int], int]): + if isinstance(slots, int): + slots = [slots] + + for slot in slots: + for port in range(1, NUM_LF_FEM_INPUT_PORTS + 1): + channel = InstrumentChannelLfFemInput(con=con, slot=slot, port=port) + self.available_channels.add(channel) + + for port in range(1, NUM_LF_FEM_OUTPUT_PORTS + 1): + channel = InstrumentChannelLfFemOutput(con=con, slot=slot, port=port) + self.available_channels.add(channel) + + def add_mw_fem(self, con: int, slots: Union[List[int], int]): + if isinstance(slots, int): + slots = [slots] + + for slot in slots: + for port in range(1, NUM_MW_FEM_INPUT_PORTS + 1): + channel = InstrumentChannelMwFemInput(con=con, slot=slot, port=port) + self.available_channels.add(channel) + + for port in range(1, NUM_MW_FEM_OUTPUT_PORTS + 1): + channel = InstrumentChannelMwFemOutput(con=con, slot=slot, port=port) + self.available_channels.add(channel) + + def add_opx_plus(self, cons: Union[List[int], int]): + if isinstance(cons, int): + cons = [cons] + + for con in cons: + for port in range(1, NUM_OPX_PLUS_INPUT_PORTS + 1): + channel = InstrumentChannelOpxPlusInput(con=con, port=port) + self.available_channels.add(channel) + + for port in range(1, NUM_OPX_PLUS_OUTPUT_PORTS + 1): + channel = InstrumentChannelOpxPlusOutput(con=con, port=port) + self.available_channels.add(channel) diff --git a/qualang_tools/wirer/visualizer/__init__.py b/qualang_tools/wirer/visualizer/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/qualang_tools/wirer/visualizer/visualizer.py b/qualang_tools/wirer/visualizer/visualizer.py new file mode 100644 index 00000000..d0fbad4b --- /dev/null +++ b/qualang_tools/wirer/visualizer/visualizer.py @@ -0,0 +1,148 @@ +import matplotlib.pyplot as plt +import matplotlib.patches as patches + +from quam_libs.wiring.connectivity.wiring_spec_enums import WiringLineType + +# Define the chassis dimensions +CHASSIS_WIDTH = 8 +CHASSIS_HEIGHT = 3 # Updated height for chassis + +# Define the port positions in a FEM (normalized within a slot) +PORT_SPACING_FACTOR = 0.1 # Close spacing between ports +PORT_POSITIONS = { + "output": [(0.25, 1 - i * PORT_SPACING_FACTOR) for i in range(8)], + "input": [(0.75, 1 - i * PORT_SPACING_FACTOR * 7) for i in range(2)], +} + +# Define a function to draw and annotate ports +def annotate_port(ax, port, annotations, assigned, color): + pos = PORT_POSITIONS[port.io_type][port.port - 1] + x = port.slot - 1 + pos[0] + y = pos[1] * CHASSIS_HEIGHT - 0.5 + + # Draw port as filled or empty circle based on assignment + fill_color = color if assigned else "none" + ax.add_patch(patches.Circle((x, y), 0.1, edgecolor="black", facecolor=fill_color)) + + # Place grouped annotations to the left of the port with a semi-transparent white background + for i, annotation in enumerate(annotations): + ax.text( + x - 0.15, + y + 0.15 * i, + annotation, + ha="right", + va="center", + fontsize=14, + color="black", + # fontweight="bold", + bbox=dict(facecolor="white", alpha=0.3, edgecolor="none"), + ) + + +# Define a function to label the slots +def label_slot(ax, slot, fem_type): + if fem_type: + label = f"{slot}: " + ("MW-FEM" if fem_type == "mw" else "LF-FEM") + ax.text( + slot - 0.5, + -0.2, + label, + ha="center", + va="bottom", + fontsize=12, + fontweight="bold", + ) + + +# Define a function to get port color based on line type +def get_color_for_line_type(line_type): + color_map = {WiringLineType.FLUX.value: "blue", + WiringLineType.RESONATOR.value: "orange", + WiringLineType.DRIVE.value: "yellow", + WiringLineType.COUPLER.value: "purple"} + for key in color_map: + if key in line_type: + return color_map[key] + return "grey" + + +# Define a function to visualize the chassis and FEMs +def visualize_chassis(qubit_dict): + fig, ax = plt.subplots(figsize=(CHASSIS_WIDTH*2, CHASSIS_HEIGHT*2)) + + # Draw the chassis slots with boundary lines + for slot in range(1, CHASSIS_WIDTH + 1): + # Set light grey background for the FEMs + ax.add_patch( + patches.Rectangle( + (slot - 1, 0), + 1, + CHASSIS_HEIGHT, + facecolor="lightgrey", + edgecolor="black", + ) + ) + + # Track the FEM types per slot and ports assignments + fem_types = {slot: None for slot in range(1, CHASSIS_WIDTH + 1)} + channel_assignments = { + slot: {"input": [False] * 2, "output": [False] * 8} + for slot in range(1, CHASSIS_WIDTH + 1) + } + annotations_map = { + slot: {"input": [[] for _ in range(2)], "output": [[] for _ in range(8)]} + for slot in range(1, CHASSIS_WIDTH + 1) + } + + # Annotate the ports and determine the FEM types + for qubit_ref, element in qubit_dict.items(): + for channel_type, channels in element.channels.items(): + for channel in channels: + annotation = f"q{qubit_ref.index if hasattr(qubit_ref, 'index') else f'{qubit_ref.control_index}{qubit_ref.target_index}'}.{channel_type.value}" + annotations_map[channel.slot][channel.io_type][channel.port - 1].append( + annotation + ) + channel_assignments[channel.slot][channel.io_type][channel.port - 1] = True + if "mw" in type(channel).__name__.lower(): + fem_types[channel.slot] = "mw" + elif "lf" in type(channel).__name__.lower(): + fem_types[channel.slot] = "lf" + + # Draw ports with annotations + for slot in range(1, CHASSIS_WIDTH + 1): + has_assigned_ports_slot = any( + any(assigned for assigned in ports) + for ports in channel_assignments[slot].values() + ) + + for io_type, ports in channel_assignments[slot].items(): + for i, assigned in enumerate(ports): + port = type( + "Port", (object,), {"slot": slot, "port": i + 1, "io_type": io_type} + )() + if assigned: + line_type = annotations_map[slot][io_type][i][0] + color = get_color_for_line_type(line_type) + annotate_port( + ax, + port, + annotations_map[slot][io_type][i], + assigned=True, + color=color, + ) + elif has_assigned_ports_slot: + annotate_port(ax, port, [], assigned=False, color="none") + + label_slot(ax, slot, fem_types[slot]) + + # Draw slot boundaries explicitly + for slot in range(CHASSIS_WIDTH): + ax.plot([slot, slot], [0, CHASSIS_HEIGHT], color="black", lw=1) + + # Set the limits and display the plot + ax.set_xlim(0, CHASSIS_WIDTH) + ax.set_ylim(0, CHASSIS_HEIGHT) + ax.set_aspect("equal") + plt.suptitle("OPX1000 Chassis Wiring Diagram", fontweight="bold", fontsize=20) + plt.axis("off") + plt.show() diff --git a/qualang_tools/wirer/wirer/__init__.py b/qualang_tools/wirer/wirer/__init__.py new file mode 100644 index 00000000..c8db1ca3 --- /dev/null +++ b/qualang_tools/wirer/wirer/__init__.py @@ -0,0 +1 @@ +from .wirer import allocate_wiring diff --git a/qualang_tools/wirer/wirer/wire_to_spec.py b/qualang_tools/wirer/wirer/wire_to_spec.py new file mode 100644 index 00000000..aefb119f --- /dev/null +++ b/qualang_tools/wirer/wirer/wire_to_spec.py @@ -0,0 +1,52 @@ +from typing import Literal + +from .wirer_assign_channels_to_spec import assign_channels_to_spec +from .wirer_exception import WirerException +from ..connectivity.wiring_spec import WiringSpec +from ..instruments import Instruments +from .wirer_channel_map import get_channel_mapping + + +def allocate_dc_channels(spec: WiringSpec, instruments: Instruments): + """ + Try to allocate DC channels to an LF-FEM or OPX+ to satisfy the spec. + """ + if not ( + try_allocate_channels(spec, instruments, "lf-fem") + or try_allocate_channels(spec, instruments, "opx+") + ): + raise WirerException(spec) + + +def allocate_rf_channels(spec: WiringSpec, instruments: Instruments): + """ + Try to allocate RF channels to a MW-FEM. If that doesn't work, look for a + combination of LF-FEM I/Q and Octave channels, or OPX+ I/Q and Octave + channels. + """ + if not try_allocate_channels(spec, instruments, "mw-fem"): + if not ( + try_allocate_channels(spec, instruments, "octave") + and ( + try_allocate_channels(spec, instruments, "lf-fem", num=2) + or try_allocate_channels(spec, instruments, "opx+", num=2) + ) + ): + raise WirerException(spec) + + +def try_allocate_channels( + spec: WiringSpec, + instruments: Instruments, + module: Literal["mw-fem", "lf-fem", "opx+", "octave"], + num: int = 1, +) -> bool: + """ + Identify which channel types would satisfy the spec for a specific QM + module, then try to assign such channels to the spec, returning True + if it succeeds. + """ + channel_types = get_channel_mapping(module, spec.io_spec.type, num=num) + return assign_channels_to_spec( + spec, instruments, channel_types, same_con=True, same_slot=True + ) diff --git a/qualang_tools/wirer/wirer/wirer.py b/qualang_tools/wirer/wirer/wirer.py new file mode 100644 index 00000000..b0927a71 --- /dev/null +++ b/qualang_tools/wirer/wirer/wirer.py @@ -0,0 +1,35 @@ +from ..instruments import Instruments +from ..connectivity import Connectivity +from ..connectivity.wiring_spec import WiringSpec +from ..connectivity.wiring_spec_enums import ( + WiringFrequency, + WiringIOType, + WiringLineType, +) +from .wire_to_spec import allocate_dc_channels, allocate_rf_channels + + +def allocate_wiring(connectivity: Connectivity, instruments: Instruments): + line_type_fill_order = [ + WiringLineType.RESONATOR, + WiringLineType.DRIVE, + WiringLineType.FLUX, + WiringLineType.COUPLER, + ] + + specs = connectivity.specs + for line_type in line_type_fill_order: + for spec in specs: + if spec.line_type == line_type: + _allocate_channels(spec, instruments) + + +def _allocate_channels(spec: WiringSpec, instruments: Instruments): + if spec.frequency == WiringFrequency.DC: + allocate_dc_channels(spec, instruments) + + elif spec.frequency == WiringFrequency.RF: + allocate_rf_channels(spec, instruments) + + else: + raise NotImplementedError() diff --git a/qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py b/qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py new file mode 100644 index 00000000..c0e963d8 --- /dev/null +++ b/qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py @@ -0,0 +1,101 @@ +from typing import List, Type + +from quam_libs.wiring.connectivity.wiring_spec import WiringSpec +from quam_libs.wiring.instruments import Instruments +from quam_libs.wiring.instruments.instrument_channels import InstrumentChannel + + +def assign_channels_to_spec( + spec: WiringSpec, + instruments: Instruments, + channel_types: List[Type[InstrumentChannel]], + same_con: bool = False, + same_slot: bool = False, +): + + candidate_channels = _assign_channels_to_spec( + spec, instruments, channel_types, same_con, same_slot + ) + + # if candidate channels satisfy all the required channel types + if len(candidate_channels) == len(channel_types): + for channel in candidate_channels: + # remove candidate channel from stack of available channels + instruments.available_channels[type(channel)].remove(channel) + for element in spec.elements: + # assign channel to the specified element + if spec.line_type not in element.channels: + element.channels[spec.line_type] = [] + element.channels[spec.line_type].append(channel) + + return len(candidate_channels) == len(channel_types) + + +def _assign_channels_to_spec( + spec: WiringSpec, + instruments: Instruments, + channel_types: List[Type[InstrumentChannel]], + same_con: bool, + same_slot: bool, + allocated_channels=None, +): + """ + Recursive function to find any valid combination of channel allocations + given a wiring specification, a stack of available channels in the + instruments setup, and a list of desired channel types. + """ + if allocated_channels is None: + allocated_channels = [] + + # extract the lead/initial channel type + target_channel_type = channel_types[0] + + # filter available channels according to the specification + available_channels = list( + filter( + spec.io_spec.make_channel_filter(), # filter function + instruments.available_channels.get(target_channel_type, []), # iterable + ) + ) + + candidate_channels = [] + for channel in available_channels: + # make sure to not re-allocate a channel + if channel in allocated_channels: + continue + + candidate_channels = [channel] + + # base case: all channels allocated properly + if len(channel_types) == 1: + break + + # recursive case: allocate remaining channels + else: + # require that all future channels have the same `con` and `slot` as the candidate + if same_con: + spec.io_spec.con = channel.con + if same_slot: + spec.io_spec.slot = channel.slot + + # recursively allocate the remaining channels + subsequent_channels = _assign_channels_to_spec( + spec, + instruments, + channel_types[1:], + same_con, + same_slot, + allocated_channels=candidate_channels, + ) + + candidate_channels.extend(subsequent_channels) + + # without a candidate channel for every type, try the next available channel + if len(candidate_channels) == len(channel_types): + break + + # otherwise, a successful allocation has been made + else: + continue + + return candidate_channels diff --git a/qualang_tools/wirer/wirer/wirer_channel_map.py b/qualang_tools/wirer/wirer/wirer_channel_map.py new file mode 100644 index 00000000..a3bd4de8 --- /dev/null +++ b/qualang_tools/wirer/wirer/wirer_channel_map.py @@ -0,0 +1,45 @@ +from typing import List, Type + +from ..connectivity.wiring_spec_enums import WiringIOType +from ..instruments.instrument_channels import ( + InstrumentChannelMwFemInput, + InstrumentChannelMwFemOutput, + InstrumentChannelOctaveInput, + InstrumentChannelOctaveOutput, + InstrumentChannelLfFemOutput, + InstrumentChannelLfFemInput, + InstrumentChannelOpxPlusInput, + InstrumentChannelOpxPlusOutput, + InstrumentChannel, +) + + +def get_channel_mapping( + allocation_type: str, io_type: WiringIOType, num: int = 1 +) -> List[Type[InstrumentChannel]]: + return { + "lf-fem": { + WiringIOType.INPUT: [InstrumentChannelLfFemInput] * num, + WiringIOType.OUTPUT: [InstrumentChannelLfFemOutput] * num, + WiringIOType.INPUT_AND_OUTPUT: [InstrumentChannelLfFemInput] * num + + [InstrumentChannelLfFemOutput] * num, + }, + "mw-fem": { + WiringIOType.INPUT: [InstrumentChannelMwFemInput] * num, + WiringIOType.OUTPUT: [InstrumentChannelMwFemOutput] * num, + WiringIOType.INPUT_AND_OUTPUT: [InstrumentChannelMwFemInput] * num + + [InstrumentChannelMwFemOutput], + }, + "octave": { + WiringIOType.INPUT: [InstrumentChannelOctaveInput] * num, + WiringIOType.OUTPUT: [InstrumentChannelOctaveOutput] * num, + WiringIOType.INPUT_AND_OUTPUT: [InstrumentChannelOctaveInput] * num + + [InstrumentChannelOctaveOutput] * num, + }, + "opx+": { + WiringIOType.INPUT: [InstrumentChannelOpxPlusInput] * num, + WiringIOType.OUTPUT: [InstrumentChannelOpxPlusOutput] * num, + WiringIOType.INPUT_AND_OUTPUT: [InstrumentChannelOpxPlusInput] * num + + [InstrumentChannelOpxPlusOutput] * num, + }, + }[allocation_type][io_type] diff --git a/qualang_tools/wirer/wirer/wirer_exception.py b/qualang_tools/wirer/wirer/wirer_exception.py new file mode 100644 index 00000000..0eb754f2 --- /dev/null +++ b/qualang_tools/wirer/wirer/wirer_exception.py @@ -0,0 +1,11 @@ +from ..connectivity.wiring_spec import WiringSpec + + +class WirerException(Exception): + def __init__(self, wiring_spec: WiringSpec): + message = ( + f"Couldn't find available {wiring_spec.frequency.value} channels " + f"satisfying the following specfication {wiring_spec.io_spec} for " + f"the {wiring_spec.line_type.value} line." + ) + super(WirerException, self).__init__(message) diff --git a/tests/wirer/conftest.py b/tests/wirer/conftest.py new file mode 100644 index 00000000..fb442f6d --- /dev/null +++ b/tests/wirer/conftest.py @@ -0,0 +1,41 @@ +from quam_libs.wiring.instruments import Instruments +import pytest + + +@pytest.fixture(params=["lf-fem", "opx+"]) +def instruments_qw_soprano(request) -> Instruments: + instruments = Instruments() + if request.param == "lf-fem": + instruments.add_lf_fem(con=1, slots=[1, 2, 3]) + elif request.param == "opx+": + instruments.add_opx_plus(cons=[1,2]) + instruments.add_octave(indices=[1,2]) + return instruments + +@pytest.fixture(params=["opx+"]) +def instruments_1OPX1Octave(request) -> Instruments: + instruments = Instruments() + if request.param == "lf-fem": + instruments.add_lf_fem(con=1, slots=[1]) + elif request.param == "opx+": + instruments.add_opx_plus(cons=1) + instruments.add_octave(indices=1) + return instruments + +@pytest.fixture(params=["lf-fem"])#, "opx+"]) +def instruments_1octave(request) -> Instruments: + instruments = Instruments() + if request.param == "lf-fem": + instruments.add_lf_fem(con=1, slots=[1, 2]) + elif request.param == "opx+": + instruments.add_opx_plus(cons=1) + instruments.add_octave(indices=1) + return instruments + + +@pytest.fixture() +def instruments_2lf_2mw() -> Instruments: + instruments = Instruments() + instruments.add_lf_fem(con=1, slots=[1, 2]) + instruments.add_mw_fem(con=1, slots=[3, 7]) + return instruments diff --git a/tests/wirer/test_instrument_validation.py b/tests/wirer/test_instrument_validation.py new file mode 100644 index 00000000..17ea97bc --- /dev/null +++ b/tests/wirer/test_instrument_validation.py @@ -0,0 +1,60 @@ +import pytest + +from qualang_tools.wiring.instruments import Instruments + +visualize = False + + +def test_opx_plus_and_octave_validation(): + instruments = Instruments() + instruments.add_opx_plus(cons=1) + instruments.add_octave(indices=1) + + +def test_redefinition_validation(): + instruments = Instruments() + instruments.add_opx_plus(cons=1) + with pytest.raises(ValueError): + instruments.add_opx_plus(cons=1) + + instruments = Instruments() + instruments.add_octave(indices=1) + with pytest.raises(ValueError): + instruments.add_octave(indices=1) + + +def test_slot_filled_validation(): + instruments = Instruments() + with pytest.raises(ValueError): + instruments.add_lf_fem(con=1, slots=1) + instruments.add_lf_fem(con=1, slots=1) + + instruments = Instruments() + with pytest.raises(ValueError): + instruments.add_lf_fem(con=1, slots=[1, 2]) + instruments.add_lf_fem(con=1, slots=[2, 3]) + + instruments = Instruments() + instruments.add_lf_fem(con=1, slots=1) + instruments.add_lf_fem(con=2, slots=1) + with pytest.raises(ValueError): + instruments.add_mw_fem(con=1, slots=1) + + +def test_opx_1000_and_opx_plus_mixing_validation(): + instruments = Instruments() + instruments.add_lf_fem(con=1, slots=1) + with pytest.raises(ValueError): + instruments.add_opx_plus(cons=1) + + instruments = Instruments() + instruments.add_mw_fem(con=1, slots=1) + with pytest.raises(ValueError): + instruments.add_opx_plus(cons=1) + + instruments = Instruments() + instruments.add_opx_plus(cons=1) + with pytest.raises(ValueError): + instruments.add_lf_fem(con=1, slots=1) + with pytest.raises(ValueError): + instruments.add_mw_fem(con=1, slots=1) diff --git a/tests/wirer/test_wirer_lf_and_mw.py b/tests/wirer/test_wirer_lf_and_mw.py new file mode 100644 index 00000000..dc0bde21 --- /dev/null +++ b/tests/wirer/test_wirer_lf_and_mw.py @@ -0,0 +1,59 @@ +from quam_libs.wiring.connectivity.connectivity import Connectivity +from quam_libs.wiring.instruments import Instruments +from quam_libs.wiring.visualizer.visualizer import visualize_chassis +from quam_libs.wiring.wirer import allocate_wiring + +visualize = True + +# todo: add option to switch between external mixer, mw and baseband + octave +# todo: fix wirer exception message e.g. slot 6 and IO type +# todo: flexibility in spec specifying *all* ports. + +def test_5q_allocation(instruments_2lf_2mw): + qubits = [1, 2, 3, 4, 5] + qubit_pairs = [(1, 2), (2, 3), (3, 4), (4, 5)] + + connectivity = Connectivity() + connectivity.add_resonator_line(qubits=qubits, slot=7) + connectivity.add_qubit_drive_lines(qubits=qubits, slot=7, con=1) + # connectivity.add_qubit_drive_lines(qubits=qubits[0], slot=7, con=1, port=8) + connectivity.add_qubit_flux_lines(qubits=qubits) + connectivity.add_qubit_pair_flux_lines(qubit_pairs=qubit_pairs) + + allocate_wiring(connectivity, instruments_2lf_2mw) + + connectivity.elements + print(connectivity.elements) + + if visualize: + visualize_chassis(connectivity.elements) + + +def test_4rr_allocation(instruments_2lf_2mw): + connectivity = Connectivity() + connectivity.add_resonator_line(qubits=1) + connectivity.add_qubit_drive_lines(qubits=list(range(7))) + connectivity.add_resonator_line(qubits=2) + connectivity.add_resonator_line(qubits=3) + connectivity.add_resonator_line(qubits=4) + + allocate_wiring(connectivity, instruments_2lf_2mw) + + if visualize: + visualize_chassis(connectivity.elements) + + +def test_6rr_6xy_6flux_allocation(): + instruments = Instruments() + instruments.add_lf_fem(con=1, slots=1) + instruments.add_mw_fem(con=1, slots=2) + + qubits = [1, 2, 3, 4, 5, 6] + connectivity = Connectivity() + connectivity.add_resonator_line(qubits=qubits) + connectivity.add_qubit_drive_lines(qubits=qubits) + connectivity.add_qubit_flux_lines(qubits=qubits) + + allocate_wiring(connectivity, instruments) + + visualize_chassis(connectivity.elements) \ No newline at end of file diff --git a/tests/wirer/test_wirer_lf_and_octave.py b/tests/wirer/test_wirer_lf_and_octave.py new file mode 100644 index 00000000..a25186b5 --- /dev/null +++ b/tests/wirer/test_wirer_lf_and_octave.py @@ -0,0 +1,80 @@ +from quam_libs.wiring.connectivity.connectivity import Connectivity +from quam_libs.wiring.instruments import Instruments +from quam_libs.wiring.visualizer.visualizer import visualize_chassis +from quam_libs.wiring.wirer import allocate_wiring +import pytest +from pprint import pprint + +visualize = False + + +def test_rf_io_allocation(instruments_1octave): + qubits = [1,2,3,4,5] + + connectivity = Connectivity() + # connectivity.add_resonator_line(qubits=qubits) + connectivity.add_qubit_drive_lines(qubits=qubits) + + allocate_wiring(connectivity, instruments_1octave) + + pprint(connectivity.elements) + if visualize: + visualize_chassis(connectivity.elements) + +def test_qw_soprano_allocation(instruments_qw_soprano): + qubits = [1, 2, 3, 4, 5] + + connectivity = Connectivity() + connectivity.add_resonator_line(qubits=qubits) + connectivity.add_qubit_drive_lines(qubits=qubits) + connectivity.add_qubit_flux_lines(qubits=qubits) + + allocate_wiring(connectivity, instruments_qw_soprano) + + pprint(connectivity.elements) + + if visualize: + visualize_chassis(connectivity.elements) + +def test_qw_soprano_2qb_allocation(instruments_1OPX1Octave): + active_qubits = [1, 2] + + connectivity = Connectivity() + # TODO: is the port here the Octave port? + connectivity.add_resonator_line(qubits=active_qubits, con=1, port=2) + connectivity.add_qubit_drive_lines(qubits=[1], con=1, port=2) + connectivity.add_qubit_drive_lines(qubits=[2], con=1, port=4) + connectivity.add_qubit_flux_lines(qubits=active_qubits) + + allocate_wiring(connectivity, instruments_1OPX1Octave) + + pprint(connectivity.elements) + + if visualize: + visualize_chassis(connectivity.elements) + +def test_qw_soprano_2qb_among_5_allocation(instruments_1OPX1Octave): + all_qubits = [1, 2, 3, 4, 5] + active_qubits = [1, 2] + other_qubits = list(set(all_qubits) - set(active_qubits)) + + connectivity = Connectivity() + # TODO: I want here to declare 2 qubits that I can address with my hardware (1OPX+ and 1 Octave) + connectivity.add_resonator_line(qubits=active_qubits, con=1, port=1) + connectivity.add_qubit_drive_lines(qubits=[1], con=1, port=2) + connectivity.add_qubit_drive_lines(qubits=[2], con=1, port=4) + connectivity.add_qubit_flux_lines(qubits=active_qubits) + # TODO: I want to add here the remaining qubits so that the QuAM can be created for the entire chip. + # I thus connect the other qubits to the same ports as the active qubits. + # Can I have the same ports used in several qubits as it is done for the resonator line? + connectivity.add_resonator_line(qubits=other_qubits, con=1, port=1) + connectivity.add_qubit_drive_lines(qubits=other_qubits, con=1, port=2) + connectivity.add_qubit_flux_lines(qubits=other_qubits, con=1, port=10) + + + allocate_wiring(connectivity, instruments_1OPX1Octave) + + pprint(connectivity.elements) + + if visualize: + visualize_chassis(connectivity.elements) From af34cb45d183d3b9db98f91902e9805c0c442c5d Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Sun, 25 Aug 2024 04:12:09 +1000 Subject: [PATCH 02/40] Fix broken tests after refactoring for channel_specs. --- qualang_tools/__init__.py | 1 + .../wirer/connectivity/channel_spec.py | 12 +++ .../wirer/connectivity/connectivity.py | 87 ++++++------------- qualang_tools/wirer/connectivity/element.py | 10 ++- .../wirer/connectivity/wiring_spec.py | 51 +++++++++-- .../wirer/connectivity/wiring_spec_enums.py | 55 ------------ .../wirer/instruments/instrument_channel.py | 68 +++++++++++++++ .../wirer/instruments/instrument_channels.py | 59 +------------ .../wirer/instruments/instruments.py | 2 + qualang_tools/wirer/visualizer/visualizer.py | 2 +- qualang_tools/wirer/wirer/channel_specs.py | 85 ++++++++++++++++++ ..._manager_multi_object_temp_attr_setting.py | 42 +++++++++ qualang_tools/wirer/wirer/wire_to_spec.py | 52 ----------- qualang_tools/wirer/wirer/wirer.py | 51 +++++++++-- .../wirer/wirer_assign_channels_to_spec.py | 59 +++++++------ .../wirer/wirer/wirer_channel_map.py | 45 ---------- qualang_tools/wirer/wirer/wirer_exception.py | 6 +- tests/wirer/conftest.py | 3 +- tests/wirer/test_instrument_validation.py | 2 +- tests/wirer/test_wirer_lf_and_mw.py | 22 ++--- tests/wirer/test_wirer_lf_and_octave.py | 9 +- 21 files changed, 386 insertions(+), 337 deletions(-) create mode 100644 qualang_tools/wirer/connectivity/channel_spec.py delete mode 100644 qualang_tools/wirer/connectivity/wiring_spec_enums.py create mode 100644 qualang_tools/wirer/instruments/instrument_channel.py create mode 100644 qualang_tools/wirer/wirer/channel_specs.py create mode 100644 qualang_tools/wirer/wirer/context_manager_multi_object_temp_attr_setting.py delete mode 100644 qualang_tools/wirer/wirer/wire_to_spec.py delete mode 100644 qualang_tools/wirer/wirer/wirer_channel_map.py diff --git a/qualang_tools/__init__.py b/qualang_tools/__init__.py index 07064fc2..428bc074 100644 --- a/qualang_tools/__init__.py +++ b/qualang_tools/__init__.py @@ -12,4 +12,5 @@ "units", "external_frameworks", "callable_from_qua", + "wirer", ] diff --git a/qualang_tools/wirer/connectivity/channel_spec.py b/qualang_tools/wirer/connectivity/channel_spec.py new file mode 100644 index 00000000..5763e586 --- /dev/null +++ b/qualang_tools/wirer/connectivity/channel_spec.py @@ -0,0 +1,12 @@ +class ChannelSpec: + """ A specification for fixed channel requirements. """ + def __init__(self): + self.channel_templates = None + + def __and__(self, other: 'ChannelSpec') -> 'ChannelSpec': + combined_channel_spec = ChannelSpec() + combined_channel_spec.channel_templates = self.channel_templates + other.channel_templates + return combined_channel_spec + + def is_empty(self): + return self.channel_templates is None or self.channel_templates == [] diff --git a/qualang_tools/wirer/connectivity/connectivity.py b/qualang_tools/wirer/connectivity/connectivity.py index 5505b8ab..724e711e 100644 --- a/qualang_tools/wirer/connectivity/connectivity.py +++ b/qualang_tools/wirer/connectivity/connectivity.py @@ -1,15 +1,10 @@ import copy from typing import Dict, List +from .channel_spec import ChannelSpec from .element import Element, ElementId, QubitReference, QubitPairReference from .types import QubitsType, QubitPairsType -from .wiring_spec import WiringSpec -from .wiring_spec_enums import ( - WiringFrequency, - WiringIOType, - WiringLineType, - WiringIOSpec, -) +from .wiring_spec import WiringSpec, WiringFrequency, WiringIOType, WiringLineType class Connectivity: @@ -25,68 +20,40 @@ def __init__(self): self.elements: Dict[ElementId, Element] = {} self.specs: List[WiringSpec] = [] - def add_resonator_line( - self, qubits: QubitsType, con: int = None, slot: int = None, port: int = None - ): + def add_fixed_transmons(self, qubits: QubitsType): + self.add_resonator_line(qubits) + self.add_qubit_drive_lines(qubits) + + def add_flux_tunable_transmons(self, qubits: QubitsType): + self.add_resonator_line(qubits) + self.add_qubit_drive_lines(qubits) + self.add_qubit_flux_lines(qubits) + + def add_resonator_line(self, qubits: QubitsType, channel_spec: ChannelSpec = None): elements = self._make_qubit_elements(qubits) - io_spec = WiringIOSpec( - type=WiringIOType.INPUT_AND_OUTPUT, con=con, slot=slot, port=port - ) - return self.add_wiring_spec( - elements, - WiringFrequency.RF, - io_spec, - WiringLineType.RESONATOR, - shared_line=True, - ) - - def add_qubit_drive_lines( - self, qubits: QubitsType, con: int = None, slot: int = None, port: int = None - ): + return self.add_wiring_spec(WiringFrequency.RF, WiringIOType.INPUT_AND_OUTPUT, WiringLineType.RESONATOR, channel_spec, elements, shared_line=True) + + def add_qubit_drive_lines(self, qubits: QubitsType, channel_spec: ChannelSpec = None): elements = self._make_qubit_elements(qubits) - io_spec = WiringIOSpec(type=WiringIOType.OUTPUT, con=con, slot=slot, port=port) - return self.add_wiring_spec( - elements, WiringFrequency.RF, io_spec, WiringLineType.DRIVE - ) - - def add_qubit_flux_lines( - self, qubits: QubitsType, con: int = None, slot: int = None, port: int = None - ): + return self.add_wiring_spec(WiringFrequency.RF, WiringIOType.OUTPUT, WiringLineType.DRIVE, channel_spec, elements) + + def add_qubit_flux_lines(self, qubits: QubitsType, channel_spec: ChannelSpec = None): elements = self._make_qubit_elements(qubits) - io_spec = WiringIOSpec(type=WiringIOType.OUTPUT, con=con, slot=slot, port=port) - return self.add_wiring_spec( - elements, WiringFrequency.DC, io_spec, WiringLineType.FLUX - ) - - def add_qubit_pair_flux_lines( - self, - qubit_pairs: QubitPairsType, - con: int = None, - slot: int = None, - port: int = None, - ): + return self.add_wiring_spec(WiringFrequency.DC, WiringIOType.OUTPUT, WiringLineType.FLUX, channel_spec, elements) + + def add_qubit_pair_flux_lines(self, qubit_pairs: QubitPairsType, channel_spec: ChannelSpec = None): elements = self._make_qubit_pair_elements(qubit_pairs) - io_spec = WiringIOSpec(type=WiringIOType.OUTPUT, con=con, slot=slot, port=port) - return self.add_wiring_spec( - elements, WiringFrequency.DC, io_spec, WiringLineType.COUPLER - ) - - def add_wiring_spec( - self, - elements: List[Element], - frequency: WiringFrequency, - io_spec: WiringIOSpec, - line_type: WiringLineType, - shared_line: bool = False, - ): + return self.add_wiring_spec(WiringFrequency.DC, WiringIOType.OUTPUT, WiringLineType.COUPLER, channel_spec, elements) + + def add_wiring_spec(self, frequency: WiringFrequency, io_type: WiringIOType, line_type: WiringLineType, + channel_spec: ChannelSpec, elements: List[Element], shared_line: bool = False, ): specs = [] if shared_line: - spec = WiringSpec(frequency, io_spec, line_type, elements) + spec = WiringSpec(frequency, io_type, line_type, channel_spec, elements) specs.append(spec) else: for element in elements: - io_spec = copy.deepcopy(io_spec) - spec = WiringSpec(frequency, io_spec, line_type, element) + spec = WiringSpec(frequency, io_type, line_type, channel_spec, element) specs.append(spec) self.specs.extend(specs) diff --git a/qualang_tools/wirer/connectivity/element.py b/qualang_tools/wirer/connectivity/element.py index 7aba5863..53b54ed7 100644 --- a/qualang_tools/wirer/connectivity/element.py +++ b/qualang_tools/wirer/connectivity/element.py @@ -1,20 +1,26 @@ from dataclasses import dataclass, field from typing import List, Dict, Any, Union -from .wiring_spec_enums import WiringLineType -from ..instruments.instrument_channels import InstrumentChannel +from .wiring_spec import WiringLineType +from ..instruments.instrument_channel import InstrumentChannel @dataclass(frozen=True) class QubitReference: index: int + def __str__(self): + return f"q{self.index}" @dataclass(frozen=True) class QubitPairReference: control_index: int target_index: int + def __str__(self): + return f"q{self.control_index}{self.target_index}" + + ElementId = Union[QubitReference, QubitPairReference] diff --git a/qualang_tools/wirer/connectivity/wiring_spec.py b/qualang_tools/wirer/connectivity/wiring_spec.py index beaa6322..7a7cf619 100644 --- a/qualang_tools/wirer/connectivity/wiring_spec.py +++ b/qualang_tools/wirer/connectivity/wiring_spec.py @@ -1,9 +1,25 @@ +from enum import Enum from typing import Union, List -from .element import Element -from .wiring_spec_enums import * +from .channel_spec import ChannelSpec +from ..instruments.instrument_channel import InstrumentChannel, InstrumentChannelInput, InstrumentChannelOutput +class WiringFrequency(Enum): + DC = "DC" + RF = "RF" + +class WiringIOType(Enum): + INPUT = "input" + OUTPUT = "output" + INPUT_AND_OUTPUT = "input_output" + +class WiringLineType(Enum): + RESONATOR = "rr" + DRIVE = "xy" + FLUX = "z" + COUPLER = "c" + class WiringSpec: """ A technical specification for the wiring that will be required to @@ -13,13 +29,36 @@ class WiringSpec: def __init__( self, frequency: WiringFrequency, - io_spec: WiringIOSpec, + io_type: WiringIOType, line_type: WiringLineType, - elements: Union[Element, List[Element]], + channel_specs: Union[ChannelSpec, List[ChannelSpec]], + elements: Union['Element', List['Element']], ): self.frequency = frequency - self.io_spec = io_spec + self.io_type = io_type self.line_type = line_type + if isinstance(channel_specs, ChannelSpec): + channel_specs = [channel_specs] + if channel_specs is None: + channel_specs = [] + self.channel_specs = channel_specs if not isinstance(elements, list): elements = [elements] - self.elements: List[Element] = elements + self.elements: List['Element'] = elements + + + def get_channel_template_from_spec(self, channel_spec: ChannelSpec) -> List[InstrumentChannel]: + if self.io_type == WiringIOType.INPUT_AND_OUTPUT or self.io_type is None: + return channel_spec.channel_templates + elif self.io_type == WiringIOType.INPUT: + return list(filter( + lambda channel: isinstance(channel, InstrumentChannelInput), + channel_spec.channel_templates + )) + elif self.io_type == WiringIOType.OUTPUT: + return list(filter( + lambda channel: isinstance(channel, InstrumentChannelOutput), + channel_spec.channel_templates + )) + else: + raise TypeError(f"Unrecognized input or output channel type {self.io_type}") diff --git a/qualang_tools/wirer/connectivity/wiring_spec_enums.py b/qualang_tools/wirer/connectivity/wiring_spec_enums.py deleted file mode 100644 index 045d3d3e..00000000 --- a/qualang_tools/wirer/connectivity/wiring_spec_enums.py +++ /dev/null @@ -1,55 +0,0 @@ -from enum import Enum -from dataclasses import dataclass -from typing import Optional, Callable - - -class WiringFrequency(Enum): - DC = "DC" - RF = "RF" - - -class WiringIOType(Enum): - INPUT = "input" - OUTPUT = "output" - INPUT_AND_OUTPUT = "input_output" - - -class WiringLineType(Enum): - RESONATOR = "rr" - DRIVE = "xy" - FLUX = "z" - COUPLER = "c" - - -@dataclass -class WiringIOSpec: - """ - A technical specification for what IO is required in a WiringSpecifcation. - The wiring type indicates whether the required channels are input, output - or both. The con/slot/port attributes allow the hardcoding of more - specific channels to satisfy the wiring. - """ - - type: WiringIOType - con: Optional[int] = None - slot: Optional[int] = None - port: Optional[int] = None - - def make_channel_filter(self) -> Callable[["InstrumentChannel"], bool]: - # todo: - return lambda channel: ( - (self.con is None or self.con == channel.con) - and (self.slot is None or self.slot == channel.slot) - and (self.port is None or self.port == channel.port) - ) - - def __str__(self): - return ( - f"IO type={self.type.value}" + f", con=con{self.con}" - if self.con is not None - else "" + f", slot={self.slot}" - if self.slot is not None - else "" + f", port={self.port}" - if self.port is not None - else "" - ) diff --git a/qualang_tools/wirer/instruments/instrument_channel.py b/qualang_tools/wirer/instruments/instrument_channel.py new file mode 100644 index 00000000..502b43fe --- /dev/null +++ b/qualang_tools/wirer/instruments/instrument_channel.py @@ -0,0 +1,68 @@ +from dataclasses import dataclass +from typing import Union, Literal, Callable + + +@dataclass +class InstrumentChannel: + con: int + port: int + slot: Union[None, int] = None + + def __str__(self): + return f'({", ".join([f"con{self.con}", f"{self.slot}" if self.slot else "", str(self.port)])})' + + def make_channel_filter(self) -> Callable[['InstrumentChannel'], bool]: + return lambda channel: ( + (self.con is None or self.con == channel.con) and + (self.slot is None or self.slot == channel.slot) and + (self.port is None or self.port == channel.port) + ) + + +@dataclass +class InstrumentChannelInput(InstrumentChannel): + io_type: Literal["input", "output"] = "input" + + +@dataclass +class InstrumentChannelOutput(InstrumentChannel): + io_type: Literal["input", "output"] = "output" + + +@dataclass +class InstrumentChannelLfFemInput(InstrumentChannelInput): + instrument_id = "lf-fem" + + +@dataclass +class InstrumentChannelLfFemOutput(InstrumentChannelOutput): + instrument_id = "lf-fem" + + +@dataclass +class InstrumentChannelMwFemInput(InstrumentChannelInput): + instrument_id = "mw-fem" + + +@dataclass +class InstrumentChannelMwFemOutput(InstrumentChannelOutput): + instrument_id = "mw-fem" + + +@dataclass +class InstrumentChannelOpxPlusInput(InstrumentChannelInput): + instrument_id = "opx+" + + +@dataclass +class InstrumentChannelOpxPlusOutput(InstrumentChannelOutput): + instrument_id = "opx+" + + +@dataclass +class InstrumentChannelOctaveInput(InstrumentChannelInput): + instrument_id = "octave" + + +class InstrumentChannelOctaveOutput(InstrumentChannelOutput): + instrument_id = "octave" diff --git a/qualang_tools/wirer/instruments/instrument_channels.py b/qualang_tools/wirer/instruments/instrument_channels.py index 7e0b83cb..e2bad3fd 100644 --- a/qualang_tools/wirer/instruments/instrument_channels.py +++ b/qualang_tools/wirer/instruments/instrument_channels.py @@ -1,59 +1,8 @@ -from dataclasses import dataclass -from typing import Union, Literal, Type - - -@dataclass -class InstrumentChannel: - con: int - port: int - slot: Union[None, int] = None - io_type: Literal["input", "output"] = None - - def __str__(self): - return f'({", ".join([f"con{self.con}", f"{self.slot}" if self.slot else "", str(self.port)])})' - - -@dataclass -class InstrumentChannelInput(InstrumentChannel): - io_type: Literal["input", "output"] = "input" - - -@dataclass -class InstrumentChannelOutput(InstrumentChannel): - io_type: Literal["input", "output"] = "output" - - -class InstrumentChannelLfFemInput(InstrumentChannelInput): - pass - - -class InstrumentChannelLfFemOutput(InstrumentChannelOutput): - pass - - -class InstrumentChannelMwFemInput(InstrumentChannelInput): - pass - - -class InstrumentChannelMwFemOutput(InstrumentChannelOutput): - pass - - -class InstrumentChannelOpxPlusInput(InstrumentChannelInput): - pass - - -class InstrumentChannelOpxPlusOutput(InstrumentChannelOutput): - pass - - -class InstrumentChannelOctaveInput(InstrumentChannelInput): - pass - - -class InstrumentChannelOctaveOutput(InstrumentChannelOutput): - pass +from typing import Type +from qualang_tools.wirer.instruments.instrument_channel import InstrumentChannel, InstrumentChannelLfFemInput, \ + InstrumentChannelLfFemOutput, InstrumentChannelMwFemInput, InstrumentChannelMwFemOutput, \ + InstrumentChannelOpxPlusInput, InstrumentChannelOpxPlusOutput CHANNELS_OPX_PLUS = [InstrumentChannelOpxPlusInput, InstrumentChannelOpxPlusOutput] diff --git a/qualang_tools/wirer/instruments/instruments.py b/qualang_tools/wirer/instruments/instruments.py index c067db63..1fb421ef 100644 --- a/qualang_tools/wirer/instruments/instruments.py +++ b/qualang_tools/wirer/instruments/instruments.py @@ -1,4 +1,6 @@ from typing import List, Union + +from .instrument_channel import InstrumentChannelOctaveInput, InstrumentChannelOctaveOutput from .instrument_channels import * from .constants import * diff --git a/qualang_tools/wirer/visualizer/visualizer.py b/qualang_tools/wirer/visualizer/visualizer.py index d0fbad4b..82ecfb17 100644 --- a/qualang_tools/wirer/visualizer/visualizer.py +++ b/qualang_tools/wirer/visualizer/visualizer.py @@ -1,7 +1,7 @@ import matplotlib.pyplot as plt import matplotlib.patches as patches -from quam_libs.wiring.connectivity.wiring_spec_enums import WiringLineType +from qualang_tools.wirer.connectivity.wiring_spec import WiringLineType # Define the chassis dimensions CHASSIS_WIDTH = 8 diff --git a/qualang_tools/wirer/wirer/channel_specs.py b/qualang_tools/wirer/wirer/channel_specs.py new file mode 100644 index 00000000..f34cf388 --- /dev/null +++ b/qualang_tools/wirer/wirer/channel_specs.py @@ -0,0 +1,85 @@ +from qualang_tools.wirer.connectivity.channel_spec import ChannelSpec +from qualang_tools.wirer.instruments.instrument_channel import InstrumentChannel, InstrumentChannelLfFemInput, \ + InstrumentChannelLfFemOutput, InstrumentChannelMwFemInput, InstrumentChannelMwFemOutput, \ + InstrumentChannelOpxPlusInput, InstrumentChannelOpxPlusOutput, InstrumentChannelOctaveInput, \ + InstrumentChannelOctaveOutput + +# A channel template is a partially filled InstrumentChannel object +ChannelTemplate = InstrumentChannel + + +class ChannelSpecMwFemSingle(ChannelSpec): + def __init__(self, con: int = None, slot: int = None, + in_port: int = None, out_port: int = None): + + super().__init__() + self.channel_templates = [ + InstrumentChannelMwFemInput(con=con, slot=slot, port=in_port), + InstrumentChannelMwFemOutput(con=con, slot=slot, port=out_port) + ] + +class ChannelSpecLfFemSingle(ChannelSpec): + def __init__(self, con: int = None, + in_slot: int = None,in_port: int = None, + out_slot: int = None, out_port: int = None): + + super().__init__() + self.channel_templates = [ + InstrumentChannelLfFemInput(con=con, slot=in_slot, port=in_port), + InstrumentChannelLfFemOutput(con=con, slot=out_slot, port=out_port) + ] + + +class ChannelSpecLfFemBaseband(ChannelSpec): + def __init__(self, con: int = None, slot: int = None, + in_port_i: int = None, in_port_q: int = None, + out_port_i: int = None, out_port_q: int = None): + + super().__init__() + self.channel_templates = [ + InstrumentChannelLfFemInput(con=con, slot=slot, port=in_port_i), + InstrumentChannelLfFemInput(con=con, slot=slot, port=in_port_q), + InstrumentChannelLfFemOutput(con=con, slot=slot, port=out_port_i), + InstrumentChannelLfFemOutput(con=con, slot=slot, port=out_port_q) + ] + + +class ChannelSpecOpxPlusSingle(ChannelSpec): + def __init__(self, con: int = None, in_port: int = None, out_port: int = None): + + super().__init__() + self.channel_templates = [ + InstrumentChannelOpxPlusInput(con=con, port=in_port), + InstrumentChannelOpxPlusOutput(con=con, port=out_port) + ] + + +class ChannelSpecOpxPlusBaseband(ChannelSpec): + def __init__(self, con: int = None, + in_port_i: int = None, in_port_q: int = None, + out_port_i: int = None, out_port_q: int = None): + + super().__init__() + self.channel_templates = [ + InstrumentChannelOpxPlusInput(con=con, port=in_port_i), + InstrumentChannelOpxPlusInput(con=con, port=in_port_q), + InstrumentChannelOpxPlusOutput(con=con, port=out_port_i), + InstrumentChannelOpxPlusOutput(con=con, port=out_port_q) + ] + + +class ChannelSpecOctave(ChannelSpec): + def __init__(self, index: int = None, rf_out: int = None, rf_in: int = None): + super().__init__() + self.channel_templates = [ + InstrumentChannelOctaveInput(con=index, port=rf_in), + InstrumentChannelOctaveOutput(con=index, port=rf_out), + ] + + +mw_fem_spec = ChannelSpecMwFemSingle +lf_fem_spec = ChannelSpecLfFemSingle +lf_fem_iq_spec = ChannelSpecLfFemBaseband +opx_spec = ChannelSpecOpxPlusSingle +opx_iq_spec = ChannelSpecOpxPlusBaseband +octave_spec = ChannelSpecOctave diff --git a/qualang_tools/wirer/wirer/context_manager_multi_object_temp_attr_setting.py b/qualang_tools/wirer/wirer/context_manager_multi_object_temp_attr_setting.py new file mode 100644 index 00000000..349583b8 --- /dev/null +++ b/qualang_tools/wirer/wirer/context_manager_multi_object_temp_attr_setting.py @@ -0,0 +1,42 @@ +class MultiObjectTempAttrUpdater: + def __init__(self, objects, **temp_attrs): + """ + Initialize the MultiObjectTempAttrUpdater context manager. + + Parameters: + - objects: A list of objects whose attributes will be temporarily updated. + - temp_attrs: The temporary attributes and their values to be set. + """ + self.objects = objects + self.temp_attrs = temp_attrs + self.original_attrs = [] + + def __enter__(self): + """ + Enter the runtime context related to this object. + Temporarily set the objects' attributes to the new values. + """ + # Store original attributes + for obj in self.objects: + original_attrs = {} + for attr, value in self.temp_attrs.items(): + if hasattr(obj, attr): + original_attrs[attr] = getattr(obj, attr) + else: + original_attrs[attr] = None + + # Set new temporary attribute value + setattr(obj, attr, value) + + self.original_attrs.append((obj, original_attrs)) + + return self + + def __exit__(self, exc_type, exc_value, traceback): + """ + Exit the runtime context. + Restore the objects' original attributes. + """ + for obj, original_attrs in self.original_attrs: + for attr, value in original_attrs.items(): + setattr(obj, attr, value) \ No newline at end of file diff --git a/qualang_tools/wirer/wirer/wire_to_spec.py b/qualang_tools/wirer/wirer/wire_to_spec.py deleted file mode 100644 index aefb119f..00000000 --- a/qualang_tools/wirer/wirer/wire_to_spec.py +++ /dev/null @@ -1,52 +0,0 @@ -from typing import Literal - -from .wirer_assign_channels_to_spec import assign_channels_to_spec -from .wirer_exception import WirerException -from ..connectivity.wiring_spec import WiringSpec -from ..instruments import Instruments -from .wirer_channel_map import get_channel_mapping - - -def allocate_dc_channels(spec: WiringSpec, instruments: Instruments): - """ - Try to allocate DC channels to an LF-FEM or OPX+ to satisfy the spec. - """ - if not ( - try_allocate_channels(spec, instruments, "lf-fem") - or try_allocate_channels(spec, instruments, "opx+") - ): - raise WirerException(spec) - - -def allocate_rf_channels(spec: WiringSpec, instruments: Instruments): - """ - Try to allocate RF channels to a MW-FEM. If that doesn't work, look for a - combination of LF-FEM I/Q and Octave channels, or OPX+ I/Q and Octave - channels. - """ - if not try_allocate_channels(spec, instruments, "mw-fem"): - if not ( - try_allocate_channels(spec, instruments, "octave") - and ( - try_allocate_channels(spec, instruments, "lf-fem", num=2) - or try_allocate_channels(spec, instruments, "opx+", num=2) - ) - ): - raise WirerException(spec) - - -def try_allocate_channels( - spec: WiringSpec, - instruments: Instruments, - module: Literal["mw-fem", "lf-fem", "opx+", "octave"], - num: int = 1, -) -> bool: - """ - Identify which channel types would satisfy the spec for a specific QM - module, then try to assign such channels to the spec, returning True - if it succeeds. - """ - channel_types = get_channel_mapping(module, spec.io_spec.type, num=num) - return assign_channels_to_spec( - spec, instruments, channel_types, same_con=True, same_slot=True - ) diff --git a/qualang_tools/wirer/wirer/wirer.py b/qualang_tools/wirer/wirer/wirer.py index b0927a71..c87df807 100644 --- a/qualang_tools/wirer/wirer/wirer.py +++ b/qualang_tools/wirer/wirer/wirer.py @@ -1,12 +1,10 @@ +from .channel_specs import ChannelSpecLfFemSingle, ChannelSpecOpxPlusSingle, ChannelSpecMwFemSingle, \ + ChannelSpecLfFemBaseband, ChannelSpecOctave, ChannelSpecOpxPlusBaseband +from .wirer_assign_channels_to_spec import assign_channels_to_spec +from .wirer_exception import WirerException from ..instruments import Instruments from ..connectivity import Connectivity -from ..connectivity.wiring_spec import WiringSpec -from ..connectivity.wiring_spec_enums import ( - WiringFrequency, - WiringIOType, - WiringLineType, -) -from .wire_to_spec import allocate_dc_channels, allocate_rf_channels +from ..connectivity.wiring_spec import WiringSpec, WiringFrequency, WiringLineType def allocate_wiring(connectivity: Connectivity, instruments: Instruments): @@ -33,3 +31,42 @@ def _allocate_channels(spec: WiringSpec, instruments: Instruments): else: raise NotImplementedError() + + +def allocate_dc_channels(spec: WiringSpec, instruments: Instruments): + """ + Try to allocate DC channels to an LF-FEM or OPX+ to satisfy the spec. + """ + if not spec.channel_specs: + spec.channel_specs = [ + ChannelSpecLfFemSingle(), + ChannelSpecOpxPlusSingle() + ] + + for channel_spec in spec.channel_specs: + channel_templates = spec.get_channel_template_from_spec(channel_spec) + if assign_channels_to_spec(spec, instruments, channel_templates, same_con=True, same_slot=True): + return + + raise WirerException(spec) + + +def allocate_rf_channels(spec: WiringSpec, instruments: Instruments): + """ + Try to allocate RF channels to a MW-FEM. If that doesn't work, look for a + combination of LF-FEM I/Q and Octave channels, or OPX+ I/Q and Octave + channels. + """ + if not spec.channel_specs: + spec.channel_specs = [ + ChannelSpecMwFemSingle(), + ChannelSpecLfFemBaseband() & ChannelSpecOctave(), + ChannelSpecOpxPlusBaseband() & ChannelSpecOctave(), + ] + + for channel_spec in spec.channel_specs: + channel_templates = spec.get_channel_template_from_spec(channel_spec) + if assign_channels_to_spec(spec, instruments, channel_templates, same_con=True, same_slot=True): + return + + raise WirerException(spec) diff --git a/qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py b/qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py index c0e963d8..2c0bb613 100644 --- a/qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py +++ b/qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py @@ -1,24 +1,24 @@ from typing import List, Type -from quam_libs.wiring.connectivity.wiring_spec import WiringSpec -from quam_libs.wiring.instruments import Instruments -from quam_libs.wiring.instruments.instrument_channels import InstrumentChannel +from qualang_tools.wirer.connectivity.wiring_spec import WiringSpec +from qualang_tools.wirer.instruments import Instruments +from qualang_tools.wirer.wirer.channel_specs import ChannelTemplate +from qualang_tools.wirer.wirer.context_manager_multi_object_temp_attr_setting import MultiObjectTempAttrUpdater def assign_channels_to_spec( spec: WiringSpec, instruments: Instruments, - channel_types: List[Type[InstrumentChannel]], + channel_templates: List[ChannelTemplate], same_con: bool = False, same_slot: bool = False, -): - +) -> bool: candidate_channels = _assign_channels_to_spec( - spec, instruments, channel_types, same_con, same_slot + spec, instruments, channel_templates, same_con, same_slot ) # if candidate channels satisfy all the required channel types - if len(candidate_channels) == len(channel_types): + if len(candidate_channels) == len(channel_templates): for channel in candidate_channels: # remove candidate channel from stack of available channels instruments.available_channels[type(channel)].remove(channel) @@ -28,13 +28,13 @@ def assign_channels_to_spec( element.channels[spec.line_type] = [] element.channels[spec.line_type].append(channel) - return len(candidate_channels) == len(channel_types) + return len(candidate_channels) == len(channel_templates) def _assign_channels_to_spec( spec: WiringSpec, instruments: Instruments, - channel_types: List[Type[InstrumentChannel]], + channel_templates: List[ChannelTemplate], same_con: bool, same_slot: bool, allocated_channels=None, @@ -48,13 +48,13 @@ def _assign_channels_to_spec( allocated_channels = [] # extract the lead/initial channel type - target_channel_type = channel_types[0] + target_channel_template = channel_templates[0] # filter available channels according to the specification available_channels = list( filter( - spec.io_spec.make_channel_filter(), # filter function - instruments.available_channels.get(target_channel_type, []), # iterable + target_channel_template.make_channel_filter(), # filter function + instruments.available_channels.get(type(target_channel_template), []), # iterable ) ) @@ -67,31 +67,30 @@ def _assign_channels_to_spec( candidate_channels = [channel] # base case: all channels allocated properly - if len(channel_types) == 1: + if len(channel_templates) == 1: break # recursive case: allocate remaining channels else: - # require that all future channels have the same `con` and `slot` as the candidate - if same_con: - spec.io_spec.con = channel.con - if same_slot: - spec.io_spec.slot = channel.slot - - # recursively allocate the remaining channels - subsequent_channels = _assign_channels_to_spec( - spec, - instruments, - channel_types[1:], - same_con, - same_slot, - allocated_channels=candidate_channels, - ) + templates_with_same_instr = [ + template for template in channel_templates[1:] + if template.instrument_id == channel.instrument_id + ] + with MultiObjectTempAttrUpdater(templates_with_same_instr, con=channel.con, slot=channel.slot): + # recursively allocate the remaining channels + subsequent_channels = _assign_channels_to_spec( + spec, + instruments, + channel_templates[1:], + same_con, + same_slot, + allocated_channels=candidate_channels, + ) candidate_channels.extend(subsequent_channels) # without a candidate channel for every type, try the next available channel - if len(candidate_channels) == len(channel_types): + if len(candidate_channels) == len(channel_templates): break # otherwise, a successful allocation has been made diff --git a/qualang_tools/wirer/wirer/wirer_channel_map.py b/qualang_tools/wirer/wirer/wirer_channel_map.py deleted file mode 100644 index a3bd4de8..00000000 --- a/qualang_tools/wirer/wirer/wirer_channel_map.py +++ /dev/null @@ -1,45 +0,0 @@ -from typing import List, Type - -from ..connectivity.wiring_spec_enums import WiringIOType -from ..instruments.instrument_channels import ( - InstrumentChannelMwFemInput, - InstrumentChannelMwFemOutput, - InstrumentChannelOctaveInput, - InstrumentChannelOctaveOutput, - InstrumentChannelLfFemOutput, - InstrumentChannelLfFemInput, - InstrumentChannelOpxPlusInput, - InstrumentChannelOpxPlusOutput, - InstrumentChannel, -) - - -def get_channel_mapping( - allocation_type: str, io_type: WiringIOType, num: int = 1 -) -> List[Type[InstrumentChannel]]: - return { - "lf-fem": { - WiringIOType.INPUT: [InstrumentChannelLfFemInput] * num, - WiringIOType.OUTPUT: [InstrumentChannelLfFemOutput] * num, - WiringIOType.INPUT_AND_OUTPUT: [InstrumentChannelLfFemInput] * num - + [InstrumentChannelLfFemOutput] * num, - }, - "mw-fem": { - WiringIOType.INPUT: [InstrumentChannelMwFemInput] * num, - WiringIOType.OUTPUT: [InstrumentChannelMwFemOutput] * num, - WiringIOType.INPUT_AND_OUTPUT: [InstrumentChannelMwFemInput] * num - + [InstrumentChannelMwFemOutput], - }, - "octave": { - WiringIOType.INPUT: [InstrumentChannelOctaveInput] * num, - WiringIOType.OUTPUT: [InstrumentChannelOctaveOutput] * num, - WiringIOType.INPUT_AND_OUTPUT: [InstrumentChannelOctaveInput] * num - + [InstrumentChannelOctaveOutput] * num, - }, - "opx+": { - WiringIOType.INPUT: [InstrumentChannelOpxPlusInput] * num, - WiringIOType.OUTPUT: [InstrumentChannelOpxPlusOutput] * num, - WiringIOType.INPUT_AND_OUTPUT: [InstrumentChannelOpxPlusInput] * num - + [InstrumentChannelOpxPlusOutput] * num, - }, - }[allocation_type][io_type] diff --git a/qualang_tools/wirer/wirer/wirer_exception.py b/qualang_tools/wirer/wirer/wirer_exception.py index 0eb754f2..f1b012c9 100644 --- a/qualang_tools/wirer/wirer/wirer_exception.py +++ b/qualang_tools/wirer/wirer/wirer_exception.py @@ -4,8 +4,8 @@ class WirerException(Exception): def __init__(self, wiring_spec: WiringSpec): message = ( - f"Couldn't find available {wiring_spec.frequency.value} channels " - f"satisfying the following specfication {wiring_spec.io_spec} for " - f"the {wiring_spec.line_type.value} line." + f"Couldn't find available {wiring_spec.frequency.value} {wiring_spec.io_type.value} channels " + f"satisfying the following specfication for the {wiring_spec.line_type.value} line for elements " + f"{','.join([str(e.id) for e in wiring_spec.elements])}" ) super(WirerException, self).__init__(message) diff --git a/tests/wirer/conftest.py b/tests/wirer/conftest.py index fb442f6d..4ace1678 100644 --- a/tests/wirer/conftest.py +++ b/tests/wirer/conftest.py @@ -1,9 +1,10 @@ -from quam_libs.wiring.instruments import Instruments +from qualang_tools.wirer.instruments import Instruments import pytest @pytest.fixture(params=["lf-fem", "opx+"]) def instruments_qw_soprano(request) -> Instruments: + print(request.param) instruments = Instruments() if request.param == "lf-fem": instruments.add_lf_fem(con=1, slots=[1, 2, 3]) diff --git a/tests/wirer/test_instrument_validation.py b/tests/wirer/test_instrument_validation.py index 17ea97bc..20ab9a50 100644 --- a/tests/wirer/test_instrument_validation.py +++ b/tests/wirer/test_instrument_validation.py @@ -1,6 +1,6 @@ import pytest -from qualang_tools.wiring.instruments import Instruments +from qualang_tools.wirer.instruments import Instruments visualize = False diff --git a/tests/wirer/test_wirer_lf_and_mw.py b/tests/wirer/test_wirer_lf_and_mw.py index dc0bde21..713be68a 100644 --- a/tests/wirer/test_wirer_lf_and_mw.py +++ b/tests/wirer/test_wirer_lf_and_mw.py @@ -1,28 +1,23 @@ -from quam_libs.wiring.connectivity.connectivity import Connectivity -from quam_libs.wiring.instruments import Instruments -from quam_libs.wiring.visualizer.visualizer import visualize_chassis -from quam_libs.wiring.wirer import allocate_wiring +from qualang_tools.wirer.connectivity.connectivity import Connectivity +from qualang_tools.wirer.instruments import Instruments +from qualang_tools.wirer.visualizer.visualizer import visualize_chassis +from qualang_tools.wirer.wirer import allocate_wiring +from qualang_tools.wirer.wirer.channel_specs import mw_fem_spec visualize = True -# todo: add option to switch between external mixer, mw and baseband + octave -# todo: fix wirer exception message e.g. slot 6 and IO type -# todo: flexibility in spec specifying *all* ports. - def test_5q_allocation(instruments_2lf_2mw): qubits = [1, 2, 3, 4, 5] qubit_pairs = [(1, 2), (2, 3), (3, 4), (4, 5)] connectivity = Connectivity() - connectivity.add_resonator_line(qubits=qubits, slot=7) - connectivity.add_qubit_drive_lines(qubits=qubits, slot=7, con=1) - # connectivity.add_qubit_drive_lines(qubits=qubits[0], slot=7, con=1, port=8) + connectivity.add_resonator_line(qubits=qubits, channel_spec=mw_fem_spec(slot=7)) + connectivity.add_qubit_drive_lines(qubits=qubits, channel_spec=mw_fem_spec(slot=7)) connectivity.add_qubit_flux_lines(qubits=qubits) connectivity.add_qubit_pair_flux_lines(qubit_pairs=qubit_pairs) allocate_wiring(connectivity, instruments_2lf_2mw) - connectivity.elements print(connectivity.elements) if visualize: @@ -56,4 +51,5 @@ def test_6rr_6xy_6flux_allocation(): allocate_wiring(connectivity, instruments) - visualize_chassis(connectivity.elements) \ No newline at end of file + if visualize: + visualize_chassis(connectivity.elements) \ No newline at end of file diff --git a/tests/wirer/test_wirer_lf_and_octave.py b/tests/wirer/test_wirer_lf_and_octave.py index a25186b5..094ddd66 100644 --- a/tests/wirer/test_wirer_lf_and_octave.py +++ b/tests/wirer/test_wirer_lf_and_octave.py @@ -1,13 +1,10 @@ -from quam_libs.wiring.connectivity.connectivity import Connectivity -from quam_libs.wiring.instruments import Instruments -from quam_libs.wiring.visualizer.visualizer import visualize_chassis -from quam_libs.wiring.wirer import allocate_wiring -import pytest +from qualang_tools.wirer.connectivity.connectivity import Connectivity +from qualang_tools.wirer.visualizer.visualizer import visualize_chassis +from qualang_tools.wirer.wirer import allocate_wiring from pprint import pprint visualize = False - def test_rf_io_allocation(instruments_1octave): qubits = [1,2,3,4,5] From 9c4c41ad58ada2aad12847f2e3cb9a733ed428de Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Mon, 26 Aug 2024 00:22:23 +1000 Subject: [PATCH 03/40] Refactor visualization. --- qualang_tools/wirer/visualizer/visualizer.py | 270 ++++++++++--------- tests/wirer/test_wirer_lf_and_octave.py | 2 +- 2 files changed, 137 insertions(+), 135 deletions(-) diff --git a/qualang_tools/wirer/visualizer/visualizer.py b/qualang_tools/wirer/visualizer/visualizer.py index 82ecfb17..0574ad71 100644 --- a/qualang_tools/wirer/visualizer/visualizer.py +++ b/qualang_tools/wirer/visualizer/visualizer.py @@ -1,148 +1,150 @@ import matplotlib.pyplot as plt import matplotlib.patches as patches -from qualang_tools.wirer.connectivity.wiring_spec import WiringLineType - # Define the chassis dimensions -CHASSIS_WIDTH = 8 -CHASSIS_HEIGHT = 3 # Updated height for chassis +CHASSIS_DIMENSIONS = { + "OPX1000": {"width": 8, "height": 3}, + "OPX+": {"width": 3, "height": 1}, + "Octave": {"width": 3, "height": 1} +} -# Define the port positions in a FEM (normalized within a slot) -PORT_SPACING_FACTOR = 0.1 # Close spacing between ports +# Define the port positions for different modules +PORT_SPACING_FACTOR = 0.1 PORT_POSITIONS = { - "output": [(0.25, 1 - i * PORT_SPACING_FACTOR) for i in range(8)], - "input": [(0.75, 1 - i * PORT_SPACING_FACTOR * 7) for i in range(2)], + "lf-fem": { + "output": [(0.25, 1 - i * PORT_SPACING_FACTOR) for i in range(8)], + "input": [(0.75, 1 - i * PORT_SPACING_FACTOR * 7) for i in range(2)], + }, + "mw-fem": { + "output": [(0.25, 1 - i * PORT_SPACING_FACTOR) for i in range(8)], + "input": [(0.75, 1 - i * PORT_SPACING_FACTOR * 7) for i in range(2)], + }, + "digital_output": [(0.25 + j * 0.2, 1 - i * PORT_SPACING_FACTOR * 10) for i in range(2) for j in range(5)], + "analog_output": [(0.25 + j * 0.2, 1 - i * PORT_SPACING_FACTOR * 10) for i in range(2) for j in range(5)], + "analog_input": [(0.75, 1 - i * PORT_SPACING_FACTOR * 2) for i in range(2)], } -# Define a function to draw and annotate ports -def annotate_port(ax, port, annotations, assigned, color): - pos = PORT_POSITIONS[port.io_type][port.port - 1] - x = port.slot - 1 + pos[0] - y = pos[1] * CHASSIS_HEIGHT - 0.5 - - # Draw port as filled or empty circle based on assignment - fill_color = color if assigned else "none" - ax.add_patch(patches.Circle((x, y), 0.1, edgecolor="black", facecolor=fill_color)) - - # Place grouped annotations to the left of the port with a semi-transparent white background - for i, annotation in enumerate(annotations): - ax.text( - x - 0.15, - y + 0.15 * i, - annotation, - ha="right", - va="center", - fontsize=14, - color="black", - # fontweight="bold", - bbox=dict(facecolor="white", alpha=0.3, edgecolor="none"), - ) - - -# Define a function to label the slots -def label_slot(ax, slot, fem_type): - if fem_type: - label = f"{slot}: " + ("MW-FEM" if fem_type == "mw" else "LF-FEM") - ax.text( - slot - 0.5, - -0.2, - label, - ha="center", - va="bottom", - fontsize=12, - fontweight="bold", - ) - - -# Define a function to get port color based on line type -def get_color_for_line_type(line_type): - color_map = {WiringLineType.FLUX.value: "blue", - WiringLineType.RESONATOR.value: "orange", - WiringLineType.DRIVE.value: "yellow", - WiringLineType.COUPLER.value: "purple"} - for key in color_map: - if key in line_type: - return color_map[key] - return "grey" - - -# Define a function to visualize the chassis and FEMs -def visualize_chassis(qubit_dict): - fig, ax = plt.subplots(figsize=(CHASSIS_WIDTH*2, CHASSIS_HEIGHT*2)) - - # Draw the chassis slots with boundary lines - for slot in range(1, CHASSIS_WIDTH + 1): - # Set light grey background for the FEMs - ax.add_patch( - patches.Rectangle( - (slot - 1, 0), - 1, - CHASSIS_HEIGHT, - facecolor="lightgrey", - edgecolor="black", - ) - ) - - # Track the FEM types per slot and ports assignments - fem_types = {slot: None for slot in range(1, CHASSIS_WIDTH + 1)} - channel_assignments = { - slot: {"input": [False] * 2, "output": [False] * 8} - for slot in range(1, CHASSIS_WIDTH + 1) - } - annotations_map = { - slot: {"input": [[] for _ in range(2)], "output": [[] for _ in range(8)]} - for slot in range(1, CHASSIS_WIDTH + 1) - } +instrument_id_mapping = { + "lf-fem": "OPX1000", + "mw-fem": "OPX1000", + "opx+": "OPX+", + "octave": "Octave", +} - # Annotate the ports and determine the FEM types +# Define classes for managing annotations and slots +class AnnotationPort: + def __init__(self, labels, color, con, slot, io_type, port, instrument_id): + self.labels = labels + self.color = color + self.con = con + self.slot = slot + self.io_type = io_type + self.port = port + self.instrument_id = instrument_id + +class LabelSlot: + def __init__(self, slot, label, con): + self.slot = slot + self.label = label + self.con = con + +class InstrumentFigureManager: + def __init__(self): + self.figures = {} + + def get_ax(self, con, slot, instrument_id): + instrument_id = instrument_id_mapping[instrument_id] + key = f"{instrument_id}_{con}" + if key not in self.figures: + if instrument_id == "OPX1000": + fig, axs = plt.subplots( + nrows=1, + ncols=8, + figsize=(CHASSIS_DIMENSIONS[instrument_id]["width"] * 2, + CHASSIS_DIMENSIONS[instrument_id]["height"] * 2)) + for ax in axs.flat: + ax.set_ylim([0, 1]) + ax.axis("off") + self.figures[key] = axs + else: + raise NotImplementedError() + fig.suptitle(f"{instrument_id} Wiring Diagram - con{con}", fontweight="bold", fontsize=20) + return self.figures[key][slot] if slot is not None else self.figures[key] + +def invert_qubit_dict(qubit_dict): + inverted_dict = {} for qubit_ref, element in qubit_dict.items(): for channel_type, channels in element.channels.items(): for channel in channels: + key = (channel.con, channel.slot, channel.io_type, channel.port, channel.instrument_id, channel_type) + if key not in inverted_dict: + inverted_dict[key] = [] annotation = f"q{qubit_ref.index if hasattr(qubit_ref, 'index') else f'{qubit_ref.control_index}{qubit_ref.target_index}'}.{channel_type.value}" - annotations_map[channel.slot][channel.io_type][channel.port - 1].append( - annotation - ) - channel_assignments[channel.slot][channel.io_type][channel.port - 1] = True - if "mw" in type(channel).__name__.lower(): - fem_types[channel.slot] = "mw" - elif "lf" in type(channel).__name__.lower(): - fem_types[channel.slot] = "lf" - - # Draw ports with annotations - for slot in range(1, CHASSIS_WIDTH + 1): - has_assigned_ports_slot = any( - any(assigned for assigned in ports) - for ports in channel_assignments[slot].values() - ) - - for io_type, ports in channel_assignments[slot].items(): - for i, assigned in enumerate(ports): - port = type( - "Port", (object,), {"slot": slot, "port": i + 1, "io_type": io_type} - )() - if assigned: - line_type = annotations_map[slot][io_type][i][0] - color = get_color_for_line_type(line_type) - annotate_port( - ax, - port, - annotations_map[slot][io_type][i], - assigned=True, - color=color, - ) - elif has_assigned_ports_slot: - annotate_port(ax, port, [], assigned=False, color="none") - - label_slot(ax, slot, fem_types[slot]) - - # Draw slot boundaries explicitly - for slot in range(CHASSIS_WIDTH): - ax.plot([slot, slot], [0, CHASSIS_HEIGHT], color="black", lw=1) - - # Set the limits and display the plot - ax.set_xlim(0, CHASSIS_WIDTH) - ax.set_ylim(0, CHASSIS_HEIGHT) - ax.set_aspect("equal") - plt.suptitle("OPX1000 Chassis Wiring Diagram", fontweight="bold", fontsize=20) - plt.axis("off") + inverted_dict[key].append((annotation, channel)) + return inverted_dict + +def prepare_annotations(inverted_dict): + annotations = [] + for key, values in inverted_dict.items(): + con, slot, io_type, port, instrument_id, channel_type = key + labels = [value[0] for value in values] + color = get_color_for_line_type(channel_type) + annotations.append(AnnotationPort(labels, color, con, slot, io_type, port, instrument_id)) + return annotations + +def prepare_slot_labels(inverted_dict): + labels = [] + slots_seen = set() + for (con, slot, io_type, port, instrument_id, channel_type), values in inverted_dict.items(): + if (con, slot) not in slots_seen: + label = f"{slot}: {instrument_id}" + labels.append(LabelSlot(slot, label, con)) + slots_seen.add((con, slot)) + return labels + +def draw_annotations(ax, annotations): + for annotation in annotations: + pos = PORT_POSITIONS[annotation.instrument_id][annotation.io_type][annotation.port - 1] + x, y = pos + fill_color = annotation.color if annotation.labels else "none" + ax.add_patch(patches.Circle((x, y), 0.1, edgecolor="black", facecolor=fill_color)) + for i, label in enumerate(annotation.labels): + ax.text( + x - 0.15, + y + 0.15 * i, + label, + ha="right", + va="center", + fontsize=14, + color="black", + bbox=dict(facecolor="white", alpha=0.3, edgecolor="none"), + ) + +def draw_slot_label(ax, annotation): + ax.set_title(f"{annotation.slot}: {annotation.instrument_id}", fontweight="bold") + +def get_color_for_line_type(line_type): + color_map = { + "lf-fem": "blue", + "mw-fem": "orange", + "opx+": "yellow", + "octave": "purple" + } + return color_map.get(line_type, "grey") + +def visualize_chassis(qubit_dict): + # Invert the qubit dictionary for easier annotation processing + inverted_dict = invert_qubit_dict(qubit_dict) + + # Prepare annotations and labels + annotations = prepare_annotations(inverted_dict) + + # Manage figures and draw + manager = InstrumentFigureManager() + for annotation in annotations: + ax = manager.get_ax(annotation.con, annotation.slot, annotation.instrument_id) + draw_annotations(ax, annotations) + draw_slot_label(ax, annotation) + + plt.tight_layout() plt.show() diff --git a/tests/wirer/test_wirer_lf_and_octave.py b/tests/wirer/test_wirer_lf_and_octave.py index 094ddd66..4da958d3 100644 --- a/tests/wirer/test_wirer_lf_and_octave.py +++ b/tests/wirer/test_wirer_lf_and_octave.py @@ -3,7 +3,7 @@ from qualang_tools.wirer.wirer import allocate_wiring from pprint import pprint -visualize = False +visualize = True def test_rf_io_allocation(instruments_1octave): qubits = [1,2,3,4,5] From 52065470cbe4fc1afef68184030a579da284745f Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Mon, 26 Aug 2024 03:05:43 +1000 Subject: [PATCH 04/40] Get OPX1000 visualization working and draft OPX+/Octave. --- qualang_tools/wirer/visualizer/constants.py | 39 +++++ .../visualizer/instrument_figure_manager.py | 79 +++++++++ .../wirer/visualizer/port_annotation.py | 69 ++++++++ qualang_tools/wirer/visualizer/visualizer.py | 160 +++++------------- tests/wirer/test_wirer_lf_and_mw.py | 4 +- 5 files changed, 231 insertions(+), 120 deletions(-) create mode 100644 qualang_tools/wirer/visualizer/constants.py create mode 100644 qualang_tools/wirer/visualizer/instrument_figure_manager.py create mode 100644 qualang_tools/wirer/visualizer/port_annotation.py diff --git a/qualang_tools/wirer/visualizer/constants.py b/qualang_tools/wirer/visualizer/constants.py new file mode 100644 index 00000000..3f50b0ad --- /dev/null +++ b/qualang_tools/wirer/visualizer/constants.py @@ -0,0 +1,39 @@ +# Map InstrumentChannel.instrument_id to containing instrument type +instrument_id_mapping = { + "lf-fem": "OPX1000", + "mw-fem": "OPX1000", + "opx+": "OPX+", + "octave": "Octave", +} + +# Define the chassis dimensions +INSTRUMENT_FIGURE_DIMENSIONS = { + "OPX1000": {"width": 8, "height": 3}, + "OPX+": {"width": 8, "height": 1}, + "Octave": {"width": 3, "height": 1} +} + +# Define the port positions for different modules +PORT_SPACING_FACTOR = 0.1 +PORT_SIZE = 0.04 +PORT_POSITIONS = { + "lf-fem": { + "output": [(0.05 + 0.25/8*3, 1 - i * PORT_SPACING_FACTOR) for i in range(8)], + "input": [(0.05 + 0.75/8*3, 1 - (6+i) * PORT_SPACING_FACTOR) for i in range(2)], + }, + "mw-fem": { + "output": [(0.05 + 0.25/8*3, 1 - i * PORT_SPACING_FACTOR) for i in range(8)], + "input": [(0.05 + 0.75/8*3, 1 - i * PORT_SPACING_FACTOR * 7) for i in range(2)], + }, + "opx+": { + "input": [(0.75, 1 - i * PORT_SPACING_FACTOR * 2) for i in range(2)], + "output": [(0.25 + j * PORT_SPACING_FACTOR, 1 - i * PORT_SPACING_FACTOR * 5) for i in range(2) for j in range(5)], + "digital_output": [(0.25 + j * 0.2, 1 - i * PORT_SPACING_FACTOR * 10) for i in range(2) for j in range(5)], + }, + "octave": { + "input": [(0.75, 1 - i * PORT_SPACING_FACTOR * 2) for i in range(2)], + "output": [(0.25 + j * 0.2, 1 - i * PORT_SPACING_FACTOR * 10) for i in range(2) for j in range(5)], + "digital_output": [(0.25 + j * 0.2, 1 - i * PORT_SPACING_FACTOR * 10) for i in range(2) for j in range(5)], + } +} + diff --git a/qualang_tools/wirer/visualizer/instrument_figure_manager.py b/qualang_tools/wirer/visualizer/instrument_figure_manager.py new file mode 100644 index 00000000..ebf3154f --- /dev/null +++ b/qualang_tools/wirer/visualizer/instrument_figure_manager.py @@ -0,0 +1,79 @@ +from typing import Tuple, List + +from matplotlib import pyplot as plt +from matplotlib.axes import Axes +from matplotlib.figure import Figure + +from qualang_tools.wirer.visualizer.constants import INSTRUMENT_FIGURE_DIMENSIONS, instrument_id_mapping + + +def _key(instrument_id: str, con: int): + return f"{instrument_id}_{con}" + + +class InstrumentFigureManager: + def __init__(self): + self.figures = {} + + def get_ax(self, con: int, slot: int, instrument_id: str) -> Axes: + instrument_id = instrument_id_mapping[instrument_id] + key = _key(instrument_id, con) + + if key not in self.figures: + if instrument_id == "OPX1000": + fig, axs = self._make_opx1000_figure() + self.figures[key] = {i+1: ax for i, ax in enumerate(axs)} + elif instrument_id == "OPX+": + raise NotImplementedError() + fig = self._make_opx_plus_figure() + self.figures[key] = fig.axes[0] + elif instrument_id == "Octave": + raise NotImplementedError() + fig = self._make_octave_figure() + self.figures[key] = fig.axes[0] + else: + raise NotImplementedError() + fig.suptitle(f"con{con} - {instrument_id} Wiring Diagram", fontweight="bold", fontsize=14) + + return self.figures[key][slot] if slot is not None else self.figures[key] + + @staticmethod + def _make_opx1000_figure() -> Tuple[Figure, List[Axes]]: + fig, axs = plt.subplots( + nrows=1, ncols=8, + figsize=(INSTRUMENT_FIGURE_DIMENSIONS["OPX1000"]["width"] * 2, + INSTRUMENT_FIGURE_DIMENSIONS["OPX1000"]["height"] * 2) + ) + for i, ax in enumerate(axs.flat): + ax.set_ylim([0.15, 1.15]) + ax.set_xlim([0.15 / 8 * 3, 1.15 / 8 * 3]) + ax.set_facecolor('darkgrey') + ax.set_xticks([]) + ax.set_yticks([]) + ax.set_xticklabels([]) + ax.set_yticklabels([]) + ax.set_aspect('equal') + fig.subplots_adjust(wspace=0) + + return fig, axs + + @staticmethod + def _make_opx_plus_figure() -> Figure: + fig, ax = plt.subplots(1, 1, + figsize=(INSTRUMENT_FIGURE_DIMENSIONS["OPX+"]["width"] * 2, + INSTRUMENT_FIGURE_DIMENSIONS["OPX+"]["height"] * 2) + ) + ax.set_ylim([0.15, 1.15]) + ax.set_xlim([0.15 * 8, 1.15 * 8]) + ax.set_facecolor('darkgrey') + ax.set_xticks([]) + ax.set_yticks([]) + ax.set_xticklabels([]) + ax.set_yticklabels([]) + ax.set_aspect('equal') + + return fig + + @classmethod + def _make_octave_figure(cls) -> Figure: + return cls._make_opx_plus_figure() diff --git a/qualang_tools/wirer/visualizer/port_annotation.py b/qualang_tools/wirer/visualizer/port_annotation.py new file mode 100644 index 00000000..6aafb311 --- /dev/null +++ b/qualang_tools/wirer/visualizer/port_annotation.py @@ -0,0 +1,69 @@ +from matplotlib import patches +from matplotlib.axes import Axes +import matplotlib.colors as mcolors + +from .constants import PORT_POSITIONS, PORT_SIZE, PORT_SPACING_FACTOR + +class PortAnnotation: + def __init__(self, labels, color, con, slot, io_type, port, instrument_id): + self.labels = labels + self.color = color + self.con = con + self.slot = slot + self.io_type = io_type + self.port = port + self.instrument_id = instrument_id + + def draw(self, ax: Axes): + pos = PORT_POSITIONS[self.instrument_id][self.io_type][self.port - 1] + x, y = pos + fill_color = self.color if self.labels else "none" + ax.add_patch(patches.Circle((x, y), PORT_SIZE, edgecolor="black", facecolor=fill_color)) + for i, label in enumerate(self.labels): + # port annotation + ax.text( + x, + y, + str(self.port), + ha="center", + va="center", + fontsize=10, + color=get_contrast_color(self.color), + # fontweight="bold", + ) + # qubit line annotation + ax.text( + x - PORT_SPACING_FACTOR / 1.75, + y + (PORT_SPACING_FACTOR/2) * i, + label, + ha="right", + va="center", + fontsize=14, + # fontweight="bold", + color="black", + ) + ax.set_facecolor('lightgrey') + + def title_axes(self, ax: Axes): + ax.set_title(f"{self.slot}: {self.instrument_id}", fontweight="bold", y=-0.1, va="bottom") + + +def get_contrast_color(color): + """ + Returns black or white depending on the luminance of the input color. + + Parameters: + color (str or tuple): The color to evaluate. Can be a string ('red', '#00FF00', etc.) + or an RGB tuple ((1, 0, 0), (0.5, 0.5, 0.5), etc.) + + Returns: + str: 'black' if the input color is light, 'white' if it is dark. + """ + # Convert color to RGB format if it's a string + rgb = mcolors.to_rgb(color) + + # Calculate luminance + luminance = 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2] + + # Return 'black' if luminance is high (light color), 'white' if luminance is low (dark color) + return 'black' if luminance > 0.5 else 'white' \ No newline at end of file diff --git a/qualang_tools/wirer/visualizer/visualizer.py b/qualang_tools/wirer/visualizer/visualizer.py index 0574ad71..f022c1d9 100644 --- a/qualang_tools/wirer/visualizer/visualizer.py +++ b/qualang_tools/wirer/visualizer/visualizer.py @@ -1,77 +1,13 @@ +from typing import List + import matplotlib.pyplot as plt -import matplotlib.patches as patches - -# Define the chassis dimensions -CHASSIS_DIMENSIONS = { - "OPX1000": {"width": 8, "height": 3}, - "OPX+": {"width": 3, "height": 1}, - "Octave": {"width": 3, "height": 1} -} - -# Define the port positions for different modules -PORT_SPACING_FACTOR = 0.1 -PORT_POSITIONS = { - "lf-fem": { - "output": [(0.25, 1 - i * PORT_SPACING_FACTOR) for i in range(8)], - "input": [(0.75, 1 - i * PORT_SPACING_FACTOR * 7) for i in range(2)], - }, - "mw-fem": { - "output": [(0.25, 1 - i * PORT_SPACING_FACTOR) for i in range(8)], - "input": [(0.75, 1 - i * PORT_SPACING_FACTOR * 7) for i in range(2)], - }, - "digital_output": [(0.25 + j * 0.2, 1 - i * PORT_SPACING_FACTOR * 10) for i in range(2) for j in range(5)], - "analog_output": [(0.25 + j * 0.2, 1 - i * PORT_SPACING_FACTOR * 10) for i in range(2) for j in range(5)], - "analog_input": [(0.75, 1 - i * PORT_SPACING_FACTOR * 2) for i in range(2)], -} - -instrument_id_mapping = { - "lf-fem": "OPX1000", - "mw-fem": "OPX1000", - "opx+": "OPX+", - "octave": "Octave", -} - -# Define classes for managing annotations and slots -class AnnotationPort: - def __init__(self, labels, color, con, slot, io_type, port, instrument_id): - self.labels = labels - self.color = color - self.con = con - self.slot = slot - self.io_type = io_type - self.port = port - self.instrument_id = instrument_id - -class LabelSlot: - def __init__(self, slot, label, con): - self.slot = slot - self.label = label - self.con = con - -class InstrumentFigureManager: - def __init__(self): - self.figures = {} - - def get_ax(self, con, slot, instrument_id): - instrument_id = instrument_id_mapping[instrument_id] - key = f"{instrument_id}_{con}" - if key not in self.figures: - if instrument_id == "OPX1000": - fig, axs = plt.subplots( - nrows=1, - ncols=8, - figsize=(CHASSIS_DIMENSIONS[instrument_id]["width"] * 2, - CHASSIS_DIMENSIONS[instrument_id]["height"] * 2)) - for ax in axs.flat: - ax.set_ylim([0, 1]) - ax.axis("off") - self.figures[key] = axs - else: - raise NotImplementedError() - fig.suptitle(f"{instrument_id} Wiring Diagram - con{con}", fontweight="bold", fontsize=20) - return self.figures[key][slot] if slot is not None else self.figures[key] - -def invert_qubit_dict(qubit_dict): + +from qualang_tools.wirer.connectivity.wiring_spec import WiringLineType +from qualang_tools.wirer.instruments.instrument_channels import InstrumentChannels +from qualang_tools.wirer.visualizer.instrument_figure_manager import InstrumentFigureManager +from qualang_tools.wirer.visualizer.port_annotation import PortAnnotation + +def invert_qubit_dict(qubit_dict) -> dict: inverted_dict = {} for qubit_ref, element in qubit_dict.items(): for channel_type, channels in element.channels.items(): @@ -83,56 +19,44 @@ def invert_qubit_dict(qubit_dict): inverted_dict[key].append((annotation, channel)) return inverted_dict -def prepare_annotations(inverted_dict): +def prepare_annotations(inverted_dict: dict) -> List[PortAnnotation]: annotations = [] for key, values in inverted_dict.items(): con, slot, io_type, port, instrument_id, channel_type = key labels = [value[0] for value in values] color = get_color_for_line_type(channel_type) - annotations.append(AnnotationPort(labels, color, con, slot, io_type, port, instrument_id)) + annotations.append(PortAnnotation(labels, color, con, slot, io_type, port, instrument_id)) + return annotations -def prepare_slot_labels(inverted_dict): - labels = [] - slots_seen = set() - for (con, slot, io_type, port, instrument_id, channel_type), values in inverted_dict.items(): - if (con, slot) not in slots_seen: - label = f"{slot}: {instrument_id}" - labels.append(LabelSlot(slot, label, con)) - slots_seen.add((con, slot)) - return labels - -def draw_annotations(ax, annotations): - for annotation in annotations: - pos = PORT_POSITIONS[annotation.instrument_id][annotation.io_type][annotation.port - 1] - x, y = pos - fill_color = annotation.color if annotation.labels else "none" - ax.add_patch(patches.Circle((x, y), 0.1, edgecolor="black", facecolor=fill_color)) - for i, label in enumerate(annotation.labels): - ax.text( - x - 0.15, - y + 0.15 * i, - label, - ha="right", - va="center", - fontsize=14, - color="black", - bbox=dict(facecolor="white", alpha=0.3, edgecolor="none"), - ) - -def draw_slot_label(ax, annotation): - ax.set_title(f"{annotation.slot}: {annotation.instrument_id}", fontweight="bold") - -def get_color_for_line_type(line_type): + +def prepare_available_channel_annotations(available_channels: InstrumentChannels) -> List[PortAnnotation]: + annotations = [] + for _, channel_list in available_channels.stack.items(): + for channel in channel_list: + color = get_color_for_line_type(None) + annotations.append(PortAnnotation([""], color, channel.con, channel.slot, channel.io_type, channel.port, channel.instrument_id)) + + return annotations + + +def get_color_for_line_type(line_type) -> str: color_map = { - "lf-fem": "blue", - "mw-fem": "orange", - "opx+": "yellow", - "octave": "purple" + WiringLineType.FLUX: "blue", + WiringLineType.RESONATOR: "orange", + WiringLineType.DRIVE: "yellow", + WiringLineType.COUPLER: "purple" } - return color_map.get(line_type, "grey") + return color_map.get(line_type, "white") + -def visualize_chassis(qubit_dict): +def draw_annotations(manager: InstrumentFigureManager, annotations: List[PortAnnotation]): + for annotation in annotations: + ax = manager.get_ax(annotation.con, annotation.slot, annotation.instrument_id) + annotation.draw(ax) + annotation.title_axes(ax) + +def visualize_chassis(qubit_dict, available_channels=None): # Invert the qubit dictionary for easier annotation processing inverted_dict = invert_qubit_dict(qubit_dict) @@ -141,10 +65,10 @@ def visualize_chassis(qubit_dict): # Manage figures and draw manager = InstrumentFigureManager() - for annotation in annotations: - ax = manager.get_ax(annotation.con, annotation.slot, annotation.instrument_id) - draw_annotations(ax, annotations) - draw_slot_label(ax, annotation) - plt.tight_layout() + draw_annotations(manager, annotations) + if available_channels is not None: + available_channel_annotations = prepare_available_channel_annotations(available_channels) + draw_annotations(manager, available_channel_annotations) + plt.show() diff --git a/tests/wirer/test_wirer_lf_and_mw.py b/tests/wirer/test_wirer_lf_and_mw.py index 713be68a..bbc4f610 100644 --- a/tests/wirer/test_wirer_lf_and_mw.py +++ b/tests/wirer/test_wirer_lf_and_mw.py @@ -21,7 +21,7 @@ def test_5q_allocation(instruments_2lf_2mw): print(connectivity.elements) if visualize: - visualize_chassis(connectivity.elements) + visualize_chassis(connectivity.elements, instruments_2lf_2mw.available_channels) def test_4rr_allocation(instruments_2lf_2mw): @@ -52,4 +52,4 @@ def test_6rr_6xy_6flux_allocation(): allocate_wiring(connectivity, instruments) if visualize: - visualize_chassis(connectivity.elements) \ No newline at end of file + visualize_chassis(connectivity.elements, instruments.available_channels) \ No newline at end of file From 3be32495266f81faabfbc68b981397b70409eaf2 Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Mon, 26 Aug 2024 23:21:00 +1000 Subject: [PATCH 05/40] Combine labels for different qubits on same line type. --- .../wirer/visualizer/port_annotation.py | 64 +++++++++++++++---- tests/wirer/test_wirer_lf_and_mw.py | 2 +- tests/wirer/test_wirer_lf_and_octave.py | 4 +- 3 files changed, 53 insertions(+), 17 deletions(-) diff --git a/qualang_tools/wirer/visualizer/port_annotation.py b/qualang_tools/wirer/visualizer/port_annotation.py index 6aafb311..da68ea08 100644 --- a/qualang_tools/wirer/visualizer/port_annotation.py +++ b/qualang_tools/wirer/visualizer/port_annotation.py @@ -1,3 +1,7 @@ +import re +from collections import defaultdict +from typing import List + from matplotlib import patches from matplotlib.axes import Axes import matplotlib.colors as mcolors @@ -19,18 +23,8 @@ def draw(self, ax: Axes): x, y = pos fill_color = self.color if self.labels else "none" ax.add_patch(patches.Circle((x, y), PORT_SIZE, edgecolor="black", facecolor=fill_color)) - for i, label in enumerate(self.labels): - # port annotation - ax.text( - x, - y, - str(self.port), - ha="center", - va="center", - fontsize=10, - color=get_contrast_color(self.color), - # fontweight="bold", - ) + labels = combine_labels_for_same_line_type(self.labels) + for i, label in enumerate(labels): # qubit line annotation ax.text( x - PORT_SPACING_FACTOR / 1.75, @@ -39,9 +33,19 @@ def draw(self, ax: Axes): ha="right", va="center", fontsize=14, - # fontweight="bold", color="black", + bbox=dict(facecolor="white", alpha=1, edgecolor="none"), ) + # port annotation + ax.text( + x, + y, + str(self.port), + ha="center", + va="center", + fontsize=10, + color=get_contrast_color(self.color), + ) ax.set_facecolor('lightgrey') def title_axes(self, ax: Axes): @@ -66,4 +70,36 @@ def get_contrast_color(color): luminance = 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2] # Return 'black' if luminance is high (light color), 'white' if luminance is low (dark color) - return 'black' if luminance > 0.5 else 'white' \ No newline at end of file + return 'black' if luminance > 0.5 else 'white' + +def combine_labels_for_same_line_type(labels: List[str]): + # Dictionary to group strings by line type + grouped_lines = defaultdict(list) + + # Regular expression to parse the qubit line label + pattern = re.compile(r'q(\d+)\.(.+)') + + # Parse each string and group by line type + for s in labels: + match = pattern.match(s) + if match: + index = int(match.group(1)) # Extract the qubit index + line_type = match.group(2) # Extract the line type + grouped_lines[line_type].append(index) + + # Result list to store the reduced strings + reduced_strings = [] + + # Process each line type group + for line_type, indices in grouped_lines.items(): + smallest_index = min(indices) + largest_index = max(indices) + # Check if there's only one index + if smallest_index == largest_index: + reduced_string = f'q{smallest_index}.{line_type}' + else: + reduced_string = f'q{smallest_index}-{largest_index}.{line_type}' + + reduced_strings.append(reduced_string) + + return reduced_strings \ No newline at end of file diff --git a/tests/wirer/test_wirer_lf_and_mw.py b/tests/wirer/test_wirer_lf_and_mw.py index bbc4f610..bcf27d54 100644 --- a/tests/wirer/test_wirer_lf_and_mw.py +++ b/tests/wirer/test_wirer_lf_and_mw.py @@ -2,7 +2,7 @@ from qualang_tools.wirer.instruments import Instruments from qualang_tools.wirer.visualizer.visualizer import visualize_chassis from qualang_tools.wirer.wirer import allocate_wiring -from qualang_tools.wirer.wirer.channel_specs import mw_fem_spec +from qualang_tools.wirer.wirer.channel_specs import mw_fem_spec, ChannelSpecLfFemSingle visualize = True diff --git a/tests/wirer/test_wirer_lf_and_octave.py b/tests/wirer/test_wirer_lf_and_octave.py index 4da958d3..d709d088 100644 --- a/tests/wirer/test_wirer_lf_and_octave.py +++ b/tests/wirer/test_wirer_lf_and_octave.py @@ -47,8 +47,8 @@ def test_qw_soprano_2qb_allocation(instruments_1OPX1Octave): pprint(connectivity.elements) - if visualize: - visualize_chassis(connectivity.elements) + # if visualize: + # visualize_chassis(connectivity.elements) def test_qw_soprano_2qb_among_5_allocation(instruments_1OPX1Octave): all_qubits = [1, 2, 3, 4, 5] From 41d08f146be45685f289a5fb0da72cba3a2b5ff4 Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Mon, 26 Aug 2024 23:30:22 +1000 Subject: [PATCH 06/40] Combine labels for different qubits on same line type and fix hyphenation for non-continuous ranges. --- .../wirer/visualizer/port_annotation.py | 59 ++++++++++++------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/qualang_tools/wirer/visualizer/port_annotation.py b/qualang_tools/wirer/visualizer/port_annotation.py index da68ea08..8535966e 100644 --- a/qualang_tools/wirer/visualizer/port_annotation.py +++ b/qualang_tools/wirer/visualizer/port_annotation.py @@ -24,18 +24,6 @@ def draw(self, ax: Axes): fill_color = self.color if self.labels else "none" ax.add_patch(patches.Circle((x, y), PORT_SIZE, edgecolor="black", facecolor=fill_color)) labels = combine_labels_for_same_line_type(self.labels) - for i, label in enumerate(labels): - # qubit line annotation - ax.text( - x - PORT_SPACING_FACTOR / 1.75, - y + (PORT_SPACING_FACTOR/2) * i, - label, - ha="right", - va="center", - fontsize=14, - color="black", - bbox=dict(facecolor="white", alpha=1, edgecolor="none"), - ) # port annotation ax.text( x, @@ -46,6 +34,18 @@ def draw(self, ax: Axes): fontsize=10, color=get_contrast_color(self.color), ) + for i, label in enumerate(labels): + # qubit line annotation + ax.text( + x - PORT_SPACING_FACTOR / 1.75, + y + (PORT_SPACING_FACTOR / 1.75) * i, + label, + ha="right", + va="center", + fontsize=12, + color="black", + bbox=dict(facecolor="white", alpha=1, edgecolor="none"), + ) ax.set_facecolor('lightgrey') def title_axes(self, ax: Axes): @@ -89,17 +89,34 @@ def combine_labels_for_same_line_type(labels: List[str]): # Result list to store the reduced strings reduced_strings = [] - # Process each line type group for line_type, indices in grouped_lines.items(): - smallest_index = min(indices) - largest_index = max(indices) - # Check if there's only one index - if smallest_index == largest_index: - reduced_string = f'q{smallest_index}.{line_type}' + indices.sort() + ranges = [] + start = indices[0] + end = start + + # Identify contiguous ranges of indices + for i in range(1, len(indices)): + if indices[i] == end + 1: + # Extend the current range + end = indices[i] + else: + # Save the current range and start a new one + if start == end: + ranges.append(f'{start}') + else: + ranges.append(f'{start}-{end}') + start = indices[i] + end = start + + # Append the final range or index + if start == end: + ranges.append(f'{start}') else: - reduced_string = f'q{smallest_index}-{largest_index}.{line_type}' + ranges.append(f'{start}-{end}') - reduced_strings.append(reduced_string) + # Combine ranges into the final format + reduced_strings.extend([f"q{range}.{line_type}" for range in ranges]) - return reduced_strings \ No newline at end of file + return reduced_strings From 08ccd32d212f5f1b155cd88d54a5229510c4033a Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Tue, 27 Aug 2024 16:30:12 +1000 Subject: [PATCH 07/40] Add some constants. --- qualang_tools/wirer/visualizer/constants.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/qualang_tools/wirer/visualizer/constants.py b/qualang_tools/wirer/visualizer/constants.py index 3f50b0ad..94451846 100644 --- a/qualang_tools/wirer/visualizer/constants.py +++ b/qualang_tools/wirer/visualizer/constants.py @@ -13,17 +13,21 @@ "Octave": {"width": 3, "height": 1} } +OPX_PLUS_ASPECT = (INSTRUMENT_FIGURE_DIMENSIONS["OPX+"]["height"] / + INSTRUMENT_FIGURE_DIMENSIONS["OPX+"]["width"]) +OPX_1000_ASPECT = (INSTRUMENT_FIGURE_DIMENSIONS["OPX1000"]["height"] / + INSTRUMENT_FIGURE_DIMENSIONS["OPX1000"]["width"]) # Define the port positions for different modules PORT_SPACING_FACTOR = 0.1 PORT_SIZE = 0.04 PORT_POSITIONS = { "lf-fem": { - "output": [(0.05 + 0.25/8*3, 1 - i * PORT_SPACING_FACTOR) for i in range(8)], - "input": [(0.05 + 0.75/8*3, 1 - (6+i) * PORT_SPACING_FACTOR) for i in range(2)], + "output": [(0.05 + 0.25 * OPX_1000_ASPECT, 1 - i * PORT_SPACING_FACTOR) for i in range(8)], + "input": [(0.05 + 0.75 * OPX_1000_ASPECT, 1 - (6+i) * PORT_SPACING_FACTOR) for i in range(2)], }, "mw-fem": { - "output": [(0.05 + 0.25/8*3, 1 - i * PORT_SPACING_FACTOR) for i in range(8)], - "input": [(0.05 + 0.75/8*3, 1 - i * PORT_SPACING_FACTOR * 7) for i in range(2)], + "output": [(0.05 + 0.25 * OPX_1000_ASPECT, 1 - i * PORT_SPACING_FACTOR) for i in range(8)], + "input": [(0.05 + 0.75 * OPX_1000_ASPECT, 1 - i * PORT_SPACING_FACTOR * 7) for i in range(2)], }, "opx+": { "input": [(0.75, 1 - i * PORT_SPACING_FACTOR * 2) for i in range(2)], From 69efe217f9edefb1c540d2799363f9456c1ab35a Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Wed, 28 Aug 2024 15:15:24 +1000 Subject: [PATCH 08/40] Enhance accessibility of core features. --- qualang_tools/wirer/__init__.py | 4 ++++ qualang_tools/wirer/visualizer/visualizer.py | 2 +- tests/wirer/test_wirer_lf_and_mw.py | 14 +++++++------- tests/wirer/test_wirer_lf_and_octave.py | 8 ++++---- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/qualang_tools/wirer/__init__.py b/qualang_tools/wirer/__init__.py index e69de29b..755b837a 100644 --- a/qualang_tools/wirer/__init__.py +++ b/qualang_tools/wirer/__init__.py @@ -0,0 +1,4 @@ +from .instruments import Instruments +from .connectivity import Connectivity +from .wirer.wirer import allocate_wiring +from .visualizer.visualizer import visualize diff --git a/qualang_tools/wirer/visualizer/visualizer.py b/qualang_tools/wirer/visualizer/visualizer.py index f022c1d9..3e369c3c 100644 --- a/qualang_tools/wirer/visualizer/visualizer.py +++ b/qualang_tools/wirer/visualizer/visualizer.py @@ -56,7 +56,7 @@ def draw_annotations(manager: InstrumentFigureManager, annotations: List[PortAnn annotation.draw(ax) annotation.title_axes(ax) -def visualize_chassis(qubit_dict, available_channels=None): +def visualize(qubit_dict, available_channels=None): # Invert the qubit dictionary for easier annotation processing inverted_dict = invert_qubit_dict(qubit_dict) diff --git a/tests/wirer/test_wirer_lf_and_mw.py b/tests/wirer/test_wirer_lf_and_mw.py index bcf27d54..a863da01 100644 --- a/tests/wirer/test_wirer_lf_and_mw.py +++ b/tests/wirer/test_wirer_lf_and_mw.py @@ -1,6 +1,6 @@ from qualang_tools.wirer.connectivity.connectivity import Connectivity from qualang_tools.wirer.instruments import Instruments -from qualang_tools.wirer.visualizer.visualizer import visualize_chassis +from qualang_tools.wirer.visualizer.visualizer import visualize from qualang_tools.wirer.wirer import allocate_wiring from qualang_tools.wirer.wirer.channel_specs import mw_fem_spec, ChannelSpecLfFemSingle @@ -11,17 +11,17 @@ def test_5q_allocation(instruments_2lf_2mw): qubit_pairs = [(1, 2), (2, 3), (3, 4), (4, 5)] connectivity = Connectivity() - connectivity.add_resonator_line(qubits=qubits, channel_spec=mw_fem_spec(slot=7)) - connectivity.add_qubit_drive_lines(qubits=qubits, channel_spec=mw_fem_spec(slot=7)) + connectivity.add_resonator_line(qubits=qubits)#, channel_spec=mw_fem_spec(slot=7)) + connectivity.add_qubit_drive_lines(qubits=qubits)#, channel_spec=mw_fem_spec(slot=7)) connectivity.add_qubit_flux_lines(qubits=qubits) - connectivity.add_qubit_pair_flux_lines(qubit_pairs=qubit_pairs) + connectivity.add_qubit_pair_flux_lines(qubit_pairs=qubit_pairs, channel_spec=ChannelSpecLfFemSingle(out_slot=2)) allocate_wiring(connectivity, instruments_2lf_2mw) print(connectivity.elements) if visualize: - visualize_chassis(connectivity.elements, instruments_2lf_2mw.available_channels) + visualize(connectivity.elements, instruments_2lf_2mw.available_channels) def test_4rr_allocation(instruments_2lf_2mw): @@ -35,7 +35,7 @@ def test_4rr_allocation(instruments_2lf_2mw): allocate_wiring(connectivity, instruments_2lf_2mw) if visualize: - visualize_chassis(connectivity.elements) + visualize(connectivity.elements) def test_6rr_6xy_6flux_allocation(): @@ -52,4 +52,4 @@ def test_6rr_6xy_6flux_allocation(): allocate_wiring(connectivity, instruments) if visualize: - visualize_chassis(connectivity.elements, instruments.available_channels) \ No newline at end of file + visualize(connectivity.elements, instruments.available_channels) \ No newline at end of file diff --git a/tests/wirer/test_wirer_lf_and_octave.py b/tests/wirer/test_wirer_lf_and_octave.py index d709d088..97d3d0f3 100644 --- a/tests/wirer/test_wirer_lf_and_octave.py +++ b/tests/wirer/test_wirer_lf_and_octave.py @@ -1,5 +1,5 @@ from qualang_tools.wirer.connectivity.connectivity import Connectivity -from qualang_tools.wirer.visualizer.visualizer import visualize_chassis +from qualang_tools.wirer.visualizer.visualizer import visualize from qualang_tools.wirer.wirer import allocate_wiring from pprint import pprint @@ -16,7 +16,7 @@ def test_rf_io_allocation(instruments_1octave): pprint(connectivity.elements) if visualize: - visualize_chassis(connectivity.elements) + visualize(connectivity.elements) def test_qw_soprano_allocation(instruments_qw_soprano): qubits = [1, 2, 3, 4, 5] @@ -31,7 +31,7 @@ def test_qw_soprano_allocation(instruments_qw_soprano): pprint(connectivity.elements) if visualize: - visualize_chassis(connectivity.elements) + visualize(connectivity.elements) def test_qw_soprano_2qb_allocation(instruments_1OPX1Octave): active_qubits = [1, 2] @@ -74,4 +74,4 @@ def test_qw_soprano_2qb_among_5_allocation(instruments_1OPX1Octave): pprint(connectivity.elements) if visualize: - visualize_chassis(connectivity.elements) + visualize(connectivity.elements) From a97e6f439a428703a204c66240b67123d3e28716 Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Thu, 29 Aug 2024 01:11:01 +1000 Subject: [PATCH 09/40] Add channel reuse, digital channels, proper channel masking, lf/opx+ visualization and more. --- qualang_tools/wirer/__init__.py | 6 + qualang_tools/wirer/connectivity/__init__.py | 2 +- .../wirer/connectivity/channel_spec.py | 86 +++++++++++ .../{connectivity.py => connectivity_base.py} | 34 +---- .../connectivity_transmon_interface.py | 41 ++++++ qualang_tools/wirer/connectivity/element.py | 6 +- .../wirer/connectivity/wiring_spec.py | 32 +---- qualang_tools/wirer/instruments/constants.py | 4 + .../wirer/instruments/instrument_channel.py | 135 +++++++++++++++--- .../wirer/instruments/instrument_channels.py | 26 +++- .../wirer/instruments/instruments.py | 45 ++++-- qualang_tools/wirer/visualizer/constants.py | 24 ++-- .../visualizer/instrument_figure_manager.py | 11 +- .../wirer/visualizer/port_annotation.py | 37 ++--- qualang_tools/wirer/visualizer/visualizer.py | 11 +- qualang_tools/wirer/wirer/channel_specs.py | 66 ++++++++- .../wirer/wirer/channel_specs_interface.py | 19 +++ qualang_tools/wirer/wirer/wirer.py | 81 +++++++---- .../wirer/wirer_assign_channels_to_spec.py | 1 + qualang_tools/wirer/wirer/wirer_exception.py | 11 -- qualang_tools/wirer/wirer/wirer_exceptions.py | 26 ++++ tests/wirer/conftest.py | 16 +-- tests/wirer/test_wirer_channel_reuse.py | 21 +++ tests/wirer/test_wirer_constraining.py | 20 +++ tests/wirer/test_wirer_digital.py | 20 +++ tests/wirer/test_wirer_lf_and_mw.py | 24 ++-- tests/wirer/test_wirer_lf_and_octave.py | 35 +++-- 27 files changed, 625 insertions(+), 215 deletions(-) rename qualang_tools/wirer/connectivity/{connectivity.py => connectivity_base.py} (54%) create mode 100644 qualang_tools/wirer/connectivity/connectivity_transmon_interface.py create mode 100644 qualang_tools/wirer/wirer/channel_specs_interface.py delete mode 100644 qualang_tools/wirer/wirer/wirer_exception.py create mode 100644 qualang_tools/wirer/wirer/wirer_exceptions.py create mode 100644 tests/wirer/test_wirer_channel_reuse.py create mode 100644 tests/wirer/test_wirer_constraining.py create mode 100644 tests/wirer/test_wirer_digital.py diff --git a/qualang_tools/wirer/__init__.py b/qualang_tools/wirer/__init__.py index 755b837a..e5343e1f 100644 --- a/qualang_tools/wirer/__init__.py +++ b/qualang_tools/wirer/__init__.py @@ -2,3 +2,9 @@ from .connectivity import Connectivity from .wirer.wirer import allocate_wiring from .visualizer.visualizer import visualize +from .wirer.channel_specs_interface import ( + mw_fem_spec, + lf_fem_spec, lf_fem_iq_spec, lf_fem_iq_octave_spec, + opx_spec, opx_iq_spec, opx_iq_octave_spec, + octave_spec +) diff --git a/qualang_tools/wirer/connectivity/__init__.py b/qualang_tools/wirer/connectivity/__init__.py index 9daef322..a7a1b838 100644 --- a/qualang_tools/wirer/connectivity/__init__.py +++ b/qualang_tools/wirer/connectivity/__init__.py @@ -1 +1 @@ -from .connectivity import Connectivity +from .connectivity_transmon_interface import Connectivity diff --git a/qualang_tools/wirer/connectivity/channel_spec.py b/qualang_tools/wirer/connectivity/channel_spec.py index 5763e586..2608f383 100644 --- a/qualang_tools/wirer/connectivity/channel_spec.py +++ b/qualang_tools/wirer/connectivity/channel_spec.py @@ -1,3 +1,11 @@ +from collections import Counter +from os import setsid +from typing import List + +from qualang_tools.wirer.connectivity.wiring_spec import WiringSpec +from qualang_tools.wirer.instruments.instrument_channel import AnyInstrumentChannel + + class ChannelSpec: """ A specification for fixed channel requirements. """ def __init__(self): @@ -10,3 +18,81 @@ def __and__(self, other: 'ChannelSpec') -> 'ChannelSpec': def is_empty(self): return self.channel_templates is None or self.channel_templates == [] + + def filter_by_wiring_spec(self, wiring_spec: WiringSpec) -> 'ChannelSpec': + """ + Filters out channels from the specification if: + 1. Their I/O type is not required by the wiring specification + 2. They are digital but the wiring specification doesn't require triggering + """ + def filter_func(channel: AnyInstrumentChannel): + analog_channel_matches_io_type = channel.signal_type == "digital" or channel.io_type in wiring_spec.io_type.value + digital_channel_only_if_triggered = channel.signal_type == "analog" or wiring_spec.triggered + return analog_channel_matches_io_type and digital_channel_only_if_triggered + filtered_channel_spec = ChannelSpec() + filtered_channel_spec.channel_templates = list(filter(filter_func, self.channel_templates)) + + return filtered_channel_spec + + def apply_constraints(self, constraints: 'ChannelSpec') -> bool: + """ + Attempt to constrain the channel specifications according to the + filled attributes of all the specs in the `constraints` list. + + Returns: + True: if constraints are successful, modifying the spec. + False: if any of the constraints cannot be applied and are not already met. + """ + # make sure there are at least enough channel templates in this object + # of the correct type to satisfy each of the provided constraints. + # e.g., it is invalid to apply a MW-FEM mask if there are only OPX+ + # channel templates present in this object. + counts_templates = Counter([type(spec) for spec in self.channel_templates]) + counts_constraints = Counter([type(spec) for spec in constraints.channel_templates]) + + for _type, count_constraints in counts_constraints.items(): + if count_constraints > counts_templates.get(_type, 0): + return False + + # try to constraints to any possible spec in the list in order that + # the constraint is provided. + constrained_templates = [] + for constraint in constraints.channel_templates: + constrained = False + for template in self.channel_templates: + if template in constrained_templates: + # can't constrain already constrained spec + continue + if type(template) != type(constraint): + # can't constrain a spec of a different type + continue + else: + attrs_to_constrain = ["con", "slot", "port"] + attrs_constrainable = 0 + # make sure every filled attribute of the constraint can be + # applied to the spec without interfering with existing + # constraints. + for attr in attrs_to_constrain: + spec_attr = getattr(template, attr, None) + constraint_attr = getattr(constraint, attr, None) + + constrainable = spec_attr is None + nothing_to_constrain = constraint_attr is None + constrained_already_met = constraint_attr == spec_attr + + if constrainable or nothing_to_constrain or constrained_already_met: + attrs_constrainable += 1 + else: + break + + if attrs_constrainable == len(attrs_to_constrain): + for attr in attrs_to_constrain: + setattr(template, attr, getattr(constraint, attr, None)) + constrained = True + constrained_templates.append(template) + break + + if not constrained: + return False + + return True diff --git a/qualang_tools/wirer/connectivity/connectivity.py b/qualang_tools/wirer/connectivity/connectivity_base.py similarity index 54% rename from qualang_tools/wirer/connectivity/connectivity.py rename to qualang_tools/wirer/connectivity/connectivity_base.py index 724e711e..c427bfd5 100644 --- a/qualang_tools/wirer/connectivity/connectivity.py +++ b/qualang_tools/wirer/connectivity/connectivity_base.py @@ -7,7 +7,7 @@ from .wiring_spec import WiringSpec, WiringFrequency, WiringIOType, WiringLineType -class Connectivity: +class ConnectivityBase: """ This class stores placeholders for the quantum elements which will be used in a setup, as well as the wiring specification for each of those elements. @@ -20,40 +20,16 @@ def __init__(self): self.elements: Dict[ElementId, Element] = {} self.specs: List[WiringSpec] = [] - def add_fixed_transmons(self, qubits: QubitsType): - self.add_resonator_line(qubits) - self.add_qubit_drive_lines(qubits) - - def add_flux_tunable_transmons(self, qubits: QubitsType): - self.add_resonator_line(qubits) - self.add_qubit_drive_lines(qubits) - self.add_qubit_flux_lines(qubits) - - def add_resonator_line(self, qubits: QubitsType, channel_spec: ChannelSpec = None): - elements = self._make_qubit_elements(qubits) - return self.add_wiring_spec(WiringFrequency.RF, WiringIOType.INPUT_AND_OUTPUT, WiringLineType.RESONATOR, channel_spec, elements, shared_line=True) - - def add_qubit_drive_lines(self, qubits: QubitsType, channel_spec: ChannelSpec = None): - elements = self._make_qubit_elements(qubits) - return self.add_wiring_spec(WiringFrequency.RF, WiringIOType.OUTPUT, WiringLineType.DRIVE, channel_spec, elements) - - def add_qubit_flux_lines(self, qubits: QubitsType, channel_spec: ChannelSpec = None): - elements = self._make_qubit_elements(qubits) - return self.add_wiring_spec(WiringFrequency.DC, WiringIOType.OUTPUT, WiringLineType.FLUX, channel_spec, elements) - - def add_qubit_pair_flux_lines(self, qubit_pairs: QubitPairsType, channel_spec: ChannelSpec = None): - elements = self._make_qubit_pair_elements(qubit_pairs) - return self.add_wiring_spec(WiringFrequency.DC, WiringIOType.OUTPUT, WiringLineType.COUPLER, channel_spec, elements) - def add_wiring_spec(self, frequency: WiringFrequency, io_type: WiringIOType, line_type: WiringLineType, - channel_spec: ChannelSpec, elements: List[Element], shared_line: bool = False, ): + triggered: bool, constraints: ChannelSpec, elements: List[Element], + shared_line: bool = False, ): specs = [] if shared_line: - spec = WiringSpec(frequency, io_type, line_type, channel_spec, elements) + spec = WiringSpec(frequency, io_type, line_type, triggered, constraints, elements) specs.append(spec) else: for element in elements: - spec = WiringSpec(frequency, io_type, line_type, channel_spec, element) + spec = WiringSpec(frequency, io_type, line_type, triggered, constraints, element) specs.append(spec) self.specs.extend(specs) diff --git a/qualang_tools/wirer/connectivity/connectivity_transmon_interface.py b/qualang_tools/wirer/connectivity/connectivity_transmon_interface.py new file mode 100644 index 00000000..19156fb8 --- /dev/null +++ b/qualang_tools/wirer/connectivity/connectivity_transmon_interface.py @@ -0,0 +1,41 @@ +from .channel_spec import ChannelSpec +from .types import QubitsType, QubitPairsType +from .wiring_spec import WiringFrequency, WiringIOType, WiringLineType +from .connectivity_base import ConnectivityBase + + +class Connectivity(ConnectivityBase): + def add_fixed_transmons(self, qubits: QubitsType): + self.add_resonator_line(qubits) + self.add_qubit_drive_lines(qubits) + + + def add_flux_tunable_transmons(self, qubits: QubitsType): + self.add_resonator_line(qubits) + self.add_qubit_drive_lines(qubits) + self.add_qubit_flux_lines(qubits) + + + def add_resonator_line(self, qubits: QubitsType, triggered: bool = False, constraints: ChannelSpec = None): + elements = self._make_qubit_elements(qubits) + return self.add_wiring_spec(WiringFrequency.RF, WiringIOType.INPUT_AND_OUTPUT, WiringLineType.RESONATOR, + triggered, constraints, elements, shared_line=True) + + + def add_qubit_drive_lines(self, qubits: QubitsType, triggered: bool = False, constraints: ChannelSpec = None): + elements = self._make_qubit_elements(qubits) + return self.add_wiring_spec(WiringFrequency.RF, WiringIOType.OUTPUT, WiringLineType.DRIVE, + triggered, constraints, elements) + + + def add_qubit_flux_lines(self, qubits: QubitsType, constraints: ChannelSpec = None): + elements = self._make_qubit_elements(qubits) + return self.add_wiring_spec(WiringFrequency.DC, WiringIOType.OUTPUT, WiringLineType.FLUX, + False, constraints, elements) + + + def add_qubit_pair_flux_lines(self, qubit_pairs: QubitPairsType, constraints: ChannelSpec = None): + elements = self._make_qubit_pair_elements(qubit_pairs) + return self.add_wiring_spec(WiringFrequency.DC, WiringIOType.OUTPUT, WiringLineType.COUPLER, + False, constraints, elements) + diff --git a/qualang_tools/wirer/connectivity/element.py b/qualang_tools/wirer/connectivity/element.py index 53b54ed7..6449a50b 100644 --- a/qualang_tools/wirer/connectivity/element.py +++ b/qualang_tools/wirer/connectivity/element.py @@ -2,7 +2,7 @@ from typing import List, Dict, Any, Union from .wiring_spec import WiringLineType -from ..instruments.instrument_channel import InstrumentChannel +from ..instruments.instrument_channel import AnyInstrumentChannel @dataclass(frozen=True) @@ -28,10 +28,10 @@ def __str__(self): @dataclass class Element: id: ElementId - channels: Dict[WiringLineType, List[InstrumentChannel]] = field(default_factory=dict) + channels: Dict[WiringLineType, List[AnyInstrumentChannel]] = field(default_factory=dict) def __str__(self): return str(self.channels) def __eq__(self, other): - return self.id == other.id + return self.id == other.id \ No newline at end of file diff --git a/qualang_tools/wirer/connectivity/wiring_spec.py b/qualang_tools/wirer/connectivity/wiring_spec.py index 7a7cf619..29bafa83 100644 --- a/qualang_tools/wirer/connectivity/wiring_spec.py +++ b/qualang_tools/wirer/connectivity/wiring_spec.py @@ -1,9 +1,6 @@ from enum import Enum from typing import Union, List -from .channel_spec import ChannelSpec -from ..instruments.instrument_channel import InstrumentChannel, InstrumentChannelInput, InstrumentChannelOutput - class WiringFrequency(Enum): DC = "DC" @@ -12,7 +9,7 @@ class WiringFrequency(Enum): class WiringIOType(Enum): INPUT = "input" OUTPUT = "output" - INPUT_AND_OUTPUT = "input_output" + INPUT_AND_OUTPUT = "input/output" class WiringLineType(Enum): RESONATOR = "rr" @@ -31,34 +28,15 @@ def __init__( frequency: WiringFrequency, io_type: WiringIOType, line_type: WiringLineType, - channel_specs: Union[ChannelSpec, List[ChannelSpec]], + triggered: bool, + constraints: 'ChannelSpec', elements: Union['Element', List['Element']], ): self.frequency = frequency self.io_type = io_type self.line_type = line_type - if isinstance(channel_specs, ChannelSpec): - channel_specs = [channel_specs] - if channel_specs is None: - channel_specs = [] - self.channel_specs = channel_specs + self.triggered = triggered + self.constraints = constraints if not isinstance(elements, list): elements = [elements] self.elements: List['Element'] = elements - - - def get_channel_template_from_spec(self, channel_spec: ChannelSpec) -> List[InstrumentChannel]: - if self.io_type == WiringIOType.INPUT_AND_OUTPUT or self.io_type is None: - return channel_spec.channel_templates - elif self.io_type == WiringIOType.INPUT: - return list(filter( - lambda channel: isinstance(channel, InstrumentChannelInput), - channel_spec.channel_templates - )) - elif self.io_type == WiringIOType.OUTPUT: - return list(filter( - lambda channel: isinstance(channel, InstrumentChannelOutput), - channel_spec.channel_templates - )) - else: - raise TypeError(f"Unrecognized input or output channel type {self.io_type}") diff --git a/qualang_tools/wirer/instruments/constants.py b/qualang_tools/wirer/instruments/constants.py index 4d5629e4..e4238c7f 100644 --- a/qualang_tools/wirer/instruments/constants.py +++ b/qualang_tools/wirer/instruments/constants.py @@ -1,8 +1,12 @@ NUM_OCTAVE_INPUT_PORTS = 2 +NUM_OCTAVE_DIGITAL_INPUT_PORTS = 5 NUM_OCTAVE_OUTPUT_PORTS = 5 NUM_OPX_PLUS_INPUT_PORTS = 2 NUM_OPX_PLUS_OUTPUT_PORTS = 10 +NUM_OPX_PLUS_DIGITAL_OUTPUT_PORTS = 10 NUM_LF_FEM_INPUT_PORTS = 2 NUM_LF_FEM_OUTPUT_PORTS = 8 +NUM_LF_FEM_DIGITAL_OUTPUT_PORTS = 8 NUM_MW_FEM_INPUT_PORTS = 2 NUM_MW_FEM_OUTPUT_PORTS = 8 +NUM_MW_FEM_DIGITAL_OUTPUT_PORTS = 8 diff --git a/qualang_tools/wirer/instruments/instrument_channel.py b/qualang_tools/wirer/instruments/instrument_channel.py index 502b43fe..8edecf23 100644 --- a/qualang_tools/wirer/instruments/instrument_channel.py +++ b/qualang_tools/wirer/instruments/instrument_channel.py @@ -1,7 +1,6 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Union, Literal, Callable - @dataclass class InstrumentChannel: con: int @@ -20,49 +19,143 @@ def make_channel_filter(self) -> Callable[['InstrumentChannel'], bool]: @dataclass -class InstrumentChannelInput(InstrumentChannel): +class InstrumentChannelInput: io_type: Literal["input", "output"] = "input" - @dataclass -class InstrumentChannelOutput(InstrumentChannel): +class InstrumentChannelOutput: io_type: Literal["input", "output"] = "output" +@dataclass +class InstrumentChannelDigital: + signal_type = "digital" @dataclass -class InstrumentChannelLfFemInput(InstrumentChannelInput): - instrument_id = "lf-fem" +class InstrumentChannelAnalog: + signal_type = "analog" +@dataclass +class InstrumentChannelLfFem: + instrument_id: Literal["lf-fem", "mw-fem", "opx+", "octave"] = "lf-fem" @dataclass -class InstrumentChannelLfFemOutput(InstrumentChannelOutput): - instrument_id = "lf-fem" +class InstrumentChannelMwFem: + instrument_id: Literal["lf-fem", "mw-fem", "opx+", "octave"] = "mw-fem" +@dataclass +class InstrumentChannelOpxPlus: + instrument_id: Literal["lf-fem", "mw-fem", "opx+", "octave"] = "opx+" @dataclass -class InstrumentChannelMwFemInput(InstrumentChannelInput): - instrument_id = "mw-fem" +class InstrumentChannelOctave: + instrument_id: Literal["lf-fem", "mw-fem", "opx+", "octave"] = "octave" @dataclass -class InstrumentChannelMwFemOutput(InstrumentChannelOutput): - instrument_id = "mw-fem" +class InstrumentChannelLfFemInput( + InstrumentChannelAnalog, + InstrumentChannelLfFem, + InstrumentChannelInput, + InstrumentChannel +): pass +@dataclass +class InstrumentChannelLfFemOutput( + InstrumentChannelAnalog, + InstrumentChannelLfFem, + InstrumentChannelOutput, + InstrumentChannel +): pass @dataclass -class InstrumentChannelOpxPlusInput(InstrumentChannelInput): - instrument_id = "opx+" +class InstrumentChannelLfFemDigitalOutput( + InstrumentChannelDigital, + InstrumentChannelLfFem, + InstrumentChannelOutput, + InstrumentChannel +): pass @dataclass -class InstrumentChannelOpxPlusOutput(InstrumentChannelOutput): - instrument_id = "opx+" +class InstrumentChannelMwFemInput( + InstrumentChannelAnalog, + InstrumentChannelMwFem, + InstrumentChannelInput, + InstrumentChannel +): pass +@dataclass +class InstrumentChannelMwFemOutput( + InstrumentChannelAnalog, + InstrumentChannelMwFem, + InstrumentChannelOutput, + InstrumentChannel +): pass @dataclass -class InstrumentChannelOctaveInput(InstrumentChannelInput): - instrument_id = "octave" +class InstrumentChannelMwFemDigitalOutput( + InstrumentChannelDigital, + InstrumentChannelMwFem, + InstrumentChannelOutput, + InstrumentChannel +): pass +@dataclass +class InstrumentChannelOpxPlusInput( + InstrumentChannelAnalog, + InstrumentChannelOpxPlus, + InstrumentChannelInput, + InstrumentChannel +): pass -class InstrumentChannelOctaveOutput(InstrumentChannelOutput): - instrument_id = "octave" +@dataclass +class InstrumentChannelOpxPlusOutput( + InstrumentChannelAnalog, + InstrumentChannelOpxPlus, + InstrumentChannelOutput, + InstrumentChannel +): pass + +@dataclass +class InstrumentChannelOpxPlusDigitalOutput( + InstrumentChannelDigital, + InstrumentChannelOpxPlus, + InstrumentChannelOutput, + InstrumentChannel +): pass + +@dataclass +class InstrumentChannelOctaveInput( + InstrumentChannelAnalog, + InstrumentChannelOctave, + InstrumentChannelInput, + InstrumentChannel +): pass + +@dataclass +class InstrumentChannelOctaveOutput( + InstrumentChannelAnalog, + InstrumentChannelOctave, + InstrumentChannelOutput, + InstrumentChannel +): pass + +@dataclass +class InstrumentChannelOctaveDigitalInput( + InstrumentChannelDigital, + InstrumentChannelOctave, + InstrumentChannelOutput, + InstrumentChannel +): pass + + +AnyInstrumentChannel = Union[ + InstrumentChannelLfFemInput, + InstrumentChannelLfFemOutput, + InstrumentChannelMwFemInput, + InstrumentChannelMwFemOutput, + InstrumentChannelOpxPlusInput, + InstrumentChannelOpxPlusOutput, + InstrumentChannelOctaveInput, + InstrumentChannelOctaveOutput, +] diff --git a/qualang_tools/wirer/instruments/instrument_channels.py b/qualang_tools/wirer/instruments/instrument_channels.py index e2bad3fd..4009414f 100644 --- a/qualang_tools/wirer/instruments/instrument_channels.py +++ b/qualang_tools/wirer/instruments/instrument_channels.py @@ -2,15 +2,18 @@ from qualang_tools.wirer.instruments.instrument_channel import InstrumentChannel, InstrumentChannelLfFemInput, \ InstrumentChannelLfFemOutput, InstrumentChannelMwFemInput, InstrumentChannelMwFemOutput, \ - InstrumentChannelOpxPlusInput, InstrumentChannelOpxPlusOutput + InstrumentChannelOpxPlusInput, InstrumentChannelOpxPlusOutput, AnyInstrumentChannel, \ + InstrumentChannelLfFemDigitalOutput, InstrumentChannelMwFemDigitalOutput, InstrumentChannelOpxPlusDigitalOutput -CHANNELS_OPX_PLUS = [InstrumentChannelOpxPlusInput, InstrumentChannelOpxPlusOutput] +CHANNELS_OPX_PLUS = [InstrumentChannelOpxPlusInput, InstrumentChannelOpxPlusOutput, InstrumentChannelOpxPlusDigitalOutput] CHANNELS_OPX_1000 = [ InstrumentChannelLfFemInput, InstrumentChannelLfFemOutput, + InstrumentChannelLfFemDigitalOutput, InstrumentChannelMwFemInput, InstrumentChannelMwFemOutput, + InstrumentChannelMwFemDigitalOutput, ] @@ -24,7 +27,7 @@ class InstrumentChannels: def __init__(self): self.stack = {} - def check_if_already_occupied(self, channel: InstrumentChannel): + def check_if_already_occupied(self, channel: AnyInstrumentChannel): for channel_type in self.stack: for existing_channel in self.stack[channel_type]: if ( @@ -32,6 +35,7 @@ def check_if_already_occupied(self, channel: InstrumentChannel): and channel.slot == existing_channel.slot and channel.port == existing_channel.port and channel.io_type == existing_channel.io_type + and channel.signal_type == existing_channel.signal_type ): if channel.slot is None: if type(channel) != type(existing_channel): @@ -59,6 +63,16 @@ def check_if_mixing_opx_1000_and_opx_plus(self, channel: InstrumentChannel): f"Can't add an OPX+ to a setup with an OPX1000 FEM." ) + def insert(self, pos: int, channel: InstrumentChannel): + self.check_if_already_occupied(channel) + self.check_if_mixing_opx_1000_and_opx_plus(channel) + + channel_type = type(channel) + if channel_type not in self.stack: + self.stack[channel_type] = [] + + self.stack[channel_type].insert(pos, channel) + def add(self, channel: InstrumentChannel): self.check_if_already_occupied(channel) self.check_if_mixing_opx_1000_and_opx_plus(channel) @@ -72,6 +86,9 @@ def add(self, channel: InstrumentChannel): def pop(self, channel: Type[InstrumentChannel]): return self.stack[channel].pop(0) + def remove(self, channel: InstrumentChannel): + return self.stack[type(channel)].remove(channel) + def get(self, key: InstrumentChannel, fallback=None): return self.stack.get(key, fallback) @@ -80,3 +97,6 @@ def __getitem__(self, item): def __iter__(self): return iter(self.stack) + + def items(self): + return self.stack.items() \ No newline at end of file diff --git a/qualang_tools/wirer/instruments/instruments.py b/qualang_tools/wirer/instruments/instruments.py index 1fb421ef..f4a507ac 100644 --- a/qualang_tools/wirer/instruments/instruments.py +++ b/qualang_tools/wirer/instruments/instruments.py @@ -1,6 +1,8 @@ from typing import List, Union -from .instrument_channel import InstrumentChannelOctaveInput, InstrumentChannelOctaveOutput +from .instrument_channel import InstrumentChannelOctaveInput, InstrumentChannelOctaveOutput, \ + InstrumentChannelOctaveDigitalInput, InstrumentChannelLfFemDigitalOutput, InstrumentChannelMwFemDigitalOutput, \ + InstrumentChannelOpxPlusDigitalOutput from .instrument_channels import * from .constants import * @@ -13,6 +15,7 @@ class Instruments: """ def __init__(self): + self.used_channels = InstrumentChannels() self.available_channels = InstrumentChannels() def add_octave(self, indices: Union[List[int], int]): @@ -28,41 +31,57 @@ def add_octave(self, indices: Union[List[int], int]): channel = InstrumentChannelOctaveOutput(con=index, port=port) self.available_channels.add(channel) - def add_lf_fem(self, con: int, slots: Union[List[int], int]): + for port in range(1, NUM_OCTAVE_DIGITAL_INPUT_PORTS + 1): + channel = InstrumentChannelOctaveDigitalInput(con=index, port=port) + self.available_channels.add(channel) + + def add_lf_fem(self, controller: int, slots: Union[List[int], int]): if isinstance(slots, int): slots = [slots] for slot in slots: for port in range(1, NUM_LF_FEM_INPUT_PORTS + 1): - channel = InstrumentChannelLfFemInput(con=con, slot=slot, port=port) + channel = InstrumentChannelLfFemInput(con=controller, slot=slot, port=port) self.available_channels.add(channel) for port in range(1, NUM_LF_FEM_OUTPUT_PORTS + 1): - channel = InstrumentChannelLfFemOutput(con=con, slot=slot, port=port) + channel = InstrumentChannelLfFemOutput(con=controller, slot=slot, port=port) self.available_channels.add(channel) - def add_mw_fem(self, con: int, slots: Union[List[int], int]): + for port in range(1, NUM_LF_FEM_DIGITAL_OUTPUT_PORTS + 1): + channel = InstrumentChannelLfFemDigitalOutput(con=controller, slot=slot, port=port) + self.available_channels.add(channel) + + def add_mw_fem(self, controller: int, slots: Union[List[int], int]): if isinstance(slots, int): slots = [slots] for slot in slots: for port in range(1, NUM_MW_FEM_INPUT_PORTS + 1): - channel = InstrumentChannelMwFemInput(con=con, slot=slot, port=port) + channel = InstrumentChannelMwFemInput(con=controller, slot=slot, port=port) self.available_channels.add(channel) for port in range(1, NUM_MW_FEM_OUTPUT_PORTS + 1): - channel = InstrumentChannelMwFemOutput(con=con, slot=slot, port=port) + channel = InstrumentChannelMwFemOutput(con=controller, slot=slot, port=port) self.available_channels.add(channel) - def add_opx_plus(self, cons: Union[List[int], int]): - if isinstance(cons, int): - cons = [cons] + for port in range(1, NUM_MW_FEM_OUTPUT_PORTS + 1): + channel = InstrumentChannelMwFemDigitalOutput(con=controller, slot=slot, port=port) + self.available_channels.add(channel) + + def add_opx_plus(self, controllers: Union[List[int], int]): + if isinstance(controllers, int): + controllers = [controllers] - for con in cons: + for controller in controllers: for port in range(1, NUM_OPX_PLUS_INPUT_PORTS + 1): - channel = InstrumentChannelOpxPlusInput(con=con, port=port) + channel = InstrumentChannelOpxPlusInput(con=controller, port=port) self.available_channels.add(channel) for port in range(1, NUM_OPX_PLUS_OUTPUT_PORTS + 1): - channel = InstrumentChannelOpxPlusOutput(con=con, port=port) + channel = InstrumentChannelOpxPlusOutput(con=controller, port=port) + self.available_channels.add(channel) + + for port in range(1, NUM_OPX_PLUS_DIGITAL_OUTPUT_PORTS + 1): + channel = InstrumentChannelOpxPlusDigitalOutput(con=controller, port=port) self.available_channels.add(channel) diff --git a/qualang_tools/wirer/visualizer/constants.py b/qualang_tools/wirer/visualizer/constants.py index 94451846..ecc13b49 100644 --- a/qualang_tools/wirer/visualizer/constants.py +++ b/qualang_tools/wirer/visualizer/constants.py @@ -18,26 +18,26 @@ OPX_1000_ASPECT = (INSTRUMENT_FIGURE_DIMENSIONS["OPX1000"]["height"] / INSTRUMENT_FIGURE_DIMENSIONS["OPX1000"]["width"]) # Define the port positions for different modules -PORT_SPACING_FACTOR = 0.1 -PORT_SIZE = 0.04 +PORT_SPACING_FACTOR = 0.12 +PORT_SIZE = 0.05 PORT_POSITIONS = { "lf-fem": { - "output": [(0.05 + 0.25 * OPX_1000_ASPECT, 1 - i * PORT_SPACING_FACTOR) for i in range(8)], - "input": [(0.05 + 0.75 * OPX_1000_ASPECT, 1 - (6+i) * PORT_SPACING_FACTOR) for i in range(2)], + "output": [(0.05 + 0.25 * OPX_1000_ASPECT, 1.06 - i * PORT_SPACING_FACTOR) for i in range(8)], + "input": [(0.05 + 0.75 * OPX_1000_ASPECT, 1.06 - (6+i) * PORT_SPACING_FACTOR) for i in range(2)], }, "mw-fem": { - "output": [(0.05 + 0.25 * OPX_1000_ASPECT, 1 - i * PORT_SPACING_FACTOR) for i in range(8)], - "input": [(0.05 + 0.75 * OPX_1000_ASPECT, 1 - i * PORT_SPACING_FACTOR * 7) for i in range(2)], + "output": [(0.05 + 0.25 * OPX_1000_ASPECT, 1.06 - i * PORT_SPACING_FACTOR) for i in range(8)], + "input": [(0.05 + 0.75 * OPX_1000_ASPECT, 1.06 - i * PORT_SPACING_FACTOR * 7) for i in range(2)], }, "opx+": { - "input": [(0.75, 1 - i * PORT_SPACING_FACTOR * 2) for i in range(2)], - "output": [(0.25 + j * PORT_SPACING_FACTOR, 1 - i * PORT_SPACING_FACTOR * 5) for i in range(2) for j in range(5)], - "digital_output": [(0.25 + j * 0.2, 1 - i * PORT_SPACING_FACTOR * 10) for i in range(2) for j in range(5)], + "input": [(1.1*3, 0.01 + 0.32 / (i+1)) for i in range(2)], + "output": [((0.7 + j * 0.06)*3, 0.01 + 0.32 / (i+1)) for j in range(5) for i in range(2)], + "digital_output": [((0.3 + j * 0.06)*3, 0.01 + 0.32 / (i+1)) for j in range(5) for i in range(2)], }, "octave": { - "input": [(0.75, 1 - i * PORT_SPACING_FACTOR * 2) for i in range(2)], - "output": [(0.25 + j * 0.2, 1 - i * PORT_SPACING_FACTOR * 10) for i in range(2) for j in range(5)], - "digital_output": [(0.25 + j * 0.2, 1 - i * PORT_SPACING_FACTOR * 10) for i in range(2) for j in range(5)], + "input": [((0.19 + j * 0.06)*3, 0.25) for j in range(2)], + "output": [((0.3 + j * 0.06)*3, 0.32) for j in range(5)], + "digital_output": [((0.3 + j * 0.06)*3, 0.18) for j in range(5)], } } diff --git a/qualang_tools/wirer/visualizer/instrument_figure_manager.py b/qualang_tools/wirer/visualizer/instrument_figure_manager.py index ebf3154f..acc369ac 100644 --- a/qualang_tools/wirer/visualizer/instrument_figure_manager.py +++ b/qualang_tools/wirer/visualizer/instrument_figure_manager.py @@ -23,17 +23,17 @@ def get_ax(self, con: int, slot: int, instrument_id: str) -> Axes: if instrument_id == "OPX1000": fig, axs = self._make_opx1000_figure() self.figures[key] = {i+1: ax for i, ax in enumerate(axs)} + fig.suptitle(f"con{con} - {instrument_id} Wiring Diagram", fontweight="bold", fontsize=14) elif instrument_id == "OPX+": - raise NotImplementedError() fig = self._make_opx_plus_figure() self.figures[key] = fig.axes[0] + fig.suptitle(f"con{con} - {instrument_id} Wiring Diagram", fontweight="bold", fontsize=14) elif instrument_id == "Octave": - raise NotImplementedError() fig = self._make_octave_figure() self.figures[key] = fig.axes[0] + fig.suptitle(f"oct{con} - {instrument_id} Wiring Diagram", fontweight="bold", fontsize=14) else: raise NotImplementedError() - fig.suptitle(f"con{con} - {instrument_id} Wiring Diagram", fontweight="bold", fontsize=14) return self.figures[key][slot] if slot is not None else self.figures[key] @@ -63,8 +63,9 @@ def _make_opx_plus_figure() -> Figure: figsize=(INSTRUMENT_FIGURE_DIMENSIONS["OPX+"]["width"] * 2, INSTRUMENT_FIGURE_DIMENSIONS["OPX+"]["height"] * 2) ) - ax.set_ylim([0.15, 1.15]) - ax.set_xlim([0.15 * 8, 1.15 * 8]) + ax.set_ylim([0.15 / 8 * 3, 1.15/ 8 * 3]) + print(1.15/8*3) + ax.set_xlim([0.15 * 3, 1.15 * 3]) ax.set_facecolor('darkgrey') ax.set_xticks([]) ax.set_yticks([]) diff --git a/qualang_tools/wirer/visualizer/port_annotation.py b/qualang_tools/wirer/visualizer/port_annotation.py index 8535966e..4eb8e9b1 100644 --- a/qualang_tools/wirer/visualizer/port_annotation.py +++ b/qualang_tools/wirer/visualizer/port_annotation.py @@ -9,47 +9,52 @@ from .constants import PORT_POSITIONS, PORT_SIZE, PORT_SPACING_FACTOR class PortAnnotation: - def __init__(self, labels, color, con, slot, io_type, port, instrument_id): + def __init__(self, labels, color, con, slot, port, io_type, signal_type, instrument_id): self.labels = labels self.color = color self.con = con self.slot = slot - self.io_type = io_type self.port = port + self.io_type = io_type + self.signal_type = signal_type self.instrument_id = instrument_id def draw(self, ax: Axes): + if self.signal_type == "digital": + return pos = PORT_POSITIONS[self.instrument_id][self.io_type][self.port - 1] x, y = pos fill_color = self.color if self.labels else "none" ax.add_patch(patches.Circle((x, y), PORT_SIZE, edgecolor="black", facecolor=fill_color)) labels = combine_labels_for_same_line_type(self.labels) - # port annotation - ax.text( - x, - y, - str(self.port), - ha="center", - va="center", - fontsize=10, - color=get_contrast_color(self.color), - ) + # # port annotation + # ax.text( + # x, + # y, + # str(self.port), + # ha="center", + # va="center", + # fontsize=10, + # color=get_contrast_color(self.color), + # ) for i, label in enumerate(labels): # qubit line annotation ax.text( - x - PORT_SPACING_FACTOR / 1.75, + x, # - PORT_SPACING_FACTOR / 2.25, y + (PORT_SPACING_FACTOR / 1.75) * i, label, - ha="right", + ha="center", va="center", fontsize=12, color="black", - bbox=dict(facecolor="white", alpha=1, edgecolor="none"), + fontweight="bold", + bbox=dict(facecolor="white", alpha=1.0, edgecolor="none"), ) ax.set_facecolor('lightgrey') def title_axes(self, ax: Axes): - ax.set_title(f"{self.slot}: {self.instrument_id}", fontweight="bold", y=-0.1, va="bottom") + if self.slot is not None: + ax.set_title(f"{self.slot}: {self.instrument_id}", fontweight="bold", y=-0.1, va="bottom") def get_contrast_color(color): diff --git a/qualang_tools/wirer/visualizer/visualizer.py b/qualang_tools/wirer/visualizer/visualizer.py index 3e369c3c..caa4be70 100644 --- a/qualang_tools/wirer/visualizer/visualizer.py +++ b/qualang_tools/wirer/visualizer/visualizer.py @@ -12,7 +12,7 @@ def invert_qubit_dict(qubit_dict) -> dict: for qubit_ref, element in qubit_dict.items(): for channel_type, channels in element.channels.items(): for channel in channels: - key = (channel.con, channel.slot, channel.io_type, channel.port, channel.instrument_id, channel_type) + key = (channel.con, channel.slot, channel.port, channel.io_type, channel.signal_type, channel.instrument_id, channel_type) if key not in inverted_dict: inverted_dict[key] = [] annotation = f"q{qubit_ref.index if hasattr(qubit_ref, 'index') else f'{qubit_ref.control_index}{qubit_ref.target_index}'}.{channel_type.value}" @@ -22,10 +22,10 @@ def invert_qubit_dict(qubit_dict) -> dict: def prepare_annotations(inverted_dict: dict) -> List[PortAnnotation]: annotations = [] for key, values in inverted_dict.items(): - con, slot, io_type, port, instrument_id, channel_type = key + con, slot, port, io_type, signal_type, instrument_id, channel_type = key labels = [value[0] for value in values] color = get_color_for_line_type(channel_type) - annotations.append(PortAnnotation(labels, color, con, slot, io_type, port, instrument_id)) + annotations.append(PortAnnotation(labels, color, con, slot, port, io_type, signal_type, instrument_id)) return annotations @@ -35,7 +35,7 @@ def prepare_available_channel_annotations(available_channels: InstrumentChannels for _, channel_list in available_channels.stack.items(): for channel in channel_list: color = get_color_for_line_type(None) - annotations.append(PortAnnotation([""], color, channel.con, channel.slot, channel.io_type, channel.port, channel.instrument_id)) + annotations.append(PortAnnotation([""], color, channel.con, channel.slot, channel.port, channel.io_type, channel.signal_type, channel.instrument_id)) return annotations @@ -66,9 +66,10 @@ def visualize(qubit_dict, available_channels=None): # Manage figures and draw manager = InstrumentFigureManager() - draw_annotations(manager, annotations) if available_channels is not None: available_channel_annotations = prepare_available_channel_annotations(available_channels) draw_annotations(manager, available_channel_annotations) + draw_annotations(manager, annotations) + plt.show() diff --git a/qualang_tools/wirer/wirer/channel_specs.py b/qualang_tools/wirer/wirer/channel_specs.py index f34cf388..72166d94 100644 --- a/qualang_tools/wirer/wirer/channel_specs.py +++ b/qualang_tools/wirer/wirer/channel_specs.py @@ -2,7 +2,8 @@ from qualang_tools.wirer.instruments.instrument_channel import InstrumentChannel, InstrumentChannelLfFemInput, \ InstrumentChannelLfFemOutput, InstrumentChannelMwFemInput, InstrumentChannelMwFemOutput, \ InstrumentChannelOpxPlusInput, InstrumentChannelOpxPlusOutput, InstrumentChannelOctaveInput, \ - InstrumentChannelOctaveOutput + InstrumentChannelOctaveOutput, InstrumentChannelOpxPlusDigitalOutput, InstrumentChannelMwFemDigitalOutput, \ + InstrumentChannelLfFemDigitalOutput, InstrumentChannelOctaveDigitalInput # A channel template is a partially filled InstrumentChannel object ChannelTemplate = InstrumentChannel @@ -77,9 +78,72 @@ def __init__(self, index: int = None, rf_out: int = None, rf_in: int = None): ] +class ChannelSpecLfFemBasebandAndOctave(ChannelSpec): + def __init__(self, + con: int = None, slot: int = None, + in_port_i: int = None, in_port_q: int = None, + out_port_i: int = None, out_port_q: int = None, + octave_index: int = None, rf_out: int = None, rf_in: int = None): + super().__init__() + self.channel_templates = [ + InstrumentChannelLfFemInput(con=con, slot=slot, port=in_port_i), + InstrumentChannelLfFemInput(con=con, slot=slot, port=in_port_q), + InstrumentChannelLfFemOutput(con=con, slot=slot, port=out_port_i), + InstrumentChannelLfFemOutput(con=con, slot=slot, port=out_port_q), + InstrumentChannelOctaveInput(con=octave_index, port=rf_in), + InstrumentChannelOctaveOutput(con=octave_index, port=rf_out), + ] + + +class ChannelSpecOpxPlusBasebandAndOctave(ChannelSpec): + def __init__(self, + con: int = None, + in_port_i: int = None, in_port_q: int = None, + out_port_i: int = None, out_port_q: int = None, + octave_index: int = None, rf_out: int = None, rf_in: int = None): + super().__init__() + self.channel_templates = [ + InstrumentChannelOpxPlusInput(con=con, port=in_port_i), + InstrumentChannelOpxPlusInput(con=con, port=in_port_q), + InstrumentChannelOpxPlusOutput(con=con, port=out_port_i), + InstrumentChannelOpxPlusOutput(con=con, port=out_port_q), + InstrumentChannelOctaveInput(con=octave_index, port=rf_in), + InstrumentChannelOctaveOutput(con=octave_index, port=rf_out), + ] + +class ChannelSpecOpxPlusDigital(ChannelSpec): + def __init__(self, con: int = None, out_port: int = None): + super().__init__() + self.channel_templates = [ + InstrumentChannelOpxPlusDigitalOutput(con=con, port=out_port) + ] + +class ChannelSpecMwFemDigital(ChannelSpec): + def __init__(self, con: int = None, out_port: int = None): + super().__init__() + self.channel_templates = [ + InstrumentChannelMwFemDigitalOutput(con=con, port=out_port) + ] + +class ChannelSpecLfFemDigital(ChannelSpec): + def __init__(self, con: int = None, out_port: int = None): + super().__init__() + self.channel_templates = [ + InstrumentChannelLfFemDigitalOutput(con=con, port=out_port) + ] + +class ChannelSpecOctaveDigital(ChannelSpec): + def __init__(self, con: int = None, in_port: int = None): + super().__init__() + self.channel_templates = [ + InstrumentChannelOctaveDigitalInput(con=con, port=in_port) + ] + mw_fem_spec = ChannelSpecMwFemSingle lf_fem_spec = ChannelSpecLfFemSingle lf_fem_iq_spec = ChannelSpecLfFemBaseband +lf_fem_iq_octave_spec = ChannelSpecLfFemBasebandAndOctave opx_spec = ChannelSpecOpxPlusSingle opx_iq_spec = ChannelSpecOpxPlusBaseband +opx_iq_octave_spec = ChannelSpecOpxPlusBasebandAndOctave octave_spec = ChannelSpecOctave diff --git a/qualang_tools/wirer/wirer/channel_specs_interface.py b/qualang_tools/wirer/wirer/channel_specs_interface.py new file mode 100644 index 00000000..19504366 --- /dev/null +++ b/qualang_tools/wirer/wirer/channel_specs_interface.py @@ -0,0 +1,19 @@ +from .channel_specs import ( + ChannelSpecMwFemSingle, + ChannelSpecLfFemSingle, + ChannelSpecLfFemBaseband, + ChannelSpecLfFemBasebandAndOctave, + ChannelSpecOpxPlusSingle, + ChannelSpecOpxPlusBaseband, + ChannelSpecOpxPlusBasebandAndOctave, + ChannelSpecOctave +) + +mw_fem_spec = ChannelSpecMwFemSingle +lf_fem_spec = ChannelSpecLfFemSingle +lf_fem_iq_spec = ChannelSpecLfFemBaseband +lf_fem_iq_octave_spec = ChannelSpecLfFemBasebandAndOctave +opx_spec = ChannelSpecOpxPlusSingle +opx_iq_spec = ChannelSpecOpxPlusBaseband +opx_iq_octave_spec = ChannelSpecOpxPlusBasebandAndOctave +octave_spec = ChannelSpecOctave diff --git a/qualang_tools/wirer/wirer/wirer.py b/qualang_tools/wirer/wirer/wirer.py index c87df807..18f09b0e 100644 --- a/qualang_tools/wirer/wirer/wirer.py +++ b/qualang_tools/wirer/wirer/wirer.py @@ -1,13 +1,23 @@ +""" +The purpose of this module is to compile a sequence of wiring specifications +into potential channel specifications, then to allocate them to the first valid +combination of instrument channels. +""" +from typing import List + from .channel_specs import ChannelSpecLfFemSingle, ChannelSpecOpxPlusSingle, ChannelSpecMwFemSingle, \ - ChannelSpecLfFemBaseband, ChannelSpecOctave, ChannelSpecOpxPlusBaseband + ChannelSpecLfFemBaseband, ChannelSpecOctave, ChannelSpecOpxPlusBaseband, ChannelSpecMwFemDigital, \ + ChannelSpecLfFemDigital, ChannelSpecOctaveDigital, ChannelSpecOpxPlusDigital from .wirer_assign_channels_to_spec import assign_channels_to_spec -from .wirer_exception import WirerException +from .wirer_exceptions import ConstraintsTooStrictException, NotEnoughChannelsException +from ..connectivity.channel_spec import ChannelSpec from ..instruments import Instruments from ..connectivity import Connectivity from ..connectivity.wiring_spec import WiringSpec, WiringFrequency, WiringLineType -def allocate_wiring(connectivity: Connectivity, instruments: Instruments): +def allocate_wiring(connectivity: Connectivity, instruments: Instruments, + block_used_channels: bool = True, clear_wiring_specifications: bool = True): line_type_fill_order = [ WiringLineType.RESONATOR, WiringLineType.DRIVE, @@ -19,10 +29,19 @@ def allocate_wiring(connectivity: Connectivity, instruments: Instruments): for line_type in line_type_fill_order: for spec in specs: if spec.line_type == line_type: - _allocate_channels(spec, instruments) + _allocate_wiring(spec, instruments) + + if clear_wiring_specifications: + connectivity.specs = [] + + if not block_used_channels: + for channel_type, used_channels in instruments.used_channels.items(): + for i, used_channel in enumerate(reversed(used_channels)): + instruments.available_channels.insert(0, used_channel) + instruments.used_channels.remove(used_channel) -def _allocate_channels(spec: WiringSpec, instruments: Instruments): +def _allocate_wiring(spec: WiringSpec, instruments: Instruments): if spec.frequency == WiringFrequency.DC: allocate_dc_channels(spec, instruments) @@ -37,19 +56,12 @@ def allocate_dc_channels(spec: WiringSpec, instruments: Instruments): """ Try to allocate DC channels to an LF-FEM or OPX+ to satisfy the spec. """ - if not spec.channel_specs: - spec.channel_specs = [ - ChannelSpecLfFemSingle(), - ChannelSpecOpxPlusSingle() - ] - - for channel_spec in spec.channel_specs: - channel_templates = spec.get_channel_template_from_spec(channel_spec) - if assign_channels_to_spec(spec, instruments, channel_templates, same_con=True, same_slot=True): - return - - raise WirerException(spec) + dc_specs = [ + ChannelSpecLfFemSingle(), + ChannelSpecOpxPlusSingle() + ] + allocate_channels(spec, dc_specs, instruments, same_con=True, same_slot=True) def allocate_rf_channels(spec: WiringSpec, instruments: Instruments): """ @@ -57,16 +69,29 @@ def allocate_rf_channels(spec: WiringSpec, instruments: Instruments): combination of LF-FEM I/Q and Octave channels, or OPX+ I/Q and Octave channels. """ - if not spec.channel_specs: - spec.channel_specs = [ - ChannelSpecMwFemSingle(), - ChannelSpecLfFemBaseband() & ChannelSpecOctave(), - ChannelSpecOpxPlusBaseband() & ChannelSpecOctave(), - ] - - for channel_spec in spec.channel_specs: - channel_templates = spec.get_channel_template_from_spec(channel_spec) - if assign_channels_to_spec(spec, instruments, channel_templates, same_con=True, same_slot=True): + rf_specs = [ + ChannelSpecMwFemSingle() & ChannelSpecMwFemDigital(), + ChannelSpecLfFemBaseband() & ChannelSpecLfFemDigital() & ChannelSpecOctave() & ChannelSpecOctaveDigital(), + ChannelSpecOpxPlusBaseband() & ChannelSpecOpxPlusDigital() & ChannelSpecOctave() & ChannelSpecOctaveDigital() + ] + + allocate_channels(spec, rf_specs, instruments, same_con=True, same_slot=True) + + +def allocate_channels(wiring_spec: WiringSpec, channel_specs: List[ChannelSpec], + instruments: Instruments, same_con: bool, same_slot: bool): + mask_failures = 0 + for channel_spec in channel_specs: + channel_spec = channel_spec.filter_by_wiring_spec(wiring_spec) + if wiring_spec.constraints: + constraints = wiring_spec.constraints.filter_by_wiring_spec(wiring_spec) + if not channel_spec.apply_constraints(constraints): + mask_failures += 1 + continue + if assign_channels_to_spec(wiring_spec, instruments, channel_spec.channel_templates, same_con=same_con, same_slot=same_slot): return - raise WirerException(spec) + if mask_failures == len(channel_specs): + raise ConstraintsTooStrictException(wiring_spec, wiring_spec.constraints) + else: + raise NotEnoughChannelsException(wiring_spec) diff --git a/qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py b/qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py index 2c0bb613..04a301a7 100644 --- a/qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py +++ b/qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py @@ -21,6 +21,7 @@ def assign_channels_to_spec( if len(candidate_channels) == len(channel_templates): for channel in candidate_channels: # remove candidate channel from stack of available channels + instruments.used_channels.add(channel) instruments.available_channels[type(channel)].remove(channel) for element in spec.elements: # assign channel to the specified element diff --git a/qualang_tools/wirer/wirer/wirer_exception.py b/qualang_tools/wirer/wirer/wirer_exception.py deleted file mode 100644 index f1b012c9..00000000 --- a/qualang_tools/wirer/wirer/wirer_exception.py +++ /dev/null @@ -1,11 +0,0 @@ -from ..connectivity.wiring_spec import WiringSpec - - -class WirerException(Exception): - def __init__(self, wiring_spec: WiringSpec): - message = ( - f"Couldn't find available {wiring_spec.frequency.value} {wiring_spec.io_type.value} channels " - f"satisfying the following specfication for the {wiring_spec.line_type.value} line for elements " - f"{','.join([str(e.id) for e in wiring_spec.elements])}" - ) - super(WirerException, self).__init__(message) diff --git a/qualang_tools/wirer/wirer/wirer_exceptions.py b/qualang_tools/wirer/wirer/wirer_exceptions.py new file mode 100644 index 00000000..61361b3a --- /dev/null +++ b/qualang_tools/wirer/wirer/wirer_exceptions.py @@ -0,0 +1,26 @@ +from typing import List + +from ..connectivity.channel_spec import ChannelSpec +from ..connectivity.wiring_spec import WiringSpec + + +class ConstraintsTooStrictException(Exception): + def __init__(self, wiring_spec: WiringSpec, constraints: List[ChannelSpec]): + message = ( + f"Failed to find a valid channel template for {wiring_spec.frequency} " + f"{wiring_spec.io_type.value} channels on the " + f"{wiring_spec.line_type.value} line for elements " + f"{','.join([str(e.id) for e in wiring_spec.elements])} with the " + f"following constraints: {constraints}" + ) + super(ConstraintsTooStrictException, self).__init__(message) + +class NotEnoughChannelsException(Exception): + def __init__(self, wiring_spec: WiringSpec): + message = ( + f"Couldn't find enough available {wiring_spec.frequency.value} " + f"{wiring_spec.io_type.value} channels satisfying the wiring " + f"specfication for the {wiring_spec.line_type.value} line for elements " + f"{','.join([str(e.id) for e in wiring_spec.elements])}" + ) + super(NotEnoughChannelsException, self).__init__(message) diff --git a/tests/wirer/conftest.py b/tests/wirer/conftest.py index 4ace1678..07855f8f 100644 --- a/tests/wirer/conftest.py +++ b/tests/wirer/conftest.py @@ -7,9 +7,9 @@ def instruments_qw_soprano(request) -> Instruments: print(request.param) instruments = Instruments() if request.param == "lf-fem": - instruments.add_lf_fem(con=1, slots=[1, 2, 3]) + instruments.add_lf_fem(controller=1, slots=[1, 2, 3]) elif request.param == "opx+": - instruments.add_opx_plus(cons=[1,2]) + instruments.add_opx_plus(controllers=[1,2]) instruments.add_octave(indices=[1,2]) return instruments @@ -17,9 +17,9 @@ def instruments_qw_soprano(request) -> Instruments: def instruments_1OPX1Octave(request) -> Instruments: instruments = Instruments() if request.param == "lf-fem": - instruments.add_lf_fem(con=1, slots=[1]) + instruments.add_lf_fem(controller=1, slots=[1]) elif request.param == "opx+": - instruments.add_opx_plus(cons=1) + instruments.add_opx_plus(controllers=[1]) instruments.add_octave(indices=1) return instruments @@ -27,9 +27,9 @@ def instruments_1OPX1Octave(request) -> Instruments: def instruments_1octave(request) -> Instruments: instruments = Instruments() if request.param == "lf-fem": - instruments.add_lf_fem(con=1, slots=[1, 2]) + instruments.add_lf_fem(controller=1, slots=[1, 2]) elif request.param == "opx+": - instruments.add_opx_plus(cons=1) + instruments.add_opx_plus(controllers=[1]) instruments.add_octave(indices=1) return instruments @@ -37,6 +37,6 @@ def instruments_1octave(request) -> Instruments: @pytest.fixture() def instruments_2lf_2mw() -> Instruments: instruments = Instruments() - instruments.add_lf_fem(con=1, slots=[1, 2]) - instruments.add_mw_fem(con=1, slots=[3, 7]) + instruments.add_lf_fem(controller=1, slots=[1, 2]) + instruments.add_mw_fem(controller=1, slots=[3, 7]) return instruments diff --git a/tests/wirer/test_wirer_channel_reuse.py b/tests/wirer/test_wirer_channel_reuse.py new file mode 100644 index 00000000..7ce87349 --- /dev/null +++ b/tests/wirer/test_wirer_channel_reuse.py @@ -0,0 +1,21 @@ +from qualang_tools.wirer import * + +visualize_flag = True + +def test_5q_allocation_with_channel_reuse(instruments_2lf_2mw): + connectivity = Connectivity() + for i in [0, 2]: + qubits = [1+i, 2+i] + qubit_pairs = [(1+i, 2+i)] + + connectivity.add_resonator_line(qubits=qubits) + connectivity.add_qubit_drive_lines(qubits=qubits) + connectivity.add_qubit_flux_lines(qubits=qubits) + connectivity.add_qubit_pair_flux_lines(qubit_pairs=qubit_pairs) + + allocate_wiring(connectivity, instruments_2lf_2mw, block_used_channels=False) + + print(connectivity.elements) + + if visualize_flag: + visualize(connectivity.elements, instruments_2lf_2mw.available_channels) diff --git a/tests/wirer/test_wirer_constraining.py b/tests/wirer/test_wirer_constraining.py new file mode 100644 index 00000000..d2dc3ac0 --- /dev/null +++ b/tests/wirer/test_wirer_constraining.py @@ -0,0 +1,20 @@ +from qualang_tools.wirer import * + +visualize_flag = True + +def test_5q_allocation(instruments_2lf_2mw): + connectivity = Connectivity() + qubits = [1, 2] + qubit_pairs = [(1, 2)] + + connectivity.add_resonator_line(qubits=qubits, triggered=True) + connectivity.add_qubit_drive_lines(qubits=qubits) + connectivity.add_qubit_flux_lines(qubits=qubits) + connectivity.add_qubit_pair_flux_lines(qubit_pairs=qubit_pairs) + + allocate_wiring(connectivity, instruments_2lf_2mw) + + print(connectivity.elements) + + if visualize_flag: + visualize(connectivity.elements, instruments_2lf_2mw.available_channels) diff --git a/tests/wirer/test_wirer_digital.py b/tests/wirer/test_wirer_digital.py new file mode 100644 index 00000000..d2dc3ac0 --- /dev/null +++ b/tests/wirer/test_wirer_digital.py @@ -0,0 +1,20 @@ +from qualang_tools.wirer import * + +visualize_flag = True + +def test_5q_allocation(instruments_2lf_2mw): + connectivity = Connectivity() + qubits = [1, 2] + qubit_pairs = [(1, 2)] + + connectivity.add_resonator_line(qubits=qubits, triggered=True) + connectivity.add_qubit_drive_lines(qubits=qubits) + connectivity.add_qubit_flux_lines(qubits=qubits) + connectivity.add_qubit_pair_flux_lines(qubit_pairs=qubit_pairs) + + allocate_wiring(connectivity, instruments_2lf_2mw) + + print(connectivity.elements) + + if visualize_flag: + visualize(connectivity.elements, instruments_2lf_2mw.available_channels) diff --git a/tests/wirer/test_wirer_lf_and_mw.py b/tests/wirer/test_wirer_lf_and_mw.py index a863da01..76eff4a2 100644 --- a/tests/wirer/test_wirer_lf_and_mw.py +++ b/tests/wirer/test_wirer_lf_and_mw.py @@ -1,26 +1,22 @@ -from qualang_tools.wirer.connectivity.connectivity import Connectivity -from qualang_tools.wirer.instruments import Instruments -from qualang_tools.wirer.visualizer.visualizer import visualize -from qualang_tools.wirer.wirer import allocate_wiring -from qualang_tools.wirer.wirer.channel_specs import mw_fem_spec, ChannelSpecLfFemSingle +from qualang_tools.wirer import * -visualize = True +visualize_flag = True def test_5q_allocation(instruments_2lf_2mw): qubits = [1, 2, 3, 4, 5] qubit_pairs = [(1, 2), (2, 3), (3, 4), (4, 5)] connectivity = Connectivity() - connectivity.add_resonator_line(qubits=qubits)#, channel_spec=mw_fem_spec(slot=7)) - connectivity.add_qubit_drive_lines(qubits=qubits)#, channel_spec=mw_fem_spec(slot=7)) + connectivity.add_resonator_line(qubits=qubits, constraints=mw_fem_spec(slot=7)) + connectivity.add_qubit_drive_lines(qubits=qubits, constraints=mw_fem_spec(slot=7)) connectivity.add_qubit_flux_lines(qubits=qubits) - connectivity.add_qubit_pair_flux_lines(qubit_pairs=qubit_pairs, channel_spec=ChannelSpecLfFemSingle(out_slot=2)) + connectivity.add_qubit_pair_flux_lines(qubit_pairs=qubit_pairs, constraints=lf_fem_spec(out_slot=2)) allocate_wiring(connectivity, instruments_2lf_2mw) print(connectivity.elements) - if visualize: + if visualize_flag: visualize(connectivity.elements, instruments_2lf_2mw.available_channels) @@ -34,14 +30,14 @@ def test_4rr_allocation(instruments_2lf_2mw): allocate_wiring(connectivity, instruments_2lf_2mw) - if visualize: + if visualize_flag: visualize(connectivity.elements) def test_6rr_6xy_6flux_allocation(): instruments = Instruments() - instruments.add_lf_fem(con=1, slots=1) - instruments.add_mw_fem(con=1, slots=2) + instruments.add_lf_fem(controller=1, slots=1) + instruments.add_mw_fem(controller=1, slots=2) qubits = [1, 2, 3, 4, 5, 6] connectivity = Connectivity() @@ -51,5 +47,5 @@ def test_6rr_6xy_6flux_allocation(): allocate_wiring(connectivity, instruments) - if visualize: + if visualize_flag: visualize(connectivity.elements, instruments.available_channels) \ No newline at end of file diff --git a/tests/wirer/test_wirer_lf_and_octave.py b/tests/wirer/test_wirer_lf_and_octave.py index 97d3d0f3..1d9e2bbb 100644 --- a/tests/wirer/test_wirer_lf_and_octave.py +++ b/tests/wirer/test_wirer_lf_and_octave.py @@ -1,22 +1,20 @@ -from qualang_tools.wirer.connectivity.connectivity import Connectivity -from qualang_tools.wirer.visualizer.visualizer import visualize -from qualang_tools.wirer.wirer import allocate_wiring +from qualang_tools.wirer import * from pprint import pprint -visualize = True +visualize_flag = True def test_rf_io_allocation(instruments_1octave): - qubits = [1,2,3,4,5] + qubits = [1,2,3,4] connectivity = Connectivity() - # connectivity.add_resonator_line(qubits=qubits) + connectivity.add_resonator_line(qubits=qubits) connectivity.add_qubit_drive_lines(qubits=qubits) allocate_wiring(connectivity, instruments_1octave) pprint(connectivity.elements) - if visualize: - visualize(connectivity.elements) + if visualize_flag: + visualize(connectivity.elements, available_channels=instruments_1octave.available_channels) def test_qw_soprano_allocation(instruments_qw_soprano): qubits = [1, 2, 3, 4, 5] @@ -30,25 +28,24 @@ def test_qw_soprano_allocation(instruments_qw_soprano): pprint(connectivity.elements) - if visualize: - visualize(connectivity.elements) + if visualize_flag: + visualize(connectivity.elements, available_channels=instruments_qw_soprano.available_channels) def test_qw_soprano_2qb_allocation(instruments_1OPX1Octave): active_qubits = [1, 2] connectivity = Connectivity() - # TODO: is the port here the Octave port? - connectivity.add_resonator_line(qubits=active_qubits, con=1, port=2) - connectivity.add_qubit_drive_lines(qubits=[1], con=1, port=2) - connectivity.add_qubit_drive_lines(qubits=[2], con=1, port=4) + connectivity.add_resonator_line(qubits=active_qubits, channel_spec=opx_iq_octave_spec(rf_out=1)) + connectivity.add_qubit_drive_lines(qubits=[1], channel_spec=opx_iq_octave_spec(rf_out=2)) + connectivity.add_qubit_drive_lines(qubits=[2], channel_spec=opx_iq_octave_spec(rf_out=4)) connectivity.add_qubit_flux_lines(qubits=active_qubits) allocate_wiring(connectivity, instruments_1OPX1Octave) pprint(connectivity.elements) - # if visualize: - # visualize_chassis(connectivity.elements) + if visualize_flag: + visualize(connectivity.elements, available_channels=instruments_1OPX1Octave.available_channels) def test_qw_soprano_2qb_among_5_allocation(instruments_1OPX1Octave): all_qubits = [1, 2, 3, 4, 5] @@ -61,17 +58,19 @@ def test_qw_soprano_2qb_among_5_allocation(instruments_1OPX1Octave): connectivity.add_qubit_drive_lines(qubits=[1], con=1, port=2) connectivity.add_qubit_drive_lines(qubits=[2], con=1, port=4) connectivity.add_qubit_flux_lines(qubits=active_qubits) + allocate_wiring(connectivity, instruments_1OPX1Octave, keep_channels_free=True) + # TODO: I want to add here the remaining qubits so that the QuAM can be created for the entire chip. # I thus connect the other qubits to the same ports as the active qubits. # Can I have the same ports used in several qubits as it is done for the resonator line? connectivity.add_resonator_line(qubits=other_qubits, con=1, port=1) connectivity.add_qubit_drive_lines(qubits=other_qubits, con=1, port=2) connectivity.add_qubit_flux_lines(qubits=other_qubits, con=1, port=10) - + allocate_wiring(connectivity, instruments_1OPX1Octave) allocate_wiring(connectivity, instruments_1OPX1Octave) pprint(connectivity.elements) - if visualize: + if visualize_flag: visualize(connectivity.elements) From afc66d16910b136b7e2550ca9a3ad4b082fb9821 Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Thu, 29 Aug 2024 01:18:19 +1000 Subject: [PATCH 10/40] Clean up plotting. --- qualang_tools/wirer/visualizer/constants.py | 2 +- .../wirer/visualizer/port_annotation.py | 31 ++++++++++--------- qualang_tools/wirer/visualizer/visualizer.py | 8 ++--- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/qualang_tools/wirer/visualizer/constants.py b/qualang_tools/wirer/visualizer/constants.py index ecc13b49..dd1913ad 100644 --- a/qualang_tools/wirer/visualizer/constants.py +++ b/qualang_tools/wirer/visualizer/constants.py @@ -19,7 +19,7 @@ INSTRUMENT_FIGURE_DIMENSIONS["OPX1000"]["width"]) # Define the port positions for different modules PORT_SPACING_FACTOR = 0.12 -PORT_SIZE = 0.05 +PORT_SIZE = 0.055 PORT_POSITIONS = { "lf-fem": { "output": [(0.05 + 0.25 * OPX_1000_ASPECT, 1.06 - i * PORT_SPACING_FACTOR) for i in range(8)], diff --git a/qualang_tools/wirer/visualizer/port_annotation.py b/qualang_tools/wirer/visualizer/port_annotation.py index 4eb8e9b1..f719186d 100644 --- a/qualang_tools/wirer/visualizer/port_annotation.py +++ b/qualang_tools/wirer/visualizer/port_annotation.py @@ -25,30 +25,31 @@ def draw(self, ax: Axes): pos = PORT_POSITIONS[self.instrument_id][self.io_type][self.port - 1] x, y = pos fill_color = self.color if self.labels else "none" - ax.add_patch(patches.Circle((x, y), PORT_SIZE, edgecolor="black", facecolor=fill_color)) + ax.add_patch(patches.Circle((x, y), PORT_SIZE, edgecolor="dimgrey", facecolor=fill_color)) labels = combine_labels_for_same_line_type(self.labels) - # # port annotation - # ax.text( - # x, - # y, - # str(self.port), - # ha="center", - # va="center", - # fontsize=10, - # color=get_contrast_color(self.color), - # ) + # port annotation + ax.text( + x - PORT_SPACING_FACTOR / 1.65, + y, + str(self.port), + ha="center", + va="center", + fontsize=8, + fontweight="bold", + color="dimgrey", + ) for i, label in enumerate(labels): # qubit line annotation ax.text( - x, # - PORT_SPACING_FACTOR / 2.25, + x, y + (PORT_SPACING_FACTOR / 1.75) * i, label, ha="center", va="center", - fontsize=12, + fontsize=9, color="black", - fontweight="bold", - bbox=dict(facecolor="white", alpha=1.0, edgecolor="none"), + # fontweight="bold", + # bbox=dict(facecolor="white", alpha=0.7, edgecolor="none"), ) ax.set_facecolor('lightgrey') diff --git a/qualang_tools/wirer/visualizer/visualizer.py b/qualang_tools/wirer/visualizer/visualizer.py index caa4be70..3c3156e3 100644 --- a/qualang_tools/wirer/visualizer/visualizer.py +++ b/qualang_tools/wirer/visualizer/visualizer.py @@ -42,10 +42,10 @@ def prepare_available_channel_annotations(available_channels: InstrumentChannels def get_color_for_line_type(line_type) -> str: color_map = { - WiringLineType.FLUX: "blue", - WiringLineType.RESONATOR: "orange", - WiringLineType.DRIVE: "yellow", - WiringLineType.COUPLER: "purple" + WiringLineType.FLUX: "powderblue", + WiringLineType.RESONATOR: "peachpuff", + WiringLineType.DRIVE: "lemonchiffon", + WiringLineType.COUPLER: "thistle" } return color_map.get(line_type, "white") From 743275e8f1032134414dc3d7ff5e7cefa56185f1 Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Thu, 29 Aug 2024 01:24:04 +1000 Subject: [PATCH 11/40] Clean up plotting. --- .../wirer/visualizer/port_annotation.py | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/qualang_tools/wirer/visualizer/port_annotation.py b/qualang_tools/wirer/visualizer/port_annotation.py index f719186d..5b2d0944 100644 --- a/qualang_tools/wirer/visualizer/port_annotation.py +++ b/qualang_tools/wirer/visualizer/port_annotation.py @@ -29,7 +29,7 @@ def draw(self, ax: Axes): labels = combine_labels_for_same_line_type(self.labels) # port annotation ax.text( - x - PORT_SPACING_FACTOR / 1.65, + x - PORT_SIZE * 1.3, y, str(self.port), ha="center", @@ -38,19 +38,18 @@ def draw(self, ax: Axes): fontweight="bold", color="dimgrey", ) - for i, label in enumerate(labels): - # qubit line annotation - ax.text( - x, - y + (PORT_SPACING_FACTOR / 1.75) * i, - label, - ha="center", - va="center", - fontsize=9, - color="black", - # fontweight="bold", - # bbox=dict(facecolor="white", alpha=0.7, edgecolor="none"), - ) + # qubit line annotation + ax.text( + x, + y, + "\n".join(labels), + ha="center", + va="center", + fontsize=9, + color="black", + # fontweight="bold", + # bbox=dict(facecolor="white", alpha=0.7, edgecolor="none"), + ) ax.set_facecolor('lightgrey') def title_axes(self, ax: Axes): From 9e1f253f1b85b53b1bb24d6da695eb8c99672b2b Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Thu, 29 Aug 2024 15:35:06 +1000 Subject: [PATCH 12/40] Remove bolding from Slot title. --- qualang_tools/wirer/visualizer/port_annotation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qualang_tools/wirer/visualizer/port_annotation.py b/qualang_tools/wirer/visualizer/port_annotation.py index 5b2d0944..3c28db15 100644 --- a/qualang_tools/wirer/visualizer/port_annotation.py +++ b/qualang_tools/wirer/visualizer/port_annotation.py @@ -54,7 +54,7 @@ def draw(self, ax: Axes): def title_axes(self, ax: Axes): if self.slot is not None: - ax.set_title(f"{self.slot}: {self.instrument_id}", fontweight="bold", y=-0.1, va="bottom") + ax.set_title(f"{self.slot}: {self.instrument_id}", y=-0.1, va="bottom") def get_contrast_color(color): From e147b4af87346ace22728f03a272d1dc3aa0548b Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Fri, 30 Aug 2024 18:39:16 +1000 Subject: [PATCH 13/40] Fix unwanted import. --- qualang_tools/wirer/connectivity/channel_spec.py | 2 -- tests/wirer/test_wirer_lf_and_octave.py | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/qualang_tools/wirer/connectivity/channel_spec.py b/qualang_tools/wirer/connectivity/channel_spec.py index 2608f383..63ce878e 100644 --- a/qualang_tools/wirer/connectivity/channel_spec.py +++ b/qualang_tools/wirer/connectivity/channel_spec.py @@ -1,6 +1,4 @@ from collections import Counter -from os import setsid -from typing import List from qualang_tools.wirer.connectivity.wiring_spec import WiringSpec from qualang_tools.wirer.instruments.instrument_channel import AnyInstrumentChannel diff --git a/tests/wirer/test_wirer_lf_and_octave.py b/tests/wirer/test_wirer_lf_and_octave.py index 1d9e2bbb..9ae443ea 100644 --- a/tests/wirer/test_wirer_lf_and_octave.py +++ b/tests/wirer/test_wirer_lf_and_octave.py @@ -35,9 +35,9 @@ def test_qw_soprano_2qb_allocation(instruments_1OPX1Octave): active_qubits = [1, 2] connectivity = Connectivity() - connectivity.add_resonator_line(qubits=active_qubits, channel_spec=opx_iq_octave_spec(rf_out=1)) - connectivity.add_qubit_drive_lines(qubits=[1], channel_spec=opx_iq_octave_spec(rf_out=2)) - connectivity.add_qubit_drive_lines(qubits=[2], channel_spec=opx_iq_octave_spec(rf_out=4)) + connectivity.add_resonator_line(qubits=active_qubits, constraints=octave_spec(rf_out=1)) + connectivity.add_qubit_drive_lines(qubits=[1], constraints=opx_iq_octave_spec(rf_out=2)) + connectivity.add_qubit_drive_lines(qubits=[2], constraints=opx_iq_octave_spec(rf_out=4)) connectivity.add_qubit_flux_lines(qubits=active_qubits) allocate_wiring(connectivity, instruments_1OPX1Octave) From 51336150cfdca2e5c96235a497d9b6ba0951ca18 Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Sat, 31 Aug 2024 03:18:54 +1000 Subject: [PATCH 14/40] Minor bug fixes. --- qualang_tools/wirer/instruments/instrument_channel.py | 2 +- qualang_tools/wirer/visualizer/instrument_figure_manager.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/qualang_tools/wirer/instruments/instrument_channel.py b/qualang_tools/wirer/instruments/instrument_channel.py index 8edecf23..2aac4e73 100644 --- a/qualang_tools/wirer/instruments/instrument_channel.py +++ b/qualang_tools/wirer/instruments/instrument_channel.py @@ -144,7 +144,7 @@ class InstrumentChannelOctaveOutput( class InstrumentChannelOctaveDigitalInput( InstrumentChannelDigital, InstrumentChannelOctave, - InstrumentChannelOutput, + InstrumentChannelInput, InstrumentChannel ): pass diff --git a/qualang_tools/wirer/visualizer/instrument_figure_manager.py b/qualang_tools/wirer/visualizer/instrument_figure_manager.py index acc369ac..44b3a5c7 100644 --- a/qualang_tools/wirer/visualizer/instrument_figure_manager.py +++ b/qualang_tools/wirer/visualizer/instrument_figure_manager.py @@ -64,7 +64,6 @@ def _make_opx_plus_figure() -> Figure: INSTRUMENT_FIGURE_DIMENSIONS["OPX+"]["height"] * 2) ) ax.set_ylim([0.15 / 8 * 3, 1.15/ 8 * 3]) - print(1.15/8*3) ax.set_xlim([0.15 * 3, 1.15 * 3]) ax.set_facecolor('darkgrey') ax.set_xticks([]) From 792c61341d14aea0e9c275dbbe68a57c4f797642 Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Sat, 31 Aug 2024 14:54:32 +1000 Subject: [PATCH 15/40] Fix bug where dataclass equality was based on attribute equality alone. --- .../wirer/instruments/instrument_channel.py | 42 +++++++++---------- tests/wirer/conftest.py | 3 ++ tests/wirer/test_instrument_validation.py | 36 ++++++++-------- tests/wirer/test_wirer_channel_reuse.py | 4 +- tests/wirer/test_wirer_constraining.py | 37 ++++++++++------ tests/wirer/test_wirer_digital.py | 6 ++- tests/wirer/test_wirer_lf_and_mw.py | 4 +- tests/wirer/test_wirer_lf_and_octave.py | 32 +++++++------- 8 files changed, 93 insertions(+), 71 deletions(-) diff --git a/qualang_tools/wirer/instruments/instrument_channel.py b/qualang_tools/wirer/instruments/instrument_channel.py index 2aac4e73..4ce92cd5 100644 --- a/qualang_tools/wirer/instruments/instrument_channel.py +++ b/qualang_tools/wirer/instruments/instrument_channel.py @@ -1,7 +1,7 @@ from dataclasses import dataclass, field from typing import Union, Literal, Callable -@dataclass +@dataclass(eq=False) class InstrumentChannel: con: int port: int @@ -18,40 +18,40 @@ def make_channel_filter(self) -> Callable[['InstrumentChannel'], bool]: ) -@dataclass +@dataclass(eq=False) class InstrumentChannelInput: io_type: Literal["input", "output"] = "input" -@dataclass +@dataclass(eq=False) class InstrumentChannelOutput: io_type: Literal["input", "output"] = "output" -@dataclass +@dataclass(eq=False) class InstrumentChannelDigital: signal_type = "digital" -@dataclass +@dataclass(eq=False) class InstrumentChannelAnalog: signal_type = "analog" -@dataclass +@dataclass(eq=False) class InstrumentChannelLfFem: instrument_id: Literal["lf-fem", "mw-fem", "opx+", "octave"] = "lf-fem" -@dataclass +@dataclass(eq=False) class InstrumentChannelMwFem: instrument_id: Literal["lf-fem", "mw-fem", "opx+", "octave"] = "mw-fem" -@dataclass +@dataclass(eq=False) class InstrumentChannelOpxPlus: instrument_id: Literal["lf-fem", "mw-fem", "opx+", "octave"] = "opx+" -@dataclass +@dataclass(eq=False) class InstrumentChannelOctave: instrument_id: Literal["lf-fem", "mw-fem", "opx+", "octave"] = "octave" -@dataclass +@dataclass(eq=False) class InstrumentChannelLfFemInput( InstrumentChannelAnalog, InstrumentChannelLfFem, @@ -59,7 +59,7 @@ class InstrumentChannelLfFemInput( InstrumentChannel ): pass -@dataclass +@dataclass(eq=False) class InstrumentChannelLfFemOutput( InstrumentChannelAnalog, InstrumentChannelLfFem, @@ -67,7 +67,7 @@ class InstrumentChannelLfFemOutput( InstrumentChannel ): pass -@dataclass +@dataclass(eq=False) class InstrumentChannelLfFemDigitalOutput( InstrumentChannelDigital, InstrumentChannelLfFem, @@ -76,7 +76,7 @@ class InstrumentChannelLfFemDigitalOutput( ): pass -@dataclass +@dataclass(eq=False) class InstrumentChannelMwFemInput( InstrumentChannelAnalog, InstrumentChannelMwFem, @@ -84,7 +84,7 @@ class InstrumentChannelMwFemInput( InstrumentChannel ): pass -@dataclass +@dataclass(eq=False) class InstrumentChannelMwFemOutput( InstrumentChannelAnalog, InstrumentChannelMwFem, @@ -92,7 +92,7 @@ class InstrumentChannelMwFemOutput( InstrumentChannel ): pass -@dataclass +@dataclass(eq=False) class InstrumentChannelMwFemDigitalOutput( InstrumentChannelDigital, InstrumentChannelMwFem, @@ -100,7 +100,7 @@ class InstrumentChannelMwFemDigitalOutput( InstrumentChannel ): pass -@dataclass +@dataclass(eq=False) class InstrumentChannelOpxPlusInput( InstrumentChannelAnalog, InstrumentChannelOpxPlus, @@ -108,7 +108,7 @@ class InstrumentChannelOpxPlusInput( InstrumentChannel ): pass -@dataclass +@dataclass(eq=False) class InstrumentChannelOpxPlusOutput( InstrumentChannelAnalog, InstrumentChannelOpxPlus, @@ -116,7 +116,7 @@ class InstrumentChannelOpxPlusOutput( InstrumentChannel ): pass -@dataclass +@dataclass(eq=False) class InstrumentChannelOpxPlusDigitalOutput( InstrumentChannelDigital, InstrumentChannelOpxPlus, @@ -124,7 +124,7 @@ class InstrumentChannelOpxPlusDigitalOutput( InstrumentChannel ): pass -@dataclass +@dataclass(eq=False) class InstrumentChannelOctaveInput( InstrumentChannelAnalog, InstrumentChannelOctave, @@ -132,7 +132,7 @@ class InstrumentChannelOctaveInput( InstrumentChannel ): pass -@dataclass +@dataclass(eq=False) class InstrumentChannelOctaveOutput( InstrumentChannelAnalog, InstrumentChannelOctave, @@ -140,7 +140,7 @@ class InstrumentChannelOctaveOutput( InstrumentChannel ): pass -@dataclass +@dataclass(eq=False) class InstrumentChannelOctaveDigitalInput( InstrumentChannelDigital, InstrumentChannelOctave, diff --git a/tests/wirer/conftest.py b/tests/wirer/conftest.py index 07855f8f..32440b68 100644 --- a/tests/wirer/conftest.py +++ b/tests/wirer/conftest.py @@ -2,6 +2,9 @@ import pytest +def pytest_configure(): + pytest.visualize_flag = False + @pytest.fixture(params=["lf-fem", "opx+"]) def instruments_qw_soprano(request) -> Instruments: print(request.param) diff --git a/tests/wirer/test_instrument_validation.py b/tests/wirer/test_instrument_validation.py index 20ab9a50..9fcb2b48 100644 --- a/tests/wirer/test_instrument_validation.py +++ b/tests/wirer/test_instrument_validation.py @@ -2,20 +2,20 @@ from qualang_tools.wirer.instruments import Instruments -visualize = False +visualize = pytest.visualize_flag def test_opx_plus_and_octave_validation(): instruments = Instruments() - instruments.add_opx_plus(cons=1) + instruments.add_opx_plus(controllers=1) instruments.add_octave(indices=1) def test_redefinition_validation(): instruments = Instruments() - instruments.add_opx_plus(cons=1) + instruments.add_opx_plus(controllers=1) with pytest.raises(ValueError): - instruments.add_opx_plus(cons=1) + instruments.add_opx_plus(controllers=1) instruments = Instruments() instruments.add_octave(indices=1) @@ -26,35 +26,35 @@ def test_redefinition_validation(): def test_slot_filled_validation(): instruments = Instruments() with pytest.raises(ValueError): - instruments.add_lf_fem(con=1, slots=1) - instruments.add_lf_fem(con=1, slots=1) + instruments.add_lf_fem(controller=1, slots=1) + instruments.add_lf_fem(controller=1, slots=1) instruments = Instruments() with pytest.raises(ValueError): - instruments.add_lf_fem(con=1, slots=[1, 2]) - instruments.add_lf_fem(con=1, slots=[2, 3]) + instruments.add_lf_fem(controller=1, slots=[1, 2]) + instruments.add_lf_fem(controller=1, slots=[2, 3]) instruments = Instruments() - instruments.add_lf_fem(con=1, slots=1) - instruments.add_lf_fem(con=2, slots=1) + instruments.add_lf_fem(controller=1, slots=1) + instruments.add_lf_fem(controller=2, slots=1) with pytest.raises(ValueError): - instruments.add_mw_fem(con=1, slots=1) + instruments.add_mw_fem(controller=1, slots=1) def test_opx_1000_and_opx_plus_mixing_validation(): instruments = Instruments() - instruments.add_lf_fem(con=1, slots=1) + instruments.add_lf_fem(controller=1, slots=1) with pytest.raises(ValueError): - instruments.add_opx_plus(cons=1) + instruments.add_opx_plus(controllers=1) instruments = Instruments() - instruments.add_mw_fem(con=1, slots=1) + instruments.add_mw_fem(controller=1, slots=1) with pytest.raises(ValueError): - instruments.add_opx_plus(cons=1) + instruments.add_opx_plus(controllers=1) instruments = Instruments() - instruments.add_opx_plus(cons=1) + instruments.add_opx_plus(controllers=1) with pytest.raises(ValueError): - instruments.add_lf_fem(con=1, slots=1) + instruments.add_lf_fem(controller=1, slots=1) with pytest.raises(ValueError): - instruments.add_mw_fem(con=1, slots=1) + instruments.add_mw_fem(controller=1, slots=1) diff --git a/tests/wirer/test_wirer_channel_reuse.py b/tests/wirer/test_wirer_channel_reuse.py index 7ce87349..433debf2 100644 --- a/tests/wirer/test_wirer_channel_reuse.py +++ b/tests/wirer/test_wirer_channel_reuse.py @@ -1,6 +1,8 @@ +import pytest + from qualang_tools.wirer import * -visualize_flag = True +visualize_flag = pytest.visualize_flag def test_5q_allocation_with_channel_reuse(instruments_2lf_2mw): connectivity = Connectivity() diff --git a/tests/wirer/test_wirer_constraining.py b/tests/wirer/test_wirer_constraining.py index d2dc3ac0..dcddccf2 100644 --- a/tests/wirer/test_wirer_constraining.py +++ b/tests/wirer/test_wirer_constraining.py @@ -1,20 +1,33 @@ +import pytest + from qualang_tools.wirer import * -visualize_flag = True +visualize_flag = pytest.visualize_flag -def test_5q_allocation(instruments_2lf_2mw): - connectivity = Connectivity() - qubits = [1, 2] - qubit_pairs = [(1, 2)] +def test_opx_plus_resonator_constraining(): + # Define the available instrument setup + instruments = Instruments() + instruments.add_opx_plus(controllers = [1]) + instruments.add_octave(indices = 1) - connectivity.add_resonator_line(qubits=qubits, triggered=True) - connectivity.add_qubit_drive_lines(qubits=qubits) - connectivity.add_qubit_flux_lines(qubits=qubits) - connectivity.add_qubit_pair_flux_lines(qubit_pairs=qubit_pairs) + q1_res_ch = opx_iq_octave_spec(out_port_i=9, out_port_q=10, rf_out=1) - allocate_wiring(connectivity, instruments_2lf_2mw) + qubits = [1] + connectivity = Connectivity() + connectivity.add_resonator_line(qubits=qubits, triggered=True, constraints=q1_res_ch) + connectivity.add_qubit_flux_lines(qubits=qubits) + connectivity.add_qubit_drive_lines(qubits=qubits[0], triggered=True) - print(connectivity.elements) + allocate_wiring(connectivity, instruments) if visualize_flag: - visualize(connectivity.elements, instruments_2lf_2mw.available_channels) + visualize(connectivity.elements, instruments.available_channels) + +def test_fix_attribute_equality(): + """ + Make sure that channels aren't considered equal just because their attributes + are equal. + """ + spec = opx_iq_octave_spec() + + assert all(spec.channel_templates.count(element) == 1 for element in spec.channel_templates) diff --git a/tests/wirer/test_wirer_digital.py b/tests/wirer/test_wirer_digital.py index d2dc3ac0..967828c2 100644 --- a/tests/wirer/test_wirer_digital.py +++ b/tests/wirer/test_wirer_digital.py @@ -1,8 +1,10 @@ +import pytest + from qualang_tools.wirer import * -visualize_flag = True +visualize_flag = pytest.visualize_flag -def test_5q_allocation(instruments_2lf_2mw): +def test_triggered_wiring_spec_generates_digital_channels(instruments_2lf_2mw): connectivity = Connectivity() qubits = [1, 2] qubit_pairs = [(1, 2)] diff --git a/tests/wirer/test_wirer_lf_and_mw.py b/tests/wirer/test_wirer_lf_and_mw.py index 76eff4a2..4e3c4875 100644 --- a/tests/wirer/test_wirer_lf_and_mw.py +++ b/tests/wirer/test_wirer_lf_and_mw.py @@ -1,6 +1,8 @@ +import pytest + from qualang_tools.wirer import * -visualize_flag = True +visualize_flag = pytest.visualize_flag def test_5q_allocation(instruments_2lf_2mw): qubits = [1, 2, 3, 4, 5] diff --git a/tests/wirer/test_wirer_lf_and_octave.py b/tests/wirer/test_wirer_lf_and_octave.py index 9ae443ea..c25d5fe3 100644 --- a/tests/wirer/test_wirer_lf_and_octave.py +++ b/tests/wirer/test_wirer_lf_and_octave.py @@ -1,7 +1,9 @@ +import pytest + from qualang_tools.wirer import * from pprint import pprint -visualize_flag = True +visualize_flag = pytest.visualize_flag def test_rf_io_allocation(instruments_1octave): qubits = [1,2,3,4] @@ -48,29 +50,27 @@ def test_qw_soprano_2qb_allocation(instruments_1OPX1Octave): visualize(connectivity.elements, available_channels=instruments_1OPX1Octave.available_channels) def test_qw_soprano_2qb_among_5_allocation(instruments_1OPX1Octave): - all_qubits = [1, 2, 3, 4, 5] + all_qubits = [1, 2, 3, 4] active_qubits = [1, 2] other_qubits = list(set(all_qubits) - set(active_qubits)) + q2_ch = opx_iq_octave_spec(out_port_i=5, out_port_q=6, rf_out=4) + connectivity = Connectivity() - # TODO: I want here to declare 2 qubits that I can address with my hardware (1OPX+ and 1 Octave) - connectivity.add_resonator_line(qubits=active_qubits, con=1, port=1) - connectivity.add_qubit_drive_lines(qubits=[1], con=1, port=2) - connectivity.add_qubit_drive_lines(qubits=[2], con=1, port=4) + connectivity.add_resonator_line(qubits=active_qubits) + connectivity.add_qubit_drive_lines(qubits=[1]) + connectivity.add_qubit_drive_lines(qubits=[2], constraints=q2_ch) connectivity.add_qubit_flux_lines(qubits=active_qubits) - allocate_wiring(connectivity, instruments_1OPX1Octave, keep_channels_free=True) - - # TODO: I want to add here the remaining qubits so that the QuAM can be created for the entire chip. - # I thus connect the other qubits to the same ports as the active qubits. - # Can I have the same ports used in several qubits as it is done for the resonator line? - connectivity.add_resonator_line(qubits=other_qubits, con=1, port=1) - connectivity.add_qubit_drive_lines(qubits=other_qubits, con=1, port=2) - connectivity.add_qubit_flux_lines(qubits=other_qubits, con=1, port=10) - allocate_wiring(connectivity, instruments_1OPX1Octave) + + allocate_wiring(connectivity, instruments_1OPX1Octave, block_used_channels=False) + + connectivity.add_resonator_line(qubits=other_qubits) + connectivity.add_qubit_drive_lines(qubits=other_qubits) + connectivity.add_qubit_flux_lines(qubits=other_qubits) allocate_wiring(connectivity, instruments_1OPX1Octave) pprint(connectivity.elements) if visualize_flag: - visualize(connectivity.elements) + visualize(connectivity.elements, instruments_1OPX1Octave.available_channels) From 95a73d09755b25de98ffa9b92730d04e39592b5d Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Wed, 4 Sep 2024 23:10:14 +1000 Subject: [PATCH 16/40] Fill top row, then bottom on OPX+ digital outputs. --- qualang_tools/wirer/instruments/instruments.py | 8 +++++++- qualang_tools/wirer/wirer/channel_specs.py | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/qualang_tools/wirer/instruments/instruments.py b/qualang_tools/wirer/instruments/instruments.py index f4a507ac..e65696eb 100644 --- a/qualang_tools/wirer/instruments/instruments.py +++ b/qualang_tools/wirer/instruments/instruments.py @@ -82,6 +82,12 @@ def add_opx_plus(self, controllers: Union[List[int], int]): channel = InstrumentChannelOpxPlusOutput(con=controller, port=port) self.available_channels.add(channel) - for port in range(1, NUM_OPX_PLUS_DIGITAL_OUTPUT_PORTS + 1): + # add odd first for octave connectivity (top row is more convenient) + for port in range(1, NUM_OPX_PLUS_DIGITAL_OUTPUT_PORTS + 1, 2): + channel = InstrumentChannelOpxPlusDigitalOutput(con=controller, port=port) + self.available_channels.add(channel) + + # then add evens + for port in range(2, NUM_OPX_PLUS_DIGITAL_OUTPUT_PORTS + 1, 2): channel = InstrumentChannelOpxPlusDigitalOutput(con=controller, port=port) self.available_channels.add(channel) diff --git a/qualang_tools/wirer/wirer/channel_specs.py b/qualang_tools/wirer/wirer/channel_specs.py index 72166d94..22b8af0b 100644 --- a/qualang_tools/wirer/wirer/channel_specs.py +++ b/qualang_tools/wirer/wirer/channel_specs.py @@ -147,3 +147,4 @@ def __init__(self, con: int = None, in_port: int = None): opx_iq_spec = ChannelSpecOpxPlusBaseband opx_iq_octave_spec = ChannelSpecOpxPlusBasebandAndOctave octave_spec = ChannelSpecOctave +opx_dig_spec = ChannelSpecOpxPlusDigital From 8826358bd3912caf43aaf075a7cff777dc81ff24 Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Wed, 11 Sep 2024 00:03:32 +1000 Subject: [PATCH 17/40] Add charge lines. --- qualang_tools/octave_tools/octave_tools.py | 4 ++-- .../connectivity_transmon_interface.py | 6 +++++ .../wirer/connectivity/wiring_spec.py | 1 + qualang_tools/wirer/visualizer/visualizer.py | 1 + qualang_tools/wirer/wirer/wirer.py | 1 + tests/wirer/test_wirer_lf_charge.py | 22 +++++++++++++++++++ 6 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 tests/wirer/test_wirer_lf_charge.py diff --git a/qualang_tools/octave_tools/octave_tools.py b/qualang_tools/octave_tools/octave_tools.py index b1ad6604..c2304c77 100644 --- a/qualang_tools/octave_tools/octave_tools.py +++ b/qualang_tools/octave_tools/octave_tools.py @@ -208,10 +208,10 @@ def octave_calibration_tool( :param lo_frequencies: single value or list of LO frequencies to calibrate in Hz. :param intermediate_frequencies: single value or list of Intermediate frequencies to calibrate in Hz. """ - if not isinstance(lo_frequencies, Union[list, np.ndarray]): + if not isinstance(lo_frequencies, (list, np.ndarray)): lo_frequencies = [lo_frequencies] - if not isinstance(intermediate_frequencies, Union[list, np.ndarray]): + if not isinstance(intermediate_frequencies, (list, np.ndarray)): intermediate_frequencies = [intermediate_frequencies] for lo in lo_frequencies: diff --git a/qualang_tools/wirer/connectivity/connectivity_transmon_interface.py b/qualang_tools/wirer/connectivity/connectivity_transmon_interface.py index 19156fb8..6541de46 100644 --- a/qualang_tools/wirer/connectivity/connectivity_transmon_interface.py +++ b/qualang_tools/wirer/connectivity/connectivity_transmon_interface.py @@ -28,6 +28,12 @@ def add_qubit_drive_lines(self, qubits: QubitsType, triggered: bool = False, con triggered, constraints, elements) + def add_qubit_charge_lines(self, qubits: QubitsType, constraints: ChannelSpec = None): + elements = self._make_qubit_elements(qubits) + return self.add_wiring_spec(WiringFrequency.DC, WiringIOType.OUTPUT, WiringLineType.CHARGE, + False, constraints, elements) + + def add_qubit_flux_lines(self, qubits: QubitsType, constraints: ChannelSpec = None): elements = self._make_qubit_elements(qubits) return self.add_wiring_spec(WiringFrequency.DC, WiringIOType.OUTPUT, WiringLineType.FLUX, diff --git a/qualang_tools/wirer/connectivity/wiring_spec.py b/qualang_tools/wirer/connectivity/wiring_spec.py index 29bafa83..d5ef351e 100644 --- a/qualang_tools/wirer/connectivity/wiring_spec.py +++ b/qualang_tools/wirer/connectivity/wiring_spec.py @@ -15,6 +15,7 @@ class WiringLineType(Enum): RESONATOR = "rr" DRIVE = "xy" FLUX = "z" + CHARGE = "q" COUPLER = "c" class WiringSpec: diff --git a/qualang_tools/wirer/visualizer/visualizer.py b/qualang_tools/wirer/visualizer/visualizer.py index 3c3156e3..b712c86c 100644 --- a/qualang_tools/wirer/visualizer/visualizer.py +++ b/qualang_tools/wirer/visualizer/visualizer.py @@ -43,6 +43,7 @@ def prepare_available_channel_annotations(available_channels: InstrumentChannels def get_color_for_line_type(line_type) -> str: color_map = { WiringLineType.FLUX: "powderblue", + WiringLineType.CHARGE: "lavender", WiringLineType.RESONATOR: "peachpuff", WiringLineType.DRIVE: "lemonchiffon", WiringLineType.COUPLER: "thistle" diff --git a/qualang_tools/wirer/wirer/wirer.py b/qualang_tools/wirer/wirer/wirer.py index 18f09b0e..23175745 100644 --- a/qualang_tools/wirer/wirer/wirer.py +++ b/qualang_tools/wirer/wirer/wirer.py @@ -22,6 +22,7 @@ def allocate_wiring(connectivity: Connectivity, instruments: Instruments, WiringLineType.RESONATOR, WiringLineType.DRIVE, WiringLineType.FLUX, + WiringLineType.CHARGE, WiringLineType.COUPLER, ] diff --git a/tests/wirer/test_wirer_lf_charge.py b/tests/wirer/test_wirer_lf_charge.py new file mode 100644 index 00000000..a2694bf1 --- /dev/null +++ b/tests/wirer/test_wirer_lf_charge.py @@ -0,0 +1,22 @@ +import pytest + +from qualang_tools.wirer import * + +visualize_flag = pytest.visualize_flag + +def test_5q_allocation_flux_charge(instruments_2lf_2mw): + qubits = [1, 2, 3, 4, 5] + qubit_pairs = [(1, 2), (2, 3), (3, 4), (4, 5)] + + connectivity = Connectivity() + connectivity.add_resonator_line(qubits=qubits, constraints=mw_fem_spec(slot=7)) + connectivity.add_qubit_flux_lines(qubits=qubits) + connectivity.add_qubit_charge_lines(qubits=qubits) + connectivity.add_qubit_pair_flux_lines(qubit_pairs=qubit_pairs, constraints=lf_fem_spec(out_slot=2)) + + allocate_wiring(connectivity, instruments_2lf_2mw) + + print(connectivity.elements) + + if visualize_flag: + visualize(connectivity.elements, instruments_2lf_2mw.available_channels) From d6b617c0bdeb8dc89b3117ea28613772bcfd28c9 Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Fri, 13 Sep 2024 01:55:25 +1000 Subject: [PATCH 18/40] Add support for untyped WiringLineTypes. --- .../wirer/connectivity/connectivity_base.py | 15 ++++++++-- qualang_tools/wirer/connectivity/element.py | 17 ++++++++--- .../wirer/connectivity/wiring_spec.py | 13 +++++++++ .../wirer/visualizer/port_annotation.py | 2 ++ qualang_tools/wirer/visualizer/visualizer.py | 17 ++++++----- qualang_tools/wirer/wirer/wirer.py | 7 +++++ tests/wirer/test_add_dummy_line.py | 28 +++++++++++++++++++ tests/wirer/test_wirer_lf_and_mw.py | 2 +- 8 files changed, 85 insertions(+), 16 deletions(-) create mode 100644 tests/wirer/test_add_dummy_line.py diff --git a/qualang_tools/wirer/connectivity/connectivity_base.py b/qualang_tools/wirer/connectivity/connectivity_base.py index c427bfd5..29fd1960 100644 --- a/qualang_tools/wirer/connectivity/connectivity_base.py +++ b/qualang_tools/wirer/connectivity/connectivity_base.py @@ -1,5 +1,5 @@ import copy -from typing import Dict, List +from typing import Dict, List, Union from .channel_spec import ChannelSpec from .element import Element, ElementId, QubitReference, QubitPairReference @@ -20,10 +20,14 @@ def __init__(self): self.elements: Dict[ElementId, Element] = {} self.specs: List[WiringSpec] = [] - def add_wiring_spec(self, frequency: WiringFrequency, io_type: WiringIOType, line_type: WiringLineType, + def add_wiring_spec(self, frequency: WiringFrequency, io_type: WiringIOType, line_type: Union[WiringLineType, str], triggered: bool, constraints: ChannelSpec, elements: List[Element], shared_line: bool = False, ): specs = [] + for element in elements: + if element.id not in self.elements: + self._add_element(element) + if shared_line: spec = WiringSpec(frequency, io_type, line_type, triggered, constraints, elements) specs.append(spec) @@ -60,3 +64,10 @@ def _make_qubit_pair_elements(self, qubit_pairs: QubitPairsType): elements.append(self.elements[id]) return elements + + def _add_elements(self, elements: List[Element]): + for element in elements: + self._add_element(element) + + def _add_element(self, element: Element): + self.elements[element.id] = element \ No newline at end of file diff --git a/qualang_tools/wirer/connectivity/element.py b/qualang_tools/wirer/connectivity/element.py index 6449a50b..589e79e3 100644 --- a/qualang_tools/wirer/connectivity/element.py +++ b/qualang_tools/wirer/connectivity/element.py @@ -5,6 +5,13 @@ from ..instruments.instrument_channel import AnyInstrumentChannel +@dataclass(frozen=True) +class Reference: + name: str + + def __str__(self): + return self.name + @dataclass(frozen=True) class QubitReference: index: int @@ -22,13 +29,15 @@ def __str__(self): -ElementId = Union[QubitReference, QubitPairReference] +ElementId = Union[Reference, QubitReference, QubitPairReference] -@dataclass class Element: - id: ElementId - channels: Dict[WiringLineType, List[AnyInstrumentChannel]] = field(default_factory=dict) + def __init__(self, id: Union[str, QubitReference, QubitPairReference]): + if isinstance(id, str): + id = Reference(id) + self.id = id + self.channels: Dict[WiringLineType, List[AnyInstrumentChannel]] = dict() def __str__(self): return str(self.channels) diff --git a/qualang_tools/wirer/connectivity/wiring_spec.py b/qualang_tools/wirer/connectivity/wiring_spec.py index d5ef351e..7a1fa890 100644 --- a/qualang_tools/wirer/connectivity/wiring_spec.py +++ b/qualang_tools/wirer/connectivity/wiring_spec.py @@ -6,11 +6,18 @@ class WiringFrequency(Enum): DC = "DC" RF = "RF" +DC = WiringFrequency.DC +RF = WiringFrequency.RF + class WiringIOType(Enum): INPUT = "input" OUTPUT = "output" INPUT_AND_OUTPUT = "input/output" +INPUT = WiringIOType.INPUT +OUTPUT = WiringIOType.OUTPUT +INPUT_AND_OUTPUT = WiringIOType.INPUT_AND_OUTPUT + class WiringLineType(Enum): RESONATOR = "rr" DRIVE = "xy" @@ -18,6 +25,12 @@ class WiringLineType(Enum): CHARGE = "q" COUPLER = "c" +RESONATOR = WiringLineType.RESONATOR +DRIVE = WiringLineType.DRIVE +FLUX = WiringLineType.FLUX +CHARGE = WiringLineType.CHARGE +COUPLER = WiringLineType.COUPLER + class WiringSpec: """ A technical specification for the wiring that will be required to diff --git a/qualang_tools/wirer/visualizer/port_annotation.py b/qualang_tools/wirer/visualizer/port_annotation.py index 3c28db15..9568a606 100644 --- a/qualang_tools/wirer/visualizer/port_annotation.py +++ b/qualang_tools/wirer/visualizer/port_annotation.py @@ -91,6 +91,8 @@ def combine_labels_for_same_line_type(labels: List[str]): index = int(match.group(1)) # Extract the qubit index line_type = match.group(2) # Extract the line type grouped_lines[line_type].append(index) + else: + return labels # Result list to store the reduced strings reduced_strings = [] diff --git a/qualang_tools/wirer/visualizer/visualizer.py b/qualang_tools/wirer/visualizer/visualizer.py index b712c86c..e820a3f9 100644 --- a/qualang_tools/wirer/visualizer/visualizer.py +++ b/qualang_tools/wirer/visualizer/visualizer.py @@ -15,11 +15,11 @@ def invert_qubit_dict(qubit_dict) -> dict: key = (channel.con, channel.slot, channel.port, channel.io_type, channel.signal_type, channel.instrument_id, channel_type) if key not in inverted_dict: inverted_dict[key] = [] - annotation = f"q{qubit_ref.index if hasattr(qubit_ref, 'index') else f'{qubit_ref.control_index}{qubit_ref.target_index}'}.{channel_type.value}" + annotation = f"{element.id}.{channel_type if isinstance(channel_type, str) else channel_type.value}" inverted_dict[key].append((annotation, channel)) return inverted_dict -def prepare_annotations(inverted_dict: dict) -> List[PortAnnotation]: +def make_annotations(inverted_dict: dict) -> List[PortAnnotation]: annotations = [] for key, values in inverted_dict.items(): con, slot, port, io_type, signal_type, instrument_id, channel_type = key @@ -30,12 +30,11 @@ def prepare_annotations(inverted_dict: dict) -> List[PortAnnotation]: return annotations -def prepare_available_channel_annotations(available_channels: InstrumentChannels) -> List[PortAnnotation]: +def make_unused_channel_annotations(available_channels: InstrumentChannels) -> List[PortAnnotation]: annotations = [] for _, channel_list in available_channels.stack.items(): for channel in channel_list: - color = get_color_for_line_type(None) - annotations.append(PortAnnotation([""], color, channel.con, channel.slot, channel.port, channel.io_type, channel.signal_type, channel.instrument_id)) + annotations.append(PortAnnotation([""], "white", channel.con, channel.slot, channel.port, channel.io_type, channel.signal_type, channel.instrument_id)) return annotations @@ -46,9 +45,9 @@ def get_color_for_line_type(line_type) -> str: WiringLineType.CHARGE: "lavender", WiringLineType.RESONATOR: "peachpuff", WiringLineType.DRIVE: "lemonchiffon", - WiringLineType.COUPLER: "thistle" + WiringLineType.COUPLER: "thistle", } - return color_map.get(line_type, "white") + return color_map.get(line_type, "beige") def draw_annotations(manager: InstrumentFigureManager, annotations: List[PortAnnotation]): @@ -62,13 +61,13 @@ def visualize(qubit_dict, available_channels=None): inverted_dict = invert_qubit_dict(qubit_dict) # Prepare annotations and labels - annotations = prepare_annotations(inverted_dict) + annotations = make_annotations(inverted_dict) # Manage figures and draw manager = InstrumentFigureManager() if available_channels is not None: - available_channel_annotations = prepare_available_channel_annotations(available_channels) + available_channel_annotations = make_unused_channel_annotations(available_channels) draw_annotations(manager, available_channel_annotations) draw_annotations(manager, annotations) diff --git a/qualang_tools/wirer/wirer/wirer.py b/qualang_tools/wirer/wirer/wirer.py index 23175745..f8fe578c 100644 --- a/qualang_tools/wirer/wirer/wirer.py +++ b/qualang_tools/wirer/wirer/wirer.py @@ -27,11 +27,18 @@ def allocate_wiring(connectivity: Connectivity, instruments: Instruments, ] specs = connectivity.specs + + specs_with_untyped_lines = set() for line_type in line_type_fill_order: for spec in specs: + if spec.line_type not in line_type_fill_order: + specs_with_untyped_lines.add(spec) if spec.line_type == line_type: _allocate_wiring(spec, instruments) + for spec in specs_with_untyped_lines: + _allocate_wiring(spec, instruments) + if clear_wiring_specifications: connectivity.specs = [] diff --git a/tests/wirer/test_add_dummy_line.py b/tests/wirer/test_add_dummy_line.py new file mode 100644 index 00000000..db714b30 --- /dev/null +++ b/tests/wirer/test_add_dummy_line.py @@ -0,0 +1,28 @@ +import pytest +from qualang_tools.wirer import Connectivity, allocate_wiring, visualize +from qualang_tools.wirer.connectivity.element import Element, Reference +from qualang_tools.wirer.connectivity.wiring_spec import * +from qualang_tools.wirer.wirer.channel_specs import * + + +def test_add_dummy_line(instruments_2lf_2mw): + qubits = [1, 2, 3, 4, 5] + qubit_pairs = [(1, 2), (2, 3), (3, 4), (4, 5)] + + connectivity = Connectivity() + connectivity.add_resonator_line(qubits=qubits, constraints=mw_fem_spec(slot=7)) + connectivity.add_qubit_drive_lines(qubits=qubits, constraints=mw_fem_spec(slot=7)) + connectivity.add_qubit_flux_lines(qubits=qubits) + connectivity.add_qubit_pair_flux_lines(qubit_pairs=qubit_pairs, constraints=lf_fem_spec(out_slot=2)) + + dummy_element = Element(id="test") + connectivity.add_wiring_spec( + frequency=DC, io_type=INPUT_AND_OUTPUT, line_type='ch', triggered=False, constraints=None, elements=[dummy_element] + ) + + allocate_wiring(connectivity, instruments_2lf_2mw) + + print(connectivity.elements) + + if pytest.visualize_flag: + visualize(connectivity.elements, instruments_2lf_2mw.available_channels) diff --git a/tests/wirer/test_wirer_lf_and_mw.py b/tests/wirer/test_wirer_lf_and_mw.py index 4e3c4875..bb28c841 100644 --- a/tests/wirer/test_wirer_lf_and_mw.py +++ b/tests/wirer/test_wirer_lf_and_mw.py @@ -33,7 +33,7 @@ def test_4rr_allocation(instruments_2lf_2mw): allocate_wiring(connectivity, instruments_2lf_2mw) if visualize_flag: - visualize(connectivity.elements) + visualize(connectivity.elements, instruments_2lf_2mw.available_channels) def test_6rr_6xy_6flux_allocation(): From 7828f02f1f3a0395cb9a674850e3f7ac5d1e7960 Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Fri, 11 Oct 2024 19:17:05 +1100 Subject: [PATCH 19/40] Change convention of qubit-pair naming so that it doesn't conflict with double-digit qubits. --- qualang_tools/wirer/connectivity/element.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qualang_tools/wirer/connectivity/element.py b/qualang_tools/wirer/connectivity/element.py index 589e79e3..3800fac9 100644 --- a/qualang_tools/wirer/connectivity/element.py +++ b/qualang_tools/wirer/connectivity/element.py @@ -25,7 +25,7 @@ class QubitPairReference: target_index: int def __str__(self): - return f"q{self.control_index}{self.target_index}" + return f"q{self.control_index}-{self.target_index}" From 26e0068844308562404d1dec3cd821ab5c9703b3 Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Fri, 11 Oct 2024 19:17:37 +1100 Subject: [PATCH 20/40] Swap rf_in/rf_out in channel_spec API so it is more intuitive. --- qualang_tools/wirer/wirer/channel_specs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qualang_tools/wirer/wirer/channel_specs.py b/qualang_tools/wirer/wirer/channel_specs.py index 22b8af0b..1f7c63de 100644 --- a/qualang_tools/wirer/wirer/channel_specs.py +++ b/qualang_tools/wirer/wirer/channel_specs.py @@ -21,7 +21,7 @@ def __init__(self, con: int = None, slot: int = None, class ChannelSpecLfFemSingle(ChannelSpec): def __init__(self, con: int = None, - in_slot: int = None,in_port: int = None, + in_slot: int = None, in_port: int = None, out_slot: int = None, out_port: int = None): super().__init__() @@ -70,7 +70,7 @@ def __init__(self, con: int = None, class ChannelSpecOctave(ChannelSpec): - def __init__(self, index: int = None, rf_out: int = None, rf_in: int = None): + def __init__(self, index: int = None, rf_in: int = None, rf_out: int = None): super().__init__() self.channel_templates = [ InstrumentChannelOctaveInput(con=index, port=rf_in), @@ -83,7 +83,7 @@ def __init__(self, con: int = None, slot: int = None, in_port_i: int = None, in_port_q: int = None, out_port_i: int = None, out_port_q: int = None, - octave_index: int = None, rf_out: int = None, rf_in: int = None): + octave_index: int = None, rf_in: int = None, rf_out: int = None): super().__init__() self.channel_templates = [ InstrumentChannelLfFemInput(con=con, slot=slot, port=in_port_i), @@ -100,7 +100,7 @@ def __init__(self, con: int = None, in_port_i: int = None, in_port_q: int = None, out_port_i: int = None, out_port_q: int = None, - octave_index: int = None, rf_out: int = None, rf_in: int = None): + octave_index: int = None, rf_in: int = None, rf_out: int = None): super().__init__() self.channel_templates = [ InstrumentChannelOpxPlusInput(con=con, port=in_port_i), From d5afb0756f385c7c14a112472bc0e110e6087d16 Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Fri, 11 Oct 2024 19:18:01 +1100 Subject: [PATCH 21/40] Add quantitative regression tests for exact connectivity allocations. --- tests/wirer/test_add_dummy_line.py | 12 ++++- tests/wirer/test_wirer_channel_reuse.py | 28 ++++++++++- tests/wirer/test_wirer_constraining.py | 20 ++++++++ tests/wirer/test_wirer_digital.py | 14 +++++- tests/wirer/test_wirer_lf_and_mw.py | 66 ++++++++++++++++--------- tests/wirer/test_wirer_lf_and_octave.py | 54 +++++++++++++++----- tests/wirer/test_wirer_lf_charge.py | 22 ++++++--- 7 files changed, 170 insertions(+), 46 deletions(-) diff --git a/tests/wirer/test_add_dummy_line.py b/tests/wirer/test_add_dummy_line.py index db714b30..5120d0aa 100644 --- a/tests/wirer/test_add_dummy_line.py +++ b/tests/wirer/test_add_dummy_line.py @@ -1,3 +1,5 @@ +from dataclasses import asdict + import pytest from qualang_tools.wirer import Connectivity, allocate_wiring, visualize from qualang_tools.wirer.connectivity.element import Element, Reference @@ -22,7 +24,13 @@ def test_add_dummy_line(instruments_2lf_2mw): allocate_wiring(connectivity, instruments_2lf_2mw) - print(connectivity.elements) - if pytest.visualize_flag: visualize(connectivity.elements, instruments_2lf_2mw.available_channels) + + # regression test + test_element = connectivity.elements[Reference('test')] + for i, channel in enumerate(test_element.channels['ch']): + assert asdict(channel) == asdict([ + InstrumentChannelLfFemInput(con=1, port=1, slot=1), + InstrumentChannelLfFemOutput(con=1, port=6, slot=1) + ][i]) \ No newline at end of file diff --git a/tests/wirer/test_wirer_channel_reuse.py b/tests/wirer/test_wirer_channel_reuse.py index 433debf2..c3425651 100644 --- a/tests/wirer/test_wirer_channel_reuse.py +++ b/tests/wirer/test_wirer_channel_reuse.py @@ -1,6 +1,12 @@ +from dataclasses import asdict + import pytest from qualang_tools.wirer import * +from qualang_tools.wirer.connectivity.element import QubitReference +from qualang_tools.wirer.connectivity.wiring_spec import WiringLineType +from qualang_tools.wirer.instruments.instrument_channel import InstrumentChannelMwFemInput, \ + InstrumentChannelMwFemOutput, InstrumentChannelLfFemOutput visualize_flag = pytest.visualize_flag @@ -17,7 +23,25 @@ def test_5q_allocation_with_channel_reuse(instruments_2lf_2mw): allocate_wiring(connectivity, instruments_2lf_2mw, block_used_channels=False) - print(connectivity.elements) - if visualize_flag: visualize(connectivity.elements, instruments_2lf_2mw.available_channels) + + for qubit in [1, 3]: + # resonator lines re-used for qubits 1 & 3 + for i, channel in enumerate(connectivity.elements[QubitReference(index=qubit)].channels[WiringLineType.RESONATOR]): + assert asdict(channel) == asdict([ + InstrumentChannelMwFemInput(con=1, port=1, slot=3), + InstrumentChannelMwFemOutput(con=1, port=1, slot=3) + ][i]) + + # drive lines re-used for qubits 1 & 3 + for i, channel in enumerate(connectivity.elements[QubitReference(index=qubit)].channels[WiringLineType.DRIVE]): + assert asdict(channel) == asdict([ + InstrumentChannelMwFemOutput(con=1, port=2, slot=3) + ][i]) + + # flux lines re-used for qubits 1 & 3 + for i, channel in enumerate(connectivity.elements[QubitReference(index=qubit)].channels[WiringLineType.FLUX]): + assert asdict(channel) == asdict([ + InstrumentChannelLfFemOutput(con=1, port=1, slot=1), + ][i]) diff --git a/tests/wirer/test_wirer_constraining.py b/tests/wirer/test_wirer_constraining.py index dcddccf2..1500a2e3 100644 --- a/tests/wirer/test_wirer_constraining.py +++ b/tests/wirer/test_wirer_constraining.py @@ -1,6 +1,13 @@ +from dataclasses import asdict + import pytest from qualang_tools.wirer import * +from qualang_tools.wirer.connectivity.element import QubitReference +from qualang_tools.wirer.connectivity.wiring_spec import WiringLineType +from qualang_tools.wirer.instruments.instrument_channel import InstrumentChannelOpxPlusInput, \ + InstrumentChannelOpxPlusOutput, InstrumentChannelOpxPlusDigitalOutput, InstrumentChannelOctaveInput, \ + InstrumentChannelOctaveDigitalInput, InstrumentChannelOctaveOutput visualize_flag = pytest.visualize_flag @@ -23,6 +30,19 @@ def test_opx_plus_resonator_constraining(): if visualize_flag: visualize(connectivity.elements, instruments.available_channels) + # resonator lines should be hard-coded to I=9, Q=10, rf_out=1 + for i, channel in enumerate(connectivity.elements[QubitReference(index=1)].channels[WiringLineType.RESONATOR]): + assert asdict(channel) == asdict([ + InstrumentChannelOpxPlusInput(con=1, port=1, slot=None), + InstrumentChannelOpxPlusInput(con=1, port=2, slot=None), + InstrumentChannelOpxPlusOutput(con=1, port=9, slot=None), + InstrumentChannelOpxPlusOutput(con=1, port=10, slot=None), + InstrumentChannelOpxPlusDigitalOutput(con=1, port=1, slot=None), + InstrumentChannelOctaveInput(con=1, port=1, slot=None), + InstrumentChannelOctaveOutput(con=1, port=1, slot=None), + InstrumentChannelOctaveDigitalInput(con=1, port=1, slot=None) + ][i]) + def test_fix_attribute_equality(): """ Make sure that channels aren't considered equal just because their attributes diff --git a/tests/wirer/test_wirer_digital.py b/tests/wirer/test_wirer_digital.py index 967828c2..4a0b9c41 100644 --- a/tests/wirer/test_wirer_digital.py +++ b/tests/wirer/test_wirer_digital.py @@ -1,6 +1,11 @@ +from dataclasses import asdict + import pytest from qualang_tools.wirer import * +from qualang_tools.wirer.connectivity.element import QubitReference +from qualang_tools.wirer.connectivity.wiring_spec import WiringLineType +from qualang_tools.wirer.instruments.instrument_channel import InstrumentChannelMwFemDigitalOutput visualize_flag = pytest.visualize_flag @@ -16,7 +21,12 @@ def test_triggered_wiring_spec_generates_digital_channels(instruments_2lf_2mw): allocate_wiring(connectivity, instruments_2lf_2mw) - print(connectivity.elements) - if visualize_flag: visualize(connectivity.elements, instruments_2lf_2mw.available_channels) + + # assert digital trigger channel is present + for qubit in [1, 2]: + resonator_channels = connectivity.elements[QubitReference(index=qubit)].channels[WiringLineType.RESONATOR] + resonator_channels_as_dicts = [asdict(channel) for channel in resonator_channels] + + assert asdict(InstrumentChannelMwFemDigitalOutput(con=1, port=1, slot=3)) in resonator_channels_as_dicts \ No newline at end of file diff --git a/tests/wirer/test_wirer_lf_and_mw.py b/tests/wirer/test_wirer_lf_and_mw.py index bb28c841..d2fcafbc 100644 --- a/tests/wirer/test_wirer_lf_and_mw.py +++ b/tests/wirer/test_wirer_lf_and_mw.py @@ -1,26 +1,56 @@ +from dataclasses import asdict + import pytest from qualang_tools.wirer import * +from qualang_tools.wirer.connectivity.element import QubitReference, QubitPairReference +from qualang_tools.wirer.connectivity.wiring_spec import WiringLineType +from qualang_tools.wirer.instruments.instrument_channel import InstrumentChannelLfFemOutput, \ + InstrumentChannelMwFemOutput, InstrumentChannelMwFemInput visualize_flag = pytest.visualize_flag -def test_5q_allocation(instruments_2lf_2mw): - qubits = [1, 2, 3, 4, 5] - qubit_pairs = [(1, 2), (2, 3), (3, 4), (4, 5)] +def test_6q_allocation(instruments_2lf_2mw): + qubits = [1, 2, 3, 4, 5, 6] + qubit_pairs = [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)] connectivity = Connectivity() - connectivity.add_resonator_line(qubits=qubits, constraints=mw_fem_spec(slot=7)) - connectivity.add_qubit_drive_lines(qubits=qubits, constraints=mw_fem_spec(slot=7)) + connectivity.add_resonator_line(qubits=qubits) + connectivity.add_qubit_drive_lines(qubits=qubits) connectivity.add_qubit_flux_lines(qubits=qubits) connectivity.add_qubit_pair_flux_lines(qubit_pairs=qubit_pairs, constraints=lf_fem_spec(out_slot=2)) allocate_wiring(connectivity, instruments_2lf_2mw) - print(connectivity.elements) - if visualize_flag: visualize(connectivity.elements, instruments_2lf_2mw.available_channels) + for qubit in qubits: + # flux channels should have some port as qubit index since they're allocated sequentially + flux_channels = connectivity.elements[QubitReference(qubit)].channels[WiringLineType.FLUX] + assert [asdict(ch) for ch in flux_channels] == [ + asdict(InstrumentChannelLfFemOutput(con=1, port=qubit, slot=1)) + ] + + # resonators all on same feedline, so should be first available input + outputs channels on MW-FEM + resonator_channels = connectivity.elements[QubitReference(qubit)].channels[WiringLineType.RESONATOR] + assert [asdict(ch) for ch in resonator_channels] == [ + asdict(InstrumentChannelMwFemInput(con=1, port=1, slot=3)), + asdict(InstrumentChannelMwFemOutput(con=1, port=1, slot=3)) + ] + + # drive channels are on MW-FEM + drive_channels = connectivity.elements[QubitReference(qubit)].channels[WiringLineType.DRIVE] + assert [asdict(ch) for ch in drive_channels] == [ + asdict(InstrumentChannelMwFemOutput(con=1, port=qubit+1, slot=3)) + ] + + for i, pair in enumerate(qubit_pairs): + # coupler channels should have some port as pair index since they're allocated sequentially, but on slot 2 + coupler_channels = connectivity.elements[QubitPairReference(*pair)].channels[WiringLineType.COUPLER] + assert [asdict(ch) for ch in coupler_channels] == [ + asdict(InstrumentChannelLfFemOutput(con=1, port=i+1, slot=2)) + ] def test_4rr_allocation(instruments_2lf_2mw): connectivity = Connectivity() @@ -35,19 +65,11 @@ def test_4rr_allocation(instruments_2lf_2mw): if visualize_flag: visualize(connectivity.elements, instruments_2lf_2mw.available_channels) + # resonators all on different feedlines, so should fill all 4 inputs of 2x MW-FEM + for i, qubit in enumerate([1, 2, 3, 4]): + resonator_channels = connectivity.elements[QubitReference(qubit)].channels[WiringLineType.RESONATOR] + assert [asdict(ch) for ch in resonator_channels] == [ + asdict(InstrumentChannelMwFemInput(con=1, port=[1, 2, 1, 2][i], slot=[3, 3, 7, 7][i])), + asdict(InstrumentChannelMwFemOutput(con=1, port=[1, 2, 1, 2][i], slot=[3, 3, 7, 7][i])) + ] -def test_6rr_6xy_6flux_allocation(): - instruments = Instruments() - instruments.add_lf_fem(controller=1, slots=1) - instruments.add_mw_fem(controller=1, slots=2) - - qubits = [1, 2, 3, 4, 5, 6] - connectivity = Connectivity() - connectivity.add_resonator_line(qubits=qubits) - connectivity.add_qubit_drive_lines(qubits=qubits) - connectivity.add_qubit_flux_lines(qubits=qubits) - - allocate_wiring(connectivity, instruments) - - if visualize_flag: - visualize(connectivity.elements, instruments.available_channels) \ No newline at end of file diff --git a/tests/wirer/test_wirer_lf_and_octave.py b/tests/wirer/test_wirer_lf_and_octave.py index c25d5fe3..85cd087d 100644 --- a/tests/wirer/test_wirer_lf_and_octave.py +++ b/tests/wirer/test_wirer_lf_and_octave.py @@ -1,22 +1,54 @@ +from dataclasses import asdict + import pytest from qualang_tools.wirer import * from pprint import pprint +from qualang_tools.wirer.connectivity.element import QubitReference +from qualang_tools.wirer.connectivity.wiring_spec import WiringLineType +from qualang_tools.wirer.instruments.instrument_channel import InstrumentChannelOpxPlusInput, \ + InstrumentChannelOpxPlusOutput, InstrumentChannelOpxPlusDigitalOutput, InstrumentChannelOctaveInput, \ + InstrumentChannelOctaveDigitalInput, InstrumentChannelOctaveOutput + visualize_flag = pytest.visualize_flag -def test_rf_io_allocation(instruments_1octave): - qubits = [1,2,3,4] +def test_rf_io_allocation(instruments_1OPX1Octave): + qubits = [1, 2, 3, 4] + connectivity = Connectivity() connectivity.add_resonator_line(qubits=qubits) connectivity.add_qubit_drive_lines(qubits=qubits) - allocate_wiring(connectivity, instruments_1octave) + allocate_wiring(connectivity, instruments_1OPX1Octave) - pprint(connectivity.elements) if visualize_flag: - visualize(connectivity.elements, available_channels=instruments_1octave.available_channels) + visualize(connectivity.elements, available_channels=instruments_1OPX1Octave.available_channels) + + for qubit in qubits: + # resonator lines should be the same because only 1 feedline + for i, channel in enumerate(connectivity.elements[QubitReference(index=qubit)].channels[WiringLineType.RESONATOR]): + assert asdict(channel) == asdict([ + InstrumentChannelOpxPlusInput(con=1, port=1, slot=None), + InstrumentChannelOpxPlusInput(con=1, port=2, slot=None), + InstrumentChannelOpxPlusOutput(con=1, port=1, slot=None), + InstrumentChannelOpxPlusOutput(con=1, port=2, slot=None), + # InstrumentChannelOpxPlusDigitalOutput(con=1, port=1, slot=None), + InstrumentChannelOctaveInput(con=1, port=1, slot=None), + InstrumentChannelOctaveOutput(con=1, port=1, slot=None), + # InstrumentChannelOctaveDigitalInput(con=1, port=1, slot=None) + ][i]) + + # drive lines should be allocated sequentially + for i, channel in enumerate(connectivity.elements[QubitReference(index=qubit)].channels[WiringLineType.DRIVE]): + assert asdict(channel) == asdict([ + InstrumentChannelOpxPlusOutput(con=1, port=1+2*qubit, slot=None), + InstrumentChannelOpxPlusOutput(con=1, port=2+2*qubit, slot=None), + InstrumentChannelOctaveOutput(con=1, port=1+qubit, slot=None), + ][i]) + + def test_qw_soprano_allocation(instruments_qw_soprano): qubits = [1, 2, 3, 4, 5] @@ -28,11 +60,11 @@ def test_qw_soprano_allocation(instruments_qw_soprano): allocate_wiring(connectivity, instruments_qw_soprano) - pprint(connectivity.elements) - if visualize_flag: visualize(connectivity.elements, available_channels=instruments_qw_soprano.available_channels) + # should run without error + def test_qw_soprano_2qb_allocation(instruments_1OPX1Octave): active_qubits = [1, 2] @@ -44,11 +76,11 @@ def test_qw_soprano_2qb_allocation(instruments_1OPX1Octave): allocate_wiring(connectivity, instruments_1OPX1Octave) - pprint(connectivity.elements) - if visualize_flag: visualize(connectivity.elements, available_channels=instruments_1OPX1Octave.available_channels) + # should run without error + def test_qw_soprano_2qb_among_5_allocation(instruments_1OPX1Octave): all_qubits = [1, 2, 3, 4] active_qubits = [1, 2] @@ -70,7 +102,7 @@ def test_qw_soprano_2qb_among_5_allocation(instruments_1OPX1Octave): allocate_wiring(connectivity, instruments_1OPX1Octave) - pprint(connectivity.elements) - if visualize_flag: visualize(connectivity.elements, instruments_1OPX1Octave.available_channels) + + # should run without error \ No newline at end of file diff --git a/tests/wirer/test_wirer_lf_charge.py b/tests/wirer/test_wirer_lf_charge.py index a2694bf1..c05e946c 100644 --- a/tests/wirer/test_wirer_lf_charge.py +++ b/tests/wirer/test_wirer_lf_charge.py @@ -1,22 +1,30 @@ +from dataclasses import asdict + import pytest from qualang_tools.wirer import * +from qualang_tools.wirer.connectivity.element import QubitReference +from qualang_tools.wirer.connectivity.wiring_spec import WiringLineType +from qualang_tools.wirer.instruments.instrument_channel import InstrumentChannelLfFemOutput visualize_flag = pytest.visualize_flag -def test_5q_allocation_flux_charge(instruments_2lf_2mw): +def test_1q_allocation_flux_charge(instruments_2lf_2mw): qubits = [1, 2, 3, 4, 5] - qubit_pairs = [(1, 2), (2, 3), (3, 4), (4, 5)] connectivity = Connectivity() - connectivity.add_resonator_line(qubits=qubits, constraints=mw_fem_spec(slot=7)) - connectivity.add_qubit_flux_lines(qubits=qubits) connectivity.add_qubit_charge_lines(qubits=qubits) - connectivity.add_qubit_pair_flux_lines(qubit_pairs=qubit_pairs, constraints=lf_fem_spec(out_slot=2)) allocate_wiring(connectivity, instruments_2lf_2mw) - print(connectivity.elements) - if visualize_flag: visualize(connectivity.elements, instruments_2lf_2mw.available_channels) + + for qubit_index in qubits: + charge_channels = connectivity.elements[QubitReference(qubit_index)].channels[WiringLineType.CHARGE] + assert len(charge_channels) == 1 + + for i, channel in enumerate(charge_channels): + assert asdict(channel) == asdict([ + InstrumentChannelLfFemOutput(con=1, port=qubit_index, slot=1) + ][i]) \ No newline at end of file From d299db769a29bd68eac01eed2c288a4cea59c4b3 Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Fri, 11 Oct 2024 20:14:03 +1100 Subject: [PATCH 22/40] Write tests for channel reuse.: --- tests/wirer/test_block_channels.py | 42 +++++++++++++++++++++++++ tests/wirer/test_wirer_channel_reuse.py | 4 +-- 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 tests/wirer/test_block_channels.py diff --git a/tests/wirer/test_block_channels.py b/tests/wirer/test_block_channels.py new file mode 100644 index 00000000..607d4e99 --- /dev/null +++ b/tests/wirer/test_block_channels.py @@ -0,0 +1,42 @@ +from dataclasses import asdict + +import pytest + +from qualang_tools.wirer import * +from qualang_tools.wirer.connectivity.element import QubitReference +from qualang_tools.wirer.connectivity.wiring_spec import WiringLineType +from qualang_tools.wirer.instruments.instrument_channel import InstrumentChannelLfFemOutput, \ + InstrumentChannelMwFemInput, InstrumentChannelMwFemOutput + +visualize_flag = pytest.visualize_flag + +def test_alternating_blocking_of_used_channels(instruments_2lf_2mw): + connectivity = Connectivity() + + connectivity.add_qubit_drive_lines(qubits=1) + allocate_wiring(connectivity, instruments_2lf_2mw, block_used_channels=False) + + connectivity.add_qubit_drive_lines(qubits=2) + allocate_wiring(connectivity, instruments_2lf_2mw) + + connectivity.add_qubit_drive_lines(qubits=3) + allocate_wiring(connectivity, instruments_2lf_2mw, block_used_channels=False) + + connectivity.add_qubit_drive_lines(qubits=4) + allocate_wiring(connectivity, instruments_2lf_2mw) + + if visualize_flag: + visualize(connectivity.elements, instruments_2lf_2mw.available_channels) + + expected_ports = [ + 1, # q1 allocated to 1, but channel isn't blocked + 1, # q2 allocated to 1, since it wasn't blocked + 2, # q3 allocated to 2, since it's the next available channel, but not blocked + 2 # q4 allocated to 2, since it wasn't blocked + ] + for qubit_index in [1, 2, 3, 4]: + drive_channels = connectivity.elements[QubitReference(qubit_index)].channels[WiringLineType.DRIVE] + for i, channel in enumerate(drive_channels): + assert asdict(channel) == asdict([ + InstrumentChannelMwFemOutput(con=1, slot=3, port=expected_ports[qubit_index-1]) + ][i]) \ No newline at end of file diff --git a/tests/wirer/test_wirer_channel_reuse.py b/tests/wirer/test_wirer_channel_reuse.py index c3425651..3044ecf3 100644 --- a/tests/wirer/test_wirer_channel_reuse.py +++ b/tests/wirer/test_wirer_channel_reuse.py @@ -23,8 +23,8 @@ def test_5q_allocation_with_channel_reuse(instruments_2lf_2mw): allocate_wiring(connectivity, instruments_2lf_2mw, block_used_channels=False) - if visualize_flag: - visualize(connectivity.elements, instruments_2lf_2mw.available_channels) + # if visualize_flag: + visualize(connectivity.elements, instruments_2lf_2mw.available_channels) for qubit in [1, 3]: # resonator lines re-used for qubits 1 & 3 From 8e9ea70999dc2449df2a768ade42a11dd57d4640 Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Fri, 11 Oct 2024 20:44:35 +1100 Subject: [PATCH 23/40] Fix behaviour for blocking used channels and add type-check to channel equality in test cases. --- .../wirer/instruments/instrument_channels.py | 7 +++ qualang_tools/wirer/wirer/wirer.py | 8 +++- tests/wirer/conftest.py | 3 ++ tests/wirer/test_add_dummy_line.py | 4 +- tests/wirer/test_block_channels.py | 42 ----------------- tests/wirer/test_wirer_channel_reuse.py | 42 +++++++++++++++-- tests/wirer/test_wirer_constraining.py | 2 +- tests/wirer/test_wirer_lf_and_mw.py | 46 +++++++++---------- tests/wirer/test_wirer_lf_and_octave.py | 4 +- tests/wirer/test_wirer_lf_charge.py | 2 +- 10 files changed, 80 insertions(+), 80 deletions(-) delete mode 100644 tests/wirer/test_block_channels.py diff --git a/qualang_tools/wirer/instruments/instrument_channels.py b/qualang_tools/wirer/instruments/instrument_channels.py index 4009414f..24d65035 100644 --- a/qualang_tools/wirer/instruments/instrument_channels.py +++ b/qualang_tools/wirer/instruments/instrument_channels.py @@ -1,3 +1,4 @@ +from dataclasses import asdict from typing import Type from qualang_tools.wirer.instruments.instrument_channel import InstrumentChannel, InstrumentChannelLfFemInput, \ @@ -98,5 +99,11 @@ def __getitem__(self, item): def __iter__(self): return iter(self.stack) + def __contains__(self, item: InstrumentChannel): + for channel_type in self.stack: + if asdict(item) in [asdict(channel) for channel in self.stack[channel_type]]: + return True + return False + def items(self): return self.stack.items() \ No newline at end of file diff --git a/qualang_tools/wirer/wirer/wirer.py b/qualang_tools/wirer/wirer/wirer.py index f8fe578c..b1d4eb54 100644 --- a/qualang_tools/wirer/wirer/wirer.py +++ b/qualang_tools/wirer/wirer/wirer.py @@ -3,6 +3,7 @@ into potential channel specifications, then to allocate them to the first valid combination of instrument channels. """ +import copy from typing import List from .channel_specs import ChannelSpecLfFemSingle, ChannelSpecOpxPlusSingle, ChannelSpecMwFemSingle, \ @@ -28,6 +29,8 @@ def allocate_wiring(connectivity: Connectivity, instruments: Instruments, specs = connectivity.specs + used_channel_cache = copy.deepcopy(instruments.used_channels) + specs_with_untyped_lines = set() for line_type in line_type_fill_order: for spec in specs: @@ -45,8 +48,9 @@ def allocate_wiring(connectivity: Connectivity, instruments: Instruments, if not block_used_channels: for channel_type, used_channels in instruments.used_channels.items(): for i, used_channel in enumerate(reversed(used_channels)): - instruments.available_channels.insert(0, used_channel) - instruments.used_channels.remove(used_channel) + if used_channel not in used_channel_cache: + instruments.available_channels.insert(0, used_channel) + instruments.used_channels.remove(used_channel) def _allocate_wiring(spec: WiringSpec, instruments: Instruments): diff --git a/tests/wirer/conftest.py b/tests/wirer/conftest.py index 32440b68..f948eda3 100644 --- a/tests/wirer/conftest.py +++ b/tests/wirer/conftest.py @@ -1,9 +1,12 @@ +from dataclasses import asdict + from qualang_tools.wirer.instruments import Instruments import pytest def pytest_configure(): pytest.visualize_flag = False + pytest.channels_are_equal = lambda x, y: type(x) == type(y) and asdict(x) == asdict(y) @pytest.fixture(params=["lf-fem", "opx+"]) def instruments_qw_soprano(request) -> Instruments: diff --git a/tests/wirer/test_add_dummy_line.py b/tests/wirer/test_add_dummy_line.py index 5120d0aa..e7041b75 100644 --- a/tests/wirer/test_add_dummy_line.py +++ b/tests/wirer/test_add_dummy_line.py @@ -1,5 +1,3 @@ -from dataclasses import asdict - import pytest from qualang_tools.wirer import Connectivity, allocate_wiring, visualize from qualang_tools.wirer.connectivity.element import Element, Reference @@ -30,7 +28,7 @@ def test_add_dummy_line(instruments_2lf_2mw): # regression test test_element = connectivity.elements[Reference('test')] for i, channel in enumerate(test_element.channels['ch']): - assert asdict(channel) == asdict([ + assert pytest.channels_are_equal(channel, [ InstrumentChannelLfFemInput(con=1, port=1, slot=1), InstrumentChannelLfFemOutput(con=1, port=6, slot=1) ][i]) \ No newline at end of file diff --git a/tests/wirer/test_block_channels.py b/tests/wirer/test_block_channels.py deleted file mode 100644 index 607d4e99..00000000 --- a/tests/wirer/test_block_channels.py +++ /dev/null @@ -1,42 +0,0 @@ -from dataclasses import asdict - -import pytest - -from qualang_tools.wirer import * -from qualang_tools.wirer.connectivity.element import QubitReference -from qualang_tools.wirer.connectivity.wiring_spec import WiringLineType -from qualang_tools.wirer.instruments.instrument_channel import InstrumentChannelLfFemOutput, \ - InstrumentChannelMwFemInput, InstrumentChannelMwFemOutput - -visualize_flag = pytest.visualize_flag - -def test_alternating_blocking_of_used_channels(instruments_2lf_2mw): - connectivity = Connectivity() - - connectivity.add_qubit_drive_lines(qubits=1) - allocate_wiring(connectivity, instruments_2lf_2mw, block_used_channels=False) - - connectivity.add_qubit_drive_lines(qubits=2) - allocate_wiring(connectivity, instruments_2lf_2mw) - - connectivity.add_qubit_drive_lines(qubits=3) - allocate_wiring(connectivity, instruments_2lf_2mw, block_used_channels=False) - - connectivity.add_qubit_drive_lines(qubits=4) - allocate_wiring(connectivity, instruments_2lf_2mw) - - if visualize_flag: - visualize(connectivity.elements, instruments_2lf_2mw.available_channels) - - expected_ports = [ - 1, # q1 allocated to 1, but channel isn't blocked - 1, # q2 allocated to 1, since it wasn't blocked - 2, # q3 allocated to 2, since it's the next available channel, but not blocked - 2 # q4 allocated to 2, since it wasn't blocked - ] - for qubit_index in [1, 2, 3, 4]: - drive_channels = connectivity.elements[QubitReference(qubit_index)].channels[WiringLineType.DRIVE] - for i, channel in enumerate(drive_channels): - assert asdict(channel) == asdict([ - InstrumentChannelMwFemOutput(con=1, slot=3, port=expected_ports[qubit_index-1]) - ][i]) \ No newline at end of file diff --git a/tests/wirer/test_wirer_channel_reuse.py b/tests/wirer/test_wirer_channel_reuse.py index 3044ecf3..d757976a 100644 --- a/tests/wirer/test_wirer_channel_reuse.py +++ b/tests/wirer/test_wirer_channel_reuse.py @@ -23,25 +23,57 @@ def test_5q_allocation_with_channel_reuse(instruments_2lf_2mw): allocate_wiring(connectivity, instruments_2lf_2mw, block_used_channels=False) - # if visualize_flag: - visualize(connectivity.elements, instruments_2lf_2mw.available_channels) + if visualize_flag: + visualize(connectivity.elements, instruments_2lf_2mw.available_channels) for qubit in [1, 3]: # resonator lines re-used for qubits 1 & 3 for i, channel in enumerate(connectivity.elements[QubitReference(index=qubit)].channels[WiringLineType.RESONATOR]): - assert asdict(channel) == asdict([ + assert pytest.channels_are_equal(channel, [ InstrumentChannelMwFemInput(con=1, port=1, slot=3), InstrumentChannelMwFemOutput(con=1, port=1, slot=3) ][i]) # drive lines re-used for qubits 1 & 3 for i, channel in enumerate(connectivity.elements[QubitReference(index=qubit)].channels[WiringLineType.DRIVE]): - assert asdict(channel) == asdict([ + assert pytest.channels_are_equal(channel, [ InstrumentChannelMwFemOutput(con=1, port=2, slot=3) ][i]) # flux lines re-used for qubits 1 & 3 for i, channel in enumerate(connectivity.elements[QubitReference(index=qubit)].channels[WiringLineType.FLUX]): - assert asdict(channel) == asdict([ + assert pytest.channels_are_equal(channel, [ InstrumentChannelLfFemOutput(con=1, port=1, slot=1), ][i]) + + +def test_alternating_blocking_of_used_channels(instruments_2lf_2mw): + connectivity = Connectivity() + + connectivity.add_qubit_drive_lines(qubits=1) + allocate_wiring(connectivity, instruments_2lf_2mw, block_used_channels=False) + + connectivity.add_qubit_drive_lines(qubits=2) + allocate_wiring(connectivity, instruments_2lf_2mw) + + connectivity.add_qubit_drive_lines(qubits=3) + allocate_wiring(connectivity, instruments_2lf_2mw, block_used_channels=False) + + connectivity.add_qubit_drive_lines(qubits=4) + allocate_wiring(connectivity, instruments_2lf_2mw) + + if visualize_flag: + visualize(connectivity.elements, instruments_2lf_2mw.available_channels) + + expected_ports = [ + 1, # q1 allocated to 1, but channel isn't blocked + 1, # q2 allocated to 1, since it wasn't blocked + 2, # q3 allocated to 2, since it's the next available channel, but not blocked + 2 # q4 allocated to 2, since it wasn't blocked + ] + for qubit_index in [1, 2, 3, 4]: + drive_channels = connectivity.elements[QubitReference(qubit_index)].channels[WiringLineType.DRIVE] + for i, channel in enumerate(drive_channels): + assert pytest.channels_are_equal(channel, [ + InstrumentChannelMwFemOutput(con=1, slot=3, port=expected_ports[qubit_index-1]) + ][i]) diff --git a/tests/wirer/test_wirer_constraining.py b/tests/wirer/test_wirer_constraining.py index 1500a2e3..63a9a67f 100644 --- a/tests/wirer/test_wirer_constraining.py +++ b/tests/wirer/test_wirer_constraining.py @@ -32,7 +32,7 @@ def test_opx_plus_resonator_constraining(): # resonator lines should be hard-coded to I=9, Q=10, rf_out=1 for i, channel in enumerate(connectivity.elements[QubitReference(index=1)].channels[WiringLineType.RESONATOR]): - assert asdict(channel) == asdict([ + assert pytest.channels_are_equal(channel, [ InstrumentChannelOpxPlusInput(con=1, port=1, slot=None), InstrumentChannelOpxPlusInput(con=1, port=2, slot=None), InstrumentChannelOpxPlusOutput(con=1, port=9, slot=None), diff --git a/tests/wirer/test_wirer_lf_and_mw.py b/tests/wirer/test_wirer_lf_and_mw.py index d2fcafbc..a1c98b40 100644 --- a/tests/wirer/test_wirer_lf_and_mw.py +++ b/tests/wirer/test_wirer_lf_and_mw.py @@ -1,5 +1,3 @@ -from dataclasses import asdict - import pytest from qualang_tools.wirer import * @@ -27,30 +25,30 @@ def test_6q_allocation(instruments_2lf_2mw): for qubit in qubits: # flux channels should have some port as qubit index since they're allocated sequentially - flux_channels = connectivity.elements[QubitReference(qubit)].channels[WiringLineType.FLUX] - assert [asdict(ch) for ch in flux_channels] == [ - asdict(InstrumentChannelLfFemOutput(con=1, port=qubit, slot=1)) - ] + for i, channel in enumerate(connectivity.elements[QubitReference(qubit)].channels[WiringLineType.FLUX]): + assert pytest.channels_are_equal(channel, [ + InstrumentChannelLfFemOutput(con=1, port=qubit, slot=1) + ][i]) # resonators all on same feedline, so should be first available input + outputs channels on MW-FEM - resonator_channels = connectivity.elements[QubitReference(qubit)].channels[WiringLineType.RESONATOR] - assert [asdict(ch) for ch in resonator_channels] == [ - asdict(InstrumentChannelMwFemInput(con=1, port=1, slot=3)), - asdict(InstrumentChannelMwFemOutput(con=1, port=1, slot=3)) - ] + for i, channel in enumerate(connectivity.elements[QubitReference(qubit)].channels[WiringLineType.RESONATOR]): + assert pytest.channels_are_equal(channel, [ + InstrumentChannelMwFemInput(con=1, port=1, slot=3), + InstrumentChannelMwFemOutput(con=1, port=1, slot=3) + ][i]) # drive channels are on MW-FEM - drive_channels = connectivity.elements[QubitReference(qubit)].channels[WiringLineType.DRIVE] - assert [asdict(ch) for ch in drive_channels] == [ - asdict(InstrumentChannelMwFemOutput(con=1, port=qubit+1, slot=3)) - ] + for i, channel in enumerate(connectivity.elements[QubitReference(qubit)].channels[WiringLineType.DRIVE]): + assert pytest.channels_are_equal(channel, [ + InstrumentChannelMwFemOutput(con=1, port=qubit+1, slot=3) + ][i]) for i, pair in enumerate(qubit_pairs): # coupler channels should have some port as pair index since they're allocated sequentially, but on slot 2 - coupler_channels = connectivity.elements[QubitPairReference(*pair)].channels[WiringLineType.COUPLER] - assert [asdict(ch) for ch in coupler_channels] == [ - asdict(InstrumentChannelLfFemOutput(con=1, port=i+1, slot=2)) - ] + for j, channel in enumerate(connectivity.elements[QubitPairReference(*pair)].channels[WiringLineType.COUPLER]): + assert pytest.channels_are_equal(channel, [ + InstrumentChannelLfFemOutput(con=1, port=i+1, slot=2) + ][j]) def test_4rr_allocation(instruments_2lf_2mw): connectivity = Connectivity() @@ -67,9 +65,9 @@ def test_4rr_allocation(instruments_2lf_2mw): # resonators all on different feedlines, so should fill all 4 inputs of 2x MW-FEM for i, qubit in enumerate([1, 2, 3, 4]): - resonator_channels = connectivity.elements[QubitReference(qubit)].channels[WiringLineType.RESONATOR] - assert [asdict(ch) for ch in resonator_channels] == [ - asdict(InstrumentChannelMwFemInput(con=1, port=[1, 2, 1, 2][i], slot=[3, 3, 7, 7][i])), - asdict(InstrumentChannelMwFemOutput(con=1, port=[1, 2, 1, 2][i], slot=[3, 3, 7, 7][i])) - ] + for j, channel in enumerate(connectivity.elements[QubitReference(qubit)].channels[WiringLineType.RESONATOR]): + assert pytest.channels_are_equal(channel, [ + InstrumentChannelMwFemInput(con=1, port=[1, 2, 1, 2][i], slot=[3, 3, 7, 7][i]), + InstrumentChannelMwFemOutput(con=1, port=[1, 2, 1, 2][i], slot=[3, 3, 7, 7][i]) + ][j]) diff --git a/tests/wirer/test_wirer_lf_and_octave.py b/tests/wirer/test_wirer_lf_and_octave.py index 85cd087d..5327dd2b 100644 --- a/tests/wirer/test_wirer_lf_and_octave.py +++ b/tests/wirer/test_wirer_lf_and_octave.py @@ -29,7 +29,7 @@ def test_rf_io_allocation(instruments_1OPX1Octave): for qubit in qubits: # resonator lines should be the same because only 1 feedline for i, channel in enumerate(connectivity.elements[QubitReference(index=qubit)].channels[WiringLineType.RESONATOR]): - assert asdict(channel) == asdict([ + assert pytest.channels_are_equal(channel, [ InstrumentChannelOpxPlusInput(con=1, port=1, slot=None), InstrumentChannelOpxPlusInput(con=1, port=2, slot=None), InstrumentChannelOpxPlusOutput(con=1, port=1, slot=None), @@ -42,7 +42,7 @@ def test_rf_io_allocation(instruments_1OPX1Octave): # drive lines should be allocated sequentially for i, channel in enumerate(connectivity.elements[QubitReference(index=qubit)].channels[WiringLineType.DRIVE]): - assert asdict(channel) == asdict([ + assert pytest.channels_are_equal(channel, [ InstrumentChannelOpxPlusOutput(con=1, port=1+2*qubit, slot=None), InstrumentChannelOpxPlusOutput(con=1, port=2+2*qubit, slot=None), InstrumentChannelOctaveOutput(con=1, port=1+qubit, slot=None), diff --git a/tests/wirer/test_wirer_lf_charge.py b/tests/wirer/test_wirer_lf_charge.py index c05e946c..d773640f 100644 --- a/tests/wirer/test_wirer_lf_charge.py +++ b/tests/wirer/test_wirer_lf_charge.py @@ -25,6 +25,6 @@ def test_1q_allocation_flux_charge(instruments_2lf_2mw): assert len(charge_channels) == 1 for i, channel in enumerate(charge_channels): - assert asdict(channel) == asdict([ + assert pytest.channels_are_equal(channel, [ InstrumentChannelLfFemOutput(con=1, port=qubit_index, slot=1) ][i]) \ No newline at end of file From 281ac6e1cf33c67bca09866dae2ebf66b83a88d3 Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Fri, 11 Oct 2024 22:19:59 +1100 Subject: [PATCH 24/40] Add plotting for digital channels and octave-opx inter-channel connections. --- qualang_tools/wirer/visualizer/constants.py | 43 ------------ .../visualizer/instrument_figure_manager.py | 2 +- qualang_tools/wirer/visualizer/layout.py | 67 +++++++++++++++++++ .../wirer/visualizer/port_annotation.py | 63 +++++++++-------- tests/wirer/conftest.py | 4 +- tests/wirer/test_visualizer.py | 35 ++++++++++ tests/wirer/test_wirer_lf_and_octave.py | 20 +++--- 7 files changed, 149 insertions(+), 85 deletions(-) delete mode 100644 qualang_tools/wirer/visualizer/constants.py create mode 100644 qualang_tools/wirer/visualizer/layout.py create mode 100644 tests/wirer/test_visualizer.py diff --git a/qualang_tools/wirer/visualizer/constants.py b/qualang_tools/wirer/visualizer/constants.py deleted file mode 100644 index dd1913ad..00000000 --- a/qualang_tools/wirer/visualizer/constants.py +++ /dev/null @@ -1,43 +0,0 @@ -# Map InstrumentChannel.instrument_id to containing instrument type -instrument_id_mapping = { - "lf-fem": "OPX1000", - "mw-fem": "OPX1000", - "opx+": "OPX+", - "octave": "Octave", -} - -# Define the chassis dimensions -INSTRUMENT_FIGURE_DIMENSIONS = { - "OPX1000": {"width": 8, "height": 3}, - "OPX+": {"width": 8, "height": 1}, - "Octave": {"width": 3, "height": 1} -} - -OPX_PLUS_ASPECT = (INSTRUMENT_FIGURE_DIMENSIONS["OPX+"]["height"] / - INSTRUMENT_FIGURE_DIMENSIONS["OPX+"]["width"]) -OPX_1000_ASPECT = (INSTRUMENT_FIGURE_DIMENSIONS["OPX1000"]["height"] / - INSTRUMENT_FIGURE_DIMENSIONS["OPX1000"]["width"]) -# Define the port positions for different modules -PORT_SPACING_FACTOR = 0.12 -PORT_SIZE = 0.055 -PORT_POSITIONS = { - "lf-fem": { - "output": [(0.05 + 0.25 * OPX_1000_ASPECT, 1.06 - i * PORT_SPACING_FACTOR) for i in range(8)], - "input": [(0.05 + 0.75 * OPX_1000_ASPECT, 1.06 - (6+i) * PORT_SPACING_FACTOR) for i in range(2)], - }, - "mw-fem": { - "output": [(0.05 + 0.25 * OPX_1000_ASPECT, 1.06 - i * PORT_SPACING_FACTOR) for i in range(8)], - "input": [(0.05 + 0.75 * OPX_1000_ASPECT, 1.06 - i * PORT_SPACING_FACTOR * 7) for i in range(2)], - }, - "opx+": { - "input": [(1.1*3, 0.01 + 0.32 / (i+1)) for i in range(2)], - "output": [((0.7 + j * 0.06)*3, 0.01 + 0.32 / (i+1)) for j in range(5) for i in range(2)], - "digital_output": [((0.3 + j * 0.06)*3, 0.01 + 0.32 / (i+1)) for j in range(5) for i in range(2)], - }, - "octave": { - "input": [((0.19 + j * 0.06)*3, 0.25) for j in range(2)], - "output": [((0.3 + j * 0.06)*3, 0.32) for j in range(5)], - "digital_output": [((0.3 + j * 0.06)*3, 0.18) for j in range(5)], - } -} - diff --git a/qualang_tools/wirer/visualizer/instrument_figure_manager.py b/qualang_tools/wirer/visualizer/instrument_figure_manager.py index 44b3a5c7..1fea67b9 100644 --- a/qualang_tools/wirer/visualizer/instrument_figure_manager.py +++ b/qualang_tools/wirer/visualizer/instrument_figure_manager.py @@ -4,7 +4,7 @@ from matplotlib.axes import Axes from matplotlib.figure import Figure -from qualang_tools.wirer.visualizer.constants import INSTRUMENT_FIGURE_DIMENSIONS, instrument_id_mapping +from qualang_tools.wirer.visualizer.layout import INSTRUMENT_FIGURE_DIMENSIONS, instrument_id_mapping def _key(instrument_id: str, con: int): diff --git a/qualang_tools/wirer/visualizer/layout.py b/qualang_tools/wirer/visualizer/layout.py new file mode 100644 index 00000000..0bcbad69 --- /dev/null +++ b/qualang_tools/wirer/visualizer/layout.py @@ -0,0 +1,67 @@ +# Map InstrumentChannel.instrument_id to containing instrument type +instrument_id_mapping = { + "lf-fem": "OPX1000", + "mw-fem": "OPX1000", + "opx+": "OPX+", + "octave": "Octave", +} + +# Define the chassis dimensions +INSTRUMENT_FIGURE_DIMENSIONS = { + "OPX1000": {"width": 8, "height": 3}, + "OPX+": {"width": 8, "height": 1}, + "Octave": {"width": 3, "height": 1} +} + +OPX_PLUS_ASPECT = (INSTRUMENT_FIGURE_DIMENSIONS["OPX+"]["height"] / + INSTRUMENT_FIGURE_DIMENSIONS["OPX+"]["width"]) +OPX_1000_ASPECT = (INSTRUMENT_FIGURE_DIMENSIONS["OPX1000"]["height"] / + INSTRUMENT_FIGURE_DIMENSIONS["OPX1000"]["width"]) +# Define the port positions for different modules +PORT_SPACING_FACTOR = 0.12 +PORT_SIZE = 0.055 + +fem_analog_output_positions = [(0.05 + 0.25 * OPX_1000_ASPECT, 1.06 - i * PORT_SPACING_FACTOR) for i in range(8)] +fem_digital_output_positions = [(0.05 + 0.75 * OPX_1000_ASPECT, .86 - i * PORT_SPACING_FACTOR/2.4) for i in range(8)] + +PORT_POSITIONS = { + "lf-fem": { + "analog": { + "output": fem_analog_output_positions, + "input": [(0.05 + 0.75 * OPX_1000_ASPECT, 1.06 - (6+i) * PORT_SPACING_FACTOR) for i in range(2)], + }, + "digital": { + "output": fem_digital_output_positions + } + }, + "mw-fem": { + "analog": { + "output": fem_analog_output_positions, + "input": [(0.05 + 0.75 * OPX_1000_ASPECT, 1.06 - i * PORT_SPACING_FACTOR * 7) for i in range(2)], + }, + "digital": { + "output": fem_digital_output_positions + } + }, + "opx+": { + "analog": { + "input": [(1.1*3, 0.01 + 0.32 / (i+1)) for i in range(2)], + "output": [((0.7 + j * 0.06)*3, 0.01 + 0.32 / (i+1)) for j in range(5) for i in range(2)], + }, + "digital": { + "output": [((0.3 + j * 0.06)*3, 0.01 + 0.32 / (i+1)) for j in range(5) for i in range(2)], + } + }, + "octave": { + "analog": { + "input": [((0.19 + j * 0.06)*3, 0.25) for j in range(2)], + "output": [((0.3 + j * 0.06)*3, 0.32) for j in range(5)], + "inter_input": [(1.1*3, 0.01 + 0.32 / (i+1)) for i in range(2)], + "inter_output": [((0.7 + j * 0.06) * 3, 0.01 + 0.32 / (i + 1)) for j in range(5) for i in range(2)], + }, + "digital": { + "input": [((0.3 + j * 0.06)*3, 0.18) for j in range(5)], + } + } +} + diff --git a/qualang_tools/wirer/visualizer/port_annotation.py b/qualang_tools/wirer/visualizer/port_annotation.py index 9568a606..3d97660f 100644 --- a/qualang_tools/wirer/visualizer/port_annotation.py +++ b/qualang_tools/wirer/visualizer/port_annotation.py @@ -1,3 +1,4 @@ +import copy import re from collections import defaultdict from typing import List @@ -6,7 +7,7 @@ from matplotlib.axes import Axes import matplotlib.colors as mcolors -from .constants import PORT_POSITIONS, PORT_SIZE, PORT_SPACING_FACTOR +from .layout import PORT_POSITIONS, PORT_SIZE class PortAnnotation: def __init__(self, labels, color, con, slot, port, io_type, signal_type, instrument_id): @@ -20,43 +21,47 @@ def __init__(self, labels, color, con, slot, port, io_type, signal_type, instrum self.instrument_id = instrument_id def draw(self, ax: Axes): - if self.signal_type == "digital": - return - pos = PORT_POSITIONS[self.instrument_id][self.io_type][self.port - 1] - x, y = pos + x, y = PORT_POSITIONS[self.instrument_id][self.signal_type][self.io_type][self.port - 1] fill_color = self.color if self.labels else "none" - ax.add_patch(patches.Circle((x, y), PORT_SIZE, edgecolor="dimgrey", facecolor=fill_color)) + if self.signal_type == "digital": + outline_colour = "whitesmoke" + else: + outline_colour = "dimgrey" + + if self.signal_type == "digital" and self.instrument_id in ["lf-fem", "mw-fem"]: + port_size = PORT_SIZE / 2.4 + bbox = dict(facecolor=fill_color, alpha=0.8, edgecolor="none") + else: + port_size = PORT_SIZE + port_label_distance = PORT_SIZE * 1.3 + ax.text(x - port_label_distance, y, str(self.port), ha="center", va="center", fontsize=8, fontweight="bold", color=outline_colour) + bbox = None + + ax.add_patch(patches.Circle((x, y), port_size, edgecolor=outline_colour, facecolor=fill_color)) labels = combine_labels_for_same_line_type(self.labels) - # port annotation - ax.text( - x - PORT_SIZE * 1.3, - y, - str(self.port), - ha="center", - va="center", - fontsize=8, - fontweight="bold", - color="dimgrey", - ) + # qubit line annotation - ax.text( - x, - y, - "\n".join(labels), - ha="center", - va="center", - fontsize=9, - color="black", - # fontweight="bold", - # bbox=dict(facecolor="white", alpha=0.7, edgecolor="none"), - ) + ax.text( x, y, "\n".join(labels), ha="center", va="center", fontsize=9, color="black", bbox=bbox) ax.set_facecolor('lightgrey') + if self.instrument_id == "octave" and self.signal_type == "analog": + for port in [2 * self.port, 2 * self.port - 1]: + if self.io_type == "output": + # draw inter-instrument port annotations for convenience + annotation_inter_output_I = copy.deepcopy(self) + annotation_inter_output_I.io_type = "inter_output" + annotation_inter_output_I.port = port + annotation_inter_output_I.draw(ax) + elif self.io_type == "input" and self.port == 1: + annotation_inter_input_I = copy.deepcopy(self) + annotation_inter_input_I.io_type = "inter_input" + annotation_inter_input_I.port = port + annotation_inter_input_I.draw(ax) + def title_axes(self, ax: Axes): if self.slot is not None: ax.set_title(f"{self.slot}: {self.instrument_id}", y=-0.1, va="bottom") - def get_contrast_color(color): """ Returns black or white depending on the luminance of the input color. diff --git a/tests/wirer/conftest.py b/tests/wirer/conftest.py index f948eda3..8e093301 100644 --- a/tests/wirer/conftest.py +++ b/tests/wirer/conftest.py @@ -5,7 +5,7 @@ def pytest_configure(): - pytest.visualize_flag = False + pytest.visualize_flag = True pytest.channels_are_equal = lambda x, y: type(x) == type(y) and asdict(x) == asdict(y) @pytest.fixture(params=["lf-fem", "opx+"]) @@ -20,7 +20,7 @@ def instruments_qw_soprano(request) -> Instruments: return instruments @pytest.fixture(params=["opx+"]) -def instruments_1OPX1Octave(request) -> Instruments: +def instruments_1opx_1octave(request) -> Instruments: instruments = Instruments() if request.param == "lf-fem": instruments.add_lf_fem(controller=1, slots=[1]) diff --git a/tests/wirer/test_visualizer.py b/tests/wirer/test_visualizer.py new file mode 100644 index 00000000..4db8fb1c --- /dev/null +++ b/tests/wirer/test_visualizer.py @@ -0,0 +1,35 @@ +import pytest + +from qualang_tools.wirer import visualize, Connectivity, lf_fem_spec, allocate_wiring + + +@pytest.mark.skip(reason="plotting") +def test_6q_allocation_visualization(instruments_2lf_2mw): + qubits = [1, 2, 3, 4, 5, 6] + qubit_pairs = [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)] + + connectivity = Connectivity() + connectivity.add_resonator_line(qubits=qubits, triggered=True) + connectivity.add_qubit_drive_lines(qubits=qubits, triggered=True) + connectivity.add_qubit_flux_lines(qubits=qubits) + connectivity.add_qubit_pair_flux_lines(qubit_pairs=qubit_pairs, constraints=lf_fem_spec(out_slot=2)) + + allocate_wiring(connectivity, instruments_2lf_2mw) + + visualize(connectivity.elements, instruments_2lf_2mw.available_channels) + + +@pytest.mark.skip(reason="plotting") +def test_4q_allocation_visualization(instruments_1opx_1octave): + qubits = [1, 2] + qubit_pairs = [(1, 2)] + + connectivity = Connectivity() + connectivity.add_resonator_line(qubits=qubits, triggered=True) + connectivity.add_qubit_drive_lines(qubits=qubits, triggered=True) + connectivity.add_qubit_flux_lines(qubits=qubits) + connectivity.add_qubit_pair_flux_lines(qubit_pairs=qubit_pairs) + + allocate_wiring(connectivity, instruments_1opx_1octave) + + visualize(connectivity.elements, instruments_1opx_1octave.available_channels) diff --git a/tests/wirer/test_wirer_lf_and_octave.py b/tests/wirer/test_wirer_lf_and_octave.py index 5327dd2b..bd9571b7 100644 --- a/tests/wirer/test_wirer_lf_and_octave.py +++ b/tests/wirer/test_wirer_lf_and_octave.py @@ -13,7 +13,7 @@ visualize_flag = pytest.visualize_flag -def test_rf_io_allocation(instruments_1OPX1Octave): +def test_rf_io_allocation(instruments_1opx_1octave): qubits = [1, 2, 3, 4] @@ -21,10 +21,10 @@ def test_rf_io_allocation(instruments_1OPX1Octave): connectivity.add_resonator_line(qubits=qubits) connectivity.add_qubit_drive_lines(qubits=qubits) - allocate_wiring(connectivity, instruments_1OPX1Octave) + allocate_wiring(connectivity, instruments_1opx_1octave) if visualize_flag: - visualize(connectivity.elements, available_channels=instruments_1OPX1Octave.available_channels) + visualize(connectivity.elements, available_channels=instruments_1opx_1octave.available_channels) for qubit in qubits: # resonator lines should be the same because only 1 feedline @@ -65,7 +65,7 @@ def test_qw_soprano_allocation(instruments_qw_soprano): # should run without error -def test_qw_soprano_2qb_allocation(instruments_1OPX1Octave): +def test_qw_soprano_2qb_allocation(instruments_1opx_1octave): active_qubits = [1, 2] connectivity = Connectivity() @@ -74,14 +74,14 @@ def test_qw_soprano_2qb_allocation(instruments_1OPX1Octave): connectivity.add_qubit_drive_lines(qubits=[2], constraints=opx_iq_octave_spec(rf_out=4)) connectivity.add_qubit_flux_lines(qubits=active_qubits) - allocate_wiring(connectivity, instruments_1OPX1Octave) + allocate_wiring(connectivity, instruments_1opx_1octave) if visualize_flag: - visualize(connectivity.elements, available_channels=instruments_1OPX1Octave.available_channels) + visualize(connectivity.elements, available_channels=instruments_1opx_1octave.available_channels) # should run without error -def test_qw_soprano_2qb_among_5_allocation(instruments_1OPX1Octave): +def test_qw_soprano_2qb_among_5_allocation(instruments_1opx_1octave): all_qubits = [1, 2, 3, 4] active_qubits = [1, 2] other_qubits = list(set(all_qubits) - set(active_qubits)) @@ -94,15 +94,15 @@ def test_qw_soprano_2qb_among_5_allocation(instruments_1OPX1Octave): connectivity.add_qubit_drive_lines(qubits=[2], constraints=q2_ch) connectivity.add_qubit_flux_lines(qubits=active_qubits) - allocate_wiring(connectivity, instruments_1OPX1Octave, block_used_channels=False) + allocate_wiring(connectivity, instruments_1opx_1octave, block_used_channels=False) connectivity.add_resonator_line(qubits=other_qubits) connectivity.add_qubit_drive_lines(qubits=other_qubits) connectivity.add_qubit_flux_lines(qubits=other_qubits) - allocate_wiring(connectivity, instruments_1OPX1Octave) + allocate_wiring(connectivity, instruments_1opx_1octave) if visualize_flag: - visualize(connectivity.elements, instruments_1OPX1Octave.available_channels) + visualize(connectivity.elements, instruments_1opx_1octave.available_channels) # should run without error \ No newline at end of file From ff22e769349bcb0aecc51aa93d7002122d8323ab Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Fri, 11 Oct 2024 23:03:08 +1100 Subject: [PATCH 25/40] Start README.g --- README.md | 1 + qualang_tools/wirer/.img/overview_octave.png | Bin 0 -> 51776 bytes qualang_tools/wirer/.img/overview_opx1000.png | Bin 0 -> 88953 bytes .../wirer/.img/overview_opx_plus.png | Bin 0 -> 44519 bytes qualang_tools/wirer/README.md | 93 ++++++++++++++++++ 5 files changed, 94 insertions(+) create mode 100644 qualang_tools/wirer/.img/overview_octave.png create mode 100644 qualang_tools/wirer/.img/overview_opx1000.png create mode 100644 qualang_tools/wirer/.img/overview_opx_plus.png create mode 100644 qualang_tools/wirer/README.md diff --git a/README.md b/README.md index 456d5696..17051636 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ storing them in the usual configuration file. It allows defining waveforms in a * [Digital filters](qualang_tools/digital_filters/README.md) - Library of functions allowing the derivation of the digital filter taps to correct distortions. * [Voltage Gates](qualang_tools/voltage_gates/README.md) - The `VoltageGateSequence` class facilitates the creation and management of complex pulse sequences for quantum operations, allowing for dynamic voltage control, ramping, and bias compensation across multiple gate elements. +* [Wirer](qualang_tools/wirer/README.md) - The `wirer` tool allows for automatic allocation of arbitrary channels to an arbitrary combination of instruments. ## Installation diff --git a/qualang_tools/wirer/.img/overview_octave.png b/qualang_tools/wirer/.img/overview_octave.png new file mode 100644 index 0000000000000000000000000000000000000000..1067e2a7566495afc71b123d737497c378a54659 GIT binary patch literal 51776 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%0AE|;lWT+mo;6#FRYg$vUl^K$^@u^g+2 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 literal 0 HcmV?d00001 diff --git a/qualang_tools/wirer/.img/overview_opx1000.png b/qualang_tools/wirer/.img/overview_opx1000.png new file mode 100644 index 0000000000000000000000000000000000000000..33ab81802bb570b083487c757021509409a3b2f5 GIT binary patch literal 88953 zcmeFZcR1DY|37?;>`ll{(Xh*wO;i*TvS-Xhsll1(U)T^YxoM?x}>z4w;A@8{|B z9rt}*_y51^zOMUR(ZMt^0)ZgCr>vlXK;X$B5ZLqt zc<>vcG&WxNPs~M8$K|p8GZ%LgCkuqCiHpMvdzTm1rfhB&PR`c$cDH#&d2eyES-H44 zIE(Y~+5XQ9clOFXW0(6kAJdYdT2r{OxHZOQT4s@PB*whBl94QBeNx z!&OLFCiH(FIb9|=um1PP&?Bb*6MsJX6mR5rDUeNu0v8t#&&})Dpvvu1IUOz*9iQRq zUwR(dZS+CEwo#%46FWRId7JPEFjwS?Wc5FxB)_wXcumTP$HkRLYgC-{;Mp_IvmEfRZ2Upi%xr~6SDv7&M48fSsP*hYbutT3b*qN863d8;V`!~ISDel?XeqGkrufHlf zYkiN|;_gSSjJr?xYOiT$b+d_!4_8FT$J5<^@F1#P`grv=w?QS1fPlc8Hgit@w?*ic zt+s3F#vKU)4vvm5qoPO~8yhzV&0;b>e*D$ zt?JA;!4>B1)wrz<)A}br{_-%GqyKlGK8FbEP{dzfP7nke?GOJu@_x=Z@}&W@LF^2*@w@O11WsfIE4iLvQJ z%t#eg|Ggj5N%1l9_qXlZ*0}e^CD!x3 z5&HxIv)5ZC-@o7HHK=sycf`iV9TjQ!^f=gc(C~-WLIw?W7*hc*leP>({URF9!K(XlYIN*T+y6k_LXY zy90V2dqb8{F$Vhj#ST+)2S-QjSFYgL4CKoi7%&H*_}3I6WvC_I0Db98L1#IlP;C4s+T z&_SoxM@;JMV20MQ3W;oJYQkxHO)CHV`PVJcH6fWKktd%tZhYW1AmZcW^V#j!{`vbi z9s)KOI}Z=3o0}UVz^paeHb8_(UF%AOgd_RdqB^=B}V#eaF2E2oB!E%y2o_bwMc)QQeE2@ zDXT-@zbJmkK^Xe(KB?YpBu2G%GTOm{yX|*&xIh*&l$MmlFgQ3U!@k5U(jj6WK*A_Y zQa<4)@!ZD7aeY+m#b9xU;uYJUZ!RmVsJtpDC=ipYc@`HJM?2($2>|TG*QhXaP~>{B zY2w)Sj>A-0In>I`?8@-aP?(8{$@Dvp2hEX@k+drV0|Vh3HT!9z&-<=-4-Gwi-#(Xl z|1;H>N9nwN=coJHYZ8|)%i}lHLvTxsR@uo}r*XlfddIJv+k0G6ykdQJjFRv)P=Fq#sW6N~r z2n!3Vpmw0Ar-!yxx5PBqacHUci=r$=WV_VKru@ve3&gba>EuL2R6-0hd2EF z`#0~5RPcO6?)Ty9s9pN$!svLxXDtW#f8~prdC!tIcnFNCnt@!V7pfh{`vD~ zzNGf#Z#!-K^XJc-VPA#H%F1S{a8k2+uL`KsFiOY6cDPB#a%b?saAa^W^z!A){}#&o z_wVzjk$>%7=Q}Prj#qnTW@Tk9PEWIsJGL^LnwVr<56`UdJ$7@17@rvx6}7m&&I|GF zM}L<#U3Z2g^UkLnT=7cV=Ys{ZlgM~kq1?cuYKqDNnPFENrMny;mK zG*-M=?#pFI2;91*p^>B!CnIRz|j?CWP z$Fxy}Wl2=Ixw&h8&kz*nJ71&?T&p2jmQ}7p^iZ`6h-8*1YnjQ$`w4kz$nFkNu-s^w ztzLvj(s@B(V4wmWU3^~I%0yjV#q<8Jw4H*FrDrfW=BLwcw0C6~Jj)5jpaZ^|op+9+ zH((EA5;r6@q7U3po%*YZ-N#(%;LNOjd|;%fTj-3Mf4;pE)m30nMQdNZbvLys?~dM73+^^hIcO$3+$kl3db1)qQAc%wO?axfN*ed zsQ)M%Bz|W+j*LkZ!NtWj>VJOfF&C%JZ$ExV`LjlBxwQY;9oTDIlR@MVp3?$g0Xq)C zDchbH&?sU?1aq*3^@i8fD4Vg)R| z633alv1SidRGLF6uWZ7CpV?|5l(Ej#MHN zhYdDl=5HG*WLat|^SjvC?jj@U8D}9OArxO(46Af>s)Rd3yyWRlFJ_k^1RvT-0={Ar zbwD6CA(k)#D)Py5W*r5)r|En5?mINnfk*O)!P56Gsi24m;;(smTPsC%(WAgzT%Q@G zM^G>`M*+@4-sbya0BMGY#pqC%nTaW~F8;#@H?QQfGMhC?S4bi1qAapEiHO=^~F^Vj*Q}B3MV8JW<7RiDMcL*ezPEkEJTJlfVqttfyc~b zLlD3(M8KOjZ)mrdek^>@vugmX;L&f{%~e#59n=s2;bXEd_i;{6PC!Tqe)gmEXh5`B zh=-}-!6_+B2Zx8Hl^eS&Xh!F@)>eGTG$;#3;TQGACQWKjo?H+S5wR-OMSM8E^beG+ znf>E1Iq|s#1uZ3Jt&uTwJi2|EhggEbibcxGM=BmZeR}D4 zikMk-QY8|f)#r~#e|cUW7oaP3O-)?H*xqLz#EbFjOV-xb+LF?~M=lDAimoG$BRvBK zw_d({dC=v5*1P--KrLX~+7@Rd5(z-_bxh2o_*R0+uWj>1KBERB36*1UN$cx~vx@C2 zkc(TKq@|_dvMyh{h7Z951Ky%pB%kYlIZPE!74SXUS$Xc~;qA?s%v#^y`TFl)Cs9#R zeOGi#reZ{7T~$Q|X8sPA^NI?^qWARMCqDi7K@&m8hqA9b9a#5*GeAuw*P#F`zyT1O zU!+glX7PT8U*yuYZMGXJBi$c!XZ`m5yF_GeDW0;iv2ny*T^X5x5-chjnpY6kv8-N9 z=W5L~9p@1v4zHWwx6KQH`m3g(wX=x2S8F5vhz=g~DXjJEy zaNClM#e20xYu~`oF#7X@c*ETbE{cj5AVr76IxnY}Th=uY+tGqWo|2lXcgJpeeSLgh zcumsIwu%dFy`sY$78Ml?(QJ!1QPO*lo{M9E9?^(6I$>6^%fv=AA6S~kFCZP zmLh?C*$fxn^F9|2kUsN;#Qei!ytnuJSgXMjq#GWVRY+{YHOouW0Kd0tyW5aP~;uIZJ*;|ac_y=8(aG&f$ev+^hFDOJQcz^JXVM#rsI{N zQKToqgS1NsLB?Y$m~NQKa0UJ>Y^cZ^+g1!rY35sjUiowrxwPO?sf&4i02U?(B@tVt zY-HW*W#VmUXh2B>iw|cXx^>3%@AwI-=k@30=T|>Zj=oMq6iF}8c;L~MCOHU+E#QNH z%)x;NLgwB*LaD=9>X~gIs3?;J@w`KC2OgEv^6u)(7Z5EiEj%oAw6uoWOtz4z8}=vs z?R{1Oa#irkj%us8%EYQ=wb8Zt3Pf*4DN>`CD>0+k?;@ zRm}jF-6=9b&stSkX(;3qgY*SLL!r3!HJ#(Z2bYR9fcv^WQ-{MgMFv83t$=H{&W<-2 zS*xn5%+5{@4XBRErB4F-^RyXR>kg(P4i>Xw9C}qnE~R_J3;^9gu1F2Mzwy}E*4Eai z+HFmDW@Bk-iRX3fbB4lukca0x5>X2xcB7%d#$au53fa>~!BBdX{{b=|*`=+eWeUUz z)v7I=m_$z6|90NpVRfvE?s+MLOgo7#09&eV1MTu)z!Ag zJbn5!41*vPx2GVgI7djZ>N+p=aLh1CBlw-?RdDg~4neP}mvB4ju8?Q;vW zg%JSy(pnOPcGtCS*z1H3A3j7B+L>ZV5hWHAb_53fXnO$*bsVuR$3j3l60rKi#;spL z0lVa9TU!I9lPy?TrT};8MIG2MFo#*f4S^WE zmo8n>sqrdx9ORIt*n|&l93MZcj4@DC3&4cl0}mJ(of6XvkYxe?1}7xYL&(W0E2E8| zdU{6eZ`%7GGwSQ>PfkwmH?wUo0spQNDj48-W?B_IN%eUtdY7qs1#TUghInHeX)HKOY~p^3%Szy#Rs&IXq}C zUIYrGyKugE&(YB_G%QTu;j1%Phf|Hg#GCv3t+4R^aBQoj-0`H^sNN=9URiP6TYZY5 zw=Kn*@L@b>A8gfgH67gCgzb)xR*JGJD(J;i$&7rq6qDsuKq(>7D|-O|kZAy*lLJiV zWeJHY#qYbi`a>zHa0!^a3H(KPV;!OodnRLwGG=Gh^tAKUaRbrAw%WA>Apzw|OhnZD z^XE@t^aVChSd99fm!fQ`sHk+gfvUHJMOZwvgxl+@(PFdFid#g*Qy<8o$l%}tWBN|MTRw%Eq<_lcWK*`>+04{ z1!KMx7CwaQc1m8Utf&Zuh^E~F+bunuD(q9|njwU@yfR?jFGNtXL3m*t92}fA+Nwa@ z6p;GkrqcI6|NXj4%dO+kza?xx?$htpdF}>F*S5A!d)_{F?~Ih&uzK*o$H%9-Z3N!? zk88BpTUc;BOcCuq5WRl=XJ2{er#o0|ygJ1)`8hdO?*Cz!C&~7;L(6_Z@mc`;FtG9& zR#&u~(emmKF1vxyW54Aw_bU!%`yU#q+xzDa8>+eaN9D%6e+-wj-te)W(0P8PQ{oLV zOmbBC5G8Mn*>bxYz%rjK-B9pY=NoBN+t!Lg! z^U4T80^FKuEir8&f@`ivq40oQ(u4Xn?zO@Lpn3A^y#WbR8=D9i(J~J^pvl?=XhCvi z>Re5)el`0K&q3@Ptn-(~2uj}G-h(3}W<8(o4Su&I2dy-6#X!tR*;4r`705yLK=%O` zHeZRk69}w!8um6n*(Hl`4C7$+*~B{9UBO4{lfijnPP@u|!)~89x2W!n0eCE@oSYn6 zd^;sQeJBvNjFvH3nFXUPecsmJsS+jTZB!MYk^TBb)ZN{E06b#rZ#$p+b0f?ox|mTY zlnnXKV4efuv1MRjV2f=lWO!Z(I2&I6^VcuzKZEa&;9Z@R)2<-b1Vu%4KIsA#@7QDZ ztx{yn_TRsgYinb%8JM-uO`2_QIQFpJ;{LM7#iZ%$1jC=*ufY-1(6r-9A zp1e`M>Txg?(#f<`TD6eo{juAM-{KbyE-voJ>}))MhzAqrXO{YwkilN~{gWKaXPDXk zbC!~`2U{~R1rEvW7l|%lA{0=$ANX0zy4&-eO)yL&`cggz4j2r}%gZZCe-U$LW1$}S z|73GYb@Hw4?WUO+>1fc|A)+wG^BM$}mI?u_=Jz}CxC4?i0Xeyddji^GYr2I37ptwU z?fQ)yFSYW5lam?oNEu#@*j174k2*g}PqoL8?oe*xjejZ#CJMnLQ%b34>*jyX^*`Th zh#CLu-2eUDxA33b|KBh1CHw#X3}f;C=A6ultV+hag-*nD6LTGt$!5#t(o4#{54I7}eAsi*%S2`p=g5 z_kG#KN_Rr??WC5YNFoD;(xPo*GYEK>qCJi$zx`>Bn669)v z{DuWmP|o}N*ZIzM^z@P?T$U{ge+vmcw|Sn?yTZnxplqRmNgv2c*<91Y?$WAT==*)o z`wY+YZ-=s*YuuQ1x}ZO%pP~6R!6v@HuTQ2x3j=llCCA_VmC?WS5xsIbP8o`CQ z(~{0m*I^@=CrgFk;+2@UT>6fxZ(u-pzy=S;{MMyeCoz2kgD6-0l~S$c(g*P$`=2we z2<84LF?4pY+Da=iyu!Zx6bdy@LwkBOFyIUH5{OD;r=~ z6mmE3Wu;tH){s(0zfj@I(p1 z(B%!%y!o=hSSILU%=F*q<`!u!kcqJnWx~Rtp^*ya=2w}Rm^u#b!yx5|@DJEnc4cN{ zU_l)AIopm>4n3? z;%}uv6UCGc#6+*Neo^1z$|EZ;;^9(Mlw~36(ST~F+v-?3`CCd#fX6RWQw_*#sJ^+V zB0qdcdVLb;GpIXaswzb;l>bVuaM9s7{3{X7hl|sFEsqCleI@qR#zI1s=NU@P zJ4%0O&Mwf^Hw+L_hVjbn%EYc1^__YKec7^fVwS_Zq0Tk@fX5B=r0U zDJdz*otys#^&rdefxMre6pS;!<1{7;)pvCjHC2g|;NkQ=7vj~g2mndgun@g>IWs0b zgkh!Bc>%t?#1Zg6^Eo&^ZtCwZo$)HDWEdJ6QdU)MX$Zo9`ah%i2`Wtz)KqWM|GSgf&2D`&j3sorhiaBdBK&CocRaXi1bk;*UU|5$6YZ$>T!yc~*49~(yn-irF%q^o+& zYkYdNOF&HA1Z&Rz;PSuW%r$%CoRL;GW+27Vf{gQ=+1ygJ^ zF}b|=AEg~rtIRqc^ju`!9ahKS#$WGm&`Wv=f+8dVpXu$bU|9Ru9SFRr&jG*N#(1Y! zRA^3e36@-6lY+W}t}X)^(;Wh26wm81Ke|!upepl;J-hYL3hSG4t<5t#(LBn^$})GK zadJkol0!j)R}62L1*I?HfDk5P9A)yqeIj`RZz+r}3}t1CsmKvsm8xYVBqaRV@w54W zu%nbw{c$1l9S6joFwq^a_Uo_kb1Kaz(hA8cY#R|B440zQ-M$FT>psIn#iajPaW44u z30J~>UFNCvrED_trXN$Mm*Wl2|4uikG->PLpMO0z&oz<@#w zaNDjh?9xeDxc-@LC00cb50CiOk=|bF@-a8YXPrsGz#NvGYCeQ*`A*(}2sjc3Wkwa? zvc1`=LHgro%$?cAWo0O+9PndAZYdvn>61+y4CMg*XTU~5!61^9B9o=>&z}Gg-mZv< z(D?OMw51Phn!ZsZo>wDdsnlXG5szsh%;It593Rxce_bKhPv$L}pH%7(W2ffO55Tei)cO1SfLa>AQmP!uC=$KPK&ehQ z_2q^-d2Ufr>(Opqrdk2&&&ayMY+vG7&xzH;OH{AB_oWCd&t*<(t*L=*pbJ{BwEs!4 zSy9*5jP#tDH<|tuhIu9LPcDLel^T4h`>ZdHQju<1IdH(D6|}s8{wwK;TSV^Tf;oCg zteuMZp3}jK)ZG(k(q!S&@f9ak8bu~CCtRomHB?~9dhGpPxAqfUcwrxezkr%5Ovanq z$?e5R@ZjSpXNkptae0s}{7!eZbNZj39CrDmQr*Xg)|$8#m6T?J=?rC{epr%Amljf% zgGNO)LCIo-r!!_AHr2l>QDN13<5F)vIyAJB3P8D}Ojh?Knv~HYx00d6eH3T^;~N%< zve99NAUi`uQRwv-6Sd5ViHVYKFP1$SqVO2~#sZdqJ?~(ZX2nXAbT5sIYb5%9w^L5~ z+z)n40A#w?q;;8Pl~gf&^ie-~8UFsFDH|XfmG%~Wy}Q5Wiu?cprYVHnY~ZH@H$7`F zhrD5;90hJ)RN|a=#Swtpl`sL6%_ES*PZ); zp&);Mz~=iilZLyO>s)u}4K{v)+wlHP2isk=6;4xCdRnm&X*?-x@ou zn!h@HEr34ahhwQG`zg83aoiF2r#aF5Ey)t3$=;B8L!SR?M-_Tj*UL+sj9L6uqT^Ho zrDa-}tXZIB$%_F8a|?s!LW3%=lLHJ1xD8jOnp0Sa0^&|2EnUYFBYQKBA*6ZPb$7TU z5o5CPTrN$Z$8cbf5C6>+0%mHATTJXy@P!NXJ}YvfId?i}L)hN3JsY?qdVFMZ9L)@Q z|5-$ErIM4=Eg~Xfe#=#+>#1?iBIMx$euduHblXRf&FiN39U!$rL*`Kp+DqnTqF1S~ z9%Pd$=9F`}>3S4C(bP1yH)TgP8*}QCP;67z(FqM< z@gjiQ$bSwG1`PsI(iYeoJavX6Rdi;k$cD&Ew>uafsHj9bigIIu5E_thrk$pSM&K2t z820OSg8Dw*XW_|Q_daPTbfrW9q5_0}8wf@4{rmSFc7Cciziw?D85D60hKHZ@U9&pcaq06C_a00?5k(^okek=vwf;NUQkCICG^&Ie-+7Mx$Y48f+_WneWQ7blrIC!)I3>8 zC1A(a-^>zI(~E&qZ<{)qqu*wwUO|yEwd$|PW8S_#iuFhvUxk&@4&JWMY%Ace|D$DI z7rZ~=L=nYSzl?R-Eau7ZWoGDm-Bp+fu<)*Oawh$=#VxZ(?G8Wm@bWS}+F6<>YyH{Z ze{p-Et9;d~Acp|7)IdNB$R;cYq+!(z89`wE|DLvnhK8D!Zg@_jGuzv}j;rO_f0;$; z?OrwvmU@fqc-0`AGFINddl%UAO+zI*l_WQKPFK@`29je5c&_GkS^vP2d9C>PwG3xz zK9#A93!?G#$)K$XQVS5eJU%(QpBlfM?^q}EL*i(+0Uc3C#I4)69l=H893l@(#()g~ z8q_~^eppeO-)gVe%xr^g3J529jMj}s7jujdvddCZEEyRY^JHHxC@Cp{IQs_Ta<^-7 zzCB;)5dlo=`#wz%CGvn+%dSUWdDqZaN4} zFx|GL#KoGyi;`DWC3P-{zfDg9_^4r%K>EO2G+81gk03DM!c3MrT*HU7G_qWU`c-wx zd9u$>VlnEEoc9VJtv%fEk$LCsbCQL!2+lQWP6EU~B3PdRoC=2MgA- zt~A?qHC-W>MKz53N=T@GG=qY2_dX`}1m)**_i1W3*(>KG<*j3Ds;CP6urSUx_v7E;dF!iEFc1~?^EL%V2#ZP(U&#BD+le0FwX z=~<@m8M%2iaS1F_VP=87Iw?-<-{=NHaBgjXca8%IBDQIb5$6kAsBte5v z-PIMxTnZy2qjN~_O&CR1))*+p{@b4cnEm~3mI!|O|a=u^0P`hDFV*Jhq$d2#COyurL1i@|YMUKfl@w{WhhcF)%^`b?_Q}*+5xGso_@u+CySuwC9Ra z2ojR5z@UOs#aZ1W_{3eupz^9L82xyC&X&a-M2eX2a3*So6Cg$J6D=)v35k*Ex}W#~ zFLCf2p}cl$xpc?n)$7;b=o}f%`0;=@-fU;FThw`;WJQQQ^Pwqd77=p`3piFUcy+|X zG>I&%q-PDH6TRZI^@+F01e~uj$KL-$ut#>+&@c@tpJDUoy$z1sold5Et)Daq@o>G_ zCOVMM`03Rkgvw@o@V#an`!d7ukD#DVa~#Ic+2@J!vJIFsV&aIqFJN4cFzc8%%}3*r z-m+oJW`H`5AOq>Y^LUpifyf-c3i;s6!_-Eot|Aantqz6?b%ZOhmKUi8Y~^LO>occi ze~DL5mhjC}#iA~6Q5R2t=PU9~*LK?3ku&_|2-N?l`H6BdP4;|JYz=ijjTYzHcCzb) z@z}hpsP_~%ij*~k-0=M4*e=@EFl1xEKZIC}&e+T%$ngqlDlsJOW!7n_L1&?p`qXT2 zY=;oB+4DlA#8AV4ZuivL__>#4t!_DKt|hPOQH9CDTvoiqC}+7^RO`e6?#Lbo4&p8e zN$Ur`@x_w}WXD9)rQ=7>+-(Y7cWNPw$$mjM8O@DO)pAcsZmWySvJ0n>!ji%RpA~FWqD6Vs#mf0BtS0B2I&Yjqa?`$}p3t_9I95?CB zyhWiv$3&##LudfIy*c2pk73X4H{V$&+EcZ zYS$;1mfA4PDAt3JSN$4PX|WMR#Kh`Pp1AfqLF{}-B^20nMRnZro~+c_@eZ}9H}@sO zm$LL?zMN%#`=nAM?(|o_bRrP?%FUGnQWI|3 z5g{fuQ8#FEBg7-8qLJ?O{^c4vCR3}y9&6G?rO4U_<9d;m+J@!bJatVC+|PMbUD8K) zP-Lvc2nT1U+lD30`rnzd=XLeJo18ik_v;*udW(9T2sUEZ2>Ab^_c?0f)K}37*2|8@ zLNH0kg^e~wKeDPj^5hydNZCA*_ZRds!~Y@qYVw`sIEX#6PgHq?H^jp0q-$|)%GX;q zu}&RNG&%P;(wjv#?hspxEa)n2By}P6v~=j+41?SD_V(bw+D6;l=g6-z-g%uyML`h|8A;+CBCF7shrtWM%gf7PP%vJY zxBDx+A-TtY$LDnGNsnXB_TSCrcU478gU%az(a3!s`^cBBFE{zOt~QiD})d_>{I#jA-H25ucq*T}Mr9O{JQ{lBj>=Id4w zoqH`Ag)Y+t2To)6^`kGvw|}}NIwU55_DZa*6t{8}_ozH`oEuGjR2{}<^oVBNC~Eb@ zRK;lTL}p|@uXt@;Q~>g4MVZp zilNW$w$_W2*(c3pi!olRI1gtzl7!G;n#ASP2G8~QX4;x+JF44xO=JXv;{W#b_siz> z?|qGk1YvJSVzv0>6uxVhnx;)76ISh#$; zd70aA3p@YCiz!Bi*VgW1&ISTtqNqMz4B8|mY+;T$@Bd``$?3w5*QqOWyqDJF!Zr)1 z8RrWv7ewDV;Xaby6nv3yOR>rt8SG5!*0#0BGz^uvbHYd5-kF`Q11FVu{)Q)(3i~my z*$6iGHt#CSv=a_n+`0s)!TOg_us#vu=Z))kr{4;m%p_VAmzLc12$g2h5#3JOH_}oM zPA^|?`Wt*({l~d?_2wUgirsGX+qeCDSLYw!C2!~}X#!K5u}!Z>%XsSdYsLrl4j@P} z+HhT;o$rKXjy@j0ok%0jeC`WneT)X6YBKDZ^I7eBhd0Yt^osYK_rT6R`HQFJ!^G--T z$@IZJD3e|U*TXyEN0Eq>F6ptXvx&47Z}!Bo z2qK`Sfb=eDbbWPO7L@7u?f+gUi6UwlsA;oZZ=E<^M%tg$=t^CSRqjZ#!K-1pWxe6m z1t}3!HU)LT8^RFQzmcH43X(agH+bi3MSlSWtlU?*8TSoC-g{*J42lI{CzCNS zm?@S5oKxYlM8$rEapB~Rp`8!?PPW0@sp+Ygc&d^&@86G9rEv8d(cW}3??rPtdW_Ik zpSIhpxtKCN4l=ZiOyv&BMGl<2lAS9I%$5!6`Kw_@{psV+A5$+_Dvq)4IPBwSOZxL4 zvaxs{3RG{ONYIP9m{%QR)`y#+FrE^roilTdY?uCben^}T?GXr%Jbm>dYZ*uD;4R|Q zRqWpRD4nfIX3Q5)8Y9I0RQwqCo6o{#m*>~xx|Vjvl5Lo4?5T2ppld9>YOplInrG+# z2|*;WE2V~RmH`jZH47Qc12$CmioCma;%)RX2rU~^9ZYpt?z^!%TuKaC5)m-d8e@ho zG(gR%YR4=xGN=rv_l)3EHkoei>iLIc*OGZAUK;#6CW4ytN+l!R-Q{20rBK9SSYFAn zHJfPNTWxgTsEMMhJ*jMA=E(kIQ(CkBE57!3pMLN157a`nrXf9*B<_aa5%03lu4uY1 z%Y^;Oc!mBKv~2Ikm!Oc#3d;0?Ikjv}U()4-g2i`9y?S?44;9ui;(WY1veod5+~MX7 z4Wv~+b*Wq5Z|UV{^$Vc6qjHhi-NI>1N1nO-Udl9Sz6<_2dFaWGLnfbZ^X@Z8;;AgV zNqCDpUi1o-@I+AXPIurQe5K+(2zDM8$A(Y1y*N^7D3Ik0ui z8It{S;}Xbe2GX%BLQyEDDTCWlcg^O#R8jkpp^JF+k5n@sCJ0(nffuH4-U)OgC}wEJ ze~`I2u%{;5DY>;lzIk^0k?GbStW7c^lxjIo^FaRuHp1^?bm2urN2&I?8CT8)Y@g6u29R7Oq#C4ry=IB1F(TV#=p<7H5`nkHEI@y;5tJvw! zX(rDLtrsj zPR1bfb&o+9IK#(9$wME0Y5KhU09t4&U%S7mjJSc!e$%e5rW=fPIVx7cZ-4xIg?o#) zT2wXjDYkF%&cKa>)sRj^09yKltf=~2=?a?jnecQ7d3~Y%S@-gp{7KDLufJh^ti4}& zaLhTY+-Xh8UL7A>bDiTsnZ5s5n23~NRBWC9v%faa|17C{<>FC}C14ghSRtl|N zSlYORpn0#=*p_%M1$lux>fUaW+{?f~Y|!LC;$Nw9BZZ0t504@M;d17tPhJJ}KP2XP z6lGH~PI_B@nYm9iZYzjrLxaH-osWvCVK*Z-Ns+KvPknXL$O%&aRkBLvU?}ppV zMhv$nE_^l5n&lrYe07WOO1s1O{aTl>)Cc9OI$k3h+%>k=c`NbyH-V?gMWx%U|Ku&? zms;2;Xe=ul7nC})81b9g;FICh)xLu16+;kuH|%G zRN$Rm4*HH18|YO5-(60xm5CWg`N2L3;#)YM?9viCR}_HX&l9LcL2nxrDLa|N+rYJJ z%ummqW%$pb5H&Qt7zzCHdffg0APxrt?o8fnjhpIE-`KD7t7tt92{Nen)_rcHY8(jK#GeTfhBhrTnBxijuNg z9-M+X@t}8Mmw4~nwHKk^#i~QIli04V)`u2y*6eO8hsk=ZS0N$aF=_THhIq@mXwrP` zYsvZ9E(FK=CFX_;Iv>Wr2AV}wW@eU}Q!eg>&lf zHv3`3o8RB)6B7^w1l36HebVQL!-zYH)9sS#MjTKD!n!U5!zj()?d|`lH$8=8Rqjn) zU0qJgLiqUL)oq2ttbm^>r)c4V1t^Ds7-eE|VF6$1+BNfQ*AgY(3qPTy{m3p&4(=Tg zY6>x34Jk~i4(v0pfapJV^b{a^K%8h(lY;qu3RB7Pt2w{KbOX(o5y5kz-`?oQDqjta z443O!x)YHqtw^J|D(;B&5(~@0#YNCv6oQWnGsB9|wbk(&j0A84>@(1%`XhnwFZla0 zKKb|W%}chwgl2|f%r*S{|D-A*V^9VdL06K+!Oz_6yZ3%UXZ+Rk%!Yv85s{|uHbQWf zgF09k#700bYQ^^ZS-N6c-I-2MpT&lb))lH=kXG{c(mUkRj;QcRn5@>-U`Q9mg$8VJ9CsGg&CP#E zKKh6RjC``$MDFmdcw5b^rpzuCMy4OUop0mgL$Gl05+rCqlfgt(JE}{U2+1Ek`gD56 zwWC`Ay3*y9MU(dnbEea;2;V3l2>cmQynDB?w~2b>U72j&_k4W`zjaZf`$YUE?9K&z zRww$~^#Sl3=Bdd{C`nDG-)jHauz#27;e`XKZR!XfTXffOt1`S%l? zT}FOBi4D5YqUJ8l6ZgWosDf{#0(--!FzSrlugyqFLXbvtvh!&c_O7yVa^j7TkN=Zp z;U&%YsMQU$qfT>=L6%kbd?L2@WexV!Wf)cJYY*iVpGf+Rx&IoSqQ4lYZFV~Tm^Rr4 z;Kb(kZcF({bk;+&ORD$PX|~qNX5O?F@O$l8{g=M^sl+fj6pZjEVDH3yc1{k#3c3hn z+0HKV3BO|=Ab}msWC88F!{rlloUs`1LM0hS9lT;`X$hWf?59d(8M8jw$flQud5X&@ zBV&7;5NOzh!yDv4U`m&W&Tr&)4#6pS7{L=#R{uL1Ie-?GkT5$4rBoLE5avQ$k(D8v z*YvAXiANj_RgeQhYPG!Xsj9VB6ABPvUm3J^&1h-~Ot;2_{F8|P4f^crYUET?$d${N zvA_%siZkS19i-FeIke48E5@BW;k(aFf)|98Z-l}H26aM5ASO!u=iI-;8UjHA(TY9| z4X@V!-eU4+#c?`5T`h|jHQOd4%Nw7}L%YnPiY#-SxJ&b%CV__x2ljxpb8Y*db z5|IP=q)>@J!@@c+Z!$^g;=8TKBwmA+ZmS|^c<-X!w@rO#2L~B2@``#1lS{AN54J*a zD)%fgo&4ash=Wh20No8k)BhOxZP3Y}5I=+IZa&;z=%ft+*khUQ`?|XN&ZtYQMS2Ld zDYAjUtQ5o20`)-iaW9@5gwm%w)EFs^w;r58A`lT0Zdnj%-sFfr+f04Rsfb>25v^TU zdT&@A06iB?&=k>X_Hlh*sW5l*!8G5qL>hxH=&5p?y31A9laT`JQ86+Exi!R-hHP}3>9*y%@(g5c;=6#KKmdcOP2bp;a6wdBDzi*8oK*uTCc$_ zNHfVtzrSA%oXnZQB(Q#_R4@ux3eL@7PUsJN1 zmdp@{)=JFKef^X1l67ofuH!>5lX2m$BXs}3vscL)!RIGF2&LaUqs(^kUstv!(#a=X z${2dRpB6%~(G2<=2R*r|xkOkMedW7UIPEDEU#NnS*3b9-`r{`L9Eg0v_!t;R4P_k4bU>Ew<8W3JiJ&bTWJBr3P{W@s}}H!_;I(kcX4 zztT0Yv)m2)1M*E889FMcVv zf2qRWkTUdrYHqHdrW1C;~T=_(z3M=Ghvybw}&i?R|a{N zKT&n0HR)HWLnB2;iWsqk+iE>s9cx30^C6Tp*qB~aSw@NZu@cJn$zhrxBvbQQ$ za|;blV-pLDU;~V8U?R+$ll-o~zePE7QAIUFUKvZ%;fV$b%N=^~j^9ZHE?+4N!w|KA z2mbF1(aaLBF%J5Mh77&vYvJrE-rqYOrCFAMI~U*tT-< z@}j_@?EpoO|E=p^jcvo&O2LC?{K%Aj&-aXyJ-Z`7={%H|$8I_A>zmqL_nh0m*}1t% zpr0$awA3|zEuGtFTmQjmY-mxrPgIyZCwrgBj|a{Lm4yrvG3(92C&W<6)r^2w1Ziup z)iYD$n>r-8$It63x-x5Rh`#LG4)$e7gSYpiM(&$8#nk79Rpe&)xF+KX%7Z$tV-8(e;UyLDqL$iCs$VgTD-Xln@HS+CVJd~JD0!&LH3{8d2N@+MzzF`dm;9%c; z!w2iF)QA#5q2y&H7*u3f^TkZHLOc|JD8s8 z4pwnZiI~$@Uom6*URlQVMNz%^^T{2+3mJphuk@K^&uaM3Qm!V98PtGLMv!vgwcx18~z~+H8IDV2C zC$u{l{~@^feMC-9p}dL;$@KIzw^xrkM$R_mSt*sowAR6L4F=B!+!5d;iSc6Q^=W7_ z7_9LY0ql5eRU=$pVkqH30U|Y+XfURmqLtqte*LHq5xgqq6T`a zk}Q4^x+yT0q9xm8M`08E>MXNKT6z+tM|?UGqGjHS(U5N})Gza1oV_BwhWj-H$?}%yE$eK$HTw*`1>zU)b6TxPp=Zevg)Y!W%mA9#lD}-s6!~Eih85u9KzkTtOvx}jWzJid%WMGKEtqk?3*YaO_N+5W?I;^}>R>y~Z zCv|FWo*?nZn)L`tCV{IW>9>fRZ@g+LT7S>UzY+9n6A%-}yKk{iT7;pIH4n^c+zvvY2`-~L}Af>E!CHBJEEa_4EMWPeUVS`(J3%FmS;Opi-cR12ihw;w))_e(qytfkW+^ciKInpr2{LXzCd z#f8nSn~D`|(f{*_k(_T3En;$dHL~LEmF_zKbE*9~9Dq!&QCcgdX02}$5(p4r+Lf1) zK>&RNRq*m#WpHw;Yiox9EQgzF7#6C30wWIN%$=QOFCJ28cNeaugZd3@>z8qH7r_V$ zCG#J;JW$Peo1AQuqA~_|(tvZsFi8yVrzff~85pab-iMlR9_dzWf$PIu~aBTAo|^vyj+=gy{99DyLS1e-qV zwYj9qj|?D};RO*Sst_gWlWCJ1?Zl{${XufZ-KT_FYH=|?w5CE&4*kUU?PVvZp}|Zj zf2M+3fT*ar>7O@hz{ufWngXOf^)Em4igNq^tn8w;)e=?kz+k(xF_FHjtG7FRxZSxd zWNlV6KN$cWG8kBRiy9UWcO)AD=5{YNHIlFa06FfOU|h}}r8ZaM+aW@@8q7R^BU2^@ z?hcolz>YUR9A;4i()>ScQK=)nttJTN)zTF%hTZ)Yj0_xXJM*zIw5!~7gk=& z;`mH%h)|sQ-LI$|mgkL?W2gMs%c!Q8Cu?YU>6=sE&)+v1f{EjuJNqwmcMEi2EZ~{E zecucvCUlAp4^94jl!fN7kh(f)a4}#0&${njf)>*Vz@(t6U|KSugS0}w2+5wbkO6m{ z$-qSIERe!h@2MzQ-y?(yE(NVL*0=ypMsbO)GovhLP+W~<{_5Hk3lsLo5)YpC%Fxx> ztMKaPGaP(=HcmWfHhXjbv*fPR&%lc`G;EHK*zezeFloN=s#_q7l9C>aktP#g#kr^ z_6PE#92l7~1Ss(~CY?bNXaA-H3@8yS+n6@3H}BqITiUadJaxby4U#K7EHi;wprywx zuV?Gi)?z=_p^9df3dJ<`O*e-@pYP&-QTLWynG%I|od81W1lR!z z3aR~>hQuVzYvmKsp`irISKrwTr?U|NSyl4z5QUp8U^JA~!4e#h1DanEQe(^tZxu1b zKLkeHc;RGZV*u3HS&b*JI!{%4oKOg#TYRODl} zejXUW249*9Tz~OLD1Gz?s$l>t3qSI%|M}Tq(vLW#8xGg?>oRh3W`$$7+(kK~W7d20 zogK@Xs)tHKnHd7(Cf1+*+rF+EW;GxF=oF1P5pzCF87$pRiXfivOb!_w)XCRJz5^c( z)fD_&~<7h8V;R^{4-|DqE`KuMKuQ2~*V4habx5JW;!LQp5&4U*C#NJt5YGzf@v zcZZNp|21S?aT zSfgXdI75ioj#|-J^u*MZC*t|iC^BtWtvU_<^yvx+T0bM#8ZCz@JoHP`0zrQbkSY*~ zUY;H2O8@M`t9~#1KPu{TTi3)jbFtU^1koh+y(>E|Hqe$UA)lr3(O?-F%I*kZe(pzF zcimTiWuSzWowdG^5Q14rr5r*kS}zucn7*^$NXPC;C1|W`OHKOm14Scf`s(aX7S^cc z5>k1vBsk|k^hx>jBqd7RMTFjUPZz;IUGI|i`_LSSr0%fhx)KoA*#w;!33q{+&o^H) zEL(hn+8^G#U%&k%jS(;b$-!#(lv3B**1`|fc(QcdWX~UW&3=Kn$<(jv0{9u*3&nG8 zVIBnI)qN^!l4e^T{KNz!litZ52Lif02i~)L_U{RZL8c$Ezs&#EJ5LI9nkODNg#`iDto6Pb*6L+bIt#G+xf3n=n#d=RqFX^S;*A-)4%k*^kT8=676bxoHge1%BO8Y5INr(Si( zY{YRBZx+VFE}~wvdh~DL_Rw?-9svRJH;|n|PJr-fko86}1d$-c z0HmX4sD&yq8B|`a%Eqy=mtnJj+YKerPiJzz@NjI16azm{>#iu|Oum`>pvavJ@=3&@ zI-Q!`42tOJ)>z%z5_t`sbZCS(4Z~u@Ga>pj0 zT4&aRsl0$5BAwa;guv1V@IgO5evL;=0NHMnPi@m52m6Z#o3^~=)8z97 zvc$JYc><{~`}CwnBg?z>)Y~k~G^Mz6F`~tjN~}&(Og2B@L*{jw(OD@0g3UiY&uZ?z zLm8;3fEG~;g!1>2(;q%~;B%(sibBsL$Dhw5`BogvH*lzfd`kMCg8wa6Y zqb{palKJ?u1UKw3sMslnyP5B7Kg?4nR3t5P9`CIH71=b8aCm1c5ys@rn`->ka`(q} zvb7(nr0=+NTW1}+n3N4K3ID_`i0o`!8?58j>%?Psd3UQbR>G=d-LcF`TB}`iF$i9T z^b(b`i{{;mj+b%~^?V!fq{&8}(P;*>L|iASl7cbPh@lNW}=qT?(6L z)6D&KXVg=JMDW$;SeO}(6SK(2RUdba5EG~1Hr5BfwTW5#ZGN=MaO&AAcOg#O*9_wl@KD2`cTc(=8C-*@t^mvw1S6E)0M1;n74hdJt3+1U z)U{2!M37ETr8tpK2LE%l*Xw_Av{&Qy!+6VFZFp$1lO^6q^&TUfo=>kyU*@$Wm8TOP zS#a%lJGn^yz=zt6d<(xeznkuv>)gM28)4JJ7H*|oUJgvJLP?ssv1%G;{hun%hTqZB z7H!8xf2YjE8|%HV>Z0`n9!82^a$|5qsvDu6cl6fqZlp37Q6YEYv&PiPeMK)BCP{5w z`kx{unTs?8IEWIdheECi87tc>>5oKX-)OE(OzH;4u>0OBQF)&9z`e>T=CHYgqc)t1 zxTf2L)+e|a->iK9!nry_ER<=91Ji6r9Li2`j!6Hpxa}qRm8p*r{X(?BhPZhoZTM;? zgAj^r?6jIqNu{Oz#je`g+*wOg9}~uTQqLVx>%T^3grd$XbvSjvq20=R;H_L9vR@^w zG~T`UL8JzbjCLayzUvv(kdg*gC$c--)7Mph>2UERKAw4Xv!aRK$2V7^@`(tMjW)fu zmZe?hB7ZMtYQ%4fQgC-xC9k>y1(vXN86l!!6AGupYm$?E{LWgZqhJi%~Y)HjOj30=7_r)XjNG zsbsnQC*{fB{;yF~iB&JK*r;5JueflP#c_bq=W$e^IA)!w}0qei)Zj;nZlsPz9=yw&FjX)RBq z1P|3$B$MAe-qzpktBKb8vQ?wjv?9vJ@Oqs^~2}Ef9G-I zeS!)JJ&rx|%sXo{cR2i+;!m44@zw~d!}FAeg6f9rXdcsqncLUt9xaDfT`SeGj#mhdL%*KPkPc#@OlGKc4;NtrBGxsRqgs=)S(Gch4) z|7!3E9Xz+*wBnJFfIHd9UgOtWu=nqHF0gIoxJT2u@LlrydTXxW__YaJjez{N$M zt@_&+zhltBvlrK>A=yiWL@Q~-;90XKg#86VTAz^-&HDO!M##vbKplue{SnCdSoPdN z@iM7Ozq#${RO`ExXKjxcVH~gkskD4E+J2>nON6#(I{>5oK-oc5gcQ1a>(%*%u8Yu=?B2=;Z#D>b&5*HiX;ge6SZ2!fkLs z0_v}CePs`pOqMO?M(*ZKE|WELG@J1E|0n!%8?KOL)6aRC7HlbZ&FuJl_t822tnBQs z&yTq!9o?(u>rmQ{Nw7tK7>hrD-Zhapga7y6-b3mA;NjG3^UaSzy6<})S4rWy`g`Gm zDUHz(sZJ!6N~%n$bCgTGV`LxqU4iv1k|~-3yOME%0Xag z^8Z`M6>m|(>c2PbO(x_dN|o!ndwxlzILYY$6n3d;*s+l-#oX+*5{W^x*^%)PQin%G zQpYe}xPPH>28LXpTZMnP(yhIXQLc$sr%eW)#3RQj5PQA1-R1IUf?uB|pRL8T?Pko3 z(T_EEwl)4ut65(0EirV+nX~E`e(HDNNa!EX7AW^|DYh0JoKjwXB}DW@V5Mr6ynMYs zQti$u^PY}r8kFV!&X!#Lv9TD$%F14&Whd(^)ooiBeHLN4+*ghFRljmJc}CAB4V826Fj5~03HFS3g3~D#@myx^Bd!nDuW&;<$l}Rk*xor=6u{go|v91t!;?&%Xv4* zVn3BDr+WVSWBdIW{vURhrgl32Z$THNsURT`H{??q37V^(Q;UZ1$X0ymB;UWI&&Kap ze5ao)=$bkV8mEb@+F8YXjEx99J)Uwiw=`e4<1=o1;*(X*4n^b_DyoyKtEY4V{Q@5@ z1-!F49yS^eCRTBESE5Q~^k>=#*a(gOJg7$(r`>cmo76pjZD(#C9f@)~_TCdcEILif z$)Pvu)U0Po$mQq6%v-1BV(z8()V(56)X9~UmL5(B-Q{p2oN6SfVPQBSbW<0lvEy*1 zcdQsIJ{ptgXi(KLtKO9`ZTQn*H9YdnGvdEH>DjWaF6}8?xAYU3P5;Mg1VU4!K8*Sp z7l)*0QSL=Wf}oRz3e5%7@7@--vya*7MNngsHO2G4ya!4TG)U)W^2}i`y((cQPVYR$fvV07Se2?@yR5dhmOM~!LeZ|;W+)>4%4YDzW8if6>C zP3$+#)?V9fxA&zogdk$-lGrG>peIozRm@C(jqCG^cg9bKTx)Vj3+-Cz}Wxa+ZU-_i+lwu?dR~Prk0(G&ZC?~UCc!$tW))hPdg7SPUCiqN}PA52cyIsey!($6yVQr zgxhEzA)Wq+v_B;=#Bzz@3f;+UCD45vX6R znQy(z3XhmBSgM8J6E1(JU-41vE2%DY%+=SUEYB{5xBH0L2xmjid*Th-2W1As1E zzj32}B=I*tlxjeJR6n29^PBGtaw>ezqXcKyD2SpVPfB$2_1B!7nvsd0w=Krs4h-CZ z_~FC9Mz8@9(Mf4(#UZb!r^f^b%K!2KXTl>HQ0Tn+eO-d10TfG*F~8l*dl6W!yKH0k znqy|8Chy-D{kN=q|1g7wWGi4EQEl}A>oRav@4q?o2KgBmR};YU&VKfe zzdu)}>+IZ2illey!C_gauW!LU&&al6iFi3#{U05=oHsVk;s)M0BG9RZUx8P7Bpws! z@#h?p%JFCVb<@N%C>xB2A#Yj>=`Ox4HqRRXRS^{dQ{{;%v9a-4ve{anw;z4i_Aiup zA#Vg(u)n~QH9Wh+!H6o04|smSVgXfFW&?ru=eSs}jS*wn*gII3B_qJOp`hY>Ljtq) z7u}d4x3q3tv(S5I8*%14n1gUACP}6|*P9V6<1l2+8g=Rk4So4xed_EM%fKP~(rwk8 zpHYcFfTs`yV>ggNA|6R5ZnGlGMJS?^^1G_&mVPds#tq0MchI6K6kSuswYD<%AJ$hDrQ-kmk#`u2Zj_|j+?VX^Slmw z`;pQgg5ek7V0Jk%T|34dl9&jy9_nI-L33O4J$w6e370!I6_}WK&fI^|P^J1XLl|T={7EjaHu3z-@TDFC5zdD@GDHeE@^jvliy=ZrD|GUd^;*7@}aLhmpU!Q9PE0^@#4wN?{aaM z0HBxLlo}8D@YDi%{msM6 zVhbO2M5b{n+8uHeI*Re^tUTCwhW@)a6*O32Xi?rrb5AMf_3Z1(CNBn1V*eZtNyjW& z+*xLHl{+F6uN04J&@8*gH#6kd%U-^ji^Qs{JNgUOj>aR_qztb47skf^t7+{}E*4&8 zS?j#J`W60xSLvDm0kCIhz?!Y??3@i(!v;i-aS0f59fDn+U(=i3=Ze?udmff9t4y~h zNzrQV3wJUM06Wg-6;fJgF&MRE&le3UvE}8%A*>wUq5C9%(?%mFy{%8JK%Wr)L$_IB zb|{D$D0`c;qk$RkIL7Rq=zbaB?Kau;n4XT9%(lbmZ}wlMN-{bkHdOw))#!&<@1g%L}`> zxEO&NFa^}$;Du5(8}J~ML2#@h6zIId@w&$DiOsE+TBB_oyVp4;17%oH8U1k4Px6HVsd2W+UF0u9m7~1F+etN>t5EP4 ziFHsB=fUi($kjI`n84MFH3kJ&r-dH>ePz~@ehllGd|XSzO1+X$bUY~irG2@Pt?EOL zVwI`DgvR50jijj~BCg*>OdP_t4pSwYiYQVYh3!w}+>&+!ClAX}OSo zo;_8}J>H=aFde~TU|@h~r5dt-h!(}l%2SimEDQ5f9gZ;9fYAatAqeLHt4t2iG)@e9 zy&?$-8EJ@NVAHbChnmEA2 z5~6;;D+H>bBy

P9oe+ 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 D["allocate_wiring()"] + C(Connectivity) --> D["allocate_wiring()"] + subgraph Wirer + D["allocate_wiring()"] + end +``` +Resulting in something like this: +![octave example](.img/overview_octave.png "Octave Example") +![opx plus example](.img/overview_opx_plus.png "OPX+ Example") +![opx1000 example](.img/overview_opx1000.png "OPX+ Example") +# Features +The wirer tool supports the following features: + - Assignment of channels to any combination of MW-FEM, LF-FEM, Octave or OPX+. + - Any mapping of N resonator lines to M qubits. + - Any mapping of N FEMs to OPX1000 chassis slots. + - Constrained-scope allocation according to user preferences. + - Natural overflowing during assignment to multiple slots, chassis, modules, octaves, etc. + - Any combination of resonator, flux line, coupler line for each qubit. + - Total visualization of the final connectivity. + +# Usage +## Instruments +Start with an empty instruments container: +```python +from qualang_tools.wirer import Instruments + +instruments = Instruments() +``` +Using the "builder" pattern, you are able to define and add instruments to the container, line-by-line. + +### Common Scenarios +Below are examples of some instrument setups: +```python +# Single OPX+ +instruments.add_opx_plus(con=1) +``` +```python +# Single OPX+ and Octave +instruments.add_opx_plus(controllers=1) +instruments.add_octave(indices=1) +``` +```python +# Multiple OPXs and Octaves +instruments.add_opx_plus(controllers=[1, 2]) +instruments.add_octave(indices=[1, 2]) +``` +```python +# Single LF-FEM and Octave +instruments.add_lf_fem(controller=1, slots=[1]) +instruments.add_octave(indices=1) +``` +```python +# Single LF-FEM and MW-FEM +instruments.add_lf_fem(controller=1, slots=[1]) +instruments.add_mw_fem(controller=1, slots=[2]) +``` +```python +# Multiple LF-FEMs and MW-FEMs +instruments.add_lf_fem(controller=1, slots=[1,2,3,4,5]) +instruments.add_mw_fem(controller=1, slots=[6,7,8]) +``` + +### Disjoint Setups +The instruments container allows you to construct a "disjoint" setup, that is one where the indices are not logically ordered or belong only partially to a full cluster: +```python +# Disjoint OPX+ and Octave addressing +instruments.add_opx_plus(controllers=[2, 5]) +instruments.add_octave(indices=[1, 3, 8]) +``` +```python +# Disjoint OPX+ and Octave addressing +instruments.add_opx_plus(controllers=[2, 5]) +instruments.add_octave(indices=[1, 3, 8]) +``` + +## Connectivity From bb82ecccf5577a26a22b856f791b494058a7d276 Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Fri, 11 Oct 2024 23:05:51 +1100 Subject: [PATCH 26/40] Test extension of table of contents. --- qualang_tools/wirer/README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/qualang_tools/wirer/README.md b/qualang_tools/wirer/README.md index 8ba30f8a..41346ea6 100644 --- a/qualang_tools/wirer/README.md +++ b/qualang_tools/wirer/README.md @@ -3,6 +3,9 @@ 2. [Overview](#Overview) 3. [Features](#Features) 4. [Usage](#Usage) + 1. [Instruments](#Instruments) + 1. [Common Setups](#Common Setups) + 2. [Disjoint Setups](#Disjoint Setups) # Description The `wirer` module provides a way to auto-assignin channels for a collection of Quantum elements given a specific QM instrument setup. @@ -22,8 +25,9 @@ graph TD end ``` Resulting in something like this: -![octave example](.img/overview_octave.png "Octave Example") -![opx plus example](.img/overview_opx_plus.png "OPX+ Example") + +[//]: # (![octave example](.img/overview_octave.png "Octave Example")) +[//]: # (![opx plus example](.img/overview_opx_plus.png "OPX+ Example")) ![opx1000 example](.img/overview_opx1000.png "OPX+ Example") # Features The wirer tool supports the following features: From cbf8b62ca12e8344567598f9a39b6119061677b2 Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Fri, 11 Oct 2024 23:08:26 +1100 Subject: [PATCH 27/40] Test extension of table of contents. --- qualang_tools/wirer/README.md | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/qualang_tools/wirer/README.md b/qualang_tools/wirer/README.md index 41346ea6..0b92886e 100644 --- a/qualang_tools/wirer/README.md +++ b/qualang_tools/wirer/README.md @@ -1,11 +1,14 @@ # Table of Contents -1. [Description](#Description) -2. [Overview](#Overview) -3. [Features](#Features) -4. [Usage](#Usage) - 1. [Instruments](#Instruments) - 1. [Common Setups](#Common Setups) - 2. [Disjoint Setups](#Disjoint Setups) +1. [Description](#description) +2. [Overview](#overview) +3. [Features](#features) +4. [Usage](#usage) + 1. [Instruments](#instruments) + 1. [Common Setups](#common-setups) + 2. [Disjoint Setups](#common-setups) + 2. [Connectivity](#connectivity) + 3. [Allocation](#allocation) + 3. [Visualization](#visualization) # Description The `wirer` module provides a way to auto-assignin channels for a collection of Quantum elements given a specific QM instrument setup. @@ -49,7 +52,7 @@ instruments = Instruments() ``` Using the "builder" pattern, you are able to define and add instruments to the container, line-by-line. -### Common Scenarios +### Common Setups Below are examples of some instrument setups: ```python # Single OPX+ @@ -95,3 +98,7 @@ instruments.add_octave(indices=[1, 3, 8]) ``` ## Connectivity + +## Allocation + +## Visualization From 38b97adc271f7e35b4f95d5cb5960c9c221d9c13 Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Fri, 11 Oct 2024 23:14:04 +1100 Subject: [PATCH 28/40] Test image. --- qualang_tools/wirer/.img/empty_octave.png | Bin 0 -> 44055 bytes qualang_tools/wirer/.img/empty_opx.png | Bin 0 -> 36890 bytes qualang_tools/wirer/.img/empty_opx1000.png | Bin 0 -> 43631 bytes qualang_tools/wirer/README.md | 13 +++++++++++-- tests/wirer/test_visualizer.py | 16 +++++++++++++++- 5 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 qualang_tools/wirer/.img/empty_octave.png create mode 100644 qualang_tools/wirer/.img/empty_opx.png create mode 100644 qualang_tools/wirer/.img/empty_opx1000.png diff --git a/qualang_tools/wirer/.img/empty_octave.png b/qualang_tools/wirer/.img/empty_octave.png new file mode 100644 index 0000000000000000000000000000000000000000..5528f1175bfbc57a8670c366801260f235f4a7c5 GIT binary patch literal 44055 zcmdqJ`9GFx^gaG)KqaJ-iqJqw5;6~kXfTu{WuC`O37JJ9Qzbc~B$XVQQs$}53CR&M zPf6xvp1$jH&iDO!|Md9-KIhfxI6R)`zOQ>AkJ=g;Cg&uPIodPRFaeE1MfZDTvb;wBiS&7&#}`>r zQ?oh4tggKFiG=;1pBJLv30|c6TTwxQgDJ3iU@A#(>DQwLyfP!p-`~H}VIkFP$!gju zT{Ad6{g|R(?Iw{TzIAJ;_A_m0Yj5AOWs6Tt46FN?Tc@0dhg4G>)9SkPY%A5K`S%L< zs6_JVd_0_*|M3RPoAPo6>z23cBO)TQ+P<~51^@VAI^0>rULAR~qR?aEnL3YHz=KXn zDXIGpANn1>oI;`8y?b|jWyx!W-OcUXxlP|&TU%mVTU+bDf4>_LKs)Vx`}XbOl^wjA zl-sgYl;(6J8m6sVsVD{p1~aU!&rb!7&kv<`PWEP-3PkY>3*W)@zkB~a?)7WhqWQ+u zalMr(z1bRD&$T;t?3gZ;mX;2G+8ZNwJ)1V_-$&Q+Y~6pW9V{DHd~5xtdygOA?OdMD ziYcpaYVz-|j*L%E4q4LC&|rRaRF6KsJ|JE(?5*#7g36PRkB?iO*VdM_7>{bZ_vA^< z^rbUrHt;fU^82`ZQ(e&AyEZvq7cOjZ8td3h(PyDHtBoA-Mi=Q=jZ3*I9%_0J*6<=I}^&6{s4 zaryfD)2OMbjek0vs&8j!_o*T{EUeeLXo1ba!J%ttXj7v4-QwaCeSLjhs$TP3y;p`C zzI^}wNqRIvW@+%D+tD|=1-tg`+jo$M=cBFXaYM*RkXJd!wA3+{HaZDsJ9N?j5#8OTzSfBm+KwRyw$C zW9H{?Po^nVH8mz)D}NePd%wT8YRekGU-mz8v02vA$+n|9tntPhKRR)OqU$<%Ak+Fg4ZFMd?M*vm z`6Bo2-@mSNuGaZk)6m$E?Y}V=vrX>bSmPlZWhW(TX$KS2NA=5>Rb4+%4K+pm{Q0w0 znoCS9%7Eq0&0mQLqb|b|9!oJUv6ppkmW4|3M`lpP!lc}$dEHu9mO59C8K%!>j+9nZ2)4JkH~gwAEG#?|wp7BtT-?FD zb!!Pl|J$1@CA?ah!BoYi<>ha)ispVq5BDPL#j7PYw{jI`a!qe3QSuy78eU5YPDtP` z;isFd!%oahEkQP%p24zSTxMIphHA2Ghn|vaTX%_n!mx;*o*tK=;AG}RhYA8gr;}!6 zWF%ZS_4+_l`D=J$v>jnV9rnThZ9CVMDMIS6;t;PEO9}-M0D1`j0t}bx0)_ ztzWG?Q9EC+V{`RkZLm*~^!AbleV#((aFhh@<+|<#6%xW z-a9SU<>eu}?MJ>JpSBNTKJxK*f3<;d^o6TeH@?5Rxl3k2D1Z99)v%JM)lT-pT$hQx zS{k$zVVgGE^x1EzRajKbbi?)ZtfG{za_fcAP

Rcb>n0zg;WK=C(pSm7US4kMmPQ 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 literal 0 HcmV?d00001 diff --git a/qualang_tools/wirer/.img/empty_opx.png b/qualang_tools/wirer/.img/empty_opx.png new file mode 100644 index 0000000000000000000000000000000000000000..30ca2c54e3077c1c100b33f632ae09449aa10a65 GIT binary patch literal 36890 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 ^kgus}scvaY1>0sm+aP{=- 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 literal 0 HcmV?d00001 diff --git a/qualang_tools/wirer/README.md b/qualang_tools/wirer/README.md index 0b92886e..3f250dd2 100644 --- a/qualang_tools/wirer/README.md +++ b/qualang_tools/wirer/README.md @@ -29,8 +29,6 @@ graph TD ``` Resulting in something like this: -[//]: # (![octave example](.img/overview_octave.png "Octave Example")) -[//]: # (![opx plus example](.img/overview_opx_plus.png "OPX+ Example")) ![opx1000 example](.img/overview_opx1000.png "OPX+ Example") # Features The wirer tool supports the following features: @@ -63,6 +61,12 @@ instruments.add_opx_plus(con=1) instruments.add_opx_plus(controllers=1) instruments.add_octave(indices=1) ``` +

+Image +![empty octave](.img/empty_octave.png "Empty Octave") +![empty opx+](.img/empty_opx_plus.png "Empty OPX") +
+ ```python # Multiple OPXs and Octaves instruments.add_opx_plus(controllers=[1, 2]) @@ -78,6 +82,11 @@ instruments.add_octave(indices=1) instruments.add_lf_fem(controller=1, slots=[1]) instruments.add_mw_fem(controller=1, slots=[2]) ``` +
+Image +![empty opx1000](.img/empty_opx1000.png "Empty OPX") +
+ ```python # Multiple LF-FEMs and MW-FEMs instruments.add_lf_fem(controller=1, slots=[1,2,3,4,5]) diff --git a/tests/wirer/test_visualizer.py b/tests/wirer/test_visualizer.py index 4db8fb1c..5dd791bd 100644 --- a/tests/wirer/test_visualizer.py +++ b/tests/wirer/test_visualizer.py @@ -1,6 +1,6 @@ import pytest -from qualang_tools.wirer import visualize, Connectivity, lf_fem_spec, allocate_wiring +from qualang_tools.wirer import visualize, Connectivity, lf_fem_spec, allocate_wiring, Instruments @pytest.mark.skip(reason="plotting") @@ -33,3 +33,17 @@ def test_4q_allocation_visualization(instruments_1opx_1octave): allocate_wiring(connectivity, instruments_1opx_1octave) visualize(connectivity.elements, instruments_1opx_1octave.available_channels) + +# @pytest.mark.skip(reason="plotting") +def test_empty_opx1000_visualization(): + instruments = Instruments() + instruments.add_lf_fem(controller=1, slots=1) + instruments.add_mw_fem(controller=1, slots=2) + connectivity = Connectivity() + allocate_wiring(connectivity, instruments) + visualize(connectivity.elements, instruments.available_channels) + +def test_empty_opx_octave_visualization(instruments_1opx_1octave): + connectivity = Connectivity() + allocate_wiring(connectivity, instruments_1opx_1octave) + visualize(connectivity.elements, instruments_1opx_1octave.available_channels) From b1ed490b62d019a9470f2e697265a4adef6b0cd9 Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Fri, 11 Oct 2024 23:15:46 +1100 Subject: [PATCH 29/40] Test image. --- qualang_tools/wirer/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qualang_tools/wirer/README.md b/qualang_tools/wirer/README.md index 3f250dd2..26bdccd2 100644 --- a/qualang_tools/wirer/README.md +++ b/qualang_tools/wirer/README.md @@ -84,7 +84,7 @@ instruments.add_mw_fem(controller=1, slots=[2]) ```
Image -![empty opx1000](.img/empty_opx1000.png "Empty OPX") +Empty OPX1000
```python From 10a1cdc4461558e9b9cbdabc8de2e567c90d9f0d Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Sat, 12 Oct 2024 00:10:45 +1100 Subject: [PATCH 30/40] Complete README. --- qualang_tools/wirer/.img/basic_sc_octave.png | Bin 0 -> 49785 bytes qualang_tools/wirer/.img/basic_sc_opx.png | Bin 0 -> 42994 bytes qualang_tools/wirer/.img/reuse_octave.png | Bin 0 -> 47875 bytes qualang_tools/wirer/.img/reuse_opx.png | Bin 0 -> 40008 bytes qualang_tools/wirer/README.md | 166 ++++++++++++++++++- tests/wirer/test_visualizer.py | 20 ++- tests/wirer/test_wirer_channel_reuse.py | 12 +- 7 files changed, 184 insertions(+), 14 deletions(-) create mode 100644 qualang_tools/wirer/.img/basic_sc_octave.png create mode 100644 qualang_tools/wirer/.img/basic_sc_opx.png create mode 100644 qualang_tools/wirer/.img/reuse_octave.png create mode 100644 qualang_tools/wirer/.img/reuse_opx.png diff --git a/qualang_tools/wirer/.img/basic_sc_octave.png b/qualang_tools/wirer/.img/basic_sc_octave.png new file mode 100644 index 0000000000000000000000000000000000000000..cc3b5b7169f71b04624989da5beed00b57f52f8a GIT binary patch literal 49785 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 WmRUDSYw$D*4h^mi!2mb~0^raX8 literal 0 HcmV?d00001 diff --git a/qualang_tools/wirer/.img/basic_sc_opx.png b/qualang_tools/wirer/.img/basic_sc_opx.png new file mode 100644 index 0000000000000000000000000000000000000000..632f3a12310d6c032867d1862cdf28cf3993ec93 GIT binary patch literal 42994 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 d|L;4jo3foBV=!P{{?bf?!*89 literal 0 HcmV?d00001 diff --git a/qualang_tools/wirer/.img/reuse_octave.png b/qualang_tools/wirer/.img/reuse_octave.png new file mode 100644 index 0000000000000000000000000000000000000000..d24309b6cc9d44170ec3fa705a250676ff803dc0 GIT binary patch literal 47875 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

=WSlDscU}iAiun@^6sV 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 guIhh37c-Qf9

Image -![empty octave](.img/empty_octave.png "Empty Octave") -![empty opx+](.img/empty_opx_plus.png "Empty OPX") +empty octave +empty opx+ ```python @@ -107,7 +140,126 @@ instruments.add_octave(indices=[1, 3, 8]) ``` ## Connectivity +The `Connectivity` class is like a blueprint for what quantum elements exist in our system, along with some optional +information about how channels should be allocated for these elements. + +To define your own `Connectivity` object, start with an empty container: +```python +from qualang_tools.wirer import Connectivity + +connectivity = Connectivity() +``` +### Basic Elements (Superconducting Qubits) +Now you can add "wiring specifications" to the container. These indicate what channels you need for a particular type +of quantum functionality. + +**Note**: Channels aren't being allocated at this stage- instead, we are adding "templates" for channels which will be allocated later. +```python +# Define arbitrary set of qubits and qubit pairs for convenience +qubits = [1, 2, 5] +qubit_pairs = [(1, 2)] + +# Define a single resonator feedline for multiple qubits +connectivity.add_resonator_line(qubits=qubits) + +# Define a RF drive-lines for controlling multiple qubits +connectivity.add_qubit_drive_lines(qubits=qubits) + +# Define DC lines for controlling flux +connectivity.add_qubit_flux_lines(qubits=qubits) + +# Define DC lines for controlling tunable coupler +connectivity.add_qubit_pair_flux_lines(qubit_pairs=qubits) +``` +
+Example on OPX+ and Octave +Basic SC-qubit example octave +Basic SC-qubit example opx +
+ +### How to define digital triggers +```python +connectivity.add_resonator_line(qubits=qubits, triggered=True) +``` +### How to constrain where channels will be allocated +In order to be more specific about the connectivity, we can add `constraints`. First, you need to select which +type of constraint you will apply, based on the instruments you wish the line to be allocated. The types you can +choose from include: +```python +from qualang_tools.wirer import ( + mw_fem_spec, + lf_fem_spec, + lf_fem_iq_spec, + lf_fem_iq_octave_spec, + opx_spec, + opx_iq_spec, + opx_iq_octave_spec, + octave_spec +) +``` +Then, you can set almost **any combination** of the available attributes according to what you want constrained. Here are some +examples of how you could constrain something to a particular set of I/Q channels between an OPX+ and an Octave. +```python +# Must allocate to OPX con1 +constraint = opx_iq_octave_spec(con=1) +``` +```python +# Must allocate to OPX con2 +constraint = opx_iq_octave_spec(con=2) +``` +```python +# Must allocate to Octave RF3 +constraint = opx_iq_octave_spec(rf_out=3) +``` +```python +# Must allocate to OPX con1, I/Q channels 7 & 8, Octave RF3 +constraint = opx_iq_octave_spec(con=1, out_port_i=7, out_port_q=8, rf_out=3) +``` +After defining some `constraint`, you can apply it to a wiring specification: +```python +connectivity.add_qubit_drive_lines(qubits=[1], constraints=constraint) +``` ## Allocation +### Allocating channels +Once you have defined suitable `Instruments` and `Connectivity` objects, you can allocate channels using the +`allocate_wiring` function: +```python +from qualang_tools.wirer import allocate_wiring + +allocate_wiring(connectivity, instruments) +``` +At the time of allocation, all of the channel information is put inside the `connectivity.elements` dictionary. + +### Make certain channels re-usable +In order to double-up allocations to channels, you can keep them "free for allocation" even after doing a round of +allocation. Here's an example of how you could allocate multiple qubits to the same channel, either if you don't +have enough channels to measure every element, or if you'd like to multiplex the outputs: +```python +connectivity = Connectivity() + +connectivity.add_qubit_drive_lines(qubits=1) +allocate_wiring(connectivity, instruments, block_used_channels=False) + +connectivity.add_qubit_drive_lines(qubits=2) +allocate_wiring(connectivity, instruments) + +connectivity.add_qubit_drive_lines(qubits=3) +allocate_wiring(connectivity, instruments, block_used_channels=False) + +connectivity.add_qubit_drive_lines(qubits=4) +allocate_wiring(connectivity, instruments) +``` +
+Image +empty octave +empty opx+ +
## Visualization +To visualize an allocated `connectivity` object, you can pass it into the visualization function: +```python +visualize(connectivity.elements, available_channels=instruments.available_channels) +``` +**Note**: Passing `instruments.available_channels` is optional, but it makes the plotting nicer by plotting white +space on un-allocated channels! \ No newline at end of file diff --git a/tests/wirer/test_visualizer.py b/tests/wirer/test_visualizer.py index 5dd791bd..0997bb87 100644 --- a/tests/wirer/test_visualizer.py +++ b/tests/wirer/test_visualizer.py @@ -1,6 +1,10 @@ +import enum + import pytest from qualang_tools.wirer import visualize, Connectivity, lf_fem_spec, allocate_wiring, Instruments +from qualang_tools.wirer.connectivity.element import QubitReference +from qualang_tools.wirer.connectivity.wiring_spec import * @pytest.mark.skip(reason="plotting") @@ -34,7 +38,7 @@ def test_4q_allocation_visualization(instruments_1opx_1octave): visualize(connectivity.elements, instruments_1opx_1octave.available_channels) -# @pytest.mark.skip(reason="plotting") +@pytest.mark.skip(reason="plotting") def test_empty_opx1000_visualization(): instruments = Instruments() instruments.add_lf_fem(controller=1, slots=1) @@ -43,7 +47,21 @@ def test_empty_opx1000_visualization(): allocate_wiring(connectivity, instruments) visualize(connectivity.elements, instruments.available_channels) +@pytest.mark.skip(reason="plotting") def test_empty_opx_octave_visualization(instruments_1opx_1octave): connectivity = Connectivity() allocate_wiring(connectivity, instruments_1opx_1octave) visualize(connectivity.elements, instruments_1opx_1octave.available_channels) + + +def test_basic_superconducting_qubit_example(instruments_1opx_1octave): + connectivity = Connectivity() + # Define arbitrary set of qubits and qubit pairs for convenience + qubits = [1, 2] + qubit_pairs = [(1, 2)] + connectivity.add_resonator_line(qubits=qubits) + connectivity.add_qubit_drive_lines(qubits=qubits) + connectivity.add_qubit_flux_lines(qubits=qubits) + connectivity.add_qubit_pair_flux_lines(qubit_pairs=qubit_pairs) + allocate_wiring(connectivity, instruments_1opx_1octave) + visualize(connectivity.elements, instruments_1opx_1octave.available_channels) diff --git a/tests/wirer/test_wirer_channel_reuse.py b/tests/wirer/test_wirer_channel_reuse.py index d757976a..0d6361d4 100644 --- a/tests/wirer/test_wirer_channel_reuse.py +++ b/tests/wirer/test_wirer_channel_reuse.py @@ -47,23 +47,23 @@ def test_5q_allocation_with_channel_reuse(instruments_2lf_2mw): ][i]) -def test_alternating_blocking_of_used_channels(instruments_2lf_2mw): +def test_alternating_blocking_of_used_channels(instruments_1opx_1octave): connectivity = Connectivity() connectivity.add_qubit_drive_lines(qubits=1) - allocate_wiring(connectivity, instruments_2lf_2mw, block_used_channels=False) + allocate_wiring(connectivity, instruments_1opx_1octave, block_used_channels=False) connectivity.add_qubit_drive_lines(qubits=2) - allocate_wiring(connectivity, instruments_2lf_2mw) + allocate_wiring(connectivity, instruments_1opx_1octave) connectivity.add_qubit_drive_lines(qubits=3) - allocate_wiring(connectivity, instruments_2lf_2mw, block_used_channels=False) + allocate_wiring(connectivity, instruments_1opx_1octave, block_used_channels=False) connectivity.add_qubit_drive_lines(qubits=4) - allocate_wiring(connectivity, instruments_2lf_2mw) + allocate_wiring(connectivity, instruments_1opx_1octave) if visualize_flag: - visualize(connectivity.elements, instruments_2lf_2mw.available_channels) + visualize(connectivity.elements, instruments_1opx_1octave.available_channels) expected_ports = [ 1, # q1 allocated to 1, but channel isn't blocked From 944d1b416801d7970acac9ab2e9f69d92b48de15 Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Sat, 12 Oct 2024 00:12:16 +1100 Subject: [PATCH 31/40] Update changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d35dcf86..7b62f094 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] ### Added - results - Allow the data saver to create the root folder if it doesn't exist. +- wirer - Automatic tool to allocate channels for arbitrary combinations of QM instruments and superconducting qubits. ### Fixed - data_handler - Fix figure saving cutting off title text if it is long using `bbox_inches="tight"`. From 62ae26829a53ec6207e707397d6d6b743c30d02e Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Sat, 12 Oct 2024 00:13:50 +1100 Subject: [PATCH 32/40] Update formatting. --- .../callable_from_qua/_callable_from_qua.py | 9 +- qualang_tools/wirer/__init__.py | 12 +- .../wirer/connectivity/channel_spec.py | 15 ++- .../wirer/connectivity/connectivity_base.py | 15 ++- .../connectivity_transmon_interface.py | 38 +++--- qualang_tools/wirer/connectivity/element.py | 5 +- .../wirer/connectivity/wiring_spec.py | 16 ++- .../wirer/instruments/instrument_channel.py | 122 +++++++++--------- .../wirer/instruments/instrument_channels.py | 29 +++-- .../wirer/instruments/instruments.py | 11 +- .../visualizer/instrument_figure_manager.py | 31 +++-- qualang_tools/wirer/visualizer/layout.py | 41 +++--- .../wirer/visualizer/port_annotation.py | 30 +++-- qualang_tools/wirer/visualizer/visualizer.py | 26 +++- qualang_tools/wirer/wirer/channel_specs.py | 119 ++++++++++------- .../wirer/wirer/channel_specs_interface.py | 2 +- ..._manager_multi_object_temp_attr_setting.py | 2 +- qualang_tools/wirer/wirer/wirer.py | 40 ++++-- .../wirer/wirer_assign_channels_to_spec.py | 7 +- qualang_tools/wirer/wirer/wirer_exceptions.py | 1 + tests/wirer/conftest.py | 9 +- tests/wirer/test_add_dummy_line.py | 21 ++- tests/wirer/test_visualizer.py | 7 +- tests/wirer/test_wirer_channel_reuse.py | 48 ++++--- tests/wirer/test_wirer_constraining.py | 40 +++--- tests/wirer/test_wirer_digital.py | 3 +- tests/wirer/test_wirer_lf_and_mw.py | 44 ++++--- tests/wirer/test_wirer_lf_and_octave.py | 58 +++++---- tests/wirer/test_wirer_lf_charge.py | 7 +- 29 files changed, 487 insertions(+), 321 deletions(-) diff --git a/qualang_tools/callable_from_qua/_callable_from_qua.py b/qualang_tools/callable_from_qua/_callable_from_qua.py index f524a91d..ec4948f1 100644 --- a/qualang_tools/callable_from_qua/_callable_from_qua.py +++ b/qualang_tools/callable_from_qua/_callable_from_qua.py @@ -79,13 +79,16 @@ def run(self, job: QmJob): class ProgramAddon(ABC): @abstractmethod - def enter_program(self, program: Program): ... # noqa: E704 + def enter_program(self, program: Program): + ... # noqa: E704 @abstractmethod - def exit_program(self, exc_type, exc_val, exc_tb): ... # noqa: E704 + def exit_program(self, exc_type, exc_val, exc_tb): + ... # noqa: E704 @abstractmethod - def execute_program(self, program: Program, quantum_machine: QuantumMachine): ... # noqa: E704 + def execute_program(self, program: Program, quantum_machine: QuantumMachine): + ... # noqa: E704 class QuaCallableEventManager(ProgramAddon): diff --git a/qualang_tools/wirer/__init__.py b/qualang_tools/wirer/__init__.py index e5343e1f..bc57d6b8 100644 --- a/qualang_tools/wirer/__init__.py +++ b/qualang_tools/wirer/__init__.py @@ -2,9 +2,13 @@ from .connectivity import Connectivity from .wirer.wirer import allocate_wiring from .visualizer.visualizer import visualize -from .wirer.channel_specs_interface import ( +from .wirer.channel_specs_interface import ( mw_fem_spec, - lf_fem_spec, lf_fem_iq_spec, lf_fem_iq_octave_spec, - opx_spec, opx_iq_spec, opx_iq_octave_spec, - octave_spec + lf_fem_spec, + lf_fem_iq_spec, + lf_fem_iq_octave_spec, + opx_spec, + opx_iq_spec, + opx_iq_octave_spec, + octave_spec, ) diff --git a/qualang_tools/wirer/connectivity/channel_spec.py b/qualang_tools/wirer/connectivity/channel_spec.py index 63ce878e..8d532789 100644 --- a/qualang_tools/wirer/connectivity/channel_spec.py +++ b/qualang_tools/wirer/connectivity/channel_spec.py @@ -5,11 +5,12 @@ class ChannelSpec: - """ A specification for fixed channel requirements. """ + """A specification for fixed channel requirements.""" + def __init__(self): self.channel_templates = None - def __and__(self, other: 'ChannelSpec') -> 'ChannelSpec': + def __and__(self, other: "ChannelSpec") -> "ChannelSpec": combined_channel_spec = ChannelSpec() combined_channel_spec.channel_templates = self.channel_templates + other.channel_templates return combined_channel_spec @@ -17,22 +18,26 @@ def __and__(self, other: 'ChannelSpec') -> 'ChannelSpec': def is_empty(self): return self.channel_templates is None or self.channel_templates == [] - def filter_by_wiring_spec(self, wiring_spec: WiringSpec) -> 'ChannelSpec': + def filter_by_wiring_spec(self, wiring_spec: WiringSpec) -> "ChannelSpec": """ Filters out channels from the specification if: 1. Their I/O type is not required by the wiring specification 2. They are digital but the wiring specification doesn't require triggering """ + def filter_func(channel: AnyInstrumentChannel): - analog_channel_matches_io_type = channel.signal_type == "digital" or channel.io_type in wiring_spec.io_type.value + analog_channel_matches_io_type = ( + channel.signal_type == "digital" or channel.io_type in wiring_spec.io_type.value + ) digital_channel_only_if_triggered = channel.signal_type == "analog" or wiring_spec.triggered return analog_channel_matches_io_type and digital_channel_only_if_triggered + filtered_channel_spec = ChannelSpec() filtered_channel_spec.channel_templates = list(filter(filter_func, self.channel_templates)) return filtered_channel_spec - def apply_constraints(self, constraints: 'ChannelSpec') -> bool: + def apply_constraints(self, constraints: "ChannelSpec") -> bool: """ Attempt to constrain the channel specifications according to the filled attributes of all the specs in the `constraints` list. diff --git a/qualang_tools/wirer/connectivity/connectivity_base.py b/qualang_tools/wirer/connectivity/connectivity_base.py index 29fd1960..bcdb1f74 100644 --- a/qualang_tools/wirer/connectivity/connectivity_base.py +++ b/qualang_tools/wirer/connectivity/connectivity_base.py @@ -20,9 +20,16 @@ def __init__(self): self.elements: Dict[ElementId, Element] = {} self.specs: List[WiringSpec] = [] - def add_wiring_spec(self, frequency: WiringFrequency, io_type: WiringIOType, line_type: Union[WiringLineType, str], - triggered: bool, constraints: ChannelSpec, elements: List[Element], - shared_line: bool = False, ): + def add_wiring_spec( + self, + frequency: WiringFrequency, + io_type: WiringIOType, + line_type: Union[WiringLineType, str], + triggered: bool, + constraints: ChannelSpec, + elements: List[Element], + shared_line: bool = False, + ): specs = [] for element in elements: if element.id not in self.elements: @@ -70,4 +77,4 @@ def _add_elements(self, elements: List[Element]): self._add_element(element) def _add_element(self, element: Element): - self.elements[element.id] = element \ No newline at end of file + self.elements[element.id] = element diff --git a/qualang_tools/wirer/connectivity/connectivity_transmon_interface.py b/qualang_tools/wirer/connectivity/connectivity_transmon_interface.py index 6541de46..d7eacc3a 100644 --- a/qualang_tools/wirer/connectivity/connectivity_transmon_interface.py +++ b/qualang_tools/wirer/connectivity/connectivity_transmon_interface.py @@ -9,39 +9,43 @@ def add_fixed_transmons(self, qubits: QubitsType): self.add_resonator_line(qubits) self.add_qubit_drive_lines(qubits) - def add_flux_tunable_transmons(self, qubits: QubitsType): self.add_resonator_line(qubits) self.add_qubit_drive_lines(qubits) self.add_qubit_flux_lines(qubits) - def add_resonator_line(self, qubits: QubitsType, triggered: bool = False, constraints: ChannelSpec = None): elements = self._make_qubit_elements(qubits) - return self.add_wiring_spec(WiringFrequency.RF, WiringIOType.INPUT_AND_OUTPUT, WiringLineType.RESONATOR, - triggered, constraints, elements, shared_line=True) - + return self.add_wiring_spec( + WiringFrequency.RF, + WiringIOType.INPUT_AND_OUTPUT, + WiringLineType.RESONATOR, + triggered, + constraints, + elements, + shared_line=True, + ) def add_qubit_drive_lines(self, qubits: QubitsType, triggered: bool = False, constraints: ChannelSpec = None): elements = self._make_qubit_elements(qubits) - return self.add_wiring_spec(WiringFrequency.RF, WiringIOType.OUTPUT, WiringLineType.DRIVE, - triggered, constraints, elements) - + return self.add_wiring_spec( + WiringFrequency.RF, WiringIOType.OUTPUT, WiringLineType.DRIVE, triggered, constraints, elements + ) def add_qubit_charge_lines(self, qubits: QubitsType, constraints: ChannelSpec = None): elements = self._make_qubit_elements(qubits) - return self.add_wiring_spec(WiringFrequency.DC, WiringIOType.OUTPUT, WiringLineType.CHARGE, - False, constraints, elements) - + return self.add_wiring_spec( + WiringFrequency.DC, WiringIOType.OUTPUT, WiringLineType.CHARGE, False, constraints, elements + ) def add_qubit_flux_lines(self, qubits: QubitsType, constraints: ChannelSpec = None): elements = self._make_qubit_elements(qubits) - return self.add_wiring_spec(WiringFrequency.DC, WiringIOType.OUTPUT, WiringLineType.FLUX, - False, constraints, elements) - + return self.add_wiring_spec( + WiringFrequency.DC, WiringIOType.OUTPUT, WiringLineType.FLUX, False, constraints, elements + ) def add_qubit_pair_flux_lines(self, qubit_pairs: QubitPairsType, constraints: ChannelSpec = None): elements = self._make_qubit_pair_elements(qubit_pairs) - return self.add_wiring_spec(WiringFrequency.DC, WiringIOType.OUTPUT, WiringLineType.COUPLER, - False, constraints, elements) - + return self.add_wiring_spec( + WiringFrequency.DC, WiringIOType.OUTPUT, WiringLineType.COUPLER, False, constraints, elements + ) diff --git a/qualang_tools/wirer/connectivity/element.py b/qualang_tools/wirer/connectivity/element.py index 3800fac9..1e53832c 100644 --- a/qualang_tools/wirer/connectivity/element.py +++ b/qualang_tools/wirer/connectivity/element.py @@ -12,6 +12,7 @@ class Reference: def __str__(self): return self.name + @dataclass(frozen=True) class QubitReference: index: int @@ -19,6 +20,7 @@ class QubitReference: def __str__(self): return f"q{self.index}" + @dataclass(frozen=True) class QubitPairReference: control_index: int @@ -28,7 +30,6 @@ def __str__(self): return f"q{self.control_index}-{self.target_index}" - ElementId = Union[Reference, QubitReference, QubitPairReference] @@ -43,4 +44,4 @@ def __str__(self): return str(self.channels) def __eq__(self, other): - return self.id == other.id \ No newline at end of file + return self.id == other.id diff --git a/qualang_tools/wirer/connectivity/wiring_spec.py b/qualang_tools/wirer/connectivity/wiring_spec.py index 7a1fa890..de0dca26 100644 --- a/qualang_tools/wirer/connectivity/wiring_spec.py +++ b/qualang_tools/wirer/connectivity/wiring_spec.py @@ -6,18 +6,22 @@ class WiringFrequency(Enum): DC = "DC" RF = "RF" -DC = WiringFrequency.DC -RF = WiringFrequency.RF + +DC = WiringFrequency.DC +RF = WiringFrequency.RF + class WiringIOType(Enum): INPUT = "input" OUTPUT = "output" INPUT_AND_OUTPUT = "input/output" + INPUT = WiringIOType.INPUT OUTPUT = WiringIOType.OUTPUT INPUT_AND_OUTPUT = WiringIOType.INPUT_AND_OUTPUT + class WiringLineType(Enum): RESONATOR = "rr" DRIVE = "xy" @@ -25,12 +29,14 @@ class WiringLineType(Enum): CHARGE = "q" COUPLER = "c" + RESONATOR = WiringLineType.RESONATOR DRIVE = WiringLineType.DRIVE FLUX = WiringLineType.FLUX CHARGE = WiringLineType.CHARGE COUPLER = WiringLineType.COUPLER + class WiringSpec: """ A technical specification for the wiring that will be required to @@ -43,8 +49,8 @@ def __init__( io_type: WiringIOType, line_type: WiringLineType, triggered: bool, - constraints: 'ChannelSpec', - elements: Union['Element', List['Element']], + constraints: "ChannelSpec", + elements: Union["Element", List["Element"]], ): self.frequency = frequency self.io_type = io_type @@ -53,4 +59,4 @@ def __init__( self.constraints = constraints if not isinstance(elements, list): elements = [elements] - self.elements: List['Element'] = elements + self.elements: List["Element"] = elements diff --git a/qualang_tools/wirer/instruments/instrument_channel.py b/qualang_tools/wirer/instruments/instrument_channel.py index 4ce92cd5..1cbb1673 100644 --- a/qualang_tools/wirer/instruments/instrument_channel.py +++ b/qualang_tools/wirer/instruments/instrument_channel.py @@ -1,6 +1,7 @@ from dataclasses import dataclass, field from typing import Union, Literal, Callable + @dataclass(eq=False) class InstrumentChannel: con: int @@ -10,11 +11,11 @@ class InstrumentChannel: def __str__(self): return f'({", ".join([f"con{self.con}", f"{self.slot}" if self.slot else "", str(self.port)])})' - def make_channel_filter(self) -> Callable[['InstrumentChannel'], bool]: + def make_channel_filter(self) -> Callable[["InstrumentChannel"], bool]: return lambda channel: ( - (self.con is None or self.con == channel.con) and - (self.slot is None or self.slot == channel.slot) and - (self.port is None or self.port == channel.port) + (self.con is None or self.con == channel.con) + and (self.slot is None or self.slot == channel.slot) + and (self.port is None or self.port == channel.port) ) @@ -22,30 +23,37 @@ def make_channel_filter(self) -> Callable[['InstrumentChannel'], bool]: class InstrumentChannelInput: io_type: Literal["input", "output"] = "input" + @dataclass(eq=False) class InstrumentChannelOutput: io_type: Literal["input", "output"] = "output" + @dataclass(eq=False) class InstrumentChannelDigital: signal_type = "digital" + @dataclass(eq=False) class InstrumentChannelAnalog: signal_type = "analog" + @dataclass(eq=False) class InstrumentChannelLfFem: instrument_id: Literal["lf-fem", "mw-fem", "opx+", "octave"] = "lf-fem" + @dataclass(eq=False) class InstrumentChannelMwFem: instrument_id: Literal["lf-fem", "mw-fem", "opx+", "octave"] = "mw-fem" + @dataclass(eq=False) class InstrumentChannelOpxPlus: instrument_id: Literal["lf-fem", "mw-fem", "opx+", "octave"] = "opx+" + @dataclass(eq=False) class InstrumentChannelOctave: instrument_id: Literal["lf-fem", "mw-fem", "opx+", "octave"] = "octave" @@ -53,100 +61,86 @@ class InstrumentChannelOctave: @dataclass(eq=False) class InstrumentChannelLfFemInput( - InstrumentChannelAnalog, - InstrumentChannelLfFem, - InstrumentChannelInput, - InstrumentChannel -): pass + InstrumentChannelAnalog, InstrumentChannelLfFem, InstrumentChannelInput, InstrumentChannel +): + pass + @dataclass(eq=False) class InstrumentChannelLfFemOutput( - InstrumentChannelAnalog, - InstrumentChannelLfFem, - InstrumentChannelOutput, - InstrumentChannel -): pass + InstrumentChannelAnalog, InstrumentChannelLfFem, InstrumentChannelOutput, InstrumentChannel +): + pass + @dataclass(eq=False) class InstrumentChannelLfFemDigitalOutput( - InstrumentChannelDigital, - InstrumentChannelLfFem, - InstrumentChannelOutput, - InstrumentChannel -): pass + InstrumentChannelDigital, InstrumentChannelLfFem, InstrumentChannelOutput, InstrumentChannel +): + pass @dataclass(eq=False) class InstrumentChannelMwFemInput( - InstrumentChannelAnalog, - InstrumentChannelMwFem, - InstrumentChannelInput, - InstrumentChannel -): pass + InstrumentChannelAnalog, InstrumentChannelMwFem, InstrumentChannelInput, InstrumentChannel +): + pass + @dataclass(eq=False) class InstrumentChannelMwFemOutput( - InstrumentChannelAnalog, - InstrumentChannelMwFem, - InstrumentChannelOutput, - InstrumentChannel -): pass + InstrumentChannelAnalog, InstrumentChannelMwFem, InstrumentChannelOutput, InstrumentChannel +): + pass + @dataclass(eq=False) class InstrumentChannelMwFemDigitalOutput( - InstrumentChannelDigital, - InstrumentChannelMwFem, - InstrumentChannelOutput, - InstrumentChannel -): pass + InstrumentChannelDigital, InstrumentChannelMwFem, InstrumentChannelOutput, InstrumentChannel +): + pass + @dataclass(eq=False) class InstrumentChannelOpxPlusInput( - InstrumentChannelAnalog, - InstrumentChannelOpxPlus, - InstrumentChannelInput, - InstrumentChannel -): pass + InstrumentChannelAnalog, InstrumentChannelOpxPlus, InstrumentChannelInput, InstrumentChannel +): + pass + @dataclass(eq=False) class InstrumentChannelOpxPlusOutput( - InstrumentChannelAnalog, - InstrumentChannelOpxPlus, - InstrumentChannelOutput, - InstrumentChannel -): pass + InstrumentChannelAnalog, InstrumentChannelOpxPlus, InstrumentChannelOutput, InstrumentChannel +): + pass + @dataclass(eq=False) class InstrumentChannelOpxPlusDigitalOutput( - InstrumentChannelDigital, - InstrumentChannelOpxPlus, - InstrumentChannelOutput, - InstrumentChannel -): pass + InstrumentChannelDigital, InstrumentChannelOpxPlus, InstrumentChannelOutput, InstrumentChannel +): + pass + @dataclass(eq=False) class InstrumentChannelOctaveInput( - InstrumentChannelAnalog, - InstrumentChannelOctave, - InstrumentChannelInput, - InstrumentChannel -): pass + InstrumentChannelAnalog, InstrumentChannelOctave, InstrumentChannelInput, InstrumentChannel +): + pass + @dataclass(eq=False) class InstrumentChannelOctaveOutput( - InstrumentChannelAnalog, - InstrumentChannelOctave, - InstrumentChannelOutput, - InstrumentChannel -): pass + InstrumentChannelAnalog, InstrumentChannelOctave, InstrumentChannelOutput, InstrumentChannel +): + pass + @dataclass(eq=False) class InstrumentChannelOctaveDigitalInput( - InstrumentChannelDigital, - InstrumentChannelOctave, - InstrumentChannelInput, - InstrumentChannel -): pass + InstrumentChannelDigital, InstrumentChannelOctave, InstrumentChannelInput, InstrumentChannel +): + pass AnyInstrumentChannel = Union[ diff --git a/qualang_tools/wirer/instruments/instrument_channels.py b/qualang_tools/wirer/instruments/instrument_channels.py index 24d65035..87a81e38 100644 --- a/qualang_tools/wirer/instruments/instrument_channels.py +++ b/qualang_tools/wirer/instruments/instrument_channels.py @@ -1,12 +1,25 @@ from dataclasses import asdict from typing import Type -from qualang_tools.wirer.instruments.instrument_channel import InstrumentChannel, InstrumentChannelLfFemInput, \ - InstrumentChannelLfFemOutput, InstrumentChannelMwFemInput, InstrumentChannelMwFemOutput, \ - InstrumentChannelOpxPlusInput, InstrumentChannelOpxPlusOutput, AnyInstrumentChannel, \ - InstrumentChannelLfFemDigitalOutput, InstrumentChannelMwFemDigitalOutput, InstrumentChannelOpxPlusDigitalOutput +from qualang_tools.wirer.instruments.instrument_channel import ( + InstrumentChannel, + InstrumentChannelLfFemInput, + InstrumentChannelLfFemOutput, + InstrumentChannelMwFemInput, + InstrumentChannelMwFemOutput, + InstrumentChannelOpxPlusInput, + InstrumentChannelOpxPlusOutput, + AnyInstrumentChannel, + InstrumentChannelLfFemDigitalOutput, + InstrumentChannelMwFemDigitalOutput, + InstrumentChannelOpxPlusDigitalOutput, +) -CHANNELS_OPX_PLUS = [InstrumentChannelOpxPlusInput, InstrumentChannelOpxPlusOutput, InstrumentChannelOpxPlusDigitalOutput] +CHANNELS_OPX_PLUS = [ + InstrumentChannelOpxPlusInput, + InstrumentChannelOpxPlusOutput, + InstrumentChannelOpxPlusDigitalOutput, +] CHANNELS_OPX_1000 = [ InstrumentChannelLfFemInput, @@ -60,9 +73,7 @@ def check_if_mixing_opx_1000_and_opx_plus(self, channel: InstrumentChannel): elif type(channel) in CHANNELS_OPX_PLUS: for channel_type in CHANNELS_OPX_1000: if channel_type in self.stack: - raise ValueError( - f"Can't add an OPX+ to a setup with an OPX1000 FEM." - ) + raise ValueError(f"Can't add an OPX+ to a setup with an OPX1000 FEM.") def insert(self, pos: int, channel: InstrumentChannel): self.check_if_already_occupied(channel) @@ -106,4 +117,4 @@ def __contains__(self, item: InstrumentChannel): return False def items(self): - return self.stack.items() \ No newline at end of file + return self.stack.items() diff --git a/qualang_tools/wirer/instruments/instruments.py b/qualang_tools/wirer/instruments/instruments.py index e65696eb..46f33404 100644 --- a/qualang_tools/wirer/instruments/instruments.py +++ b/qualang_tools/wirer/instruments/instruments.py @@ -1,8 +1,13 @@ from typing import List, Union -from .instrument_channel import InstrumentChannelOctaveInput, InstrumentChannelOctaveOutput, \ - InstrumentChannelOctaveDigitalInput, InstrumentChannelLfFemDigitalOutput, InstrumentChannelMwFemDigitalOutput, \ - InstrumentChannelOpxPlusDigitalOutput +from .instrument_channel import ( + InstrumentChannelOctaveInput, + InstrumentChannelOctaveOutput, + InstrumentChannelOctaveDigitalInput, + InstrumentChannelLfFemDigitalOutput, + InstrumentChannelMwFemDigitalOutput, + InstrumentChannelOpxPlusDigitalOutput, +) from .instrument_channels import * from .constants import * diff --git a/qualang_tools/wirer/visualizer/instrument_figure_manager.py b/qualang_tools/wirer/visualizer/instrument_figure_manager.py index 1fea67b9..75db24c2 100644 --- a/qualang_tools/wirer/visualizer/instrument_figure_manager.py +++ b/qualang_tools/wirer/visualizer/instrument_figure_manager.py @@ -22,7 +22,7 @@ def get_ax(self, con: int, slot: int, instrument_id: str) -> Axes: if key not in self.figures: if instrument_id == "OPX1000": fig, axs = self._make_opx1000_figure() - self.figures[key] = {i+1: ax for i, ax in enumerate(axs)} + self.figures[key] = {i + 1: ax for i, ax in enumerate(axs)} fig.suptitle(f"con{con} - {instrument_id} Wiring Diagram", fontweight="bold", fontsize=14) elif instrument_id == "OPX+": fig = self._make_opx_plus_figure() @@ -40,37 +40,44 @@ def get_ax(self, con: int, slot: int, instrument_id: str) -> Axes: @staticmethod def _make_opx1000_figure() -> Tuple[Figure, List[Axes]]: fig, axs = plt.subplots( - nrows=1, ncols=8, - figsize=(INSTRUMENT_FIGURE_DIMENSIONS["OPX1000"]["width"] * 2, - INSTRUMENT_FIGURE_DIMENSIONS["OPX1000"]["height"] * 2) + nrows=1, + ncols=8, + figsize=( + INSTRUMENT_FIGURE_DIMENSIONS["OPX1000"]["width"] * 2, + INSTRUMENT_FIGURE_DIMENSIONS["OPX1000"]["height"] * 2, + ), ) for i, ax in enumerate(axs.flat): ax.set_ylim([0.15, 1.15]) ax.set_xlim([0.15 / 8 * 3, 1.15 / 8 * 3]) - ax.set_facecolor('darkgrey') + ax.set_facecolor("darkgrey") ax.set_xticks([]) ax.set_yticks([]) ax.set_xticklabels([]) ax.set_yticklabels([]) - ax.set_aspect('equal') + ax.set_aspect("equal") fig.subplots_adjust(wspace=0) return fig, axs @staticmethod def _make_opx_plus_figure() -> Figure: - fig, ax = plt.subplots(1, 1, - figsize=(INSTRUMENT_FIGURE_DIMENSIONS["OPX+"]["width"] * 2, - INSTRUMENT_FIGURE_DIMENSIONS["OPX+"]["height"] * 2) + fig, ax = plt.subplots( + 1, + 1, + figsize=( + INSTRUMENT_FIGURE_DIMENSIONS["OPX+"]["width"] * 2, + INSTRUMENT_FIGURE_DIMENSIONS["OPX+"]["height"] * 2, + ), ) - ax.set_ylim([0.15 / 8 * 3, 1.15/ 8 * 3]) + ax.set_ylim([0.15 / 8 * 3, 1.15 / 8 * 3]) ax.set_xlim([0.15 * 3, 1.15 * 3]) - ax.set_facecolor('darkgrey') + ax.set_facecolor("darkgrey") ax.set_xticks([]) ax.set_yticks([]) ax.set_xticklabels([]) ax.set_yticklabels([]) - ax.set_aspect('equal') + ax.set_aspect("equal") return fig diff --git a/qualang_tools/wirer/visualizer/layout.py b/qualang_tools/wirer/visualizer/layout.py index 0bcbad69..ef1c886d 100644 --- a/qualang_tools/wirer/visualizer/layout.py +++ b/qualang_tools/wirer/visualizer/layout.py @@ -10,58 +10,51 @@ INSTRUMENT_FIGURE_DIMENSIONS = { "OPX1000": {"width": 8, "height": 3}, "OPX+": {"width": 8, "height": 1}, - "Octave": {"width": 3, "height": 1} + "Octave": {"width": 3, "height": 1}, } -OPX_PLUS_ASPECT = (INSTRUMENT_FIGURE_DIMENSIONS["OPX+"]["height"] / - INSTRUMENT_FIGURE_DIMENSIONS["OPX+"]["width"]) -OPX_1000_ASPECT = (INSTRUMENT_FIGURE_DIMENSIONS["OPX1000"]["height"] / - INSTRUMENT_FIGURE_DIMENSIONS["OPX1000"]["width"]) +OPX_PLUS_ASPECT = INSTRUMENT_FIGURE_DIMENSIONS["OPX+"]["height"] / INSTRUMENT_FIGURE_DIMENSIONS["OPX+"]["width"] +OPX_1000_ASPECT = INSTRUMENT_FIGURE_DIMENSIONS["OPX1000"]["height"] / INSTRUMENT_FIGURE_DIMENSIONS["OPX1000"]["width"] # Define the port positions for different modules PORT_SPACING_FACTOR = 0.12 PORT_SIZE = 0.055 fem_analog_output_positions = [(0.05 + 0.25 * OPX_1000_ASPECT, 1.06 - i * PORT_SPACING_FACTOR) for i in range(8)] -fem_digital_output_positions = [(0.05 + 0.75 * OPX_1000_ASPECT, .86 - i * PORT_SPACING_FACTOR/2.4) for i in range(8)] +fem_digital_output_positions = [(0.05 + 0.75 * OPX_1000_ASPECT, 0.86 - i * PORT_SPACING_FACTOR / 2.4) for i in range(8)] PORT_POSITIONS = { "lf-fem": { "analog": { "output": fem_analog_output_positions, - "input": [(0.05 + 0.75 * OPX_1000_ASPECT, 1.06 - (6+i) * PORT_SPACING_FACTOR) for i in range(2)], + "input": [(0.05 + 0.75 * OPX_1000_ASPECT, 1.06 - (6 + i) * PORT_SPACING_FACTOR) for i in range(2)], }, - "digital": { - "output": fem_digital_output_positions - } + "digital": {"output": fem_digital_output_positions}, }, "mw-fem": { "analog": { "output": fem_analog_output_positions, "input": [(0.05 + 0.75 * OPX_1000_ASPECT, 1.06 - i * PORT_SPACING_FACTOR * 7) for i in range(2)], }, - "digital": { - "output": fem_digital_output_positions - } + "digital": {"output": fem_digital_output_positions}, }, "opx+": { "analog": { - "input": [(1.1*3, 0.01 + 0.32 / (i+1)) for i in range(2)], - "output": [((0.7 + j * 0.06)*3, 0.01 + 0.32 / (i+1)) for j in range(5) for i in range(2)], + "input": [(1.1 * 3, 0.01 + 0.32 / (i + 1)) for i in range(2)], + "output": [((0.7 + j * 0.06) * 3, 0.01 + 0.32 / (i + 1)) for j in range(5) for i in range(2)], }, "digital": { - "output": [((0.3 + j * 0.06)*3, 0.01 + 0.32 / (i+1)) for j in range(5) for i in range(2)], - } + "output": [((0.3 + j * 0.06) * 3, 0.01 + 0.32 / (i + 1)) for j in range(5) for i in range(2)], + }, }, "octave": { "analog": { - "input": [((0.19 + j * 0.06)*3, 0.25) for j in range(2)], - "output": [((0.3 + j * 0.06)*3, 0.32) for j in range(5)], - "inter_input": [(1.1*3, 0.01 + 0.32 / (i+1)) for i in range(2)], + "input": [((0.19 + j * 0.06) * 3, 0.25) for j in range(2)], + "output": [((0.3 + j * 0.06) * 3, 0.32) for j in range(5)], + "inter_input": [(1.1 * 3, 0.01 + 0.32 / (i + 1)) for i in range(2)], "inter_output": [((0.7 + j * 0.06) * 3, 0.01 + 0.32 / (i + 1)) for j in range(5) for i in range(2)], }, "digital": { - "input": [((0.3 + j * 0.06)*3, 0.18) for j in range(5)], - } - } + "input": [((0.3 + j * 0.06) * 3, 0.18) for j in range(5)], + }, + }, } - diff --git a/qualang_tools/wirer/visualizer/port_annotation.py b/qualang_tools/wirer/visualizer/port_annotation.py index 3d97660f..1848c3c4 100644 --- a/qualang_tools/wirer/visualizer/port_annotation.py +++ b/qualang_tools/wirer/visualizer/port_annotation.py @@ -9,6 +9,7 @@ from .layout import PORT_POSITIONS, PORT_SIZE + class PortAnnotation: def __init__(self, labels, color, con, slot, port, io_type, signal_type, instrument_id): self.labels = labels @@ -34,15 +35,24 @@ def draw(self, ax: Axes): else: port_size = PORT_SIZE port_label_distance = PORT_SIZE * 1.3 - ax.text(x - port_label_distance, y, str(self.port), ha="center", va="center", fontsize=8, fontweight="bold", color=outline_colour) + ax.text( + x - port_label_distance, + y, + str(self.port), + ha="center", + va="center", + fontsize=8, + fontweight="bold", + color=outline_colour, + ) bbox = None ax.add_patch(patches.Circle((x, y), port_size, edgecolor=outline_colour, facecolor=fill_color)) labels = combine_labels_for_same_line_type(self.labels) # qubit line annotation - ax.text( x, y, "\n".join(labels), ha="center", va="center", fontsize=9, color="black", bbox=bbox) - ax.set_facecolor('lightgrey') + ax.text(x, y, "\n".join(labels), ha="center", va="center", fontsize=9, color="black", bbox=bbox) + ax.set_facecolor("lightgrey") if self.instrument_id == "octave" and self.signal_type == "analog": for port in [2 * self.port, 2 * self.port - 1]: @@ -62,6 +72,7 @@ def title_axes(self, ax: Axes): if self.slot is not None: ax.set_title(f"{self.slot}: {self.instrument_id}", y=-0.1, va="bottom") + def get_contrast_color(color): """ Returns black or white depending on the luminance of the input color. @@ -80,14 +91,15 @@ def get_contrast_color(color): luminance = 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2] # Return 'black' if luminance is high (light color), 'white' if luminance is low (dark color) - return 'black' if luminance > 0.5 else 'white' + return "black" if luminance > 0.5 else "white" + def combine_labels_for_same_line_type(labels: List[str]): # Dictionary to group strings by line type grouped_lines = defaultdict(list) # Regular expression to parse the qubit line label - pattern = re.compile(r'q(\d+)\.(.+)') + pattern = re.compile(r"q(\d+)\.(.+)") # Parse each string and group by line type for s in labels: @@ -116,17 +128,17 @@ def combine_labels_for_same_line_type(labels: List[str]): else: # Save the current range and start a new one if start == end: - ranges.append(f'{start}') + ranges.append(f"{start}") else: - ranges.append(f'{start}-{end}') + ranges.append(f"{start}-{end}") start = indices[i] end = start # Append the final range or index if start == end: - ranges.append(f'{start}') + ranges.append(f"{start}") else: - ranges.append(f'{start}-{end}') + ranges.append(f"{start}-{end}") # Combine ranges into the final format reduced_strings.extend([f"q{range}.{line_type}" for range in ranges]) diff --git a/qualang_tools/wirer/visualizer/visualizer.py b/qualang_tools/wirer/visualizer/visualizer.py index e820a3f9..c1566eb7 100644 --- a/qualang_tools/wirer/visualizer/visualizer.py +++ b/qualang_tools/wirer/visualizer/visualizer.py @@ -7,18 +7,28 @@ from qualang_tools.wirer.visualizer.instrument_figure_manager import InstrumentFigureManager from qualang_tools.wirer.visualizer.port_annotation import PortAnnotation + def invert_qubit_dict(qubit_dict) -> dict: inverted_dict = {} for qubit_ref, element in qubit_dict.items(): for channel_type, channels in element.channels.items(): for channel in channels: - key = (channel.con, channel.slot, channel.port, channel.io_type, channel.signal_type, channel.instrument_id, channel_type) + key = ( + channel.con, + channel.slot, + channel.port, + channel.io_type, + channel.signal_type, + channel.instrument_id, + channel_type, + ) if key not in inverted_dict: inverted_dict[key] = [] annotation = f"{element.id}.{channel_type if isinstance(channel_type, str) else channel_type.value}" inverted_dict[key].append((annotation, channel)) return inverted_dict + def make_annotations(inverted_dict: dict) -> List[PortAnnotation]: annotations = [] for key, values in inverted_dict.items(): @@ -34,7 +44,18 @@ def make_unused_channel_annotations(available_channels: InstrumentChannels) -> L annotations = [] for _, channel_list in available_channels.stack.items(): for channel in channel_list: - annotations.append(PortAnnotation([""], "white", channel.con, channel.slot, channel.port, channel.io_type, channel.signal_type, channel.instrument_id)) + annotations.append( + PortAnnotation( + [""], + "white", + channel.con, + channel.slot, + channel.port, + channel.io_type, + channel.signal_type, + channel.instrument_id, + ) + ) return annotations @@ -56,6 +77,7 @@ def draw_annotations(manager: InstrumentFigureManager, annotations: List[PortAnn annotation.draw(ax) annotation.title_axes(ax) + def visualize(qubit_dict, available_channels=None): # Invert the qubit dictionary for easier annotation processing inverted_dict = invert_qubit_dict(qubit_dict) diff --git a/qualang_tools/wirer/wirer/channel_specs.py b/qualang_tools/wirer/wirer/channel_specs.py index 1f7c63de..bf1bd686 100644 --- a/qualang_tools/wirer/wirer/channel_specs.py +++ b/qualang_tools/wirer/wirer/channel_specs.py @@ -1,47 +1,63 @@ from qualang_tools.wirer.connectivity.channel_spec import ChannelSpec -from qualang_tools.wirer.instruments.instrument_channel import InstrumentChannel, InstrumentChannelLfFemInput, \ - InstrumentChannelLfFemOutput, InstrumentChannelMwFemInput, InstrumentChannelMwFemOutput, \ - InstrumentChannelOpxPlusInput, InstrumentChannelOpxPlusOutput, InstrumentChannelOctaveInput, \ - InstrumentChannelOctaveOutput, InstrumentChannelOpxPlusDigitalOutput, InstrumentChannelMwFemDigitalOutput, \ - InstrumentChannelLfFemDigitalOutput, InstrumentChannelOctaveDigitalInput +from qualang_tools.wirer.instruments.instrument_channel import ( + InstrumentChannel, + InstrumentChannelLfFemInput, + InstrumentChannelLfFemOutput, + InstrumentChannelMwFemInput, + InstrumentChannelMwFemOutput, + InstrumentChannelOpxPlusInput, + InstrumentChannelOpxPlusOutput, + InstrumentChannelOctaveInput, + InstrumentChannelOctaveOutput, + InstrumentChannelOpxPlusDigitalOutput, + InstrumentChannelMwFemDigitalOutput, + InstrumentChannelLfFemDigitalOutput, + InstrumentChannelOctaveDigitalInput, +) # A channel template is a partially filled InstrumentChannel object ChannelTemplate = InstrumentChannel class ChannelSpecMwFemSingle(ChannelSpec): - def __init__(self, con: int = None, slot: int = None, - in_port: int = None, out_port: int = None): + def __init__(self, con: int = None, slot: int = None, in_port: int = None, out_port: int = None): super().__init__() self.channel_templates = [ InstrumentChannelMwFemInput(con=con, slot=slot, port=in_port), - InstrumentChannelMwFemOutput(con=con, slot=slot, port=out_port) + InstrumentChannelMwFemOutput(con=con, slot=slot, port=out_port), ] + class ChannelSpecLfFemSingle(ChannelSpec): - def __init__(self, con: int = None, - in_slot: int = None, in_port: int = None, - out_slot: int = None, out_port: int = None): + def __init__( + self, con: int = None, in_slot: int = None, in_port: int = None, out_slot: int = None, out_port: int = None + ): super().__init__() self.channel_templates = [ InstrumentChannelLfFemInput(con=con, slot=in_slot, port=in_port), - InstrumentChannelLfFemOutput(con=con, slot=out_slot, port=out_port) + InstrumentChannelLfFemOutput(con=con, slot=out_slot, port=out_port), ] class ChannelSpecLfFemBaseband(ChannelSpec): - def __init__(self, con: int = None, slot: int = None, - in_port_i: int = None, in_port_q: int = None, - out_port_i: int = None, out_port_q: int = None): + def __init__( + self, + con: int = None, + slot: int = None, + in_port_i: int = None, + in_port_q: int = None, + out_port_i: int = None, + out_port_q: int = None, + ): super().__init__() self.channel_templates = [ InstrumentChannelLfFemInput(con=con, slot=slot, port=in_port_i), InstrumentChannelLfFemInput(con=con, slot=slot, port=in_port_q), InstrumentChannelLfFemOutput(con=con, slot=slot, port=out_port_i), - InstrumentChannelLfFemOutput(con=con, slot=slot, port=out_port_q) + InstrumentChannelLfFemOutput(con=con, slot=slot, port=out_port_q), ] @@ -50,22 +66,27 @@ def __init__(self, con: int = None, in_port: int = None, out_port: int = None): super().__init__() self.channel_templates = [ - InstrumentChannelOpxPlusInput(con=con, port=in_port), - InstrumentChannelOpxPlusOutput(con=con, port=out_port) + InstrumentChannelOpxPlusInput(con=con, port=in_port), + InstrumentChannelOpxPlusOutput(con=con, port=out_port), ] class ChannelSpecOpxPlusBaseband(ChannelSpec): - def __init__(self, con: int = None, - in_port_i: int = None, in_port_q: int = None, - out_port_i: int = None, out_port_q: int = None): + def __init__( + self, + con: int = None, + in_port_i: int = None, + in_port_q: int = None, + out_port_i: int = None, + out_port_q: int = None, + ): super().__init__() self.channel_templates = [ InstrumentChannelOpxPlusInput(con=con, port=in_port_i), InstrumentChannelOpxPlusInput(con=con, port=in_port_q), InstrumentChannelOpxPlusOutput(con=con, port=out_port_i), - InstrumentChannelOpxPlusOutput(con=con, port=out_port_q) + InstrumentChannelOpxPlusOutput(con=con, port=out_port_q), ] @@ -79,11 +100,18 @@ def __init__(self, index: int = None, rf_in: int = None, rf_out: int = None): class ChannelSpecLfFemBasebandAndOctave(ChannelSpec): - def __init__(self, - con: int = None, slot: int = None, - in_port_i: int = None, in_port_q: int = None, - out_port_i: int = None, out_port_q: int = None, - octave_index: int = None, rf_in: int = None, rf_out: int = None): + def __init__( + self, + con: int = None, + slot: int = None, + in_port_i: int = None, + in_port_q: int = None, + out_port_i: int = None, + out_port_q: int = None, + octave_index: int = None, + rf_in: int = None, + rf_out: int = None, + ): super().__init__() self.channel_templates = [ InstrumentChannelLfFemInput(con=con, slot=slot, port=in_port_i), @@ -96,11 +124,17 @@ def __init__(self, class ChannelSpecOpxPlusBasebandAndOctave(ChannelSpec): - def __init__(self, - con: int = None, - in_port_i: int = None, in_port_q: int = None, - out_port_i: int = None, out_port_q: int = None, - octave_index: int = None, rf_in: int = None, rf_out: int = None): + def __init__( + self, + con: int = None, + in_port_i: int = None, + in_port_q: int = None, + out_port_i: int = None, + out_port_q: int = None, + octave_index: int = None, + rf_in: int = None, + rf_out: int = None, + ): super().__init__() self.channel_templates = [ InstrumentChannelOpxPlusInput(con=con, port=in_port_i), @@ -111,33 +145,30 @@ def __init__(self, InstrumentChannelOctaveOutput(con=octave_index, port=rf_out), ] + class ChannelSpecOpxPlusDigital(ChannelSpec): def __init__(self, con: int = None, out_port: int = None): super().__init__() - self.channel_templates = [ - InstrumentChannelOpxPlusDigitalOutput(con=con, port=out_port) - ] + self.channel_templates = [InstrumentChannelOpxPlusDigitalOutput(con=con, port=out_port)] + class ChannelSpecMwFemDigital(ChannelSpec): def __init__(self, con: int = None, out_port: int = None): super().__init__() - self.channel_templates = [ - InstrumentChannelMwFemDigitalOutput(con=con, port=out_port) - ] + self.channel_templates = [InstrumentChannelMwFemDigitalOutput(con=con, port=out_port)] + class ChannelSpecLfFemDigital(ChannelSpec): def __init__(self, con: int = None, out_port: int = None): super().__init__() - self.channel_templates = [ - InstrumentChannelLfFemDigitalOutput(con=con, port=out_port) - ] + self.channel_templates = [InstrumentChannelLfFemDigitalOutput(con=con, port=out_port)] + class ChannelSpecOctaveDigital(ChannelSpec): def __init__(self, con: int = None, in_port: int = None): super().__init__() - self.channel_templates = [ - InstrumentChannelOctaveDigitalInput(con=con, port=in_port) - ] + self.channel_templates = [InstrumentChannelOctaveDigitalInput(con=con, port=in_port)] + mw_fem_spec = ChannelSpecMwFemSingle lf_fem_spec = ChannelSpecLfFemSingle diff --git a/qualang_tools/wirer/wirer/channel_specs_interface.py b/qualang_tools/wirer/wirer/channel_specs_interface.py index 19504366..65b7bee9 100644 --- a/qualang_tools/wirer/wirer/channel_specs_interface.py +++ b/qualang_tools/wirer/wirer/channel_specs_interface.py @@ -6,7 +6,7 @@ ChannelSpecOpxPlusSingle, ChannelSpecOpxPlusBaseband, ChannelSpecOpxPlusBasebandAndOctave, - ChannelSpecOctave + ChannelSpecOctave, ) mw_fem_spec = ChannelSpecMwFemSingle diff --git a/qualang_tools/wirer/wirer/context_manager_multi_object_temp_attr_setting.py b/qualang_tools/wirer/wirer/context_manager_multi_object_temp_attr_setting.py index 349583b8..d81ff688 100644 --- a/qualang_tools/wirer/wirer/context_manager_multi_object_temp_attr_setting.py +++ b/qualang_tools/wirer/wirer/context_manager_multi_object_temp_attr_setting.py @@ -39,4 +39,4 @@ def __exit__(self, exc_type, exc_value, traceback): """ for obj, original_attrs in self.original_attrs: for attr, value in original_attrs.items(): - setattr(obj, attr, value) \ No newline at end of file + setattr(obj, attr, value) diff --git a/qualang_tools/wirer/wirer/wirer.py b/qualang_tools/wirer/wirer/wirer.py index b1d4eb54..e9de7bfe 100644 --- a/qualang_tools/wirer/wirer/wirer.py +++ b/qualang_tools/wirer/wirer/wirer.py @@ -6,9 +6,18 @@ import copy from typing import List -from .channel_specs import ChannelSpecLfFemSingle, ChannelSpecOpxPlusSingle, ChannelSpecMwFemSingle, \ - ChannelSpecLfFemBaseband, ChannelSpecOctave, ChannelSpecOpxPlusBaseband, ChannelSpecMwFemDigital, \ - ChannelSpecLfFemDigital, ChannelSpecOctaveDigital, ChannelSpecOpxPlusDigital +from .channel_specs import ( + ChannelSpecLfFemSingle, + ChannelSpecOpxPlusSingle, + ChannelSpecMwFemSingle, + ChannelSpecLfFemBaseband, + ChannelSpecOctave, + ChannelSpecOpxPlusBaseband, + ChannelSpecMwFemDigital, + ChannelSpecLfFemDigital, + ChannelSpecOctaveDigital, + ChannelSpecOpxPlusDigital, +) from .wirer_assign_channels_to_spec import assign_channels_to_spec from .wirer_exceptions import ConstraintsTooStrictException, NotEnoughChannelsException from ..connectivity.channel_spec import ChannelSpec @@ -17,8 +26,12 @@ from ..connectivity.wiring_spec import WiringSpec, WiringFrequency, WiringLineType -def allocate_wiring(connectivity: Connectivity, instruments: Instruments, - block_used_channels: bool = True, clear_wiring_specifications: bool = True): +def allocate_wiring( + connectivity: Connectivity, + instruments: Instruments, + block_used_channels: bool = True, + clear_wiring_specifications: bool = True, +): line_type_fill_order = [ WiringLineType.RESONATOR, WiringLineType.DRIVE, @@ -68,13 +81,11 @@ def allocate_dc_channels(spec: WiringSpec, instruments: Instruments): """ Try to allocate DC channels to an LF-FEM or OPX+ to satisfy the spec. """ - dc_specs = [ - ChannelSpecLfFemSingle(), - ChannelSpecOpxPlusSingle() - ] + dc_specs = [ChannelSpecLfFemSingle(), ChannelSpecOpxPlusSingle()] allocate_channels(spec, dc_specs, instruments, same_con=True, same_slot=True) + def allocate_rf_channels(spec: WiringSpec, instruments: Instruments): """ Try to allocate RF channels to a MW-FEM. If that doesn't work, look for a @@ -84,14 +95,15 @@ def allocate_rf_channels(spec: WiringSpec, instruments: Instruments): rf_specs = [ ChannelSpecMwFemSingle() & ChannelSpecMwFemDigital(), ChannelSpecLfFemBaseband() & ChannelSpecLfFemDigital() & ChannelSpecOctave() & ChannelSpecOctaveDigital(), - ChannelSpecOpxPlusBaseband() & ChannelSpecOpxPlusDigital() & ChannelSpecOctave() & ChannelSpecOctaveDigital() + ChannelSpecOpxPlusBaseband() & ChannelSpecOpxPlusDigital() & ChannelSpecOctave() & ChannelSpecOctaveDigital(), ] allocate_channels(spec, rf_specs, instruments, same_con=True, same_slot=True) -def allocate_channels(wiring_spec: WiringSpec, channel_specs: List[ChannelSpec], - instruments: Instruments, same_con: bool, same_slot: bool): +def allocate_channels( + wiring_spec: WiringSpec, channel_specs: List[ChannelSpec], instruments: Instruments, same_con: bool, same_slot: bool +): mask_failures = 0 for channel_spec in channel_specs: channel_spec = channel_spec.filter_by_wiring_spec(wiring_spec) @@ -100,7 +112,9 @@ def allocate_channels(wiring_spec: WiringSpec, channel_specs: List[ChannelSpec], if not channel_spec.apply_constraints(constraints): mask_failures += 1 continue - if assign_channels_to_spec(wiring_spec, instruments, channel_spec.channel_templates, same_con=same_con, same_slot=same_slot): + if assign_channels_to_spec( + wiring_spec, instruments, channel_spec.channel_templates, same_con=same_con, same_slot=same_slot + ): return if mask_failures == len(channel_specs): diff --git a/qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py b/qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py index 04a301a7..50e555e9 100644 --- a/qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py +++ b/qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py @@ -13,9 +13,7 @@ def assign_channels_to_spec( same_con: bool = False, same_slot: bool = False, ) -> bool: - candidate_channels = _assign_channels_to_spec( - spec, instruments, channel_templates, same_con, same_slot - ) + candidate_channels = _assign_channels_to_spec(spec, instruments, channel_templates, same_con, same_slot) # if candidate channels satisfy all the required channel types if len(candidate_channels) == len(channel_templates): @@ -74,8 +72,7 @@ def _assign_channels_to_spec( # recursive case: allocate remaining channels else: templates_with_same_instr = [ - template for template in channel_templates[1:] - if template.instrument_id == channel.instrument_id + template for template in channel_templates[1:] if template.instrument_id == channel.instrument_id ] with MultiObjectTempAttrUpdater(templates_with_same_instr, con=channel.con, slot=channel.slot): # recursively allocate the remaining channels diff --git a/qualang_tools/wirer/wirer/wirer_exceptions.py b/qualang_tools/wirer/wirer/wirer_exceptions.py index 61361b3a..b5e65b54 100644 --- a/qualang_tools/wirer/wirer/wirer_exceptions.py +++ b/qualang_tools/wirer/wirer/wirer_exceptions.py @@ -15,6 +15,7 @@ def __init__(self, wiring_spec: WiringSpec, constraints: List[ChannelSpec]): ) super(ConstraintsTooStrictException, self).__init__(message) + class NotEnoughChannelsException(Exception): def __init__(self, wiring_spec: WiringSpec): message = ( diff --git a/tests/wirer/conftest.py b/tests/wirer/conftest.py index 8e093301..7e2019ec 100644 --- a/tests/wirer/conftest.py +++ b/tests/wirer/conftest.py @@ -8,6 +8,7 @@ def pytest_configure(): pytest.visualize_flag = True pytest.channels_are_equal = lambda x, y: type(x) == type(y) and asdict(x) == asdict(y) + @pytest.fixture(params=["lf-fem", "opx+"]) def instruments_qw_soprano(request) -> Instruments: print(request.param) @@ -15,10 +16,11 @@ def instruments_qw_soprano(request) -> Instruments: if request.param == "lf-fem": instruments.add_lf_fem(controller=1, slots=[1, 2, 3]) elif request.param == "opx+": - instruments.add_opx_plus(controllers=[1,2]) - instruments.add_octave(indices=[1,2]) + instruments.add_opx_plus(controllers=[1, 2]) + instruments.add_octave(indices=[1, 2]) return instruments + @pytest.fixture(params=["opx+"]) def instruments_1opx_1octave(request) -> Instruments: instruments = Instruments() @@ -29,7 +31,8 @@ def instruments_1opx_1octave(request) -> Instruments: instruments.add_octave(indices=1) return instruments -@pytest.fixture(params=["lf-fem"])#, "opx+"]) + +@pytest.fixture(params=["lf-fem"]) # , "opx+"]) def instruments_1octave(request) -> Instruments: instruments = Instruments() if request.param == "lf-fem": diff --git a/tests/wirer/test_add_dummy_line.py b/tests/wirer/test_add_dummy_line.py index e7041b75..085f6016 100644 --- a/tests/wirer/test_add_dummy_line.py +++ b/tests/wirer/test_add_dummy_line.py @@ -17,7 +17,12 @@ def test_add_dummy_line(instruments_2lf_2mw): dummy_element = Element(id="test") connectivity.add_wiring_spec( - frequency=DC, io_type=INPUT_AND_OUTPUT, line_type='ch', triggered=False, constraints=None, elements=[dummy_element] + frequency=DC, + io_type=INPUT_AND_OUTPUT, + line_type="ch", + triggered=False, + constraints=None, + elements=[dummy_element], ) allocate_wiring(connectivity, instruments_2lf_2mw) @@ -26,9 +31,11 @@ def test_add_dummy_line(instruments_2lf_2mw): visualize(connectivity.elements, instruments_2lf_2mw.available_channels) # regression test - test_element = connectivity.elements[Reference('test')] - for i, channel in enumerate(test_element.channels['ch']): - assert pytest.channels_are_equal(channel, [ - InstrumentChannelLfFemInput(con=1, port=1, slot=1), - InstrumentChannelLfFemOutput(con=1, port=6, slot=1) - ][i]) \ No newline at end of file + test_element = connectivity.elements[Reference("test")] + for i, channel in enumerate(test_element.channels["ch"]): + assert pytest.channels_are_equal( + channel, + [InstrumentChannelLfFemInput(con=1, port=1, slot=1), InstrumentChannelLfFemOutput(con=1, port=6, slot=1)][ + i + ], + ) diff --git a/tests/wirer/test_visualizer.py b/tests/wirer/test_visualizer.py index 0997bb87..046aec5d 100644 --- a/tests/wirer/test_visualizer.py +++ b/tests/wirer/test_visualizer.py @@ -1,10 +1,6 @@ -import enum - import pytest from qualang_tools.wirer import visualize, Connectivity, lf_fem_spec, allocate_wiring, Instruments -from qualang_tools.wirer.connectivity.element import QubitReference -from qualang_tools.wirer.connectivity.wiring_spec import * @pytest.mark.skip(reason="plotting") @@ -38,6 +34,7 @@ def test_4q_allocation_visualization(instruments_1opx_1octave): visualize(connectivity.elements, instruments_1opx_1octave.available_channels) + @pytest.mark.skip(reason="plotting") def test_empty_opx1000_visualization(): instruments = Instruments() @@ -47,6 +44,7 @@ def test_empty_opx1000_visualization(): allocate_wiring(connectivity, instruments) visualize(connectivity.elements, instruments.available_channels) + @pytest.mark.skip(reason="plotting") def test_empty_opx_octave_visualization(instruments_1opx_1octave): connectivity = Connectivity() @@ -54,6 +52,7 @@ def test_empty_opx_octave_visualization(instruments_1opx_1octave): visualize(connectivity.elements, instruments_1opx_1octave.available_channels) +@pytest.mark.skip(reason="plotting") def test_basic_superconducting_qubit_example(instruments_1opx_1octave): connectivity = Connectivity() # Define arbitrary set of qubits and qubit pairs for convenience diff --git a/tests/wirer/test_wirer_channel_reuse.py b/tests/wirer/test_wirer_channel_reuse.py index 0d6361d4..0c4b2ddc 100644 --- a/tests/wirer/test_wirer_channel_reuse.py +++ b/tests/wirer/test_wirer_channel_reuse.py @@ -5,16 +5,20 @@ from qualang_tools.wirer import * from qualang_tools.wirer.connectivity.element import QubitReference from qualang_tools.wirer.connectivity.wiring_spec import WiringLineType -from qualang_tools.wirer.instruments.instrument_channel import InstrumentChannelMwFemInput, \ - InstrumentChannelMwFemOutput, InstrumentChannelLfFemOutput +from qualang_tools.wirer.instruments.instrument_channel import ( + InstrumentChannelMwFemInput, + InstrumentChannelMwFemOutput, + InstrumentChannelLfFemOutput, +) visualize_flag = pytest.visualize_flag + def test_5q_allocation_with_channel_reuse(instruments_2lf_2mw): connectivity = Connectivity() for i in [0, 2]: - qubits = [1+i, 2+i] - qubit_pairs = [(1+i, 2+i)] + qubits = [1 + i, 2 + i] + qubit_pairs = [(1 + i, 2 + i)] connectivity.add_resonator_line(qubits=qubits) connectivity.add_qubit_drive_lines(qubits=qubits) @@ -28,23 +32,29 @@ def test_5q_allocation_with_channel_reuse(instruments_2lf_2mw): for qubit in [1, 3]: # resonator lines re-used for qubits 1 & 3 - for i, channel in enumerate(connectivity.elements[QubitReference(index=qubit)].channels[WiringLineType.RESONATOR]): - assert pytest.channels_are_equal(channel, [ - InstrumentChannelMwFemInput(con=1, port=1, slot=3), - InstrumentChannelMwFemOutput(con=1, port=1, slot=3) - ][i]) + for i, channel in enumerate( + connectivity.elements[QubitReference(index=qubit)].channels[WiringLineType.RESONATOR] + ): + assert pytest.channels_are_equal( + channel, + [ + InstrumentChannelMwFemInput(con=1, port=1, slot=3), + InstrumentChannelMwFemOutput(con=1, port=1, slot=3), + ][i], + ) # drive lines re-used for qubits 1 & 3 for i, channel in enumerate(connectivity.elements[QubitReference(index=qubit)].channels[WiringLineType.DRIVE]): - assert pytest.channels_are_equal(channel, [ - InstrumentChannelMwFemOutput(con=1, port=2, slot=3) - ][i]) + assert pytest.channels_are_equal(channel, [InstrumentChannelMwFemOutput(con=1, port=2, slot=3)][i]) # flux lines re-used for qubits 1 & 3 for i, channel in enumerate(connectivity.elements[QubitReference(index=qubit)].channels[WiringLineType.FLUX]): - assert pytest.channels_are_equal(channel, [ - InstrumentChannelLfFemOutput(con=1, port=1, slot=1), - ][i]) + assert pytest.channels_are_equal( + channel, + [ + InstrumentChannelLfFemOutput(con=1, port=1, slot=1), + ][i], + ) def test_alternating_blocking_of_used_channels(instruments_1opx_1octave): @@ -69,11 +79,11 @@ def test_alternating_blocking_of_used_channels(instruments_1opx_1octave): 1, # q1 allocated to 1, but channel isn't blocked 1, # q2 allocated to 1, since it wasn't blocked 2, # q3 allocated to 2, since it's the next available channel, but not blocked - 2 # q4 allocated to 2, since it wasn't blocked + 2, # q4 allocated to 2, since it wasn't blocked ] for qubit_index in [1, 2, 3, 4]: drive_channels = connectivity.elements[QubitReference(qubit_index)].channels[WiringLineType.DRIVE] for i, channel in enumerate(drive_channels): - assert pytest.channels_are_equal(channel, [ - InstrumentChannelMwFemOutput(con=1, slot=3, port=expected_ports[qubit_index-1]) - ][i]) + assert pytest.channels_are_equal( + channel, [InstrumentChannelMwFemOutput(con=1, slot=3, port=expected_ports[qubit_index - 1])][i] + ) diff --git a/tests/wirer/test_wirer_constraining.py b/tests/wirer/test_wirer_constraining.py index 63a9a67f..9317e25a 100644 --- a/tests/wirer/test_wirer_constraining.py +++ b/tests/wirer/test_wirer_constraining.py @@ -5,17 +5,23 @@ from qualang_tools.wirer import * from qualang_tools.wirer.connectivity.element import QubitReference from qualang_tools.wirer.connectivity.wiring_spec import WiringLineType -from qualang_tools.wirer.instruments.instrument_channel import InstrumentChannelOpxPlusInput, \ - InstrumentChannelOpxPlusOutput, InstrumentChannelOpxPlusDigitalOutput, InstrumentChannelOctaveInput, \ - InstrumentChannelOctaveDigitalInput, InstrumentChannelOctaveOutput +from qualang_tools.wirer.instruments.instrument_channel import ( + InstrumentChannelOpxPlusInput, + InstrumentChannelOpxPlusOutput, + InstrumentChannelOpxPlusDigitalOutput, + InstrumentChannelOctaveInput, + InstrumentChannelOctaveDigitalInput, + InstrumentChannelOctaveOutput, +) visualize_flag = pytest.visualize_flag + def test_opx_plus_resonator_constraining(): # Define the available instrument setup instruments = Instruments() - instruments.add_opx_plus(controllers = [1]) - instruments.add_octave(indices = 1) + instruments.add_opx_plus(controllers=[1]) + instruments.add_octave(indices=1) q1_res_ch = opx_iq_octave_spec(out_port_i=9, out_port_q=10, rf_out=1) @@ -32,16 +38,20 @@ def test_opx_plus_resonator_constraining(): # resonator lines should be hard-coded to I=9, Q=10, rf_out=1 for i, channel in enumerate(connectivity.elements[QubitReference(index=1)].channels[WiringLineType.RESONATOR]): - assert pytest.channels_are_equal(channel, [ - InstrumentChannelOpxPlusInput(con=1, port=1, slot=None), - InstrumentChannelOpxPlusInput(con=1, port=2, slot=None), - InstrumentChannelOpxPlusOutput(con=1, port=9, slot=None), - InstrumentChannelOpxPlusOutput(con=1, port=10, slot=None), - InstrumentChannelOpxPlusDigitalOutput(con=1, port=1, slot=None), - InstrumentChannelOctaveInput(con=1, port=1, slot=None), - InstrumentChannelOctaveOutput(con=1, port=1, slot=None), - InstrumentChannelOctaveDigitalInput(con=1, port=1, slot=None) - ][i]) + assert pytest.channels_are_equal( + channel, + [ + InstrumentChannelOpxPlusInput(con=1, port=1, slot=None), + InstrumentChannelOpxPlusInput(con=1, port=2, slot=None), + InstrumentChannelOpxPlusOutput(con=1, port=9, slot=None), + InstrumentChannelOpxPlusOutput(con=1, port=10, slot=None), + InstrumentChannelOpxPlusDigitalOutput(con=1, port=1, slot=None), + InstrumentChannelOctaveInput(con=1, port=1, slot=None), + InstrumentChannelOctaveOutput(con=1, port=1, slot=None), + InstrumentChannelOctaveDigitalInput(con=1, port=1, slot=None), + ][i], + ) + def test_fix_attribute_equality(): """ diff --git a/tests/wirer/test_wirer_digital.py b/tests/wirer/test_wirer_digital.py index 4a0b9c41..b7371a4a 100644 --- a/tests/wirer/test_wirer_digital.py +++ b/tests/wirer/test_wirer_digital.py @@ -9,6 +9,7 @@ visualize_flag = pytest.visualize_flag + def test_triggered_wiring_spec_generates_digital_channels(instruments_2lf_2mw): connectivity = Connectivity() qubits = [1, 2] @@ -29,4 +30,4 @@ def test_triggered_wiring_spec_generates_digital_channels(instruments_2lf_2mw): resonator_channels = connectivity.elements[QubitReference(index=qubit)].channels[WiringLineType.RESONATOR] resonator_channels_as_dicts = [asdict(channel) for channel in resonator_channels] - assert asdict(InstrumentChannelMwFemDigitalOutput(con=1, port=1, slot=3)) in resonator_channels_as_dicts \ No newline at end of file + assert asdict(InstrumentChannelMwFemDigitalOutput(con=1, port=1, slot=3)) in resonator_channels_as_dicts diff --git a/tests/wirer/test_wirer_lf_and_mw.py b/tests/wirer/test_wirer_lf_and_mw.py index a1c98b40..a5443805 100644 --- a/tests/wirer/test_wirer_lf_and_mw.py +++ b/tests/wirer/test_wirer_lf_and_mw.py @@ -3,11 +3,15 @@ from qualang_tools.wirer import * from qualang_tools.wirer.connectivity.element import QubitReference, QubitPairReference from qualang_tools.wirer.connectivity.wiring_spec import WiringLineType -from qualang_tools.wirer.instruments.instrument_channel import InstrumentChannelLfFemOutput, \ - InstrumentChannelMwFemOutput, InstrumentChannelMwFemInput +from qualang_tools.wirer.instruments.instrument_channel import ( + InstrumentChannelLfFemOutput, + InstrumentChannelMwFemOutput, + InstrumentChannelMwFemInput, +) visualize_flag = pytest.visualize_flag + def test_6q_allocation(instruments_2lf_2mw): qubits = [1, 2, 3, 4, 5, 6] qubit_pairs = [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)] @@ -26,29 +30,27 @@ def test_6q_allocation(instruments_2lf_2mw): for qubit in qubits: # flux channels should have some port as qubit index since they're allocated sequentially for i, channel in enumerate(connectivity.elements[QubitReference(qubit)].channels[WiringLineType.FLUX]): - assert pytest.channels_are_equal(channel, [ - InstrumentChannelLfFemOutput(con=1, port=qubit, slot=1) - ][i]) + assert pytest.channels_are_equal(channel, [InstrumentChannelLfFemOutput(con=1, port=qubit, slot=1)][i]) # resonators all on same feedline, so should be first available input + outputs channels on MW-FEM for i, channel in enumerate(connectivity.elements[QubitReference(qubit)].channels[WiringLineType.RESONATOR]): - assert pytest.channels_are_equal(channel, [ - InstrumentChannelMwFemInput(con=1, port=1, slot=3), - InstrumentChannelMwFemOutput(con=1, port=1, slot=3) - ][i]) + assert pytest.channels_are_equal( + channel, + [ + InstrumentChannelMwFemInput(con=1, port=1, slot=3), + InstrumentChannelMwFemOutput(con=1, port=1, slot=3), + ][i], + ) # drive channels are on MW-FEM for i, channel in enumerate(connectivity.elements[QubitReference(qubit)].channels[WiringLineType.DRIVE]): - assert pytest.channels_are_equal(channel, [ - InstrumentChannelMwFemOutput(con=1, port=qubit+1, slot=3) - ][i]) + assert pytest.channels_are_equal(channel, [InstrumentChannelMwFemOutput(con=1, port=qubit + 1, slot=3)][i]) for i, pair in enumerate(qubit_pairs): # coupler channels should have some port as pair index since they're allocated sequentially, but on slot 2 for j, channel in enumerate(connectivity.elements[QubitPairReference(*pair)].channels[WiringLineType.COUPLER]): - assert pytest.channels_are_equal(channel, [ - InstrumentChannelLfFemOutput(con=1, port=i+1, slot=2) - ][j]) + assert pytest.channels_are_equal(channel, [InstrumentChannelLfFemOutput(con=1, port=i + 1, slot=2)][j]) + def test_4rr_allocation(instruments_2lf_2mw): connectivity = Connectivity() @@ -66,8 +68,10 @@ def test_4rr_allocation(instruments_2lf_2mw): # resonators all on different feedlines, so should fill all 4 inputs of 2x MW-FEM for i, qubit in enumerate([1, 2, 3, 4]): for j, channel in enumerate(connectivity.elements[QubitReference(qubit)].channels[WiringLineType.RESONATOR]): - assert pytest.channels_are_equal(channel, [ - InstrumentChannelMwFemInput(con=1, port=[1, 2, 1, 2][i], slot=[3, 3, 7, 7][i]), - InstrumentChannelMwFemOutput(con=1, port=[1, 2, 1, 2][i], slot=[3, 3, 7, 7][i]) - ][j]) - + assert pytest.channels_are_equal( + channel, + [ + InstrumentChannelMwFemInput(con=1, port=[1, 2, 1, 2][i], slot=[3, 3, 7, 7][i]), + InstrumentChannelMwFemOutput(con=1, port=[1, 2, 1, 2][i], slot=[3, 3, 7, 7][i]), + ][j], + ) diff --git a/tests/wirer/test_wirer_lf_and_octave.py b/tests/wirer/test_wirer_lf_and_octave.py index bd9571b7..f9e2fb69 100644 --- a/tests/wirer/test_wirer_lf_and_octave.py +++ b/tests/wirer/test_wirer_lf_and_octave.py @@ -7,16 +7,21 @@ from qualang_tools.wirer.connectivity.element import QubitReference from qualang_tools.wirer.connectivity.wiring_spec import WiringLineType -from qualang_tools.wirer.instruments.instrument_channel import InstrumentChannelOpxPlusInput, \ - InstrumentChannelOpxPlusOutput, InstrumentChannelOpxPlusDigitalOutput, InstrumentChannelOctaveInput, \ - InstrumentChannelOctaveDigitalInput, InstrumentChannelOctaveOutput +from qualang_tools.wirer.instruments.instrument_channel import ( + InstrumentChannelOpxPlusInput, + InstrumentChannelOpxPlusOutput, + InstrumentChannelOpxPlusDigitalOutput, + InstrumentChannelOctaveInput, + InstrumentChannelOctaveDigitalInput, + InstrumentChannelOctaveOutput, +) visualize_flag = pytest.visualize_flag + def test_rf_io_allocation(instruments_1opx_1octave): qubits = [1, 2, 3, 4] - connectivity = Connectivity() connectivity.add_resonator_line(qubits=qubits) connectivity.add_qubit_drive_lines(qubits=qubits) @@ -28,26 +33,33 @@ def test_rf_io_allocation(instruments_1opx_1octave): for qubit in qubits: # resonator lines should be the same because only 1 feedline - for i, channel in enumerate(connectivity.elements[QubitReference(index=qubit)].channels[WiringLineType.RESONATOR]): - assert pytest.channels_are_equal(channel, [ - InstrumentChannelOpxPlusInput(con=1, port=1, slot=None), - InstrumentChannelOpxPlusInput(con=1, port=2, slot=None), - InstrumentChannelOpxPlusOutput(con=1, port=1, slot=None), - InstrumentChannelOpxPlusOutput(con=1, port=2, slot=None), - # InstrumentChannelOpxPlusDigitalOutput(con=1, port=1, slot=None), - InstrumentChannelOctaveInput(con=1, port=1, slot=None), - InstrumentChannelOctaveOutput(con=1, port=1, slot=None), - # InstrumentChannelOctaveDigitalInput(con=1, port=1, slot=None) - ][i]) + for i, channel in enumerate( + connectivity.elements[QubitReference(index=qubit)].channels[WiringLineType.RESONATOR] + ): + assert pytest.channels_are_equal( + channel, + [ + InstrumentChannelOpxPlusInput(con=1, port=1, slot=None), + InstrumentChannelOpxPlusInput(con=1, port=2, slot=None), + InstrumentChannelOpxPlusOutput(con=1, port=1, slot=None), + InstrumentChannelOpxPlusOutput(con=1, port=2, slot=None), + # InstrumentChannelOpxPlusDigitalOutput(con=1, port=1, slot=None), + InstrumentChannelOctaveInput(con=1, port=1, slot=None), + InstrumentChannelOctaveOutput(con=1, port=1, slot=None), + # InstrumentChannelOctaveDigitalInput(con=1, port=1, slot=None) + ][i], + ) # drive lines should be allocated sequentially for i, channel in enumerate(connectivity.elements[QubitReference(index=qubit)].channels[WiringLineType.DRIVE]): - assert pytest.channels_are_equal(channel, [ - InstrumentChannelOpxPlusOutput(con=1, port=1+2*qubit, slot=None), - InstrumentChannelOpxPlusOutput(con=1, port=2+2*qubit, slot=None), - InstrumentChannelOctaveOutput(con=1, port=1+qubit, slot=None), - ][i]) - + assert pytest.channels_are_equal( + channel, + [ + InstrumentChannelOpxPlusOutput(con=1, port=1 + 2 * qubit, slot=None), + InstrumentChannelOpxPlusOutput(con=1, port=2 + 2 * qubit, slot=None), + InstrumentChannelOctaveOutput(con=1, port=1 + qubit, slot=None), + ][i], + ) def test_qw_soprano_allocation(instruments_qw_soprano): @@ -65,6 +77,7 @@ def test_qw_soprano_allocation(instruments_qw_soprano): # should run without error + def test_qw_soprano_2qb_allocation(instruments_1opx_1octave): active_qubits = [1, 2] @@ -81,6 +94,7 @@ def test_qw_soprano_2qb_allocation(instruments_1opx_1octave): # should run without error + def test_qw_soprano_2qb_among_5_allocation(instruments_1opx_1octave): all_qubits = [1, 2, 3, 4] active_qubits = [1, 2] @@ -105,4 +119,4 @@ def test_qw_soprano_2qb_among_5_allocation(instruments_1opx_1octave): if visualize_flag: visualize(connectivity.elements, instruments_1opx_1octave.available_channels) - # should run without error \ No newline at end of file + # should run without error diff --git a/tests/wirer/test_wirer_lf_charge.py b/tests/wirer/test_wirer_lf_charge.py index d773640f..63796605 100644 --- a/tests/wirer/test_wirer_lf_charge.py +++ b/tests/wirer/test_wirer_lf_charge.py @@ -9,6 +9,7 @@ visualize_flag = pytest.visualize_flag + def test_1q_allocation_flux_charge(instruments_2lf_2mw): qubits = [1, 2, 3, 4, 5] @@ -25,6 +26,6 @@ def test_1q_allocation_flux_charge(instruments_2lf_2mw): assert len(charge_channels) == 1 for i, channel in enumerate(charge_channels): - assert pytest.channels_are_equal(channel, [ - InstrumentChannelLfFemOutput(con=1, port=qubit_index, slot=1) - ][i]) \ No newline at end of file + assert pytest.channels_are_equal( + channel, [InstrumentChannelLfFemOutput(con=1, port=qubit_index, slot=1)][i] + ) From df3575cc64743fbbcc73e051fee99e6a56c70623 Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Sat, 12 Oct 2024 00:14:49 +1100 Subject: [PATCH 33/40] Remove stray import. --- qualang_tools/wirer/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/qualang_tools/wirer/README.md b/qualang_tools/wirer/README.md index ad39f7ec..13ca8270 100644 --- a/qualang_tools/wirer/README.md +++ b/qualang_tools/wirer/README.md @@ -1,5 +1,3 @@ -from configuration.make_wiring import instruments - # Table of Contents 1. [Description](#description) 2. [Overview](#overview) From e13081729069d8e13aa3bece83ecbb249c93f7a4 Mon Sep 17 00:00:00 2001 From: TheoQM Date: Mon, 21 Oct 2024 16:59:20 +0200 Subject: [PATCH 34/40] black format --- qualang_tools/callable_from_qua/_callable_from_qua.py | 9 +++------ qualang_tools/wirer/wirer/wirer.py | 1 + 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/qualang_tools/callable_from_qua/_callable_from_qua.py b/qualang_tools/callable_from_qua/_callable_from_qua.py index ec4948f1..f524a91d 100644 --- a/qualang_tools/callable_from_qua/_callable_from_qua.py +++ b/qualang_tools/callable_from_qua/_callable_from_qua.py @@ -79,16 +79,13 @@ def run(self, job: QmJob): class ProgramAddon(ABC): @abstractmethod - def enter_program(self, program: Program): - ... # noqa: E704 + def enter_program(self, program: Program): ... # noqa: E704 @abstractmethod - def exit_program(self, exc_type, exc_val, exc_tb): - ... # noqa: E704 + def exit_program(self, exc_type, exc_val, exc_tb): ... # noqa: E704 @abstractmethod - def execute_program(self, program: Program, quantum_machine: QuantumMachine): - ... # noqa: E704 + def execute_program(self, program: Program, quantum_machine: QuantumMachine): ... # noqa: E704 class QuaCallableEventManager(ProgramAddon): diff --git a/qualang_tools/wirer/wirer/wirer.py b/qualang_tools/wirer/wirer/wirer.py index e9de7bfe..f3f3fadd 100644 --- a/qualang_tools/wirer/wirer/wirer.py +++ b/qualang_tools/wirer/wirer/wirer.py @@ -3,6 +3,7 @@ into potential channel specifications, then to allocate them to the first valid combination of instrument channels. """ + import copy from typing import List From 7190030f37ef42563abcb92a887ebfbaf66256f5 Mon Sep 17 00:00:00 2001 From: TheoQM Date: Mon, 21 Oct 2024 17:08:52 +0200 Subject: [PATCH 35/40] remove unused import --- qualang_tools/wirer/connectivity/connectivity_base.py | 1 - qualang_tools/wirer/connectivity/element.py | 4 ++-- qualang_tools/wirer/connectivity/wiring_spec.py | 2 ++ qualang_tools/wirer/instruments/instrument_channel.py | 2 +- qualang_tools/wirer/instruments/instruments.py | 3 --- qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py | 2 +- 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/qualang_tools/wirer/connectivity/connectivity_base.py b/qualang_tools/wirer/connectivity/connectivity_base.py index bcdb1f74..098d7b65 100644 --- a/qualang_tools/wirer/connectivity/connectivity_base.py +++ b/qualang_tools/wirer/connectivity/connectivity_base.py @@ -1,4 +1,3 @@ -import copy from typing import Dict, List, Union from .channel_spec import ChannelSpec diff --git a/qualang_tools/wirer/connectivity/element.py b/qualang_tools/wirer/connectivity/element.py index 1e53832c..b8be7688 100644 --- a/qualang_tools/wirer/connectivity/element.py +++ b/qualang_tools/wirer/connectivity/element.py @@ -1,5 +1,5 @@ -from dataclasses import dataclass, field -from typing import List, Dict, Any, Union +from dataclasses import dataclass +from typing import List, Dict, Union from .wiring_spec import WiringLineType from ..instruments.instrument_channel import AnyInstrumentChannel diff --git a/qualang_tools/wirer/connectivity/wiring_spec.py b/qualang_tools/wirer/connectivity/wiring_spec.py index de0dca26..0cff4dcd 100644 --- a/qualang_tools/wirer/connectivity/wiring_spec.py +++ b/qualang_tools/wirer/connectivity/wiring_spec.py @@ -1,5 +1,7 @@ from enum import Enum from typing import Union, List +from .channel_spec import ChannelSpec +from .element import Element class WiringFrequency(Enum): diff --git a/qualang_tools/wirer/instruments/instrument_channel.py b/qualang_tools/wirer/instruments/instrument_channel.py index 1cbb1673..65fda5b0 100644 --- a/qualang_tools/wirer/instruments/instrument_channel.py +++ b/qualang_tools/wirer/instruments/instrument_channel.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass, field +from dataclasses import dataclass from typing import Union, Literal, Callable diff --git a/qualang_tools/wirer/instruments/instruments.py b/qualang_tools/wirer/instruments/instruments.py index 46f33404..a11bbb8e 100644 --- a/qualang_tools/wirer/instruments/instruments.py +++ b/qualang_tools/wirer/instruments/instruments.py @@ -4,9 +4,6 @@ InstrumentChannelOctaveInput, InstrumentChannelOctaveOutput, InstrumentChannelOctaveDigitalInput, - InstrumentChannelLfFemDigitalOutput, - InstrumentChannelMwFemDigitalOutput, - InstrumentChannelOpxPlusDigitalOutput, ) from .instrument_channels import * from .constants import * diff --git a/qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py b/qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py index 50e555e9..99829363 100644 --- a/qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py +++ b/qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py @@ -1,4 +1,4 @@ -from typing import List, Type +from typing import List from qualang_tools.wirer.connectivity.wiring_spec import WiringSpec from qualang_tools.wirer.instruments import Instruments From b1140e38f35c62cee7c33048b0c60a1c13317c0d Mon Sep 17 00:00:00 2001 From: TheoQM Date: Mon, 21 Oct 2024 17:21:42 +0200 Subject: [PATCH 36/40] fis linting F541 --- qualang_tools/wirer/instruments/instrument_channels.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qualang_tools/wirer/instruments/instrument_channels.py b/qualang_tools/wirer/instruments/instrument_channels.py index 87a81e38..6787f30a 100644 --- a/qualang_tools/wirer/instruments/instrument_channels.py +++ b/qualang_tools/wirer/instruments/instrument_channels.py @@ -69,11 +69,11 @@ def check_if_mixing_opx_1000_and_opx_plus(self, channel: InstrumentChannel): if type(channel) in CHANNELS_OPX_1000: for channel_type in CHANNELS_OPX_PLUS: if channel_type in self.stack: - raise ValueError(f"Can't add an FEM to a setup with an OPX+.") + raise ValueError("Can't add an FEM to a setup with an OPX+.") elif type(channel) in CHANNELS_OPX_PLUS: for channel_type in CHANNELS_OPX_1000: if channel_type in self.stack: - raise ValueError(f"Can't add an OPX+ to a setup with an OPX1000 FEM.") + raise ValueError("Can't add an OPX+ to a setup with an OPX1000 FEM.") def insert(self, pos: int, channel: InstrumentChannel): self.check_if_already_occupied(channel) From 0dbd8e51957adcf32560f8cc06db85a2fc0dbcfa Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Wed, 23 Oct 2024 00:34:16 +1100 Subject: [PATCH 37/40] Make black and flake happy. --- qualang_tools/wirer/__init__.py | 15 +++++++++++++++ qualang_tools/wirer/connectivity/__init__.py | 2 ++ .../wirer/connectivity/connectivity_base.py | 1 - qualang_tools/wirer/connectivity/element.py | 4 ++-- qualang_tools/wirer/connectivity/wiring_spec.py | 6 ++++++ qualang_tools/wirer/instruments/__init__.py | 2 ++ .../wirer/instruments/instrument_channel.py | 2 +- .../wirer/instruments/instrument_channels.py | 4 ++-- qualang_tools/wirer/wirer/__init__.py | 2 ++ .../wirer/wirer/wirer_assign_channels_to_spec.py | 2 +- tests/wirer/conftest.py | 2 +- 11 files changed, 34 insertions(+), 8 deletions(-) diff --git a/qualang_tools/wirer/__init__.py b/qualang_tools/wirer/__init__.py index bc57d6b8..0426b17a 100644 --- a/qualang_tools/wirer/__init__.py +++ b/qualang_tools/wirer/__init__.py @@ -12,3 +12,18 @@ opx_iq_octave_spec, octave_spec, ) + +__all__ = [ + "Instruments", + "Connectivity", + "allocate_wiring", + "visualize", + "mw_fem_spec", + "lf_fem_spec", + "lf_fem_iq_spec", + "lf_fem_iq_octave_spec", + "opx_spec", + "opx_iq_spec", + "opx_iq_octave_spec", + "octave_spec", +] diff --git a/qualang_tools/wirer/connectivity/__init__.py b/qualang_tools/wirer/connectivity/__init__.py index a7a1b838..932cf991 100644 --- a/qualang_tools/wirer/connectivity/__init__.py +++ b/qualang_tools/wirer/connectivity/__init__.py @@ -1 +1,3 @@ from .connectivity_transmon_interface import Connectivity + +__all__ = ["Connectivity"] diff --git a/qualang_tools/wirer/connectivity/connectivity_base.py b/qualang_tools/wirer/connectivity/connectivity_base.py index bcdb1f74..098d7b65 100644 --- a/qualang_tools/wirer/connectivity/connectivity_base.py +++ b/qualang_tools/wirer/connectivity/connectivity_base.py @@ -1,4 +1,3 @@ -import copy from typing import Dict, List, Union from .channel_spec import ChannelSpec diff --git a/qualang_tools/wirer/connectivity/element.py b/qualang_tools/wirer/connectivity/element.py index 1e53832c..b8be7688 100644 --- a/qualang_tools/wirer/connectivity/element.py +++ b/qualang_tools/wirer/connectivity/element.py @@ -1,5 +1,5 @@ -from dataclasses import dataclass, field -from typing import List, Dict, Any, Union +from dataclasses import dataclass +from typing import List, Dict, Union from .wiring_spec import WiringLineType from ..instruments.instrument_channel import AnyInstrumentChannel diff --git a/qualang_tools/wirer/connectivity/wiring_spec.py b/qualang_tools/wirer/connectivity/wiring_spec.py index de0dca26..9fba6335 100644 --- a/qualang_tools/wirer/connectivity/wiring_spec.py +++ b/qualang_tools/wirer/connectivity/wiring_spec.py @@ -1,6 +1,12 @@ from enum import Enum from typing import Union, List +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from qualang_tools.wirer.connectivity.channel_spec import ChannelSpec + from qualang_tools.wirer.connectivity.element import Element + class WiringFrequency(Enum): DC = "DC" diff --git a/qualang_tools/wirer/instruments/__init__.py b/qualang_tools/wirer/instruments/__init__.py index 7576b8ab..f95e03e5 100644 --- a/qualang_tools/wirer/instruments/__init__.py +++ b/qualang_tools/wirer/instruments/__init__.py @@ -1 +1,3 @@ from .instruments import Instruments + +__all__ = ["Instruments"] diff --git a/qualang_tools/wirer/instruments/instrument_channel.py b/qualang_tools/wirer/instruments/instrument_channel.py index 1cbb1673..65fda5b0 100644 --- a/qualang_tools/wirer/instruments/instrument_channel.py +++ b/qualang_tools/wirer/instruments/instrument_channel.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass, field +from dataclasses import dataclass from typing import Union, Literal, Callable diff --git a/qualang_tools/wirer/instruments/instrument_channels.py b/qualang_tools/wirer/instruments/instrument_channels.py index 87a81e38..6787f30a 100644 --- a/qualang_tools/wirer/instruments/instrument_channels.py +++ b/qualang_tools/wirer/instruments/instrument_channels.py @@ -69,11 +69,11 @@ def check_if_mixing_opx_1000_and_opx_plus(self, channel: InstrumentChannel): if type(channel) in CHANNELS_OPX_1000: for channel_type in CHANNELS_OPX_PLUS: if channel_type in self.stack: - raise ValueError(f"Can't add an FEM to a setup with an OPX+.") + raise ValueError("Can't add an FEM to a setup with an OPX+.") elif type(channel) in CHANNELS_OPX_PLUS: for channel_type in CHANNELS_OPX_1000: if channel_type in self.stack: - raise ValueError(f"Can't add an OPX+ to a setup with an OPX1000 FEM.") + raise ValueError("Can't add an OPX+ to a setup with an OPX1000 FEM.") def insert(self, pos: int, channel: InstrumentChannel): self.check_if_already_occupied(channel) diff --git a/qualang_tools/wirer/wirer/__init__.py b/qualang_tools/wirer/wirer/__init__.py index c8db1ca3..43cfb72e 100644 --- a/qualang_tools/wirer/wirer/__init__.py +++ b/qualang_tools/wirer/wirer/__init__.py @@ -1 +1,3 @@ from .wirer import allocate_wiring + +__all__ = ["allocate_wiring"] diff --git a/qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py b/qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py index 50e555e9..99829363 100644 --- a/qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py +++ b/qualang_tools/wirer/wirer/wirer_assign_channels_to_spec.py @@ -1,4 +1,4 @@ -from typing import List, Type +from typing import List from qualang_tools.wirer.connectivity.wiring_spec import WiringSpec from qualang_tools.wirer.instruments import Instruments diff --git a/tests/wirer/conftest.py b/tests/wirer/conftest.py index 7e2019ec..d16c925f 100644 --- a/tests/wirer/conftest.py +++ b/tests/wirer/conftest.py @@ -5,7 +5,7 @@ def pytest_configure(): - pytest.visualize_flag = True + pytest.visualize_flag = False pytest.channels_are_equal = lambda x, y: type(x) == type(y) and asdict(x) == asdict(y) From 15ae400a90f9e6740be589f1862647ba97742268 Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Wed, 23 Oct 2024 00:44:16 +1100 Subject: [PATCH 38/40] Fix broken test and linting problems. --- .../callable_from_qua/_callable_from_qua.py | 9 ++++++--- qualang_tools/wirer/connectivity/wiring_spec.py | 2 -- tests/wirer/test_wirer_channel_reuse.py | 12 ++++++------ 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/qualang_tools/callable_from_qua/_callable_from_qua.py b/qualang_tools/callable_from_qua/_callable_from_qua.py index f524a91d..ec4948f1 100644 --- a/qualang_tools/callable_from_qua/_callable_from_qua.py +++ b/qualang_tools/callable_from_qua/_callable_from_qua.py @@ -79,13 +79,16 @@ def run(self, job: QmJob): class ProgramAddon(ABC): @abstractmethod - def enter_program(self, program: Program): ... # noqa: E704 + def enter_program(self, program: Program): + ... # noqa: E704 @abstractmethod - def exit_program(self, exc_type, exc_val, exc_tb): ... # noqa: E704 + def exit_program(self, exc_type, exc_val, exc_tb): + ... # noqa: E704 @abstractmethod - def execute_program(self, program: Program, quantum_machine: QuantumMachine): ... # noqa: E704 + def execute_program(self, program: Program, quantum_machine: QuantumMachine): + ... # noqa: E704 class QuaCallableEventManager(ProgramAddon): diff --git a/qualang_tools/wirer/connectivity/wiring_spec.py b/qualang_tools/wirer/connectivity/wiring_spec.py index 7d7477de..9fba6335 100644 --- a/qualang_tools/wirer/connectivity/wiring_spec.py +++ b/qualang_tools/wirer/connectivity/wiring_spec.py @@ -1,7 +1,5 @@ from enum import Enum from typing import Union, List -from .channel_spec import ChannelSpec -from .element import Element from typing import TYPE_CHECKING diff --git a/tests/wirer/test_wirer_channel_reuse.py b/tests/wirer/test_wirer_channel_reuse.py index 0c4b2ddc..4d59c01a 100644 --- a/tests/wirer/test_wirer_channel_reuse.py +++ b/tests/wirer/test_wirer_channel_reuse.py @@ -57,23 +57,23 @@ def test_5q_allocation_with_channel_reuse(instruments_2lf_2mw): ) -def test_alternating_blocking_of_used_channels(instruments_1opx_1octave): +def test_alternating_blocking_of_used_channels(instruments_2lf_2mw): connectivity = Connectivity() connectivity.add_qubit_drive_lines(qubits=1) - allocate_wiring(connectivity, instruments_1opx_1octave, block_used_channels=False) + allocate_wiring(connectivity, instruments_2lf_2mw, block_used_channels=False) connectivity.add_qubit_drive_lines(qubits=2) - allocate_wiring(connectivity, instruments_1opx_1octave) + allocate_wiring(connectivity, instruments_2lf_2mw) connectivity.add_qubit_drive_lines(qubits=3) - allocate_wiring(connectivity, instruments_1opx_1octave, block_used_channels=False) + allocate_wiring(connectivity, instruments_2lf_2mw, block_used_channels=False) connectivity.add_qubit_drive_lines(qubits=4) - allocate_wiring(connectivity, instruments_1opx_1octave) + allocate_wiring(connectivity, instruments_2lf_2mw) if visualize_flag: - visualize(connectivity.elements, instruments_1opx_1octave.available_channels) + visualize(connectivity.elements, instruments_2lf_2mw.available_channels) expected_ports = [ 1, # q1 allocated to 1, but channel isn't blocked From fc916d8ec02a75c6bcad972cd4835c9c8edf64dd Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Wed, 23 Oct 2024 00:45:42 +1100 Subject: [PATCH 39/40] Add note to README. --- qualang_tools/wirer/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qualang_tools/wirer/README.md b/qualang_tools/wirer/README.md index 13ca8270..7b0e83e9 100644 --- a/qualang_tools/wirer/README.md +++ b/qualang_tools/wirer/README.md @@ -41,7 +41,7 @@ The wirer tool supports the following features: - Any mapping of N FEMs to OPX1000 chassis slots. - Constrained-scope allocation according to user preferences. - Natural overflowing during assignment to multiple slots, chassis, modules, octaves, etc. - - Any combination of resonator, flux line, coupler line for each qubit. + - Any combination of resonator, drive line, flux line, coupler line for each qubit. - Total visualization of the final connectivity. # Example From 8e8945deaabf9c0698bedb59d3a897594b80c7ef Mon Sep 17 00:00:00 2001 From: Dean Poulos Date: Wed, 23 Oct 2024 00:47:30 +1100 Subject: [PATCH 40/40] Fix formatting error. --- qualang_tools/callable_from_qua/_callable_from_qua.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/qualang_tools/callable_from_qua/_callable_from_qua.py b/qualang_tools/callable_from_qua/_callable_from_qua.py index ec4948f1..f524a91d 100644 --- a/qualang_tools/callable_from_qua/_callable_from_qua.py +++ b/qualang_tools/callable_from_qua/_callable_from_qua.py @@ -79,16 +79,13 @@ def run(self, job: QmJob): class ProgramAddon(ABC): @abstractmethod - def enter_program(self, program: Program): - ... # noqa: E704 + def enter_program(self, program: Program): ... # noqa: E704 @abstractmethod - def exit_program(self, exc_type, exc_val, exc_tb): - ... # noqa: E704 + def exit_program(self, exc_type, exc_val, exc_tb): ... # noqa: E704 @abstractmethod - def execute_program(self, program: Program, quantum_machine: QuantumMachine): - ... # noqa: E704 + def execute_program(self, program: Program, quantum_machine: QuantumMachine): ... # noqa: E704 class QuaCallableEventManager(ProgramAddon):

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 literal 0 HcmV?d00001 diff --git a/qualang_tools/wirer/.img/overview_opx_plus.png b/qualang_tools/wirer/.img/overview_opx_plus.png new file mode 100644 index 0000000000000000000000000000000000000000..60cd3a1d35c8e30a706152ba24263508f271d6da GIT binary patch literal 44519 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