Skip to content

Active Reset

Hello, explorer! Brace yourself for the insights this module presents on:

  • Real-Time Feedback

Raring to go?

The QUA program

Real-time feedback leverages measurement results stored in QUA variables for two main purposes: adjusting pulses through Parametric Waveform Generation and making binary decisions within a thread. "Real-time digital feedback" refers to decision-making based on measurements, while "real-time analog feedback" pertains to adjustments in waveform generation. This module delves into digital feedback, leaving the exploration of analog feedback for an upcoming module.

A common application of this is the "active reset." When a qubit is detected in its first excited state, a pi-pulse is immediately sent to reset it.

The digital feedback latency is defined as the time gap between the last input sample and the first output sample.

The if-statement in QUA is the fundamental tool for making decisions within the Control Flow. Note that the if-statement usage goes beyond either qua.play() or not qua.play().

power_rabi_active_reset.py
# Go to lines 45 and 46
from qm.qua import *
from qm import QuantumMachinesManager
from OPX1000configuration import config, qop_ip, cluster_name, cloud_username, cloud_password # or OPXplusconfiguration, depending on your hardware
import matplotlib.pyplot as plt
import numpy as np
from qualang_tools.results import progress_counter, fetching_tool
from qualang_tools.plot import interrupt_on_close
from qm import SimulationConfig, LoopbackInterface
from qualang_tools.loops import from_array

###################
# The QUA program #
###################

n_avg = 2

amps = np.arange(0.1, 1.7, 0.1)

with qua.program() as power_rabi_active_reset:
    n = qua.declare(int)
    n_st = qua.declare_stream()
    a = qua.declare(qua.fixed)
    I = qua.declare(qua.fixed)
    Q = qua.declare(qua.fixed)
    counter = qua.declare(int)
    state = qua.declare(bool)
    state_st = qua.declare_stream()
    I_st = qua.declare_stream()
    Q_st = qua.declare_stream()

    with qua.for_(n, 0, n < n_avg, n + 1):
        with qua.for_(*from_array(a, amps)):
            # active reset
            qua.measure(
                "readout",
                "resonator",
                None,
                qua.dual_demod.full("rotated_cos", 
                                "rotated_sin", I),
                qua.dual_demod.full("rotated_minus_sin", 
                                "rotated_cos", Q),
            )

            with qua.if_(I > ge_threshold):  # (1)!
                qua.play("x180", "qubit")

            qua.align()

            qua.play("x180" * qua.amp(a), "qubit")

            qua.align("qubit", "resonator")

            qua.measure(
                    "readout",
                    "resonator",
                    None,
                    qua.dual_demod.full("rotated_cos", 
                                    "rotated_sin", I),
                    qua.dual_demod.full("rotated_minus_sin", 
                                    "rotated_cos", Q),
            )
            qua.assign(state, I > ge_threshold)
            qua.save(I, I_st)
            qua.save(Q, Q_st)
            qua.save(state, state_st)
        qua.save(n, n_st)

    with qua.stream_processing():
        I_st.buffer(len(amps)).average().save("I")
        Q_st.buffer(len(amps)).average().save("Q")
        state_st.boolean_to_int().buffer(len(amps)).average().save("state")
        n_st.save("iteration")

#####################################
#  Open Communication with the QOP  #
#####################################
simulate = True
simulation_in_cloud = True

if simulate: 

    if simulation_in_cloud:
        client = QmSaas(email=cloud_username, password=cloud_password)
        instance = client.simulator(QoPVersion.v3_2_0)
        instance.spawn()
        qmm = QuantumMachinesManager(host=instance.host,
                                    port=instance.port,
                                    connection_headers=instance.default_connection_headers)
    else:
        qmm = QuantumMachinesManager(host=qop_ip, 
                                    cluster_name=cluster_name)

    simulation_config = SimulationConfig(duration=1_000) # duration is in units of clock cycles, i.e., 4 nanoseconds
    job = qmm.simulate(config, power_rabi_active_reset, simulation_config)
    job.get_simulated_samples().con1.plot()
    if simulation_in_cloud:
        instance.close()
    plt.show()

else:

    qm = qmm.open_qm(config)

    job = qm.execute(power_rabi_active_reset)        
    results = fetching_tool(job, data_list=["I", "Q", "state", "iteration"], mode="live")
    fig = plt.figure()
    interrupt_on_close(fig, job) 
    while results.is_processing():
        I, Q, state, iteration = results.fetch_all()
        progress_counter(iteration, n_avg, start_time=results.get_start_time())
        plt.cla()
        plt.plot(amps * x180_amp, state, ".", label="state")
        plt.xlabel("Rabi pulse amplitude [V]")
        plt.ylabel("P|e>")
        plt.legend()
        plt.pause(1)

