Hardware backends#

The compiler for a hardware backend for Quantify takes a Schedule defined on the Quantum-device layer and a HardwareCompilationConfig. The InstrumentCoordinatorComponent for the hardware runs the operations within the schedule and can return the acquired data via the InstrumentCoordinator method retrieve_acquisition(). To that end, it generally consists of the following components:

  1. CompilationNodes that generate the compiled Schedule from the device-level Schedule and a HardwareCompilationConfig. This compiled Schedule generally consists of (1) the hardware level instructions that should be executed and (2) instrument settings that should be applied on the hardware.

  2. A backend-specific HardwareCompilationConfig that contains the information that is used to compile from the quantum-device layer to the control-hardware layer (see Compilation). This generally consists of:

    • A HardwareDescription (e.g., the available channels, IP addresses, etc.).

    • The Connectivity between the quantum-device ports and the control-hardware ports.

    • The HardwareOptions that includes the specific settings that are supported by the backend (e.g., the gain on an output port).

    • A list of SimpleNodeConfigs, which specifies the CompilationNodes that should be executed to compile the Schedule to the hardware-specific instructions.

  3. InstrumentCoordinatorComponents that send compiled instructions to the instruments, retrieve data, and convert the acquired data into a standardized, backend independent dataset (see Acquisition protocols).

Architecture overview#

The interfaces between Quantify and a hardware backend are illustrated in the following diagram:

        graph TD;
    user[User]

    subgraph Quantify
        ScheduleGettable
        QuantumDevice
        QuantifyCompiler
        InstrumentCoordinator
    
        QuantumDevice -->|CompilationConfig| ScheduleGettable
        ScheduleGettable -->|Schedule\n CompilationConfig| QuantifyCompiler
        InstrumentCoordinator -->|Raw Dataset| ScheduleGettable
        QuantifyCompiler -->|CompiledSchedule| ScheduleGettable
        ScheduleGettable -->|CompiledSchedule| InstrumentCoordinator
    end

    subgraph QuantifyBackend
        InstrumentCoordinatorComponent
        CompilationModule[Compilation Module] -->|CompilationNodes| HardwareCompilationConfig
        HardwareCompilationConfig
    end

    subgraph Hardware
        drivers[Hardware-specific drivers]
        instruments[Physical instruments]
    end

    user -->|Schedule| ScheduleGettable
    user -->|Device Description, Hardware Description,\n Connectivity, Hardware Options| QuantumDevice
    ScheduleGettable -->|Processed Dataset| user

    InstrumentCoordinatorComponent -->|Partial Raw Dataset| InstrumentCoordinator
    QuantumDevice -->|Hardware Description\n Connectivity\n Hardware Options| HardwareCompilationConfig
    HardwareCompilationConfig -->|Validated HardwareCompilationConfig| QuantumDevice
    
    InstrumentCoordinator -->|Compiled Instructions| InstrumentCoordinatorComponent

    InstrumentCoordinatorComponent -->|Compiled Instructions| drivers
    drivers -->|Data points| InstrumentCoordinatorComponent

    drivers <--> instruments
    

Experiment flow#

See also

This diagram is similar to the the one in the Experiment flow section in the User Guide, but provides more details on the interfaces between the objects.

        sequenceDiagram
    participant SG as ScheduleGettable
    participant QuantumDevice
    participant QuantifyCompiler
    participant IC as InstrumentCoordinator
    SG->>+QuantumDevice: generate_compilation_config()
    QuantumDevice-->>-SG: CompilationConfig
    SG->>+QuantifyCompiler: compile(Schedule, CompilationConfig)
    QuantifyCompiler-->>-SG: CompiledSchedule
    SG->>+IC: prepare(CompiledSchedule)
    SG->>IC: start()
    SG->>IC: retrieve_acquisition()
    IC-->>-SG: RawDataset	
    

Fig. 6 Diagram of the experiment flow in Quantify. Dotted lines represent the output and the non-dotted lines represent the input.#

In the above diagram several methods are called. The get() method of the ScheduleGettable executes the sequence and returns the data from the acquisitions in the Schedule. The generate_compilation_config() method of the QuantumDevice generates a CompilationConfig that is used to compile the Schedule within the ScheduleGettable. The compile() method of the QuantifyCompiler compiles the Schedule to the hardware-specific instructions. The prepare(), start(), and retrieve_acquisition() methods of the InstrumentCoordinator are used to prepare the hardware, start the acquisition, and retrieve the acquired data, respectively.

