Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VideoMode refactored #194

Merged
merged 3 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

## [Unreleased]
### Added
- Video_mode - New module to update some pre-defined parameters of a QUA program while fetching data from the OPX.
- simulator - ``create_simulator_controller_connections`` can now be used to create the connections between a subset of a large cluster.
- results - ``DataHandler`` can be used to save data (values, matplotlib figures, numpy/xarray arrays) to the local file storage.
- callable_from_qua - Framework used to call Python functions within the core of a QUA program.
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ It currently has a two-states discriminator for analyzing the ground and excited
* [Bakery](qualang_tools/bakery/README.md) - This library introduces a new framework for creating arbitrary waveforms and
storing them in the usual configuration file. It allows defining waveforms in a QUA-like manner while working with 1ns resolution (or higher).

* [External Frameworks](qualang_tools/external_frameworks/qcodes/README.md) - This library introduces drivers for integrating the OPX within external frameworks such as QCoDeS. Please refer to the [examples](./examples) section for more details about how to use these drivers.
* [External Frameworks](qualang_tools/external_frameworks/qcodes/README.md) - This library introduces drivers for integrating the OPX within external frameworks such as QCoDeS. Please refer to the [examples](./examples) section for more details about how to use these drivers.
* [Video Mode](qualang_tools/video_mode/README.md) - This module allows the user to update some pre-defined parameters of a QUA program while fetching data from the OPX for dynamic tuning. Please refer to the [examples](./examples/video_mode) section for more details about how to implement this module.

* Addons:
* [Calibrations](qualang_tools/addons/calibration/README.md) - This module allows to easily perform most of the standard single qubit calibrations from a single python file.
Expand Down
182 changes: 182 additions & 0 deletions examples/video_mode/PID_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
from matplotlib import pyplot as plt
from qm.qua import *
from qm import QuantumMachinesManager
from qualang_tools.addons.variables import assign_variables_to_element
from qualang_tools.results import fetching_tool
from configuration import *
from qualang_tools.video_mode.videomode import VideoMode


####################
# Helper functions #
####################
def PID_derivation(input_signal, bitshift_scale_factor, gain_P, gain_I, gain_D, alpha, target):
"""

:param input_signal: Result of the demodulation
:param bitshift_scale_factor: Scale factor as 2**bitshift_scale_factor that multiplies the error signal to avoid resolution issues with QUA fixed (3.28)
:param gain_P: Proportional gain.
:param gain_I: Integral gain.
:param gain_D: Derivative gain.
:param alpha: Ratio between proportional and integral error: integrator_error = (1.0 - alpha) * integrator_error + alpha * error
:param target: Setpoint to which the input signal should stabilize.
:return: The total, proportional, integral and derivative errors.
"""
error = declare(fixed)
integrator_error = declare(fixed)
derivative_error = declare(fixed)
old_error = declare(fixed)

# calculate the error
assign(error, (target - input_signal) << bitshift_scale_factor)
# calculate the integrator error with exponentially decreasing weights with coefficient alpha
assign(integrator_error, (1.0 - alpha) * integrator_error + alpha * error)
# calculate the derivative error
assign(derivative_error, old_error - error)
# save old error to be error
assign(old_error, error)

return (
gain_P * error + gain_I * integrator_error + gain_D * derivative_error,
error,
integrator_error,
derivative_error,
)


###################
# The QUA program #
###################
def PID_prog(vm: VideoMode, PDH_angle: float = 0.0):
with program() as prog:
# Results variables
I = declare(fixed)
Q = declare(fixed)
single_shot_DC = declare(fixed)
single_shot_AC = declare(fixed)
dc_offset_1 = declare(fixed)
# PID variables
vm.declare_variables()
# Streams
single_shot_st = declare_stream()
error_st = declare_stream()
integrator_error_st = declare_stream()
derivative_error_st = declare_stream()
offset_st = declare_stream()

# Ensure that the results variables are assigned to the measurement elements
assign_variables_to_element("detector_DC", single_shot_DC)
assign_variables_to_element("detector_AC", I, Q, single_shot_AC)

with infinite_loop_():
# with for_(n, 0, n < N_shots, n + 1):
# Update the PID parameters based on the user input.
vm.load_parameters()
# Ensure that the two digital oscillators will start with the same phase
reset_phase("phase_modulator")
reset_phase("detector_AC")
# Phase angle between the sideband and demodulation in units of 2pi
frame_rotation_2pi(PDH_angle, "phase_modulator")
# Sync all the elements
align()
# Play the PDH sideband
play("cw", "phase_modulator")
# Measure and integrate the signal received by the detector --> DC measurement
measure(
"readout",
"detector_DC",
None,
integration.full("constant", single_shot_DC, "out1"),
)
# Measure and demodulate the signal received by the detector --> AC measurement sqrt(I**2 + Q**2)
measure(
"readout",
"detector_AC",
None,
demod.full("constant", I, "out1"),
demod.full("constant", Q, "out1"),
)
assign(single_shot_AC, I)
# PID correction signal
correction, error, int_error, der_error = PID_derivation(single_shot_DC, *vm.variables)
# Update the DC offset
assign(dc_offset_1, dc_offset_1 + correction)
# Handle saturation - Make sure that the DAC won't be asked to output more than 0.5V
with if_(dc_offset_1 > 0.5 - phase_mod_amplitude):
assign(dc_offset_1, 0.5 - phase_mod_amplitude)
with if_(dc_offset_1 < -0.5 + phase_mod_amplitude):
assign(dc_offset_1, -0.5 + phase_mod_amplitude)
# Apply the correction
set_dc_offset("filter_cavity_1", "single", dc_offset_1)

