diff --git a/src/qibolab/instruments/qm/controller.py b/src/qibolab/instruments/qm/controller.py index 9ec6eafe0..da749ed22 100644 --- a/src/qibolab/instruments/qm/controller.py +++ b/src/qibolab/instruments/qm/controller.py @@ -27,7 +27,7 @@ from qibolab.sweeper import ParallelSweepers, Parameter, Sweeper from qibolab.unrolling import Bounds, unroll_sequences -from .config import SAMPLING_RATE, Configuration, operation +from .config import SAMPLING_RATE, Configuration from .program import ExecutionArguments, create_acquisition, program from .program.sweepers import find_lo_frequencies, sweeper_amplitude @@ -335,12 +335,12 @@ def register_duration_sweeper_pulses( if isinstance(pulse, (Align, Delay)): continue - params = args.parameters[operation(pulse)] + params = args.parameters[pulse.id] ids = args.sequence.pulse_channels(pulse.id) original_pulse = ( pulse if params.amplitude_pulse is None else params.amplitude_pulse ) - for value in sweeper.values: + for value in sweeper.values.astype(int): sweep_pulse = original_pulse.model_copy(update={"duration": value}) sweep_op = self.register_pulse(ids[0], sweep_pulse) params.duration_ops.append((value, sweep_op)) @@ -357,7 +357,7 @@ def register_amplitude_sweeper_pulses( for pulse in sweeper.pulses: sweep_pulse = pulse.model_copy(update={"amplitude": amplitude}) ids = args.sequence.pulse_channels(pulse.id) - params = args.parameters[operation(pulse)] + params = args.parameters[pulse.id] params.amplitude_pulse = sweep_pulse params.amplitude_op = self.register_pulse(ids[0], sweep_pulse) @@ -415,6 +415,9 @@ def preprocess_sweeps( find_lo_frequencies(args, channels, configs, sweeper.values) for id in sweeper.channels: args.parameters[id].element = probe_map.get(id, id) + for sweeper in find_sweepers(sweepers, Parameter.offset): + for id in sweeper.channels: + args.parameters[id].element = id for sweeper in find_sweepers(sweepers, Parameter.amplitude): self.register_amplitude_sweeper_pulses(args, sweeper) for sweeper in find_sweepers(sweepers, Parameter.duration): diff --git a/src/qibolab/instruments/qm/program/instructions.py b/src/qibolab/instruments/qm/program/instructions.py index 75030fc57..494d4e025 100644 --- a/src/qibolab/instruments/qm/program/instructions.py +++ b/src/qibolab/instruments/qm/program/instructions.py @@ -87,7 +87,7 @@ def play(args: ExecutionArguments): for channel_id, pulse in args.sequence: element = str(channel_id) op = operation(pulse) - params = args.parameters[op] + params = args.parameters[pulse.id] if isinstance(pulse, Delay): _delay(pulse, element, params) elif isinstance(pulse, Pulse): @@ -111,8 +111,13 @@ def _process_sweeper(sweeper: Sweeper, args: ExecutionArguments): if parameter not in SWEEPER_METHODS: raise NotImplementedError(f"Sweeper for {parameter} is not implemented.") - variable = declare(int) if parameter in INT_TYPE else declare(fixed) - values = sweeper.values + if parameter in INT_TYPE: + variable = declare(int) + values = sweeper.values.astype(int) + else: + variable = declare(fixed) + values = sweeper.values + if parameter is Parameter.frequency: lo_frequency = args.parameters[sweeper.channels[0]].lo_frequency values = NORMALIZERS[parameter](values, lo_frequency) @@ -148,7 +153,7 @@ def sweep( method = SWEEPER_METHODS[sweeper.parameter] if sweeper.pulses is not None: for pulse in sweeper.pulses: - params = args.parameters[operation(pulse)] + params = args.parameters[pulse.id] method(variable, params) else: for channel in sweeper.channels: diff --git a/tests/dummy_qrc/qm/parameters.json b/tests/dummy_qrc/qm/parameters.json new file mode 100644 index 000000000..cadc7ec2a --- /dev/null +++ b/tests/dummy_qrc/qm/parameters.json @@ -0,0 +1,342 @@ +{ + "settings": { + "nshots": 2048, + "relaxation_time": 100000 + }, + "configs": { + "qm/bounds": { + "kind": "bounds", + "waveforms": 40000, + "readout": 30, + "instructions": 1000000 + }, + "probe_lo": { + "kind": "oscillator", + "frequency": 7450000000, + "power": 0 + }, + "4/drive_lo": { + "kind": "oscillator", + "frequency": 5700000000, + "power": 0 + }, + "12/drive_lo": { + "kind": "oscillator", + "frequency": 5700000000, + "power": 0 + }, + "3/drive_lo": { + "kind": "oscillator", + "frequency": 6400000000, + "power": 0 + }, + "0/drive_lo": { + "kind": "oscillator", + "frequency": 5100000000, + "power": 0 + }, + "0/probe": { + "kind": "iq", + "frequency": 7137520000 + }, + "0/acquisition": { + "kind": "qm-acquisition", + "delay": 224, + "smearing": 0, + "threshold": 0.002100861788865835, + "iq_angle": -0.7669877581038627, + "gain": 10, + "offset": 0.0 + }, + "0/drive": { + "kind": "iq", + "frequency": 4958294808 + }, + "0/flux": { + "kind": "opx-output", + "offset": 0.2205, + "filter": {} + }, + "1/probe": { + "kind": "iq", + "frequency": 7379568000 + }, + "1/acquisition": { + "kind": "qm-acquisition", + "delay": 224, + "smearing": 0, + "threshold": 0.0024749776365672765, + "iq_angle": 2.985158502622976, + "gain": 10, + "offset": 0.0 + }, + "1/drive": { + "kind": "iq", + "frequency": 5564028148 + }, + "1/flux": { + "kind": "opx-output", + "offset": -0.421, + "filter": {} + }, + "2/probe": { + "kind": "iq", + "frequency": 7490960000 + }, + "2/acquisition": { + "kind": "qm-acquisition", + "delay": 224, + "smearing": 0, + "threshold": 0.0022565651549393286, + "iq_angle": -2.980448168861428, + "gain": 10, + "offset": 0.0 + }, + "2/drive": { + "kind": "iq", + "frequency": 5652497594 + }, + "2/flux": { + "kind": "opx-output", + "offset": -0.2095, + "filter": { + "feedforward": [ + 1.0684635881381783, + -1.0163217174522334 + ], + "feedback": [ + 0.947858129314055 + ] + } + }, + "3/probe": { + "kind": "iq", + "frequency": 7706570000 + }, + "3/acquisition": { + "kind": "qm-acquisition", + "delay": 224, + "smearing": 0, + "threshold": 0.0016417382238347856, + "iq_angle": 0.12674967868508633, + "gain": 10, + "offset": 0.0 + }, + "3/drive": { + "kind": "iq", + "frequency": 6150032162 + }, + "3/flux": { + "kind": "opx-output", + "offset": 0.0, + "filter": {} + }, + "4/probe": { + "kind": "iq", + "frequency": 7636110000 + }, + "4/acquisition": { + "kind": "qm-acquisition", + "delay": 224, + "smearing": 0, + "gain": 10, + "threshold": 0.0025593424931173144, + "iq_angle": 0.5464984071797813, + "offset": 0.0 + }, + "4/drive": { + "kind": "iq", + "frequency": 5421335411 + }, + "4/flux": { + "kind": "opx-output", + "offset": -0.04, + "filter": {} + } + }, + "native_gates": { + "single_qubit": { + "0": { + "RX": [ + [ + "0/drive", + { + "kind": "pulse", + "duration": 40.0, + "amplitude": 0.08329251616923446, + "envelope": { + "kind": "gaussian", + "rel_sigma": 0.2 + } + } + ] + ], + "MZ": [ + [ + "0/acquisition", + { + "kind": "readout", + "acquisition": { + "kind": "acquisition", + "duration": 2000.0 + }, + "probe": { + "kind": "pulse", + "duration": 2000.0, + "amplitude": 0.003, + "envelope": { + "kind": "rectangular" + } + } + } + ] + ] + }, + "1": { + "RX": [ + [ + "1/drive", + { + "kind": "pulse", + "duration": 40.0, + "amplitude": 0.11030836545220754, + "envelope": { + "kind": "gaussian", + "rel_sigma": 0.2 + } + } + ] + ], + "MZ": [ + [ + "1/acquisition", + { + "kind": "readout", + "acquisition": { + "kind": "acquisition", + "duration": 2000.0 + }, + "probe": { + "kind": "pulse", + "duration": 2000.0, + "amplitude": 0.0024, + "envelope": { + "kind": "rectangular" + } + } + } + ] + ] + }, + "2": { + "RX": [ + [ + "2/drive", + { + "kind": "pulse", + "duration": 40.0, + "amplitude": 0.136658279853269, + "envelope": { + "kind": "gaussian", + "rel_sigma": 0.2 + } + } + ] + ], + "MZ": [ + [ + "2/acquisition", + { + "kind": "readout", + "acquisition": { + "kind": "acquisition", + "duration": 2000.0 + }, + "probe": { + "kind": "pulse", + "duration": 2000.0, + "amplitude": 0.0018, + "envelope": { + "kind": "rectangular" + } + } + } + ] + ] + }, + "3": { + "RX": [ + [ + "3/drive", + { + "kind": "pulse", + "duration": 40.0, + "amplitude": 0.1575230136152547, + "envelope": { + "kind": "gaussian", + "rel_sigma": 0.2 + } + } + ] + ], + "MZ": [ + [ + "3/acquisition", + { + "kind": "readout", + "acquisition": { + "kind": "acquisition", + "duration": 2000.0 + }, + "probe": { + "kind": "pulse", + "duration": 2000.0, + "amplitude": 0.004, + "envelope": { + "kind": "rectangular" + } + } + } + ] + ] + }, + "4": { + "RX": [ + [ + "4/drive", + { + "kind": "pulse", + "duration": 40.0, + "amplitude": 0.3828221406541789, + "envelope": { + "kind": "gaussian", + "rel_sigma": 0.2 + } + } + ] + ], + "MZ": [ + [ + "4/acquisition", + { + "kind": "readout", + "acquisition": { + "kind": "acquisition", + "duration": 2000.0 + }, + "probe": { + "kind": "pulse", + "duration": 2000.0, + "amplitude": 0.0036, + "envelope": { + "kind": "rectangular" + } + } + } + ] + ] + } + }, + "two_qubit": {} + } +} diff --git a/tests/dummy_qrc/qm/platform.py b/tests/dummy_qrc/qm/platform.py new file mode 100644 index 000000000..3a946d6d4 --- /dev/null +++ b/tests/dummy_qrc/qm/platform.py @@ -0,0 +1,61 @@ +import pathlib + +from qibolab.components import AcquisitionChannel, Channel, DcChannel, IqChannel +from qibolab.identifier import ChannelId +from qibolab.instruments.qm import Octave, QmConfigs, QmController +from qibolab.parameters import ConfigKinds +from qibolab.platform import Platform +from qibolab.qubits import Qubit + +FOLDER = pathlib.Path(__file__).parent + +# Register QM-specific configurations for parameters loading +ConfigKinds.extend([QmConfigs]) + + +def create(): + """Example platform using Quantum Machines instruments.""" + qubits = {i: Qubit.default(i) for i in range(5)} + + # Create channels and connect to instrument ports + # Readout + channels: dict[ChannelId, Channel] = {} + for q in qubits.values(): + channels[q.probe] = IqChannel( + device="octave5", path="1", mixer=None, lo="probe_lo" + ) + + # Acquire + for q in qubits.values(): + channels[q.acquisition] = AcquisitionChannel( + device="octave5", path="1", twpa_pump=None, probe=q.probe + ) + + # Drive + def define_drive(q: str, device: str, port: int, lo: str): + drive = qubits[q].drive + channels[drive] = IqChannel(device=device, path=str(port), mixer=None, lo=lo) + + define_drive(0, "octave5", 2, "0/drive_lo") + define_drive(1, "octave5", 4, "12/drive_lo") + define_drive(2, "octave5", 5, "12/drive_lo") + define_drive(3, "octave6", 5, "3/drive_lo") + define_drive(4, "octave6", 3, "4/drive_lo") + + # Flux + for q, qubit in qubits.items(): + channels[qubit.flux] = DcChannel(device="con9", path=str(q + 3)) + + octaves = { + "octave5": Octave("octave5", port=11104, connectivity="con6"), + "octave6": Octave("octave6", port=11105, connectivity="con8"), + } + controller = QmController( + address="0.0.0.0:0", + octaves=octaves, + channels=channels, + calibration_path=FOLDER, + script_file_name="qua_script.py", + ) + instruments = {"qm": controller} + return Platform.load(path=FOLDER, instruments=instruments, qubits=qubits) diff --git a/tests/dummy_qrc/qm_octave/parameters.json b/tests/dummy_qrc/qm_octave/parameters.json deleted file mode 100644 index 822b549c3..000000000 --- a/tests/dummy_qrc/qm_octave/parameters.json +++ /dev/null @@ -1,248 +0,0 @@ -{ - "nqubits": 5, - "qubits": [0, 1, 2, 3, 4], - "settings": { - "nshots": 1024, - "relaxation_time": 50000 - }, - "topology": [ - [0, 2], - [1, 2], - [2, 3], - [2, 4] - ], - "instruments": { - "qm": { - "bounds": { - "waveforms": 10000, - "readout": 30, - "instructions": 1000000 - } - }, - "con1": { - "i1": { - "gain": 0 - }, - "i2": { - "gain": 0 - } - }, - "con2": { - "i1": { - "gain": 0 - }, - "i2": { - "gain": 0 - } - }, - "octave1": { - "o1": { - "lo_frequency": 4700000000, - "gain": 0 - }, - "o2": { - "lo_frequency": 5600000000, - "gain": 0 - }, - "o3": { - "lo_frequency": 6500000000, - "gain": 0 - }, - "o4": { - "lo_frequency": 6500000000, - "gain": 0 - }, - "o5": { - "lo_frequency": 7300000000, - "gain": 0 - }, - "i1": { - "lo_frequency": 7300000000 - } - }, - "octave2": { - "o5": { - "lo_frequency": 7900000000, - "gain": 0 - }, - "i1": { - "lo_frequency": 7900000000 - } - }, - "octave3": { - "o1": { - "lo_frequency": 4700000000, - "gain": 0 - } - }, - "twpa_a": { - "frequency": 6511000000, - "power": 4.5 - } - }, - "native_gates": { - "single_qubit": { - "0": { - "RX": { - "duration": 40, - "amplitude": 0.005, - "frequency": 4700000000, - "envelope": { "kind": "gaussian", "rel_sigma": 0.2 }, - "type": "qd" - }, - "RX12": { - "duration": 40, - "amplitude": 0.005, - "frequency": 4700000000, - "envelope": { "kind": "gaussian", "rel_sigma": 0.2 }, - "type": "qd" - }, - "MZ": { - "duration": 1000, - "amplitude": 0.0025, - "frequency": 7226500000, - "envelope": { "kind": "rectangular" }, - "type": "ro" - } - }, - "1": { - "RX": { - "duration": 40, - "amplitude": 0.0484, - "frequency": 4855663000, - "envelope": { "kind": "drag", "rel_sigma": 0.2, "beta": 0.02 }, - "type": "qd" - }, - "RX12": { - "duration": 40, - "amplitude": 0.0484, - "frequency": 4855663000, - "envelope": { "kind": "drag", "rel_sigma": 0.2, "beta": 0.02 }, - "type": "qd" - }, - "MZ": { - "duration": 620, - "amplitude": 0.003575, - "frequency": 7453265000, - "envelope": { "kind": "rectangular" }, - "type": "ro" - } - }, - "2": { - "RX": { - "duration": 40, - "amplitude": 0.05682, - "frequency": 5800563000, - "envelope": { "kind": "drag", "rel_sigma": 0.2, "beta": 0.04 }, - "type": "qd" - }, - "RX12": { - "duration": 40, - "amplitude": 0.05682, - "frequency": 5800563000, - "envelope": { "kind": "drag", "rel_sigma": 0.2, "beta": 0.04 }, - "type": "qd" - }, - "MZ": { - "duration": 960, - "amplitude": 0.00325, - "frequency": 7655107000, - "envelope": { "kind": "rectangular" }, - "type": "ro" - } - }, - "3": { - "RX": { - "duration": 40, - "amplitude": 0.138, - "frequency": 6760922000, - "envelope": { "kind": "gaussian", "rel_sigma": 0.2 }, - "type": "qd" - }, - "RX12": { - "duration": 40, - "amplitude": 0.138, - "frequency": 6760922000, - "envelope": { "kind": "gaussian", "rel_sigma": 0.2 }, - "type": "qd" - }, - "MZ": { - "duration": 960, - "amplitude": 0.004225, - "frequency": 7802191000, - "envelope": { "kind": "rectangular" }, - "type": "ro" - } - }, - "4": { - "RX": { - "duration": 40, - "amplitude": 0.0617, - "frequency": 6585053000, - "envelope": { "kind": "drag", "rel_sigma": 0.2, "beta": 0 }, - "type": "qd" - }, - "RX12": { - "duration": 40, - "amplitude": 0.0617, - "frequency": 6585053000, - "envelope": { "kind": "drag", "rel_sigma": 0.2, "beta": 0 }, - "type": "qd" - }, - "MZ": { - "duration": 640, - "amplitude": 0.0039, - "frequency": 8057668000, - "envelope": { "kind": "rectangular" }, - "type": "ro" - } - } - }, - "two_qubit": { - "1-2": { - "CZ": [ - { - "duration": 30, - "amplitude": 0.055, - "envelope": { "kind": "rectangular" }, - "qubit": 2, - "frequency": 0, - "type": "qf" - }, - { - "type": "vz", - "phase": -1.5707963267948966, - "qubit": 1 - }, - { - "type": "vz", - "phase": -1.5707963267948966, - "qubit": 2 - } - ] - }, - "2-3": { - "CZ": [ - { - "duration": 32, - "amplitude": -0.0513, - "envelope": { "kind": "rectangular" }, - "qubit": 3, - "frequency": 0, - "type": "qf" - }, - { - "type": "vz", - "phase": -1.5707963267948966, - "qubit": 2 - }, - { - "type": "vz", - "phase": -1.5707963267948966, - "qubit": 3 - } - ] - } - } - } -} diff --git a/tests/dummy_qrc/qm_octave/platform.py b/tests/dummy_qrc/qm_octave/platform.py deleted file mode 100644 index 68dcc7d16..000000000 --- a/tests/dummy_qrc/qm_octave/platform.py +++ /dev/null @@ -1,74 +0,0 @@ -import pathlib - -from qibolab.channel import Channel, ChannelMap -from qibolab.instruments.dummy import DummyLocalOscillator as LocalOscillator -from qibolab.instruments.qm import Octave, OPXplus, QMController -from qibolab.platform import Platform - -RUNCARD = pathlib.Path(__file__).parent - - -def create(runcard_path=RUNCARD): - """Dummy platform using Quantum Machines (QM) OPXs and Octaves. - - Based on QuantWare 5-qubit device. - - Used in ``test_instruments_qm.py`` and ``test_instruments_qmsim.py`` - """ - opxs = [OPXplus(f"con{i}") for i in range(1, 4)] - octave1 = Octave("octave1", port=100, connectivity=opxs[0]) - octave2 = Octave("octave2", port=101, connectivity=opxs[1]) - octave3 = Octave("octave3", port=102, connectivity=opxs[2]) - controller = QMController( - "192.168.0.101:80", - opxs=opxs, - octaves=[octave1, octave2, octave3], - time_of_flight=280, - ) - - # Create channel objects and map controllers to channels - channels = ChannelMap() - # readout - channels |= Channel("L3-25_a", port=octave1.ports(5)) - channels |= Channel("L3-25_b", port=octave2.ports(5)) - # feedback - channels |= Channel("L2-5_a", port=octave1.ports(1, output=False)) - channels |= Channel("L2-5_b", port=octave2.ports(1, output=False)) - # drive - channels |= (Channel(f"L3-1{i}", port=octave1.ports(i)) for i in range(1, 5)) - channels |= Channel("L3-15", port=octave3.ports(1)) - # flux - channels |= (Channel(f"L4-{i}", port=opxs[1].ports(i)) for i in range(1, 6)) - # TWPA - channels |= "L4-26" - - # Instantiate local oscillators - twpa = LocalOscillator("192.168.0.35") - # Map LOs to channels - channels["L4-26"].local_oscillator = twpa - - # create qubit objects - runcard = load_runcard(runcard_path) - qubits, couplers, pairs = load_qubits(runcard) - - # assign channels to qubits - for q in [0, 1]: - qubits[q].readout = channels["L3-25_a"] - qubits[q].feedback = channels["L2-5_a"] - for q in [2, 3, 4]: - qubits[q].readout = channels["L3-25_b"] - qubits[q].feedback = channels["L2-5_b"] - - qubits[0].drive = channels["L3-15"] - qubits[0].flux = channels["L4-5"] - for q in range(1, 5): - qubits[q].drive = channels[f"L3-{10 + q}"] - qubits[q].flux = channels[f"L4-{q}"] - - instruments = {"qm": controller, "twpa_a": twpa} - instruments.update(controller.opxs) - instruments.update(controller.octaves) - settings = load_settings(runcard) - return Platform( - "qm_octave", qubits, pairs, instruments, settings, resonator_type="2D" - ) diff --git a/tests/instruments/test_qm.py b/tests/instruments/test_qm.py new file mode 100644 index 000000000..9069da477 --- /dev/null +++ b/tests/instruments/test_qm.py @@ -0,0 +1,124 @@ +from collections import deque +from dataclasses import fields + +import pytest + +pytest.importorskip("qm") +pytest.importorskip("betterproto") +from betterproto import Message +from qm import qua +from qm.qua import ( + Cast, + align, + declare, + declare_stream, + dual_demod, + fixed, + for_, + measure, + play, + save, + stream_processing, + wait, +) + +from qibolab import AcquisitionType, AveragingMode, ExecutionParameters +from qibolab.instruments.qm.config import operation +from qibolab.instruments.qm.program import ( + ExecutionArguments, + create_acquisition, + program, +) +from qibolab.pulses import Acquisition, Gaussian, Pulse, Readout, Rectangular +from qibolab.sequence import PulseSequence + + +class QuaDummyBuilder: + """Builds the Abstract Syntrax Tree (AST) of a QUA program.""" + + def to_dict(self): + return {} + + +def assert_ast_nodes(node1, node2): + """Compares two AST nodes for structural equality using a queue.""" + queue = deque([(node1, node2)]) + while len(queue) > 0: + n1, n2 = queue.popleft() + # Check if types of the two nodes are different + assert type(n1) == type(n2) + # If nodes are AST objects, compare their fields + if isinstance(n1, Message): + # Ignore commands that have no ``loc`` because + # they cause an infinite loop + if not hasattr(n1, "loc") or len(n1.loc) > 0: + for field in fields(n1): + name = field.name + if name != "loc": + queue.append((getattr(n1, name), getattr(n2, name))) + # If nodes are lists, compare all elements + elif isinstance(n1, list): + assert len(n1) == len(n2) + queue.extend(zip(n1, n2)) + # For basic types, check if they are equal + else: + assert n1 == n2 + + +def assert_qua_programs(experiment, target_experiment): + """Compare QUA program generated by the driver with the target program.""" + build = experiment.build(QuaDummyBuilder()) + target_build = target_experiment.build(QuaDummyBuilder()) + assert_ast_nodes(build.script, target_build.script) + assert_ast_nodes(build.result_analysis, target_build.result_analysis) + + +def test_program(): + qd = Pulse(duration=40, amplitude=0.05, envelope=Gaussian(rel_sigma=0.2)) + ro = Readout( + acquisition=Acquisition(duration=1000), + probe=Pulse(duration=1000, amplitude=0.002, envelope=Rectangular()), + ) + sequence = PulseSequence([("drive", qd), ("readout", ro)]) + options = ExecutionParameters( + nshots=1000, + relaxation_time=1000, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.SINGLESHOT, + ) + + op = operation(ro) + acquisitions = { + (op, "readout"): create_acquisition(op, "readout", options, 1.0, 0.0) + } + args = ExecutionArguments( + sequence, acquisitions, relaxation_time=options.relaxation_time + ) + experiment = program(args, options, []) + + # write target QUA program for the above experiment + with qua.program() as target_experiment: + v1 = declare(int) + v2 = declare(fixed) + v3 = declare(fixed) + wait((4 + (0 * (Cast.to_int(v2) + Cast.to_int(v3)))), "readout") + with for_(v1, 0, (v1 < options.nshots), (v1 + 1)): + align() + play(operation(qd), "drive") + measure( + op, + "readout", + None, + dual_demod.full("cos", "out1", "sin", "out2", v2), + dual_demod.full("minus_sin", "out1", "cos", "out2", v3), + ) + r1 = declare_stream() + save(v2, r1) + r2 = declare_stream() + save(v3, r2) + wait(options.relaxation_time // 4) + with stream_processing(): + r1.buffer(options.nshots).save(f"{op}_readout_I") + r2.buffer(options.nshots).save(f"{op}_readout_Q") + + assert_qua_programs(experiment, target_experiment) diff --git a/tests/integration/test_qm.py b/tests/integration/test_qm.py new file mode 100644 index 000000000..5f50e2074 --- /dev/null +++ b/tests/integration/test_qm.py @@ -0,0 +1,444 @@ +import numpy as np +import pytest + +pytest.importorskip("qm") +pytest.importorskip("betterproto") +from qm import qua +from qm.qua import ( + Cast, + align, + amp, + assign, + declare, + declare_stream, + dual_demod, + elif_, + else_, + fixed, + for_, + for_each_, + if_, + measure, + play, + save, + set_dc_offset, + stream_processing, + update_frequency, + wait, +) + +from qibolab import AcquisitionType, AveragingMode, ExecutionParameters, create_platform +from qibolab.instruments.qm.config import operation +from qibolab.pulses import Delay, Pulse, Rectangular +from qibolab.sweeper import Parameter, Sweeper + +from ..instruments.test_qm import assert_qua_programs + + +def test_qubit_flux(dummy_qrc): + platform = create_platform("qm") + + natives = platform.natives.single_qubit + sequence = (natives[0].RX() + natives[1].RX()) | (natives[0].MZ() + natives[1].MZ()) + + f0 = platform.config("0/drive_lo").frequency + 1e8 + sweeper_freq0 = Sweeper( + parameter=Parameter.frequency, + range=(f0 - 2e8, f0 + 2e8, 5e7), + channels=[platform.qubits[0].drive], + ) + f0 = platform.config("12/drive_lo").frequency - 1e8 + sweeper_freq1 = Sweeper( + parameter=Parameter.frequency, + range=(5.7e9 - 2e8, 5.7e9 + 2e8, 5e7), + channels=[platform.qubits[1].drive], + ) + sweeper_bias = Sweeper( + parameter=Parameter.offset, + range=(-0.2, 0.2, 0.1), + channels=[platform.qubits[0].flux, platform.qubits[1].flux], + ) + + options = ExecutionParameters( + nshots=1000, + relaxation_time=1000, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, + ) + result = platform.execute( + [sequence], options, [[sweeper_bias], [sweeper_freq0, sweeper_freq1]] + ) + experiment = result["program"] + + # write target QUA program for the above experiment + qd0 = operation(sequence[0][1]) + qd1 = operation(sequence[1][1]) + ro0 = operation(sequence.acquisitions[0][1]) + ro1 = operation(sequence.acquisitions[1][1]) + with qua.program() as target_experiment: + v1 = declare(int) + v2 = declare(fixed) + v3 = declare(fixed) + v4 = declare(fixed) + v5 = declare(fixed) + v6 = declare(fixed) + v7 = declare(int) + v8 = declare(int) + a1 = declare( + int, + value=[ + -100000000, + -50000000, + 0, + 50000000, + 100000000, + 150000000, + 200000000, + 250000000, + ], + ) + a2 = declare( + int, + value=[ + -200000000, + -150000000, + -100000000, + -50000000, + 0, + 50000000, + 100000000, + 150000000, + ], + ) + wait((4 + (0 * (Cast.to_int(v2) + Cast.to_int(v3)))), "0/acquisition") + wait((4 + (0 * (Cast.to_int(v4) + Cast.to_int(v5)))), "1/acquisition") + with for_(v1, 0, (v1 < 1000), (v1 + 1)): + with for_(v6, -0.2, (v6 < 0.15000000000000002), (v6 + 0.1)): + with if_(v6 >= 0.5): + set_dc_offset("0/flux", "single", 0.5) + with elif_(v6 <= -0.5): + set_dc_offset("0/flux", "single", -0.5) + with else_(): + set_dc_offset("0/flux", "single", v6) + with if_(v6 >= 0.5): + set_dc_offset("1/flux", "single", 0.5) + with elif_(v6 <= -0.5): + set_dc_offset("1/flux", "single", -0.5) + with else_(): + set_dc_offset("1/flux", "single", v6) + with for_each_((v7, v8), (a1, a2)): + update_frequency("0/drive", v7, "Hz", False) + update_frequency("1/drive", v8, "Hz", False) + align() + play(qd0, "0/drive") + play(qd1, "1/drive") + wait(11, "0/acquisition") + wait(11, "1/acquisition") + measure( + ro0, + "0/acquisition", + None, + dual_demod.full("cos", "out1", "sin", "out2", v2), + dual_demod.full("minus_sin", "out1", "cos", "out2", v3), + ) + r1 = declare_stream() + save(v2, r1) + r2 = declare_stream() + save(v3, r2) + measure( + ro1, + "1/acquisition", + None, + dual_demod.full("cos", "out1", "sin", "out2", v4), + dual_demod.full("minus_sin", "out1", "cos", "out2", v5), + ) + r3 = declare_stream() + save(v4, r3) + r4 = declare_stream() + save(v5, r4) + wait( + 250, + ) + with stream_processing(): + r1.buffer(8).buffer(4).average().save(f"{ro0}_0/acquisition_I") + r2.buffer(8).buffer(4).average().save(f"{ro0}_0/acquisition_Q") + r3.buffer(8).buffer(4).average().save(f"{ro1}_1/acquisition_I") + r4.buffer(8).buffer(4).average().save(f"{ro1}_1/acquisition_Q") + + assert_qua_programs(experiment, target_experiment) + + +def test_rabi_sweeps(dummy_qrc): + platform = create_platform("qm") + + natives = platform.natives.single_qubit + sequence = natives[2].RX() + natives[3].RX() + delay1 = Delay(duration=sequence.duration) + delay2 = Delay(duration=sequence.duration) + sequence.append(("2/acquisition", delay1)) + sequence.append(("3/acquisition", delay2)) + sequence += natives[2].MZ() + natives[3].MZ() + + qd1 = sequence[0][1] + qd2 = sequence[1][1] + sweeper_amp = Sweeper( + parameter=Parameter.amplitude, + range=(0.1, 1.0, 0.2), + pulses=[qd1, qd2], + ) + sweeper_dur1 = Sweeper( + parameter=Parameter.duration, + range=(20, 45, 5), + pulses=[qd1, delay1], + ) + sweeper_dur2 = Sweeper( + parameter=Parameter.duration, range=(10, 20, 2), pulses=[qd2, delay2] + ) + + options = ExecutionParameters( + nshots=1000, + relaxation_time=1000, + acquisition_type=AcquisitionType.DISCRIMINATION, + averaging_mode=AveragingMode.CYCLIC, + ) + result = platform.execute( + [sequence], options, [[sweeper_dur1, sweeper_dur2], [sweeper_amp]] + ) + experiment = result["program"] + + # write target QUA program for the above experiment + ro1 = operation(sequence.acquisitions[0][1]) + ro2 = operation(sequence.acquisitions[1][1]) + with qua.program() as target_experiment: + v1 = declare(int) + v2 = declare(fixed) + v3 = declare(fixed) + v4 = declare(int) + v5 = declare(fixed) + v6 = declare(fixed) + v7 = declare(int) + v8 = declare(int) + v9 = declare(int) + a1 = declare(int, value=[20, 25, 30, 35, 40]) + a2 = declare(int, value=[10, 12, 14, 16, 18]) + v10 = declare(fixed) + wait( + (4 + (0 * ((Cast.to_int(v2) + Cast.to_int(v3)) + Cast.to_int(v4)))), + "2/acquisition", + ) + wait( + (4 + (0 * ((Cast.to_int(v5) + Cast.to_int(v6)) + Cast.to_int(v7)))), + "3/acquisition", + ) + with for_(v1, 0, (v1 < 1000), (v1 + 1)): + with for_each_((v8, v9), (a1, a2)): + with for_( + v10, + 0.2211111111111111, + (v10 < 2.2111111111111112), + (v10 + 0.44222222222222224), + ): + align() + with if_((v8 == 20), unsafe=True): + play("" * amp(v10), "2/drive") + with elif_(v8 == 25): + play("" * amp(v10), "2/drive") + with elif_(v8 == 30): + play("" * amp(v10), "2/drive") + with elif_(v8 == 35): + play("" * amp(v10), "2/drive") + with elif_(v8 == 40): + play("" * amp(v10), "2/drive") + with if_((v9 == 10), unsafe=True): + play("" * amp(v10), "3/drive") + with elif_(v9 == 12): + play("" * amp(v10), "3/drive") + with elif_(v9 == 14): + play("" * amp(v10), "3/drive") + with elif_(v9 == 16): + play("" * amp(v10), "3/drive") + with elif_(v9 == 18): + play("" * amp(v10), "3/drive") + with if_((v8 / 4) < 4): + wait(4, "2/acquisition") + with else_(): + wait((v8 / 4), "2/acquisition") + with if_((v9 / 4) < 4): + wait(4, "3/acquisition") + with else_(): + wait((v9 / 4), "3/acquisition") + measure( + ro1, + "2/acquisition", + None, + dual_demod.full("cos", "out1", "sin", "out2", v2), + dual_demod.full("minus_sin", "out1", "cos", "out2", v3), + ) + threshold = platform.config("2/acquisition").threshold + angle = platform.config("2/acquisition").iq_angle + assign( + v4, + Cast.to_int( + v2 * np.cos(angle) - v3 * np.sin(angle) > threshold + ), + ) + r1 = declare_stream() + save(v4, r1) + measure( + ro2, + "3/acquisition", + None, + dual_demod.full("cos", "out1", "sin", "out2", v5), + dual_demod.full("minus_sin", "out1", "cos", "out2", v6), + ) + threshold = platform.config("3/acquisition").threshold + angle = platform.config("3/acquisition").iq_angle + assign( + v7, + Cast.to_int( + v5 * np.cos(angle) - v6 * np.sin(angle) > threshold + ), + ) + r2 = declare_stream() + save(v7, r2) + wait(250) + with stream_processing(): + r1.buffer(5).buffer(5).average().save(f"{ro1}_2/acquisition_shots") + r2.buffer(5).buffer(5).average().save(f"{ro2}_3/acquisition_shots") + + assert_qua_programs(experiment, target_experiment) + + +def test_chevron_sweeps(dummy_qrc): + platform = create_platform("qm") + + natives = platform.natives.single_qubit + sequence = natives[1].RX(theta=np.pi / 2) + natives[2].RX(theta=np.pi / 2) + flux_pulse = Pulse(duration=20, amplitude=0.1, envelope=Rectangular()) + ro_delay1 = Delay(duration=flux_pulse.duration) + ro_delay2 = Delay(duration=flux_pulse.duration) + sequence += [ + ("1/acquisition", Delay(duration=sequence.duration)), + ("2/acquisition", Delay(duration=sequence.duration)), + ("2/flux", Delay(duration=sequence.duration)), + ("2/flux", flux_pulse), + ("1/acquisition", ro_delay1), + ("2/acquisition", ro_delay2), + ] + sequence += natives[1].MZ() + natives[2].MZ() + + sweeper_amp = Sweeper( + parameter=Parameter.amplitude, + range=(0.1, 1.0, 0.2), + pulses=[flux_pulse], + ) + sweeper_dur = Sweeper( + parameter=Parameter.duration, + range=(10, 30, 5), + pulses=[flux_pulse, ro_delay1, ro_delay2], + ) + + options = ExecutionParameters( + nshots=1000, + relaxation_time=1000, + acquisition_type=AcquisitionType.DISCRIMINATION, + averaging_mode=AveragingMode.CYCLIC, + ) + result = platform.execute([sequence], options, [[sweeper_dur], [sweeper_amp]]) + experiment = result["program"] + + # write target QUA program for the above experiment + qd1 = operation(sequence[0][1]) + qd2 = operation(sequence[1][1]) + ro1 = operation(sequence.acquisitions[0][1]) + ro2 = operation(sequence.acquisitions[1][1]) + with qua.program() as target_experiment: + v1 = declare(int) + v2 = declare(fixed) + v3 = declare(fixed) + v4 = declare(int) + v5 = declare(fixed) + v6 = declare(fixed) + v7 = declare(int) + v8 = declare(int) + v9 = declare(fixed) + wait( + (4 + (0 * ((Cast.to_int(v2) + Cast.to_int(v3)) + Cast.to_int(v4)))), + "1/acquisition", + ) + wait( + (4 + (0 * ((Cast.to_int(v5) + Cast.to_int(v6)) + Cast.to_int(v7)))), + "2/acquisition", + ) + with for_(v1, 0, (v1 < 1000), (v1 + 1)): + with for_(v8, 10, (v8 <= 25), (v8 + 5)): + with for_( + v9, + 0.2211111111111111, + (v9 < 2.2111111111111112), + (v9 + 0.44222222222222224), + ): + align() + play(qd1, "1/drive") + play(qd2, "2/drive") + wait(11, "1/acquisition") + wait(11, "2/acquisition") + wait(11, "2/flux") + with if_((v8 == 10), unsafe=True): + play("" * amp(v9), "2/flux") + with elif_(v8 == 15): + play("" * amp(v9), "2/flux") + with elif_(v8 == 20): + play("" * amp(v9), "2/flux") + with elif_(v8 == 25): + play("" * amp(v9), "2/flux") + with if_((v8 / 4) < 4): + wait(4, "1/acquisition") + with else_(): + wait((v8 / 4), "1/acquisition") + with if_((v8 / 4) < 4): + wait(4, "2/acquisition") + with else_(): + wait((v8 / 4), "2/acquisition") + measure( + ro1, + "1/acquisition", + None, + dual_demod.full("cos", "out1", "sin", "out2", v2), + dual_demod.full("minus_sin", "out1", "cos", "out2", v3), + ) + threshold = platform.config("1/acquisition").threshold + angle = platform.config("1/acquisition").iq_angle + assign( + v4, + Cast.to_int( + v2 * np.cos(angle) - v3 * np.sin(angle) > threshold + ), + ) + r1 = declare_stream() + save(v4, r1) + measure( + ro2, + "2/acquisition", + None, + dual_demod.full("cos", "out1", "sin", "out2", v5), + dual_demod.full("minus_sin", "out1", "cos", "out2", v6), + ) + threshold = platform.config("2/acquisition").threshold + angle = platform.config("2/acquisition").iq_angle + assign( + v7, + Cast.to_int( + v5 * np.cos(angle) - v6 * np.sin(angle) > threshold + ), + ) + r2 = declare_stream() + save(v7, r2) + wait( + 250, + ) + with stream_processing(): + r1.buffer(5).buffer(4).average().save(f"{ro1}_1/acquisition_shots") + r2.buffer(5).buffer(4).average().save(f"{ro2}_2/acquisition_shots") + + assert_qua_programs(experiment, target_experiment)