Get the notebook

# How to integrate Boulder Opal with QUA from Quantum Machines

Integrate Boulder Opal pulses directly into Quantum Machines hardware using the Q-CTRL QUA Python package

Boulder Opal enables you to design and benchmark noise-robust pulses, as well as characterize and tune up your quantum hardware with ease. This process becomes even simpler and more powerful when software and hardware are seamlessly integrated.

In this notebook we show how to interface Boulder Opal with QUA, a programming language developed by Quantum Machines that provides pulse-level control of their Operator-X (OPX) quantum devices. Using the qctrl-qua package, we show how to convert a pulse into an OPX-compatible format and to quickly generate the configurations required for executing experiments.

## Summary workflow

### 1. Define the QUA configuration

Before you can run any experiment, you first need to provide QUA with a thorough description of the quantum device. This “QUA configuration” is a Python dictionary containing instructions for configuring the QM hardware. It typically contains line calibrations, drive frequencies and pulse shapes for quantum gates. For more details, see the QUA examples in the QUA reference documentation.

### 2. Convert Q-CTRL pulses to QUA-compatible pulses

Now that you have a pre-existing QUA configuration, qm_config, you can take the Q-CTRL optimized pulse and add it to your QUA pulse library. For convenience, we will use add_pulse_to_config function from the qctrlqua package. This function accepts a pulse sampled at 1ns intervals and returns an appropriately-formatted Python dictionary that you can feed into the OPX. For more details, see the qctrl-qua reference documentation.

## Worked example: Export a Gaussian pulse to a QUA backend

In the following example, we will import a pulse from the libraries of pulses for Boulder Opal, as described in the reference documentation, and convert it into a QUA-compatible format. Although we use the example of a Gaussian pulse but the exact shape of the pulse we import is not important to illustrate the conversion.

Afterwards, the pulse is resampled to match the 1ns resolution of the hardware.

import numpy as np
import matplotlib.pyplot as plt

# Q-CTRL imports
from qctrl import Qctrl
import qctrlqua

from qctrlvisualizer import get_qctrl_style

plt.style.use(get_qctrl_style())

qctrl = Qctrl()

# Create Gaussian pulse.
total_rotation = np.pi  # Target rotation angle here is a \pi pulse.
omega_max = 2 * np.pi * 50e6  # Hz, this is the maximum Rabi rate in the system.

duration = total_rotation * 10 / omega_max / np.sqrt(2 * np.pi)

control_pulse = qctrl.pulses.gaussian_pulse(
amplitude=omega_max, drag=0.1 * duration, duration=duration
)

# Resample the pulse to match the
dt = 1e-9  # hardware time resolution
resampled = control_pulse.export_with_time_step(dt)

# Map the optimal pulse into input amplitudes for the hardware.
rabi_rate = 2 * np.pi * 300e6
qctrl_i, qctrl_q = np.real(resampled) / rabi_rate, np.imag(resampled) / rabi_rate

# Plot resulting arrays.
plt.figure(figsize=(10, 5))
plt.plot(qctrl_i, label="$I$")
plt.plot(qctrl_q, label="$Q$")
plt.xlabel("Time (ns)")
plt.ylabel("Voltage (V)")
plt.legend()
plt.show()


qm_config = {}
# Add the Q-CTRL robust pulse to the QUA configuration
pulse_name = "Q-CTRL"
channel_name = "drive_channel"  # corresponds to the drive channel name in qm_config
pulse_name, channel_name, qctrl_i, qctrl_q, qm_config
)

# Use the updated qm_config dictionary to run QUA on the hardware.


This notebook was run using the following package versions. It should also be compatible with newer versions of the Q-CTRL Python package.

Package version
Python 3.10.0
matplotlib 3.5.3
numpy 1.23.2
scipy 1.9.1
qctrl 19.4.0
qctrlcommons 17.1.3
qctrlqua 0.2.2
qctrltoolkit 1.7.0
qctrlvisualizer 3.4.0