Time-Resolved Resonator Photon Number¶
Prerequisites¶
This guide assumes you have a configured DeviceSetup as well as a tuned-up qubit setup with assigned parameters. Please see our tutorials if you need to create your setup and qubits for the first time.
You can run this notebook on real hardware in the lab. However, if you don't have the hardware at your disposal, you can also run the notebook "as is" using an emulated session (see below).
If you are just getting started with the LabOne Q Applications Library, please don't hesitate to reach out to us at info@zhinst.com.
Background¶
In this guide, you'll conduct a measurement to observe the time-resolved photon number in a resonator during qubit readout, when the qubit is in it's ground state. This will be achieved using the resonator_photons_time_resolved experiment available in the LabOne Q Applications Library. To know the photon number during the readout can be essential, especially when trying to optimize the readout. The physical background behind this experiment is tied to the shift in the qubit frequency in response to the photon number $n$ accumulated in the resonator by the readout pulse, as expressed by the equation:
$$\Delta\omega_\text{ge}=-2\chi n.$$
Here, $\chi$ represents the $\chi$-shift, a parameter that determines the shift in the resonator frequency based on the qubit state. Due to the resonator's finite linewidth $\kappa$, it undergoes a process of photon accumulation and depletion, known as ring up and ring down. The evolution of the resonator photons over time can be analyzed by examining the time-dependent shift in the qubit's frequency:
$$\Delta\omega_\text{ge}(t)=-2\chi n(t).$$
In this experiment, you will perform a time-resolved qubit spectroscopy by applying a short spectroscopy pulse. This pulse needs to be significantly shorter than both the pulse under investigation (inspect pulse) and the resonator's dynamic timescale, ensuring proper resolution of the time-dependent photon number. Additionally, the spectroscopy pulse frequency is swept to resolve changes in the qubit frequency.
The spectroscopy pulse is applied at various time positions $\tau$ across the inspect pulse to examine its impact on the resonator photons over time, as indicated by its effect on the qubit's frequency. A second readout pulse is necessary to measure the spectroscopy signal. The pulse sequence for this procedure is illustrated below.
The time-resolved spectroscopy experiment measuring the qubit's frequency shift during readout $\Delta\omega_\text{ge}(t)$ can be used to determine the time resolved photon number by rearranged for $n(t)$:
$$n(t)=-\frac{\Delta\omega_\text{ge}(t)}{2\chi}.$$
This experiment will result in data, as demonstrated in the plot below, recorded on a qubit chip from Prof. Stefan Filipp's group at the Walther-Meissner-Institute in Garching.
This plot illustrates the number of photons present in the resonator at various times during the inspect pulse, averaged over the duration of the short spectroscopy pulse.
Imports¶
You'll start by importing laboneq.simple.
from laboneq.simple import *
Define your experimental setup¶
Let's define our experimental setup. We will need:
a set of TunableTransmonOperations
a QPU
Here, we will be brief. We will mainly provide the code to obtain these objects. To learn more, check out these other tutorials:
We will use three TunableTransmonQubits in this guide. Change this number to the one describing your setup.
number_of_qubits = 3
DeviceSetup¶
This guide requires a setup that can drive and readout tunable transmon qubits. Your setup could contain an SHFQC+ instrument, or an SHFSG and an SHFQA instruments. Here, we will use an SHFQC+ with 6 signal generation channels and a PQSC.
If you have used LabOne Q before and already have a DeviceSetup for your setup, you can reuse that.
If you do not have a DeviceSetup, you can create one using the code below. Just change the device numbers to the ones in your rack and adjust any other input parameters as needed.
# Setting get_zsync=True below, automatically detects the zsync ports of the PQCS that
# are used by the other instruments in this descriptor.
# Here, we are not connected to instruments, so we set this flag to False.
from laboneq.contrib.example_helpers.generate_descriptor import generate_descriptor
descriptor = generate_descriptor(
pqsc=["DEV10001"],
shfqc_6=["DEV12001"],
number_data_qubits=number_of_qubits,
multiplex=True,
number_multiplex=number_of_qubits,
include_cr_lines=False,
get_zsync=False, # set to True when at a real setup
ip_address="localhost",
)
setup = DeviceSetup.from_descriptor(descriptor, "localhost")
Qubits¶
We will generate a TunableTransmonQubits from the logical signal groups in our DeviceSetup. The name of the logical signal group q0 will be the UID of the qubit. Moreover, the qubit will have the same logical signal line as the one of the logical signal group in the DeviceSetup.
from laboneq_applications.qpu_types.tunable_transmon import TunableTransmonQubit
qubits = TunableTransmonQubit.from_device_setup(setup)
for q in qubits:
print("-------------")
print("Qubit UID:", q.uid)
print("Qubit logical signals:")
for sig, lsg in q.signals.items():
print(f" {sig:<10} ('{lsg:>10}')")
Configure the qubit parameters to reflect the properties of the qubits on your QPU using the following code:
for q in qubits:
q.parameters.ge_drive_pulse["sigma"] = 0.25
q.parameters.readout_amplitude = 0.5
q.parameters.reset_delay_length = 1e-6
q.parameters.readout_range_out = -25
q.parameters.readout_lo_frequency = 7.4e9
qubits[0].parameters.drive_lo_frequency = 6.4e9
qubits[0].parameters.resonance_frequency_ge = 6.3e9
qubits[0].parameters.resonance_frequency_ef = 6.0e9
qubits[0].parameters.readout_resonator_frequency = 7.0e9
qubits[1].parameters.drive_lo_frequency = 6.4e9
qubits[1].parameters.resonance_frequency_ge = 6.5e9
qubits[1].parameters.resonance_frequency_ef = 6.3e9
qubits[1].parameters.readout_resonator_frequency = 7.3e9
qubits[2].parameters.drive_lo_frequency = 6.0e9
qubits[2].parameters.resonance_frequency_ge = 5.8e9
qubits[2].parameters.resonance_frequency_ef = 5.6e9
qubits[2].parameters.readout_resonator_frequency = 7.2e9
Quantum Operations¶
Create the set of TunableTransmonOperations:
from laboneq_applications.qpu_types.tunable_transmon import TunableTransmonOperations
qops = TunableTransmonOperations()
QPU¶
Create the QPU object from the qubits and the quantum operations
from laboneq.dsl.quantum import QPU
qpu = QPU(qubits, quantum_operations=qops)
Alternatively, load from a file¶
If you you already have a DeviceSetup and a QPU stored in .json files, you can simply load them back using the code below:
from laboneq import serializers
setup = serializers.load(full_path_to_device_setup_file)
qpu = serializers.load(full_path_to_qpu_file)
qubits = qpu.quantum_elements
qops = qpu.quantum_operations
Connect to Session¶
session = Session(setup)
session.connect(do_emulation=True) # do_emulation=False when at a real setup
Create a FolderStore for Saving Data¶
The experiment Workflows can automatically save the inputs and outputs of all their tasks to the folder path we specify when instantiating the FolderStore. Here, we choose the current working directory.
# import FolderStore from the `workflow` namespace of LabOne Q, which was imported
# from `laboneq.simple`
from pathlib import Path
folder_store = workflow.logbook.FolderStore(Path.cwd())
We disable saving in this guide. To enable it, simply run folder_store.activate().
folder_store.deactivate()
Optional: Configure the LoggingStore¶
You can also activate/deactivate the LoggingStore, which is used for displaying the Workflow logging information in the notebook; see again the tutorial on Recording Experiment Workflow Results for details.
Displaying the Workflow logging information is activated by default, but here we deactivate it to shorten the outputs, which are not very meaningful in emulation mode.
We recommend that you do not deactivate the Workflow logging in practice.
from laboneq.workflow.logbook import LoggingStore
logging_store = LoggingStore()
logging_store.deactivate()
Running the Experiment Workflow¶
You'll now instantiate the experiment workflow and run it. For more details on what experiment workflows are and what tasks they execute, see the Experiment Workflows tutorial.
You'll start by importing numpy, the resonator_photons_time_resolved experiment workflow from laboneq_applications, as well as plot_simulation for inspecting the experiment sequence.
import numpy as np
from laboneq.contrib.example_helpers.plotting.plot_helpers import plot_simulation
from laboneq_applications.contrib.experiments import resonator_photons_time_resolved
Let's first create the options class for the resonator_photons_time_resolved experiment and inspect it using the show_fields function from the workflow namespace of LabOne Q, which was imported from laboneq.simple:
options = resonator_photons_time_resolved.experiment_workflow.options()
workflow.show_fields(options)
Notice that, unless we change it:
- the analysis workflow will run automatically (
do_analysis=True) - the figures produced by the analysis are automatically closed (
close_figures=True) - there is a waiting time of 1 us between the end of every acquisition and the playback of the subsequent readout pulse (
spectroscopy_reset_delay)
Here, let's disable closing the figures produced by the analysis so we see them in the cell output. Note however that the fit attempted by the analysis routine in emulation mode will not be representative, because we do not acquire data from a real experiment.
options.close_figures(False)
Now we run the experiment workflow on the first two qubits in parallel.
Note that the fit fails in emulation mode and the qubit frequency can't be extracted.
It is also important to note that the qubit spectroscopy pulse length must be short relative to the system's time dynamics. In this case, with the readout pulse set to 2 µs, the spectroscopy pulse length will be set to 80 ns. Due to the short duration of the pulse, the power needs to be increased to achieve reasonably good spectroscopy contrast. The adjustments for pulse power and length are highly dependent on the specific experimental setup. The option parameter delay_between_measurements is set to 1 microsecond as the resonator linewith $\kappa/2\pi$ is around 1 MHz in this example.
options.delay_between_measurements(1e-6)
from copy import deepcopy
# our qubits live here in the demo setup:
qubits = qpu.quantum_elements
center_q1 = qubits[0].parameters.resonance_frequency_ge
center_q2 = qubits[1].parameters.resonance_frequency_ge
center_q3 = qubits[2].parameters.resonance_frequency_ge
off = 40e6
temporary_parameters = {}
for q in qubits:
temp_pars = deepcopy(q.parameters)
temp_pars.drive_range = -10
temp_pars.spectroscopy_amplitude = 0.25
temp_pars.spectroscopy_length = 80e-9
temporary_parameters[q.uid] = temp_pars
exp_workflow = resonator_photons_time_resolved.experiment_workflow(
session=session,
qpu=qpu,
qubits=[q.uid for q in qubits],
times=[np.linspace(0, 3e-6, 21),
np.linspace(0, 3e-6, 21),
np.linspace(0, 3e-6, 21),
],
frequencies=[np.linspace(center_q1 - off, center_q1 + off, 151),
np.linspace(center_q2 - off, center_q2 + off, 151),
np.linspace(center_q3 - off, center_q3 + off, 151),
],
temporary_parameters=temporary_parameters,
options=options
)
workflow_results = exp_workflow.run()
Inspect the Tasks That Were Run¶
for t in workflow_results.tasks:
print(t)
Inspect the Output Simulation¶
You can also inspect the compiled experiment and plot the simulated output:
compiled_experiment = workflow_results.tasks["compile_experiment"].output
plot_simulation(compiled_experiment, length=80e-6)
Inspecting the Source Code of the Pulse-Sequence Creation Task¶
You can inspect the source code of the create_experiment task defined in resonator_photons_time_resolved to see how the experiment pulse sequence is created using quantum operations. To learn more about the latter, see the Quantum Operations tutorial.
resonator_photons_time_resolved.create_experiment.src
To learn more about how to work with experiment Workflows, check out the Experiment Workflows tutorial.
Here, let's briefly inspect the analysis-workflow results.
Analysis Results¶
Let's check what tasks were run as part of the analysis workflow:
analysis_workflow_results = workflow_results.tasks["analysis_workflow"]
for t in analysis_workflow_results.tasks:
print(t)
We can access the analysis from the output of the analysis-workflow.
from pprint import pprint
pprint(analysis_workflow_results.output)
Great! You've now run your qubit spectroscopy during qubit readout. Check out other experiments in this manual to keep characterizing your qubits.