Developing a new backend#

To develop a new backend, the following approach is advised:

  1. Implement a custom Gettable that takes a set of instructions in the form that the hardware can directly execute. The compiled schedule that the backend should return once developed should therefore be the same as the instructions that this Gettable accepts. This enables testing of the InstrumentCoordinatorComponents without having to worry about the compilation of the Schedule to the hardware.

  2. Implement InstrumentCoordinatorComponents for each of the instruments (starting with the instrument with the acquisition channel to enable testing).

  3. Implement CompilationNodes to generate hardware instructions from a Schedule and a CompilationConfig.

  4. Integrate the (previously developed + tested) QuantifyCompiler and InstrumentCoordinatorComponents with the ScheduleGettable.

  5. Optionally test the ScheduleGettable with the MeasurementControl.

Mock device#

To illustrate how to do this, let’s consider a basic example of an interface with a mock hardware device for which we would like to create a corresponding Quantify hardware backend. Our mock device resembles a readout module that can play and simultaneously acquire waveforms through the “TRACE” instruction. It is described by the following class:

class MockReadoutModule(name: str, sampling_rate: float = 1000000000.0, gain: float = 1.0)[source]

Mock readout module that just supports “TRACE” instruction.

The device has three methods, execute, upload_waveforms, and upload_instructions, and a property sampling_rate.

In our endeavor to create a backend between Quantify and the mock device we start by creating a mock readout module so we can show its functionality:

import numpy as np
from quantify_scheduler.backends.mock.mock_rom import MockReadoutModule

rom = MockReadoutModule(name="mock_rom")

We first upload two waveforms (defined on a 1 ns grid) to the readout module:

intermodulation_freq = 1e8  # 100 MHz
amplitude = 0.5 # 0.5 V
duration = 1e-7  # 100 ns

time_grid = np.arange(0, duration, 1e-9)
complex_trace = np.exp(2j * np.pi * intermodulation_freq * time_grid)

wfs = {
    "I": complex_trace.real,
    "Q": complex_trace.imag
}
rom.upload_waveforms(wfs)

The mock readout module samples the uploaded waveforms and applies a certain gain to the acquired data. The sampling rate and gain can be set as follows:

rom.sampling_rate = 1.5e9  # 1.5 GSa/s
rom.gain = 2.0

The mock readout module takes a list of strings as instructions input:

rom.upload_instructions(["TRACE"])

We can now execute the instructions on the readout module:

rom.execute()

The data that is returned by our mock readout module is a dictionary containing the acquired I and Q traces:

import matplotlib.pyplot as plt

data = rom.get_results()
plt.plot(data[0])
plt.plot(data[1])
plt.show()
../_images/35843925448cd03f9c9d2d6e99d77c454ba91f2cc7ae9436b24db3c8bc1283d1.png

The goal is now to implement a backend that can compile and execute a Schedule that consists of a single trace acquisition and returns a Quantify dataset.

1. Implement a custom Gettable#

A good first step to integrating this mock readout module with Quantify is to implement a custom Gettable that takes a set of instructions and waveforms that can be readily executed on the hardware. This Gettable can then be used to retrieve the executed waveforms from the hardware. This enables testing of the InstrumentCoordinatorComponents without having to worry about the compilation of the Schedule to the hardware.

class MockROMGettable(mock_rom: MockReadoutModule, waveforms: dict[str, quantify_scheduler.structure.types.NDArray], instructions: list[str], sampling_rate: float = 1000000000.0, gain: float = 1.0)[source]

Mock readout module gettable.

We check that this works as expected:

from quantify_scheduler.backends.mock.mock_rom import MockROMGettable

mock_rom_gettable = MockROMGettable(mock_rom=rom, waveforms=wfs, instructions=["TRACE"], sampling_rate=1.5e9, gain=2.0)
data = mock_rom_gettable.get()
plt.plot(data[0])
plt.plot(data[1])
plt.show()
../_images/35843925448cd03f9c9d2d6e99d77c454ba91f2cc7ae9436b24db3c8bc1283d1.png

