Readout weight calibration¶
In this notebook, you will learn how to calibrate and use optimal integration weights to distinguish between qubits states in circuit QED.
This demonstration runs without connection to real qubits, assuming a loopback on the readout drive line directly into the readoud acquisition line. We emulate the measurement signals corresponding to different qubit states by two different measurement pulses, differing only by a phase.
0. General Imports and Definitions¶
0.1 Python Imports¶
import matplotlib.pyplot as plt
import numpy as np
from laboneq.analysis import calculate_integration_kernels_thresholds
# Helpers:
from laboneq.contrib.example_helpers.feedback_helper import (
create_calibration_experiment,
create_integration_verification_experiment,
state_emulation_pulse,
)
from laboneq.contrib.example_helpers.generate_example_datastore import (
generate_example_datastore,
get_first_named_entry,
)
# all LabOne Q functionality
from laboneq.simple import *
# Build an in-memory data store with device setup and qubit parameters for the
# example notebooks
dummy_db = generate_example_datastore(in_memory=True)
use_emulation = True
1. Device setup and calibration¶
1.1 Load a calibrated Device Setup and qubit object¶
my_setup = get_first_named_entry(
db=dummy_db,
name="6_tuneable_qubit_setup_shfsg_shfqa_hdawg_pqsc_calibrated",
)
my_qubit = my_setup.qubits[0]
q0 = my_setup.logical_signal_groups["q0"].logical_signals
q1 = my_setup.logical_signal_groups["q1"].logical_signals
1.2 Adapt setup calibration¶
In this notebook we are using a pulse played from a second measure line to emulate the qubit being in the excited state. In this case we want to have the same instrument settings for the two used measurement lines. Additionally, for the method of readout weight calibration demonstrated in this notebook, the acquire line should not be modulated, as the calculated readout weights already contain the software modulation by construction.
readout_weight_calibration = Calibration()
readout_weight_calibration["/logical_signal_groups/q1/measure_line"] = (
my_setup.get_calibration()["/logical_signal_groups/q0/measure_line"]
)
readout_weight_calibration["/logical_signal_groups/q0/acquire_line"] = (
my_setup.get_calibration()["/logical_signal_groups/q0/acquire_line"]
)
readout_weight_calibration["/logical_signal_groups/q0/acquire_line"].oscillator = None
# print(readout_weight_calibration)
my_setup.set_calibration(readout_weight_calibration)
# print(my_setup.get_calibration())
# create and connect to a LabOne Q session
my_session = Session(device_setup=my_setup)
my_session.connect(do_emulation=use_emulation)
2. Calibration of state discrimination¶
We determine the optimal integration weights by measuring traces of the qubit states and computing an integration kernel using the toolkit routines. We simulate different qubit responses by playing pulses with different phases and amplitudes on the readout line. We have to make sure that the traces are a multiple of 16 samples long.
2.1 Obtain traces¶
num_states = 2
experiments = [
create_calibration_experiment(
state_emulation_pulse=state_emulation_pulse(),
qubit_state=i,
measure_signal=l["measure_line"],
acquire_signal=q0["acquire_line"],
)
for i, l in enumerate([q0, q1])
]
traces = []
for exp in experiments:
res = my_session.run(exp)
trace = res.get_data("raw")
traces.append(trace[: (len(trace) // 16) * 16])
# In emulation mode, the 'acquired' traces are all identical. Consequently, the computation of the optimal
# discrimination weights will fail. Instead we 'patch' the traces with an artificial phase.
if use_emulation:
for i in range(num_states):
phase = np.exp(2j * np.pi * i / num_states)
traces[i] *= phase
2.2 Compute kernels¶
We only need the number of states minus 1 kernels, the additional kernel is computed on the device.
# Calculate and plot kernels
kernels, thresholds = calculate_integration_kernels_thresholds(traces)
for i, k in enumerate(kernels):
plt.plot(k.samples.real, k.samples.imag, ["ro-", "gx-", "b+-"][i], alpha=0.2)
2.3 Plot acquired results after readout optimization¶
When using the optimized kernels calculated in the last step, the integration results for ground and excited states are rotated so that a projection onto the real axis allows for later discrimination with a real-valued threshold.
my_exp = create_integration_verification_experiment(
measure_lines=[q0["measure_line"], q1["measure_line"]],
acquire_line=q0["acquire_line"],
kernels=kernels,
state_emulation_pulse=state_emulation_pulse,
thresholds=thresholds,
)
integration_results = my_session.run(my_exp)
s0 = integration_results.get_data("data_0").real
s1 = integration_results.get_data("data_1").real
plt.plot(s0, ".b")
plt.plot(s1, ".r")