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 theConditionalReset
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.
Currently, it is not possible to use
ConditionalReset
withMeasurementControl
class, when usingBinMode.APPEND
, but it works correctly when usingBinMode.AVERAGE
.