From the plot, we observe that the waveforms are the same as what was sent into the MockReadoutModule.

2. Implement InstrumentCoordinatorComponent(s)#

Within Quantify, the InstrumentCoordinatorComponents are responsible for sending compiled instructions to the instruments, retrieving data, and converting the acquired data into a quantify-compatible Dataset (see Acquisition protocols). The InstrumentCoordinatorComponents are instrument-specific and should be based on the InstrumentCoordinatorComponentBase class.

It is convenient to wrap all settings that are required to prepare the instrument in a single DataStructure, in our example we take care of this in the MockROMAcquisitionConfig and the settings for the mock readout module can be set via the MockROMSettings class using the prepare method of the MockROMInstrumentCoordinatorComponent. The start method is used to start the acquisition and the retrieve_acquisition method is used to retrieve the acquired data:

class MockROMAcquisitionConfig(/, **data: Any)[source]

Acquisition configuration for the mock readout module.

This information is used in the instrument coordinator component to convert the acquired data to an xarray dataset.

acq_protocols: dict[int, str][source]
bin_mode: quantify_scheduler.enums.BinMode[source]
n_acquisitions: int[source]
class MockROMSettings(/, **data: Any)[source]

Settings that can be uploaded to the mock readout module.

acq_config: MockROMAcquisitionConfig[source]
gain: float = 1.0[source]
instructions: list[str][source]
sampling_rate: float = 1000000000.0[source]
waveforms: dict[str, quantify_scheduler.structure.types.NDArray][source]

We can now implement the InstrumentCoordinatorComponent for the mock readout module:

class MockROMInstrumentCoordinatorComponent(mock_rom: MockReadoutModule)[source]

Mock readout module instrument coordinator component.

get_hardware_log(compiled_schedule: quantify_scheduler.schedules.schedule.CompiledSchedule) None[source]

Return the hardware log.

prepare(options: MockROMSettings) None[source]

Upload the settings to the ROM.

retrieve_acquisition() xarray.Dataset[source]

Get the acquired data and return it as an xarray dataset.

start() None[source]

Execute the sequence.

stop() None[source]

Stop the execution.

wait_done(timeout_sec: int = 10) None[source]

Wait until the execution is done.

acq_config = None[source]
property is_running: bool[source]

Returns if the InstrumentCoordinator component is running.

The property is_running is evaluated each time it is accessed. Example:

while my_instrument_coordinator_component.is_running:
    print('running')
Returns:

The components’ running state.

rom[source]

Now we can control the mock readout module through the InstrumentCoordinatorComponent:

from quantify_scheduler.backends.mock.mock_rom import MockROMInstrumentCoordinatorComponent, MockROMSettings, MockROMAcquisitionConfig

rom_icc = MockROMInstrumentCoordinatorComponent(mock_rom=rom)
settings = MockROMSettings(
    waveforms=wfs,
    instructions=["TRACE"],
    sampling_rate=1.5e9,
    gain=2.0,
    acq_config = MockROMAcquisitionConfig(
        n_acquisitions=1,
        acq_protocols= {0: "Trace"},
        bin_mode="average",
    )
)

rom_icc.prepare(settings)
rom_icc.start()
dataset = rom_icc.retrieve_acquisition()

The acquired data is:

