Conditional Playback#

The conditional playback feature introduces a method for dynamic qubit state management.

Conditional Reset#

Overview#

This feature is centered around the ConditionalReset operation, which measures the state of a qubit and applies a corrective action based on the measurement outcome.

Conditional Reset Operation#

The ConditionalReset operation functions as follows:

  • It first measures the state of a qubit, using a ThresholdedAcquisition.

  • If the qubit is in an excited state, an X gate (DRAG pulse for transmons) is applied to bring the qubit back to the ground state.

  • Conversely, if the qubit is already in the ground state, the operation simply waits.

  • This ensures that the total duration of the ConditionalReset operation remains consistent, regardless of the qubit’s initial state.

Usage#

The ConditionalReset is used by adding it to a schedule:

schedule = Schedule("")
schedule.add(ConditionalReset("q0"))
schedule.add(...)

Internally, ConditionalReset performs a ThresholdedAcquisition. If a schedule includes multiple acquisitions on the same qubit, each ConditionalReset and ThresholdedAcquisition must have a unique acq_index.

For example:

schedule = Schedule("conditional")
schedule.add(ConditionalReset("q0", acq_index=0))
schedule.add(Measure("q0", acq_index=1, acq_protocol="ThresholdedAcquisition"))

When using multiple consecutive ConditionalReset on the same qubit, increment the acq_index for each:

schedule = Schedule()
schedule.add(ConditionalReset("q0"))
schedule.add(...)
schedule.add(ConditionalReset("q0", acq_index=1))
schedule.add(...)
schedule.add(ConditionalReset("q0", acq_index=2))

Since the ConditionalReset performs a ThresholdedAcquisition, the measured state of the qubit (0 or 1) will be saved in the acquisition dataset.

Qblox backend implementation#

The ConditionalReset is implemented as a new subschedule in the gate library:

class ConditionalReset(Schedule):
    def __init__(self, qubit_name):
        super().__init__("conditional reset")
        self.add(Measure(qubit_name, acq_protocol="ThresholdedAcquisition", feedback_trigger_label=qubit_name))
        cond_schedule = Schedule("conditional subschedule")
        cond_schedule.add(X(qubit_name))
        self.add(ConditionalOperation(body=cond_schedule, qubit_name=qubit_name))

where the Measure operation is given an additional trigger label feedback_trigger_label=qubit_name. The label needs to match the label given to Conditional to match relevant triggers with the conditional schedule. It is set here to be equal to qubit_name, but could be any string.

The total duration (t) of the ConditionalReset is calculated as the sum of various time components, typically shorter than the standard idle reset duration (reset.duration). For example, in our test suite’s mock_setup_basic_transmon_with_standard_params fixture, the following values are used:

  • q0.measure.trigger_delay() = 340 ns

  • q0.measure.acq_delay() = 100 ns

  • q0.measure.integration_time() = 1,000 ns

  • q0.rxy.duration() = 20 ns

  • q0.reset.duration() = 200,000 ns

Limitations#

  • Currently only implemented for the Qblox backend.

  • Triggers cannot be sent more frequently than once every 252 ns.

  • The interval between the end of an acquisition and the start of a conditional operation must be at least 364 ns, as specified by TRIGGER_DELAY.

  • The measurement result of ConditionalReset is saved inside the dataset as well, potentially obscuring interesting data. For example, including the ConditionalReset inside a basic Rabi schedule, we could have the following implementation:

schedule = Schedule("Rabi", repetitions)
schedule.add_resource(ClockResource(name=clock, freq=frequency))

for i, (amp, duration) in enumerate(zip(amps, durations)):
    schedule.add(ConditionalReset("q0", acq_index = 2*i), label=f"Reset {i}")
    schedule.add(
        DRAGPulse(
            duration=duration,
            G_amp=amp,
            D_amp=0,
            port=port,
            clock=clock,
            phase=0,
        ),
        label=f"Rabi_pulse {i}",
    )
    schedule.add(Measure("q0", acq_index=2*i+1), label=f"Measurement {i}")

where the relevant data is saved in the odd (2*i+1) acquisition indices. Currently, it is not possible to not save the measurement data related to ConditionalReset.

A possible workaround is to introduce an extra port-clock combination to the hardware configuration, and an extra qubit to the device configuration to separate “conditional” qubits, from regular ones. For example:

hardware_config = {
    "portclock_configs": [
        {"port": "q0:res", "clock": "q0.ro", "interm_freq": 200000000.0},
        {"port": "q0c:res", "clock": "q0.ro", "interm_freq": 200000000.0},
    ],
    ... : ...
}

# define qubit parameters
q0c = BasicTransmonElement("q0c")
...

schedule = Schedule("Rabi", repetitions)
schedule.add_resource(ClockResource(name=clock, freq=frequency))

for i, (amp, duration) in enumerate(zip(amps, durations)):
    schedule.add(ConditionalReset("q0c", acq_index=i), label=f"Reset {i}")
    schedule.add(
        DRAGPulse(
            duration=duration,
            G_amp=amp,
            D_amp=0,
            port=port,
            clock=clock,
            phase=0,
        ),
        label=f"Rabi_pulse {i}",
    )
    schedule.add(Measure("q0", acq_index=i), label=f"Measurement {i}")

Here, qubit_c and qubit will correspond to the same physical qubit and are controlled by the same port on the hardware, but the measurements will use two different sequencers and the data will be stored in two different acquisition channels. Note that this will limit your ability to do multiplexed readout.