# Save the desired variables
save(single_shot_DC, single_shot_st)
save(dc_offset_1, offset_st)
save(error, error_st)
save(der_error, derivative_error_st)
save(int_error, integrator_error_st)

# Wait between each iteration
wait(1000)

with stream_processing():
single_shot_st.buffer(1000).save("single_shot")
offset_st.buffer(1000).save("offset")
error_st.buffer(1000).save("error")
integrator_error_st.buffer(1000).save("int_err")
derivative_error_st.buffer(1000).save("der_err")
return prog


if __name__ == "__main__":
# Open the Quantum Machine Manager
qmm = QuantumMachinesManager(qop_ip, cluster_name=cluster_name)
# Open the Quantum Machine
qm = qmm.open_qm(config)
# Define the parameters to be updated in video mode with their initial value and QUA type
param_dict = {
"bitshift_scale_factor": (3, int),
"gain_P": (-1e-4, fixed), # The proportional gain
"gain_I": (0.0, fixed), # The integration gain
"gain_D": (0.0, fixed), # The derivative gain
"alpha": (0.0, fixed), # The ratio between integration and proportional error
"target": (0.0, fixed), # The target value
}
# Initialize the video mode
video_mode = VideoMode(qm, param_dict)
# Get the QUA program
qua_prog = PID_prog(video_mode)
job = video_mode.execute(qua_prog)
# Get the results from the OPX in live mode
data_list = ["error", "int_err", "der_err", "single_shot", "offset"]
results = fetching_tool(job, data_list, mode="live")
# Live plotting
fig = plt.figure()
while results.is_processing():
error, int_err, der_err, single_shot, offset = results.fetch_all()
plt.subplot(231)
plt.cla()
plt.plot(error, "-")
plt.title("Error signal [a.u.]")
plt.xlabel("Time [μs]")
plt.ylabel("Amplitude Error [arb. units]")
plt.subplot(232)
plt.cla()
plt.plot(int_err, "-")
plt.title("integration_error signal [a.u.]")
plt.xlabel("Time [μs]")
plt.subplot(233)
plt.cla()
plt.plot(der_err, "-")
plt.title("derivative_error signal [a.u.]")
plt.xlabel("Time [μs]")
plt.subplot(234)
plt.cla()
plt.plot(single_shot)
plt.title("Single shot measurement")
plt.subplot(235)
plt.cla()
plt.plot(offset)
plt.title("Applied offset [V]")
plt.tight_layout()
plt.pause(0.1)
10 changes: 10 additions & 0 deletions examples/video_mode/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Video Mode usage examples

__Package__: https://github.com/qua-platform/py-qua-tools/tree/main/qualang_tools/video_mode

This package contains modules to set the OPX in a video-mode, where pre-defined parameters of the QUA program can be
updated while looking at data extracted and plotted in real-time.

In this example folder, you will find two files:
* [simple test](basic_example.py): demonstrating the basic functionalities by updating two OPX output offsets.
* [PID loop](PID_example.py): showing how to use this framework to calibrate and optimize the parameters of a PID loop.
80 changes: 80 additions & 0 deletions examples/video_mode/basic_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from matplotlib import pyplot as plt
from qm.qua import *
from qm import QuantumMachinesManager
from qualang_tools.results import fetching_tool
from configuration import *
from qualang_tools.video_mode.videomode import VideoMode


def qua_prog(vm: VideoMode):
with program() as prog:
# Results variables
single_shot_1 = declare(fixed)
single_shot_2 = declare(fixed)
# Get the parameters from the video mode
dc_offset_1, dc_offset_2 = vm.declare_variables()
# Streams
signal1_st = declare_stream()
signal2_st = declare_stream()

with infinite_loop_():
# Update the parameters
vm.load_parameters()
# Update the dc_offset of the channel connected to the OPX analog input 1
set_dc_offset("filter_cavity_1", "single", dc_offset_1)
set_dc_offset("filter_cavity_2", "single", dc_offset_2)
# Measure and integrate the signal received by the OPX
measure(
"readout",
"detector_DC",
None,
integration.full("constant", single_shot_1, "out1"),
integration.full("constant", single_shot_2, "out2"),
)
# Save the measured value to its stream
save(single_shot_1, signal1_st)
save(single_shot_2, signal2_st)
# Wait between each iteration
wait(1000)

with stream_processing():
signal1_st.buffer(1000).save("signal1")
signal2_st.buffer(1000).save("signal2")
return prog


if __name__ == "__main__":
# Open the Quantum Machine Manager
qmm = QuantumMachinesManager(qop_ip, cluster_name=cluster_name)
# Open the Quantum Machine
qm = qmm.open_qm(config)
# Define the parameters to be updated in video mode with their initial value and QUA type
param_dict = {
"dc_offset_1": (0.0, fixed),
"dc_offset_2": (0.0, fixed),
}
# Initialize the video mode
video_mode = VideoMode(qm, param_dict)
# Get the QUA program
qua_prog = qua_prog(video_mode)
# Execute the QUA program in video mode
job = video_mode.execute(qua_prog)
# Get the results from the OPX in live mode
results = fetching_tool(job, ["signal1", "signal2"], mode="live")
# Live plotting
fig = plt.figure()
while results.is_processing():
# Fetch data from the OPX
signal1, signal2 = results.fetch_all()
# Convert the data into Volt
signal1 = -u.demod2volts(signal1, readout_len)
signal2 = -u.demod2volts(signal2, readout_len)
# Plot the data
plt.cla()
plt.plot(signal1, "b-")
plt.plot(signal2, "r-")
plt.title("Error signal [a.u.]")
plt.xlabel("Time [μs]")
plt.ylabel("Amplitude Error [arb. units]")
plt.ylim((-0.5, 0.5))
plt.pause(0.1)
Loading
Loading