plt.figure()
results = fetching_tool(job, data_list=["I", "Q", "state", "iteration"])
I, Q, state, iteration = results.fetch_all()
plt.plot(amps * x180_amp, state, ".", label="state")
plt.xlabel("Rabi pulse amplitude [V]")
plt.ylabel("P|e>")
plt.legend()
plt.tight_layout()
plt.show()
  1. If the condition I > ge_threshold evaluates to True or False, the PPU will decide in real-time if it should carry out the x180 operation on the qubit. When crafting the QUA program, keep in mind that you're setting up instructions to run, rather than converting a familiar number into a boolean state that's predetermined before execution.

The Conditional play feature produces the same result as using with qua.if_(). However, it's a unique instruction designed to reduce feedback latency (compared to with qua.if_()) when deciding whether to execute qua.play() or not.

power_rabi_active_reset.py
# Go to line 45
from qm.qua import *
from qm import QuantumMachinesManager
from OPX1000configuration import config, qop_ip, cluster_name, cloud_username, cloud_password # or OPXplusconfiguration, depending on your hardware
import matplotlib.pyplot as plt
import numpy as np
from qualang_tools.results import progress_counter, fetching_tool
from qualang_tools.plot import interrupt_on_close
from qm import SimulationConfig, LoopbackInterface
from qualang_tools.loops import from_array

###################
# The QUA program #
###################

n_avg = 2

amps = np.arange(0.1, 1.7, 0.1)

with qua.program() as power_rabi_active_reset:
    n = qua.declare(int)
    n_st = qua.declare_stream()
    a = qua.declare(qua.fixed)
    I = qua.declare(qua.fixed)
    Q = qua.declare(qua.fixed)
    counter = qua.declare(int)
    state = declare(bool)
    state_st = qua.declare_stream()
    I_st = qua.declare_stream()
    Q_st = qua.declare_stream()

    with qua.for_(n, 0, n < n_avg, n + 1):
        with qua.for_(*from_array(a, amps)):
            # active reset
            qua.measure(
                "readout",
                "resonator",
                None,
                qua.dual_demod.full("rotated_cos", "out1", 
                                "rotated_sin", "out2", I),
                qua.dual_demod.full("rotated_minus_sin", "out1", 
                                "rotated_cos", "out2", Q),
            )

            qua.play("x180", "qubit", condition=I > ge_threshold)  # (1)!

            qua.align()

            qua.play("x180" * amp(a), "qubit")

            qua.align("qubit", "resonator")

            qua.measure(
                    "readout",
                    "resonator",
                    None,
                    qua.dual_demod.full("rotated_cos", 
                                    "rotated_sin", I),
                    qua.dual_demod.full("rotated_minus_sin", 
                                    "rotated_cos", Q),
            )
            qua.assign(state, I > ge_threshold)
            qua.save(I, I_st)
            qua.save(Q, Q_st)
            qua.save(state, state_st)
        qua.save(n, n_st)

    with qua.stream_processing():
        I_st.buffer(len(amps)).average().save("I")
        Q_st.buffer(len(amps)).average().save("Q")
        state_st.boolean_to_int().buffer(len(amps)).average().save("state")
        n_st.save("iteration")

#####################################
#  Open Communication with the QOP  #
#####################################
simulate = True
simulation_in_cloud = True

if simulate: 

    if simulation_in_cloud:
        client = QmSaas(email=cloud_username, password=cloud_password)
        instance = client.simulator(QoPVersion.v3_2_0)
        instance.spawn()
        qmm = QuantumMachinesManager(host=instance.host,
                                    port=instance.port,
                                    connection_headers=instance.default_connection_headers)
    else:
        qmm = QuantumMachinesManager(host=qop_ip, 
                                    cluster_name=cluster_name)

    simulation_config = SimulationConfig(duration=1_000) # duration is in units of clock cycles, i.e., 4 nanoseconds
    job = qmm.simulate(config, power_rabi_active_reset, simulation_config)
    job.get_simulated_samples().con1.plot()
    if simulation_in_cloud:
        instance.close()
    plt.show()

else:

    qm = qmm.open_qm(config)

    job = qm.execute(power_rabi_active_reset)        
    results = fetching_tool(job, data_list=["I", "Q", "state", "iteration"], mode="live")
    fig = plt.figure()
    interrupt_on_close(fig, job) 
    while results.is_processing():
        I, Q, state, iteration = results.fetch_all()
        progress_counter(iteration, n_avg, start_time=results.get_start_time())
        plt.cla()
        plt.plot(amps * x180_amp, state, ".", label="state")
        plt.xlabel("Rabi pulse amplitude [V]")
        plt.ylabel("P|e>")
        plt.legend()
        plt.pause(1)

plt.figure()
results = fetching_tool(job, data_list=["I", "Q", "state", "iteration"])
I, Q, state, iteration = results.fetch_all()
plt.plot(amps * x180_amp, state, ".", label="state")
plt.xlabel("Rabi pulse amplitude [V]")
plt.ylabel("P|e>")
plt.legend()
plt.tight_layout()
plt.show()
  1. Implementing conditional is as simple as providing the Boolean operation to the condition input to play().

