diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c233930..4e8ec145 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] - +### Added +- wirer - Support for **QDAC-II (QDAC2)** in registration, DC channel allocation, and visualization: + - Instrument id `qdac2`; `Instruments.add_qdac2(indices)` exposes 24 DC outputs and 4 digital trigger inputs per unit on `available_channels`. + - Channel types `InstrumentChannelQdac2Output` and `InstrumentChannelQdac2DigitalInput`; constants `NUM_QDAC2_OUTPUT_PORTS` and `NUM_QDAC2_DIGITAL_INPUT_PORTS`. + - `qdac2_spec` (`ChannelSpecQdac2`) for DC voltage gates with optional external trigger input on the same QDAC2 unit; exported from `qualang_tools.wirer`. + - `allocate_dc_channels` allocates QDAC2-only lines and, when wiring constraints combine LF-FEM with QDAC2 or OPX+ with QDAC2, tries additional dual-instrument masks so each element gets the corresponding pair of channels. + - Visualizer: QDAC2 figure (3×8 DC grid and four trigger inputs) with port positions and annotations. ## [0.22.0] - 2026-04-01 ### Added diff --git a/qualang_tools/wirer/__init__.py b/qualang_tools/wirer/__init__.py index 56856592..9994c537 100644 --- a/qualang_tools/wirer/__init__.py +++ b/qualang_tools/wirer/__init__.py @@ -11,6 +11,7 @@ opx_iq_spec, opx_iq_octave_spec, octave_spec, + qdac2_spec, ) from .wirer.wirer_exceptions import NotEnoughPulsersException, NotEnoughChannelsException @@ -29,4 +30,5 @@ "opx_iq_spec", "opx_iq_octave_spec", "octave_spec", + "qdac2_spec", ] diff --git a/qualang_tools/wirer/instruments/constants.py b/qualang_tools/wirer/instruments/constants.py index d51a41c0..c6adce10 100644 --- a/qualang_tools/wirer/instruments/constants.py +++ b/qualang_tools/wirer/instruments/constants.py @@ -12,3 +12,5 @@ NUM_MW_FEM_DIGITAL_OUTPUT_PORTS = 8 NUM_THREADS_PER_FEM = 16 NUM_THREADS_PER_OPX_PLUS = 18 +NUM_QDAC2_OUTPUT_PORTS = 24 +NUM_QDAC2_DIGITAL_INPUT_PORTS = 4 diff --git a/qualang_tools/wirer/instruments/instrument_channel.py b/qualang_tools/wirer/instruments/instrument_channel.py index 71e23bac..1b5f1685 100644 --- a/qualang_tools/wirer/instruments/instrument_channel.py +++ b/qualang_tools/wirer/instruments/instrument_channel.py @@ -39,7 +39,7 @@ class InstrumentChannelAnalog: signal_type = "analog" -InstrumentIdType = Literal["lf-fem", "mw-fem", "opx+", "octave", "external-mixer"] +InstrumentIdType = Literal["lf-fem", "mw-fem", "opx+", "octave", "external-mixer", "qdac2"] @dataclass(eq=False) @@ -67,6 +67,11 @@ class InstrumentChannelExternalMixer: instrument_id: InstrumentIdType = "external-mixer" +@dataclass(eq=False) +class InstrumentChannelQdac2: + instrument_id: InstrumentIdType = "qdac2" + + @dataclass(eq=False) class InstrumentChannelLfFemInput( InstrumentChannelAnalog, InstrumentChannelLfFem, InstrumentChannelInput, InstrumentChannel @@ -172,6 +177,20 @@ class InstrumentChannelExternalMixerDigitalInput( pass +@dataclass(eq=False) +class InstrumentChannelQdac2Output( + InstrumentChannelAnalog, InstrumentChannelQdac2, InstrumentChannelOutput, InstrumentChannel +): + pass + + +@dataclass(eq=False) +class InstrumentChannelQdac2DigitalInput( + InstrumentChannelDigital, InstrumentChannelQdac2, InstrumentChannelInput, InstrumentChannel +): + pass + + AnyInstrumentChannel = Union[ InstrumentChannelLfFemInput, InstrumentChannelLfFemOutput, @@ -183,4 +202,6 @@ class InstrumentChannelExternalMixerDigitalInput( InstrumentChannelOctaveOutput, InstrumentChannelExternalMixerInput, InstrumentChannelExternalMixerOutput, + InstrumentChannelQdac2Output, + InstrumentChannelQdac2DigitalInput, ] diff --git a/qualang_tools/wirer/instruments/instruments.py b/qualang_tools/wirer/instruments/instruments.py index 18c88d8e..e29983ae 100644 --- a/qualang_tools/wirer/instruments/instruments.py +++ b/qualang_tools/wirer/instruments/instruments.py @@ -7,6 +7,8 @@ InstrumentChannelExternalMixerInput, InstrumentChannelExternalMixerOutput, InstrumentChannelExternalMixerDigitalInput, + InstrumentChannelQdac2Output, + InstrumentChannelQdac2DigitalInput, ) from .instrument_channels import * from .instrument_pulsers import * @@ -47,6 +49,27 @@ def add_external_mixer(self, indices: Union[List[int], int]): channel = InstrumentChannelExternalMixerDigitalInput(con=index, port=1) self.available_channels.add(channel) + def add_qdac2(self, indices: Union[List[int], int]): + """ + Add QDAC2 instruments to the available channels. + + Each unit exposes all DC analog output ports and external trigger input ports. + + Args: + indices (List[int] | int): One or more QDAC2 unit indices (e.g. as used in the lab stack). + """ + if isinstance(indices, int): + indices = [indices] + + for index in indices: + for port in range(1, NUM_QDAC2_OUTPUT_PORTS + 1): + channel = InstrumentChannelQdac2Output(con=index, port=port) + self.available_channels.add(channel) + + for port in range(1, NUM_QDAC2_DIGITAL_INPUT_PORTS + 1): + channel = InstrumentChannelQdac2DigitalInput(con=index, port=port) + self.available_channels.add(channel) + def add_octave(self, indices: Union[List[int], int]): """ Adds N octaves to the available instrument channels with the specified indices. diff --git a/qualang_tools/wirer/visualizer/instrument_figure_manager.py b/qualang_tools/wirer/visualizer/instrument_figure_manager.py index 473a51c2..28f3eae0 100644 --- a/qualang_tools/wirer/visualizer/instrument_figure_manager.py +++ b/qualang_tools/wirer/visualizer/instrument_figure_manager.py @@ -4,7 +4,12 @@ from matplotlib.axes import Axes from matplotlib.figure import Figure -from qualang_tools.wirer.visualizer.layout import INSTRUMENT_FIGURE_DIMENSIONS, instrument_id_mapping +from qualang_tools.wirer.visualizer.layout import ( + INSTRUMENT_FIGURE_DIMENSIONS, + _QDAC2_X0_NORM, + _QDAC2_X_SPAN_NORM, + instrument_id_mapping, +) def _key(instrument_id: str, con: int): @@ -32,6 +37,10 @@ def get_ax(self, con: int, slot: int, instrument_id: str) -> Axes: fig = self._make_octave_figure() self.figures[key] = fig.axes[0] fig.suptitle(f"oct{con} - {instrument_id} Wiring", fontweight="bold", fontsize=14) + elif instrument_id == "QDAC2": + fig = self._make_qdac2_figure() + self.figures[key] = fig.axes[0] + fig.suptitle(f"qdac{con} - {instrument_id} Wiring", fontweight="bold", fontsize=14) else: fig = self._make_external_mixer_figure() self.figures[key] = fig.axes[0] @@ -87,6 +96,29 @@ def _make_opx_plus_figure() -> Figure: def _make_octave_figure(cls) -> Figure: return cls._make_opx_plus_figure() + @staticmethod + def _make_qdac2_figure() -> Figure: + fig, ax = plt.subplots( + 1, + 1, + figsize=( + INSTRUMENT_FIGURE_DIMENSIONS["QDAC2"]["width"] * 2, + INSTRUMENT_FIGURE_DIMENSIONS["QDAC2"]["height"] * 2, + ), + ) + ax.set_xlim([0.10 * 3, 1.0 * 3]) + ax.set_ylim([0.02, 0.92]) + ax.set_facecolor("darkgrey") + ax.set_xticks([]) + ax.set_yticks([]) + ax.set_xticklabels([]) + ax.set_yticklabels([]) + ax.set_aspect("equal") + _xc = (_QDAC2_X0_NORM + _QDAC2_X_SPAN_NORM / 2) * 3.0 + ax.text(_xc, 0.87, "DC outputs 1-24", ha="center", va="center", fontsize=10, color="gainsboro") + ax.text(_xc, 0.055, "External triggers 1-4", ha="center", va="center", fontsize=9, color="gainsboro") + return fig + @classmethod def _make_external_mixer_figure(cls) -> Figure: fig, ax = plt.subplots( diff --git a/qualang_tools/wirer/visualizer/layout.py b/qualang_tools/wirer/visualizer/layout.py index ac7dbb63..3611ce19 100644 --- a/qualang_tools/wirer/visualizer/layout.py +++ b/qualang_tools/wirer/visualizer/layout.py @@ -5,6 +5,7 @@ "opx+": "OPX+", "octave": "Octave", "external-mixer": "Mixers", + "qdac2": "QDAC2", } # Define the chassis dimensions @@ -13,6 +14,7 @@ "OPX+": {"width": 8, "height": 1}, "Octave": {"width": 3, "height": 1}, "Mixers": {"width": 1, "height": 1}, + "QDAC2": {"width": 12, "height": 3}, } OPX_PLUS_ASPECT = INSTRUMENT_FIGURE_DIMENSIONS["OPX+"]["height"] / INSTRUMENT_FIGURE_DIMENSIONS["OPX+"]["width"] @@ -24,6 +26,27 @@ 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, 0.86 - i * PORT_SPACING_FACTOR / 2.4) for i in range(8)] +# QDAC-II style: three front-panel rows of eight DC outputs (channels 1–24, top to bottom), +# plus a row of four external trigger inputs along the lower edge. +# X positions must stay inside the QDAC2 axes xlim (see instrument_figure_manager) so port labels +# (drawn slightly left of each port) are not clipped in the margin. +_QDAC2_X_SCALE = 3.0 +_QDAC2_X0_NORM = 0.155 # left edge of 8-column grid (normalized, before * _QDAC2_X_SCALE) +_QDAC2_X_SPAN_NORM = 0.745 # (x_last - x_first) for the eight columns +qdac2_analog_output_positions = [ + ( + (_QDAC2_X0_NORM + col * (_QDAC2_X_SPAN_NORM / 7)) * _QDAC2_X_SCALE, + 0.8 - row * 0.24, + ) + for row in range(3) + for col in range(8) +] +_qdac2_trig_u0 = 0.22 +_qdac2_trig_span = 0.52 +qdac2_digital_input_positions = [ + ((_qdac2_trig_u0 + i * (_qdac2_trig_span / 3)) * _QDAC2_X_SCALE, 0.11) for i in range(4) +] + PORT_POSITIONS = { "lf-fem": { "analog": { @@ -68,4 +91,8 @@ "input": [(0.25, 0.85)], }, }, + "qdac2": { + "analog": {"output": qdac2_analog_output_positions}, + "digital": {"input": qdac2_digital_input_positions}, + }, } diff --git a/qualang_tools/wirer/visualizer/port_annotation.py b/qualang_tools/wirer/visualizer/port_annotation.py index 1fef717a..81dac45b 100644 --- a/qualang_tools/wirer/visualizer/port_annotation.py +++ b/qualang_tools/wirer/visualizer/port_annotation.py @@ -60,6 +60,20 @@ def draw(self, ax: Axes): color=outline_colour, ) bbox = None + elif self.instrument_id in ["qdac2"]: + port_size = PORT_SIZE * 0.72 + port_label_distance = PORT_SIZE * 1.05 + ax.text( + x - port_label_distance, + y, + str(self.port), + ha="center", + va="center", + fontsize=7, + fontweight="bold", + color=outline_colour, + ) + bbox = None else: raise NotImplementedError(f"No port-annotation drawing for {self.instrument_id}") diff --git a/qualang_tools/wirer/wirer/channel_specs.py b/qualang_tools/wirer/wirer/channel_specs.py index 7d84400f..06b37847 100644 --- a/qualang_tools/wirer/wirer/channel_specs.py +++ b/qualang_tools/wirer/wirer/channel_specs.py @@ -16,6 +16,8 @@ InstrumentChannelLfFemDigitalOutput, InstrumentChannelOctaveDigitalInput, InstrumentChannelExternalMixerDigitalInput, + InstrumentChannelQdac2Output, + InstrumentChannelQdac2DigitalInput, ) # A channel template is a partially filled InstrumentChannel object @@ -214,6 +216,28 @@ def __init__(self, index: int = None, rf_in: int = None, rf_out: int = None): ] +class ChannelSpecQdac2(ChannelSpec): + def __init__(self, index: int = None, out_port: int = None, trigger_in_port: int = None): + """ + Represents a DC voltage gate on a QDAC2 (analog output plus optional external trigger input). + + Use with ``add_voltage_gate_lines`` (and related quantum-dot helpers). For a triggered line, + allocation picks one DC output and one digital trigger input on the same QDAC2 unit. For an + untriggered line, only the analog output is used. + + Args: + index (int, optional): QDAC2 unit index (maps to ``con`` on the channel; any positive integer). + out_port (int, optional): DC analog output port (1-24; only used when allocating the output). + trigger_in_port (int, optional): External trigger input port (1-4; only used when the line + is triggered). + """ + super().__init__() + self.channel_templates = [ + InstrumentChannelQdac2Output(con=index, port=out_port), + InstrumentChannelQdac2DigitalInput(con=index, port=trigger_in_port), + ] + + class ChannelSpecExternalMixer(ChannelSpec): def __init__(self, index: int = None, rf_in: int = None, rf_out: int = None): """ @@ -515,6 +539,7 @@ def __init__(self, con: int = None, in_port: int = None): opx_iq_ext_mixer_spec = ChannelSpecOpxPlusBasebandAndExternalMixer octave_spec = ChannelSpecOctave ext_mixer_spec = ChannelSpecExternalMixer +qdac2_spec = ChannelSpecQdac2 opx_dig_spec = ChannelSpecOpxPlusDigital lf_fem_dig_spec = ChannelSpecLfFemDigital mw_fem_dig_spec = ChannelSpecMwFemDigital diff --git a/qualang_tools/wirer/wirer/channel_specs_interface.py b/qualang_tools/wirer/wirer/channel_specs_interface.py index 65b7bee9..f611aa6d 100644 --- a/qualang_tools/wirer/wirer/channel_specs_interface.py +++ b/qualang_tools/wirer/wirer/channel_specs_interface.py @@ -7,6 +7,7 @@ ChannelSpecOpxPlusBaseband, ChannelSpecOpxPlusBasebandAndOctave, ChannelSpecOctave, + ChannelSpecQdac2, ) mw_fem_spec = ChannelSpecMwFemSingle @@ -17,3 +18,4 @@ opx_iq_spec = ChannelSpecOpxPlusBaseband opx_iq_octave_spec = ChannelSpecOpxPlusBasebandAndOctave octave_spec = ChannelSpecOctave +qdac2_spec = ChannelSpecQdac2 diff --git a/qualang_tools/wirer/wirer/wirer.py b/qualang_tools/wirer/wirer/wirer.py index 0cd9607c..5bfdc369 100644 --- a/qualang_tools/wirer/wirer/wirer.py +++ b/qualang_tools/wirer/wirer/wirer.py @@ -20,6 +20,7 @@ ChannelSpecOctaveDigital, ChannelSpecOpxPlusDigital, ChannelSpecExternalMixerDigital, + ChannelSpecQdac2, ) from .wirer_assign_channels_to_spec import assign_channels_to_spec from .wirer_exceptions import ConstraintsTooStrictException, NotEnoughChannelsException @@ -136,15 +137,33 @@ def _allocate_wiring(spec: WiringSpec, instruments: Instruments, observe_pulser_ def allocate_dc_channels(spec: WiringSpec, instruments: Instruments, observe_pulser_allocation: bool = False): """ - Try to allocate DC channels to an LF-FEM or OPX+ to satisfy the spec. + Try to allocate DC channels to an LF-FEM, OPX+, and/or QDAC2 to satisfy the spec. + + Single-instrument masks are tried first. If the (filtered) constraints mention both an LF-FEM and + QDAC2 (e.g. ``lf_fem_spec(...) & qdac2_spec(...)``), an extra LF-FEM+QDAC2 mask is appended so each + element receives one LF analog output (and digital if triggered) plus one QDAC2 output (and trigger + input if triggered). The same idea applies for OPX+ combined with QDAC2. """ dc_specs = [ # LF-FEM, Single analog output ChannelSpecLfFemSingle() & ChannelSpecLfFemDigital(), # OPX+, Single analog output ChannelSpecOpxPlusSingle() & ChannelSpecOpxPlusDigital(), + # QDAC2, DC output + external trigger input (digital) + ChannelSpecQdac2(), ] + if spec.constraints: + filtered_c = spec.constraints.filter_by_wiring_spec(spec) + instr_ids = {t.instrument_id for t in filtered_c.channel_templates} + wants_lf = "lf-fem" in instr_ids + wants_opx = "opx+" in instr_ids + wants_qdac = "qdac2" in instr_ids + if wants_lf and wants_qdac: + dc_specs.append(ChannelSpecLfFemSingle() & ChannelSpecLfFemDigital() & ChannelSpecQdac2()) + elif wants_opx and wants_qdac: + dc_specs.append(ChannelSpecOpxPlusSingle() & ChannelSpecOpxPlusDigital() & ChannelSpecQdac2()) + allocate_channels( spec, dc_specs, instruments, same_con=True, same_slot=True, observe_pulser_allocation=observe_pulser_allocation ) diff --git a/tests/wirer/test_instrument_validation.py b/tests/wirer/test_instrument_validation.py index 9fcb2b48..85ead5fb 100644 --- a/tests/wirer/test_instrument_validation.py +++ b/tests/wirer/test_instrument_validation.py @@ -11,6 +11,12 @@ def test_opx_plus_and_octave_validation(): instruments.add_octave(indices=1) +def test_opx_plus_and_qdac2_validation(): + instruments = Instruments() + instruments.add_opx_plus(controllers=1) + instruments.add_qdac2(indices=1) + + def test_redefinition_validation(): instruments = Instruments() instruments.add_opx_plus(controllers=1) @@ -22,6 +28,11 @@ def test_redefinition_validation(): with pytest.raises(ValueError): instruments.add_octave(indices=1) + instruments = Instruments() + instruments.add_qdac2(indices=1) + with pytest.raises(ValueError): + instruments.add_qdac2(indices=1) + def test_slot_filled_validation(): instruments = Instruments() diff --git a/tests/wirer/test_visualizer.py b/tests/wirer/test_visualizer.py index d9f6dabe..e68a1de0 100644 --- a/tests/wirer/test_visualizer.py +++ b/tests/wirer/test_visualizer.py @@ -1,6 +1,6 @@ import pytest -from qualang_tools.wirer import Connectivity, lf_fem_spec, allocate_wiring, Instruments +from qualang_tools.wirer import Connectivity, lf_fem_spec, allocate_wiring, Instruments, qdac2_spec from qualang_tools.wirer.visualizer.web_visualizer import visualize @pytest.mark.skip(reason="plotting") @@ -78,3 +78,33 @@ def test_basic_superconducting_qubit_example(instruments_1opx_1octave): connectivity.add_qubit_pair_flux_lines(qubit_pairs=qubit_pairs) allocate_wiring(connectivity, instruments_1opx_1octave) visualize(connectivity.elements, instruments_1opx_1octave.available_channels) + + +# @pytest.mark.skip(reason="plotting") +def test_five_voltage_gates_qdac2_visualization(): + """ + Manual check: five DC gates on one QDAC2 (untriggered so only analog outputs are used). + """ + instruments = Instruments() + instruments.add_qdac2(indices=1) + connectivity = Connectivity() + connectivity.add_voltage_gate_lines([1, 2, 3, 4, 5], triggered=False) + allocate_wiring(connectivity, instruments) + visualize(connectivity.elements, instruments.available_channels) + +# @pytest.mark.skip(reason="plotting") +def test_five_voltage_gates_qdac2_opx_visualization(): + """ + Manual check: five DC gates on one QDAC2 (untriggered so only analog outputs are used). + """ + instruments = Instruments() + instruments.add_qdac2(indices=1) + instruments.add_lf_fem(controller=1, slots=[1]) + connectivity = Connectivity() + connectivity.add_voltage_gate_lines([1, 2, 3, 4, 5], triggered=False, constraints=lf_fem_spec(1) & qdac2_spec(1)) + connectivity.add_voltage_gate_lines([6], triggered=True, constraints=lf_fem_spec(1) & qdac2_spec(index=1, trigger_in_port=2)) + allocate_wiring(connectivity, instruments, block_used_channels=False) + connectivity.add_voltage_gate_lines([7], triggered=True, constraints=lf_fem_spec(1, out_port=7) & qdac2_spec(index=1, out_port=11,trigger_in_port=2)) + allocate_wiring(connectivity, instruments) + + visualize(connectivity.elements, instruments.available_channels) diff --git a/tests/wirer/test_wirer_qdac2_voltage_gates.py b/tests/wirer/test_wirer_qdac2_voltage_gates.py new file mode 100644 index 00000000..42aaa9a8 --- /dev/null +++ b/tests/wirer/test_wirer_qdac2_voltage_gates.py @@ -0,0 +1,133 @@ +import pytest + +from qualang_tools.wirer import ( + Connectivity, + Instruments, + allocate_wiring, + lf_fem_spec, + opx_spec, + qdac2_spec, +) +from qualang_tools.wirer.connectivity.element import ElementReference +from qualang_tools.wirer.connectivity.wiring_spec import WiringLineType +from qualang_tools.wirer.instruments.instrument_channel import ( + InstrumentChannelLfFemOutput, + InstrumentChannelOpxPlusOutput, + InstrumentChannelQdac2DigitalInput, + InstrumentChannelQdac2Output, +) + + +def test_voltage_gate_lines_qdac2_untriggered(): + instruments = Instruments() + instruments.add_qdac2(indices=1) + connectivity = Connectivity() + connectivity.add_voltage_gate_lines([1, 2, 3], triggered=False) + + allocate_wiring(connectivity, instruments) + + for i in (1, 2, 3): + chans = connectivity.elements[ElementReference("vg", i)].channels[WiringLineType.GLOBAL_GATE] + assert len(chans) == 1 + assert isinstance(chans[0], InstrumentChannelQdac2Output) + + +def test_voltage_gate_lines_qdac2_triggered(): + instruments = Instruments() + instruments.add_qdac2(indices=1) + connectivity = Connectivity() + connectivity.add_voltage_gate_lines([1, 2], triggered=True) + + allocate_wiring(connectivity, instruments) + + for i in (1, 2): + chans = connectivity.elements[ElementReference("vg", i)].channels[WiringLineType.GLOBAL_GATE] + assert len(chans) == 2 + outs = [c for c in chans if isinstance(c, InstrumentChannelQdac2Output)] + trigs = [c for c in chans if isinstance(c, InstrumentChannelQdac2DigitalInput)] + assert len(outs) == 1 and len(trigs) == 1 + assert outs[0].con == trigs[0].con == 1 + + +def test_voltage_gate_lines_qdac2_constrained(): + instruments = Instruments() + instruments.add_qdac2(indices=1) + connectivity = Connectivity() + c = qdac2_spec(index=1, out_port=7, trigger_in_port=2) + connectivity.add_voltage_gate_lines([1], triggered=True, constraints=c) + + allocate_wiring(connectivity, instruments) + + chans = connectivity.elements[ElementReference("vg", 1)].channels[WiringLineType.GLOBAL_GATE] + assert any( + pytest.channels_are_equal(c, InstrumentChannelQdac2Output(con=1, port=7)) for c in chans + ) + assert any( + pytest.channels_are_equal(c, InstrumentChannelQdac2DigitalInput(con=1, port=2)) for c in chans + ) + + +def test_voltage_gate_lf_fem_and_qdac_dual_channels(): + instruments = Instruments() + instruments.add_lf_fem(controller=1, slots=1) + instruments.add_qdac2(indices=1) + connectivity = Connectivity() + c = lf_fem_spec(con=1, out_slot=1) & qdac2_spec(index=1) + connectivity.add_voltage_gate_lines([1], triggered=False, constraints=c) + + allocate_wiring(connectivity, instruments) + + chans = connectivity.elements[ElementReference("vg", 1)].channels[WiringLineType.GLOBAL_GATE] + assert len(chans) == 2 + assert any(isinstance(c, InstrumentChannelLfFemOutput) for c in chans) + assert any(isinstance(c, InstrumentChannelQdac2Output) for c in chans) + + +def test_voltage_gate_five_lf_fem_and_qdac_dual_channels(): + instruments = Instruments() + instruments.add_lf_fem(controller=1, slots=1) + instruments.add_qdac2(indices=1) + connectivity = Connectivity() + c = lf_fem_spec(con=1, out_slot=1) & qdac2_spec(index=1) + connectivity.add_voltage_gate_lines([1, 2, 3, 4, 5], triggered=False, constraints=c) + + allocate_wiring(connectivity, instruments) + + for i in range(1, 6): + chans = connectivity.elements[ElementReference("vg", i)].channels[WiringLineType.GLOBAL_GATE] + assert len(chans) == 2 + assert any(isinstance(c, InstrumentChannelLfFemOutput) for c in chans) + assert any(isinstance(c, InstrumentChannelQdac2Output) for c in chans) + + +def test_voltage_gate_opx_plus_and_qdac_dual_channels(): + instruments = Instruments() + instruments.add_opx_plus(controllers=1) + instruments.add_qdac2(indices=1) + connectivity = Connectivity() + c = opx_spec(con=1) & qdac2_spec(index=1) + connectivity.add_voltage_gate_lines([1], triggered=False, constraints=c) + + allocate_wiring(connectivity, instruments) + + chans = connectivity.elements[ElementReference("vg", 1)].channels[WiringLineType.GLOBAL_GATE] + assert len(chans) == 2 + assert any(isinstance(c, InstrumentChannelOpxPlusOutput) for c in chans) + assert any(isinstance(c, InstrumentChannelQdac2Output) for c in chans) + + +def test_voltage_gate_lf_fem_and_qdac_triggered_dual(): + instruments = Instruments() + instruments.add_lf_fem(controller=1, slots=1) + instruments.add_qdac2(indices=1) + connectivity = Connectivity() + c = lf_fem_spec(con=1, out_slot=1) & qdac2_spec(index=1) + connectivity.add_voltage_gate_lines([1], triggered=True, constraints=c) + + allocate_wiring(connectivity, instruments) + + chans = connectivity.elements[ElementReference("vg", 1)].channels[WiringLineType.GLOBAL_GATE] + assert len(chans) == 4 + assert sum(isinstance(c, InstrumentChannelLfFemOutput) for c in chans) == 1 + assert sum(isinstance(c, InstrumentChannelQdac2Output) for c in chans) == 1 + assert sum(c.signal_type == "digital" for c in chans) == 2