dataset
<xarray.Dataset> Size: 2kB
Dimensions:  (acq_index_0: 1, trace_index_0: 100)
Dimensions without coordinates: acq_index_0, trace_index_0
Data variables:
    0        (acq_index_0, trace_index_0) complex128 2kB (2+0j) ... (1.618033...

3. Implement CompilationNodes#

The next step is to implement a QuantifyCompiler that generates the hardware instructions from a Schedule and a CompilationConfig. The QuantumDevice class already includes the generate_compilation_config() method that generates a CompilationConfig that can be used to perform the compilation from the quantum-circuit layer to the quantum-device layer. For the backend-specific compiler, we need to add a CompilationNode and an associated HardwareCompilationConfig that contains the information that is used to compile from the quantum-device layer to the control-hardware layer (see Compilation).

First, we define a DataStructure that contains the information that is required to compile to the mock readout module:

class MockROMHardwareCompilationConfig(/, **data: Any)[source]

Information required to compile a schedule to the control-hardware layer.

From a point of view of Compilation this information is needed to convert a schedule defined on a quantum-device layer to compiled instructions that can be executed on the control hardware.

This datastructure defines the overall structure of a HardwareCompilationConfig. Specific hardware backends should customize fields within this structure by inheriting from this class and specifying their own “config_type”, see e.g., QbloxHardwareCompilationConfig, ZIHardwareCompilationConfig.

compilation_passes: list[quantify_scheduler.backends.graph_compilation.SimpleNodeConfig][source]

The list of compilation nodes that should be called in succession to compile a schedule to instructions for the control hardware.

config_type: type[MockROMHardwareCompilationConfig] = None[source]

A reference to the HardwareCompilationConfig DataStructure for the Mock ROM backend.

hardware_description: dict[str, MockROMDescription][source]

Datastructure describing the control hardware instruments in the setup and their high-level settings.

hardware_options: MockROMHardwareOptions[source]

The HardwareOptions used in the compilation from the quantum-device layer to the control-hardware layer.

We then implement a CompilationNode that uses a Schedule and a MockROMHardwareCompilationConfig and generates the hardware-specific instructions:

hardware_compile(schedule: quantify_scheduler.schedules.schedule.Schedule, config: quantify_scheduler.backends.graph_compilation.CompilationConfig) quantify_scheduler.schedules.schedule.Schedule[source]

Compile the schedule to the mock ROM.

To test the implemented CompilationNode, we first create a raw trace Schedule:

from quantify_scheduler.schedules.trace_schedules import trace_schedule

sched = trace_schedule(
    pulse_amp=0.1,
    pulse_duration=1e-7,
    pulse_delay=0,
    frequency=3e9,
    acquisition_delay=0,
    integration_time=2e-7,
    port="q0:res",
    clock="q0.ro"
)
sched.plot_circuit_diagram()
(<Figure size 1000x100 with 1 Axes>,
 <Axes: title={'center': 'Raw trace acquisition'}>)
../_images/74930e903d36491ab5aefaea48b4ff9fa64d7ff69ad674e7d41cee8a15df90d1.png

Currently, the QuantumDevice is responsible for generating the full CompilationConfig. We therefore create an empty QuantumDevice:

from quantify_scheduler.device_under_test.quantum_device import QuantumDevice

quantum_device = QuantumDevice("quantum_device")

We supply the hardware compilation config input to the QuantumDevice, which will be used to generate the full CompilationConfig:

from quantify_scheduler.backends.mock.mock_rom import hardware_compilation_config as hw_cfg
quantum_device.hardware_config(hw_cfg)

The QuantumDevice can now generate the full CompilationConfig:

from rich import print

print(quantum_device.generate_compilation_config())
SerialCompilationConfig(
    name='QuantumDevice-generated SerialCompilationConfig',
    version='v0.6',
    keep_original_schedule=True,
    backend=<class 'quantify_scheduler.backends.graph_compilation.SerialCompiler'>,
    device_compilation_config=DeviceCompilationConfig(
        clocks={},
        elements={},
        edges={},
        scheduling_strategy='asap',
        compilation_passes=[
            SimpleNodeConfig(
                name='circuit_to_device',
                compilation_func=<function compile_circuit_to_device_with_config_validation at 0x7bcc71789550>
            ),
            SimpleNodeConfig(
                name='set_pulse_and_acquisition_clock',
                compilation_func=<function set_pulse_and_acquisition_clock at 0x7bcc71789af0>
            ),
            SimpleNodeConfig(
                name='process_compensation_pulses',
                compilation_func=<function process_compensation_pulses at 0x7bcc71791c10>
            ),
            SimpleNodeConfig(
                name='determine_absolute_timing',
                compilation_func=<function _determine_absolute_timing at 0x7bcc7186df70>
            )
        ]
    ),
    hardware_compilation_config=MockROMHardwareCompilationConfig(
        config_type=<class 'quantify_scheduler.backends.mock.mock_rom.MockROMHardwareCompilationConfig'>,
        hardware_description={
            'mock_rom': MockROMDescription(instrument_type='Mock readout module', sampling_rate=1500000000.0)
        },
        hardware_options=MockROMHardwareOptions(
            crosstalk=None,
            latency_corrections=None,
            distortion_corrections=None,
            modulation_frequencies={'q0:res-q0.ro': ModulationFrequencies(interm_freq=100000000.0, lo_freq=None)},
            mixer_corrections=None,
            gain={'q0:res-q0.ro': 2.0}
        ),
        connectivity=Connectivity(graph=<quantify_scheduler.structure.types.Graph object at 0x7bcc7185af70>),
        compilation_passes=[
            SimpleNodeConfig(
                name='mock_rom_hardware_compile',
                compilation_func=<function hardware_compile at 0x7bcc74cab4c0>
            )
        ]
    ),
    debug_mode=False
)

We can now compile the Schedule to settings for the mock readout module:

from quantify_scheduler.backends.graph_compilation import SerialCompiler

compiler = SerialCompiler(name="compiler", quantum_device=quantum_device)
compiled_schedule = compiler.compile(schedule=sched)

We can now check that the compiled settings are correct:

print(compiled_schedule.compiled_instructions)
{
    'mock_rom': MockROMSettings(
        waveforms={
            '6411970002789515056_I': NDArray([ 0.1       ,  0.09135455,  0.06691306,  0.0309017 , -0.01045285,
         -0.05      , -0.0809017 , -0.09781476, -0.09781476, -0.0809017 ,
         -0.05      , -0.01045285,  0.0309017 ,  0.06691306,  0.09135455,
          0.1       ,  0.09135455,  0.06691306,  0.0309017 , -0.01045285,
         -0.05      , -0.0809017 , -0.09781476, -0.09781476, -0.0809017 ,
         -0.05      , -0.01045285,  0.0309017 ,  0.06691306,  0.09135455,
          0.1       ,  0.09135455,  0.06691306,  0.0309017 , -0.01045285,
         -0.05      , -0.0809017 , -0.09781476, -0.09781476, -0.0809017 ,
         -0.05      , -0.01045285,  0.0309017 ,  0.06691306,  0.09135455,
          0.1       ,  0.09135455,  0.06691306,  0.0309017 , -0.01045285,
         -0.05      , -0.0809017 , -0.09781476, -0.09781476, -0.0809017 ,
         -0.05      , -0.01045285,  0.0309017 ,  0.06691306,  0.09135455,
          0.1       ,  0.09135455,  0.06691306,  0.0309017 , -0.01045285,
         -0.05      , -0.0809017 , -0.09781476, -0.09781476, -0.0809017 ,
         -0.05      , -0.01045285,  0.0309017 ,  0.06691306,  0.09135455,
          0.1       ,  0.09135455,  0.06691306,  0.0309017 , -0.01045285,
         -0.05      , -0.0809017 , -0.09781476, -0.09781476, -0.0809017 ,
         -0.05      , -0.01045285,  0.0309017 ,  0.06691306,  0.09135455,
          0.1       ,  0.09135455,  0.06691306,  0.0309017 , -0.01045285,
         -0.05      , -0.0809017 , -0.09781476, -0.09781476, -0.0809017 ,
         -0.05      , -0.01045285,  0.0309017 ,  0.06691306,  0.09135455,
          0.1       ,  0.09135455,  0.06691306,  0.0309017 , -0.01045285,
         -0.05      , -0.0809017 , -0.09781476, -0.09781476, -0.0809017 ,
         -0.05      , -0.01045285,  0.0309017 ,  0.06691306,  0.09135455,
          0.1       ,  0.09135455,  0.06691306,  0.0309017 , -0.01045285,
         -0.05      , -0.0809017 , -0.09781476, -0.09781476, -0.0809017 ,
         -0.05      , -0.01045285,  0.0309017 ,  0.06691306,  0.09135455,
          0.1       ,  0.09135455,  0.06691306,  0.0309017 , -0.01045285,
         -0.05      , -0.0809017 , -0.09781476, -0.09781476, -0.0809017 ,
         -0.05      , -0.01045285,  0.0309017 ,  0.06691306,  0.09135455]),
            '6411970002789515056_Q': NDArray([ 0.00000000e+00,  4.06736643e-02,  7.43144825e-02,
          9.51056516e-02,  9.94521895e-02,  8.66025404e-02,
          5.87785252e-02,  2.07911691e-02, -2.07911691e-02,
         -5.87785252e-02, -8.66025404e-02, -9.94521895e-02,
         -9.51056516e-02, -7.43144825e-02, -4.06736643e-02,
         -2.44929360e-17,  4.06736643e-02,  7.43144825e-02,
          9.51056516e-02,  9.94521895e-02,  8.66025404e-02,
          5.87785252e-02,  2.07911691e-02, -2.07911691e-02,
         -5.87785252e-02, -8.66025404e-02, -9.94521895e-02,
         -9.51056516e-02, -7.43144825e-02, -4.06736643e-02,
         -4.89858720e-17,  4.06736643e-02,  7.43144825e-02,
          9.51056516e-02,  9.94521895e-02,  8.66025404e-02,
          5.87785252e-02,  2.07911691e-02, -2.07911691e-02,
         -5.87785252e-02, -8.66025404e-02, -9.94521895e-02,
         -9.51056516e-02, -7.43144825e-02, -4.06736643e-02,
         -4.28750176e-16,  4.06736643e-02,  7.43144825e-02,
          9.51056516e-02,  9.94521895e-02,  8.66025404e-02,
          5.87785252e-02,  2.07911691e-02, -2.07911691e-02,
         -5.87785252e-02, -8.66025404e-02, -9.94521895e-02,
         -9.51056516e-02, -7.43144825e-02, -4.06736643e-02,
         -9.79717439e-17,  4.06736643e-02,  7.43144825e-02,
          9.51056516e-02,  9.94521895e-02,  8.66025404e-02,
          5.87785252e-02,  2.07911691e-02, -2.07911691e-02,
         -5.87785252e-02, -8.66025404e-02, -9.94521895e-02,
         -9.51056516e-02, -7.43144825e-02, -4.06736643e-02,
         -4.77736048e-16,  4.06736643e-02,  7.43144825e-02,
          9.51056516e-02,  9.94521895e-02,  8.66025404e-02,
          5.87785252e-02,  2.07911691e-02, -2.07911691e-02,
         -5.87785252e-02, -8.66025404e-02, -9.94521895e-02,
         -9.51056516e-02, -7.43144825e-02, -4.06736643e-02,
         -8.57500352e-16,  4.06736643e-02,  7.43144825e-02,
          9.51056516e-02,  9.94521895e-02,  8.66025404e-02,
          5.87785252e-02,  2.07911691e-02, -2.07911691e-02,
         -5.87785252e-02, -8.66025404e-02, -9.94521895e-02,
         -9.51056516e-02, -7.43144825e-02, -4.06736643e-02,
         -8.81993288e-16,  4.06736643e-02,  7.43144825e-02,
          9.51056516e-02,  9.94521895e-02,  8.66025404e-02,
          5.87785252e-02,  2.07911691e-02, -2.07911691e-02,
         -5.87785252e-02, -8.66025404e-02, -9.94521895e-02,
         -9.51056516e-02, -7.43144825e-02, -4.06736643e-02,
         -1.95943488e-16,  4.06736643e-02,  7.43144825e-02,
          9.51056516e-02,  9.94521895e-02,  8.66025404e-02,
          5.87785252e-02,  2.07911691e-02, -2.07911691e-02,
         -5.87785252e-02, -8.66025404e-02, -9.94521895e-02,
         -9.51056516e-02, -7.43144825e-02, -4.06736643e-02,
         -9.30979160e-16,  4.06736643e-02,  7.43144825e-02,
          9.51056516e-02,  9.94521895e-02,  8.66025404e-02,
          5.87785252e-02,  2.07911691e-02, -2.07911691e-02,
         -5.87785252e-02, -8.66025404e-02, -9.94521895e-02,
         -9.51056516e-02, -7.43144825e-02, -4.06736643e-02])
        },
        instructions=['TRACE_input0'],
        sampling_rate=1500000000.0,
        gain=2.0,
        acq_config=MockROMAcquisitionConfig(
            n_acquisitions=1,
            acq_protocols={0: 'Trace'},
            bin_mode=<BinMode.AVERAGE: 'average'>
        )
    )
}

4. Integration with the ScheduleGettable#

The ScheduleGettable integrates the InstrumentCoordinatorComponents with the QuantifyCompiler to provide a straightforward interface for the user to execute a Schedule on the hardware and retrieve the acquired data. The ScheduleGettable takes a QuantumDevice and a Schedule as input and returns the data from the acquisitions in the Schedule.

Note

This is also mainly a validation step. If we did everything correctly, no new development should be needed in this step.

We first instantiate the InstrumentCoordinator, add the InstrumentCoordinatorComponent for the mock readout module, and add a reference to the InstrumentCoordinator to the QuantumDevice:

from quantify_scheduler.instrument_coordinator.instrument_coordinator import InstrumentCoordinator

ic = InstrumentCoordinator("IC")
ic.add_component(rom_icc)
quantum_device.instr_instrument_coordinator(ic.name)

We then create a ScheduleGettable:

from quantify_scheduler.gettables import ScheduleGettable
from quantify_scheduler.schedules.trace_schedules import trace_schedule

schedule_gettable = ScheduleGettable(
    quantum_device=quantum_device,
    schedule_function=trace_schedule,
    schedule_kwargs=dict(
        pulse_amp=0.1,
        pulse_duration=1e-7,
        pulse_delay=0,
        frequency=3e9,
        acquisition_delay=0,
        integration_time=2e-7,
        port="q0:res",
        clock="q0.ro"
    ),
    batched=True
)
I, Q = schedule_gettable.get()

We can now plot the acquired data:

plt.plot(I)
plt.plot(Q)
plt.show()
../_images/534f1f0e412ce40d452d8d75176d7bdc110afb79b2af98734745619c4d8dc962.png

5. Integration with the MeasurementControl#

Note

This is mainly a validation step. If we did everything correctly, no new development should be needed in this step.

Example of a MeasurementControl that sweeps the pulse amplitude in the trace ScheduleGettable:

from quantify_core.measurement.control import MeasurementControl
from quantify_core.data.handling import set_datadir, to_gridded_dataset

set_datadir()

meas_ctrl = MeasurementControl(name="meas_ctrl")

from qcodes.parameters import ManualParameter

amp_par = ManualParameter(name="trace_amplitude")
amp_par.batched = False
sample_par = ManualParameter("sample", label="Sample time", unit="s")
sample_par.batched = True

amps = [0.1,0.2,0.3]
integration_time = 2e-7
sampling_rate = quantum_device.generate_hardware_compilation_config().hardware_description["mock_rom"].sampling_rate

schedule_gettable = ScheduleGettable(
    quantum_device=quantum_device,
    schedule_function=trace_schedule,
    schedule_kwargs=dict(
        pulse_amp=amp_par,
        pulse_duration=1e-7,
        pulse_delay=0,
        frequency=3e9,
        acquisition_delay=0,
        integration_time=integration_time,
        port="q0:res",
        clock="q0.ro"
    ),
    batched=True
)
meas_ctrl.settables([amp_par, sample_par])
# problem: we don't necessarily know the size of the returned traces (solved by using xarray?)
# workaround: use sample_par ManualParameter that predicts this using the sampling rate
meas_ctrl.setpoints_grid([np.array(amps), np.arange(0, integration_time, 1 / sampling_rate)])
meas_ctrl.gettables(schedule_gettable)

data = meas_ctrl.run()
gridded_data = to_gridded_dataset(data)
Data will be saved in:
/root/quantify-data
Starting batched measurement...
Iterative settable(s) [outer loop(s)]:
	 trace_amplitude 
Batched settable(s):
	 sample 
Batch size limit: 900

Data processing and plotting:

import xarray as xr

magnitude_data = abs(gridded_data.y0 + 1j * gridded_data.y1)
phase_data = xr.DataArray(np.angle(gridded_data.y0 + 1j * gridded_data.y1))
magnitude_data.plot()
<matplotlib.collections.QuadMesh at 0x7bcc7156b220>
../_images/45fbc05d034a3e9fe9e8dc19dbd2fc8e1d2e031ab34afbfcff5f2ba61e8e13ca.png
phase_data.plot()
<matplotlib.collections.QuadMesh at 0x7bcc7010f9d0>
../_images/03963eb0d367b63c77ac0f0825750bef196883a57410a4ffc12fa106941b4087.png