= instead of qua.assign()

When using QUA within Python, you might be inclined to write state = I > ge_threshold instead of qua.assign(state, I > ge_threshold). However, it's important to note that using = won't be correctly interpreted by the compiler and will not translate accurately to real-time operations.

Python if-statement vs. QUA if-statement

If you mistakenly write I > ge_threshold (on line 47), Python will evaluate the I value before sending the code over the network. Depending on this evaluation, the indented instructions within the Python if block will either be sent or not to the compiler. This will not provide real-time decision making.

Suggested usage of Python if-statement

You can definitely use Python's if-statements when making decisions at the Python editor level regarding which code to send to the compiler. For instance, when choosing between sequences performing qubit initilization either via active reset or thermal decay. For example,

active_reset = True

with qua.program() as prog:

    I = qua.declare(qua.fixed)

    if active_reset:
        qua.measure(
            "readout",
            "resonator",
            None,
            qua.dual_demod.full("rotated_cos", "out1", 
                            "rotated_sin", "out2", I),
            qua.dual_demod.full("rotated_minus_sin", "out1", 
                            "rotated_cos", "out2", Q),
        )

        with qua.if_(I > ge_threshold):  
            qua.play("x180", "qubit")
    else:
        qua.wait(thermal_decay_time)

Linking QUA program to Configuration

Check the highlighted lines to see how power_rabi_active_reset.py links to the configuration dictionary. Specifically, line 32-42 the QUA code measure("readout", "resonator", None, dual_demod.full("rotated_cos", "rotated_sin", I), dual_demod.full("rotated_minus_sin", "rotated_cos", Q)) directly use the dictionary. To succesfully achieve active reset you have had to set the integration weights rotation angle and discrminiation ge_threshold correctly.

The Configuration
configuration.py
# Go to lines 5, 22, and 46
...
# IQ Plane
rotation_angle = (0.0 / 180) * np.pi
ge_threshold = 0.0  # (1)!

config = {
    ...
    "pulses": {
        ...
        "readout_pulse": {
            "operation": "measurement",
            "length": readout_len,
            "waveforms": {
                "I": "readout_wf",
                "Q": "zero_wf",
            },
            "integration_weights": {
                "cos": "cosine_weights",
                "sin": "sine_weights",
                "minus_sin": "minus_sine_weights",
                "rotated_cos": "rotated_cosine_weights", # (2)!
                "rotated_sin": "rotated_sine_weights",
                "rotated_minus_sin": "rotated_minus_sine_weights",
                "opt_cos": "opt_cosine_weights",
                "opt_sin": "opt_sine_weights",
                "opt_minus_sin": "opt_minus_sine_weights",
            },
            "digital_marker": "ON",
        },
    },
    ...
    "integration_weights": {
        "cosine_weights": {
            "cosine": [(1.0, readout_len)],
            "sine": [(0.0, readout_len)],
        },
        "sine_weights": {
            "cosine": [(0.0, readout_len)],
            "sine": [(1.0, readout_len)],
        },
        "minus_sine_weights": {
            "cosine": [(0.0, readout_len)],
            "sine": [(-1.0, readout_len)],
        },
        "rotated_cosine_weights": {  # (3)!
            "cosine": [(np.cos(rotation_angle), readout_len)],
            "sine": [(-np.sin(rotation_angle), readout_len)],
        },
        "rotated_sine_weights": {
            "cosine": [(np.sin(rotation_angle), readout_len)],
            "sine": [(np.cos(rotation_angle), readout_len)],
        },
        "rotated_minus_sine_weights": {
            "cosine": [(-np.sin(rotation_angle), readout_len)],
            "sine": [(-np.cos(rotation_angle), readout_len)],
        },
    },
}
  1. After experimentally determining the discrimination threshold, we can add it to the configuration dictionary as a semi-static variable. This allows us to import and use it in the relevant QUA programs when needed.

  2. To distinguish the weights associated with dictionaries that have the rotated angle, we give them unique names. Specifically, we use rotated_cos in place of cos.

  3. To incorporate an arbitrary rotation angle into the digitized waveform, create a rotation matrix and use its values, such as with np.cos(rotation_angle), in the weights. For more details, refer to the documentation.

Now it is your turn

  1. Quantify the Real-Time Feedback latency for the two cases of using if-statements and conditional-play.
  2. Modify the original QUA code to implement Repeat Until Success for Active Reset.

Test your knowledge

  1. Given the distribution of the two states provided below, formulate various Active Reset methods,

    1. Optimize for the preparation fidelity of the ground state.
    2. What strategy is best to balance preparation fidelity and time spend in the Active Reset block.

state_distribtuions

Coming up

While all the prior modules to Active Reset showcase the distinct advantages of using the QM Platform over a traditional AWG, our real-time implementation of Randomized Benchmarking stands out. It's a prime example of how to save time and achieve results more swiftly.