Download

Download this file as Jupyter notebook: branching_logic.ipynb.

Real-time decision logic#

Using the basic program structure as outlined in Program basics, we now show how to add branching logic to a Program using a ConditionalOperation.

There are two ways decision logic can be implemented:

  1. Specifying a measurement in a program with reset=True, e.g. program.add_measurement(qubits, reset=True).

  2. Implementing an Acquisition that uses a Classifier with a Register to which the results are written, followed by a ConditionalOperation that uses the outcome of the register to determine which operation to execute.

We begin by demonstrating the basic use case of qubit reset and then show how the reset operations can be customized.

Qubit reset#

For quantum systems with long T1 times and no direct ground state initialization method, it is common practice to reset the qubit state to zero after a single execution of an experiment when it is not in that state. This requires a measurement and an optional subsequent reset operation.

In its simplest form, this can be done with an argument to the add_measurement() method:

[2]:
import keysight.qcs as qcs

# generate a simple quantum circuit on two qubits
qubits = qcs.Qudits(range(2))

circuit = qcs.Program()
circuit.add_gate(qcs.GATES.h, qubits[0])
circuit.add_gate(qcs.GATES.cx, (qubits[0], qubits[1]))

# add a measurement on both qubits with reset
circuit.add_measurement(qubits, reset=True)

circuit.draw()
Program
keysight-logo-svg
Program Body
Program
Duration undefined
Layers 5
Targets 2
Layer #0
Layer #0
Duration undefined
Layer #1
Layer #1
Duration undefined
Layer #2
Layer #2
Duration undefined
Layer #3
Layer #3 (Data Transactions)
Duration 0 s
Transaction Source Measure on ('qudits', 0)
Transaction Destinations ('qudits', 0)
Layer #4
Layer #4
Duration undefined
qudits 0
H
Gate H on ('qudits', 0)

Matrix:
0.71 0.71
0.71 -0.71
CX
Gate CX on (('qudits', 0), ('qudits', 1))

Matrix:
1 0 0 0
0 1 0 0
0 0 0 1
0 0 1 0
Measure on ('qudits', 0)

Parameters
Dim 2
Register Register(name=qudits_results, num_bits=2, dim=2)
ConditionalOperation on ('qudits', 0)

Operations
Outcome 0
Gate ID

Matrix:
1 0
0 1
Outcome 1
Gate X

Matrix:
0 1
1 0

Register
Name qudits_results
Num Bits 2
Dim 2
1
CX
Gate CX on (('qudits', 1), ('qudits', 0))

Matrix:
1 0 0 0
0 1 0 0
0 0 0 1
0 0 1 0
Measure on ('qudits', 1)

Parameters
Dim 2
Register Register(name=qudits_results, num_bits=2, dim=2)
ConditionalOperation on ('qudits', 1)

Operations
Outcome 0
Gate ID

Matrix:
1 0
0 1
Outcome 1
Gate X

Matrix:
0 1
1 0

Register
Name qudits_results
Num Bits 2
Dim 2

When setting reset=True, a new empty layer (the data transaction layer) gets inserted after the measurement layer, followed by another layer with the conditional operation. The data transaction layer contains a list of DataTransaction objects that map measurements to operations, as can be seen in the tooltip when hovering over the layer column header. If we hover over the conditional operations in the last layer, we can inspect which operations are played depending on the outcome of the measurement. Let’s take a closer look at those:

[3]:
# print the program layer that contains the conditional operation:
circuit[-1].operations
[3]:
{Qudits(labels=[0, 1], name=qudits, dim=2): [ConditionalOperation(operations=[Gate(matrix=[[(1+0j), 0j], [0j, (1+0j)]], name=ID), Gate(matrix=[[0j, (1+0j)], [(1+0j), 0j]], name=X)], register=Register(name=qudits_results, num_bits=2, dim=2))]}

This conditional operation will execute the first operation, the identity gate, if the outcome of the measurement is 0, and the second operation, an X gate, if the outcome is 1, thereby resetting the qubit state to zero after each execution of the program. It is also possible to pass a list of operations to the reset argument whose length should match the dimension of the measurement:

[4]:
circuit = qcs.Program()
circuit.add_gate(qcs.GATES.h, qubits[0])
circuit.add_gate(qcs.GATES.cx, (qubits[0], qubits[1]))

# add a measurement on both qubits with a reset operation
# if the state is 0, play the identity
# if the state is 1, play a Y gate
circuit.add_measurement(qubits, reset=[qcs.GATES.id, qcs.GATES.y])

circuit.draw()
Program
keysight-logo-svg
Program Body
Program
Duration undefined
Layers 5
Targets 2
Layer #0
Layer #0
Duration undefined
Layer #1
Layer #1
Duration undefined
Layer #2
Layer #2
Duration undefined
Layer #3
Layer #3 (Data Transactions)
Duration 0 s
Transaction Source Measure on ('qudits', 0)
Transaction Destinations ('qudits', 0)
Layer #4
Layer #4
Duration undefined
qudits 0
H
Gate H on ('qudits', 0)

Matrix:
0.71 0.71
0.71 -0.71
CX
Gate CX on (('qudits', 0), ('qudits', 1))

Matrix:
1 0 0 0
0 1 0 0
0 0 0 1
0 0 1 0
Measure on ('qudits', 0)

Parameters
Dim 2
Register Register(name=qudits_results, num_bits=2, dim=2)
ConditionalOperation on ('qudits', 0)

Operations
Outcome 0
Gate ID

Matrix:
1 0
0 1
Outcome 1
Gate Y

Matrix:
0 -1j
1j 0

Register
Name qudits_results
Num Bits 2
Dim 2
1
CX
Gate CX on (('qudits', 1), ('qudits', 0))

Matrix:
1 0 0 0
0 1 0 0
0 0 0 1
0 0 1 0
Measure on ('qudits', 1)

Parameters
Dim 2
Register Register(name=qudits_results, num_bits=2, dim=2)
ConditionalOperation on ('qudits', 1)

Operations
Outcome 0
Gate ID

Matrix:
1 0
0 1
Outcome 1
Gate Y

Matrix:
0 -1j
1j 0

Register
Name qudits_results
Num Bits 2
Dim 2

We can repeat reset instruction multiple times by setting repeat_reset:

[5]:
circuit = qcs.Program()

# add a reset operation at the start of the program and repeat it three times
circuit.add_measurement(qubits[0], reset=True, repeat_reset=3)
circuit.draw()
Program
keysight-logo-svg
Program Body
Program
Duration undefined
Layers 7
Targets 1
Layer #0
Layer #0
Duration undefined
Layer #1
Layer #1 (Data Transactions)
Duration 0 s
Transaction Source Measure on ('qudits', 0)
Transaction Destinations ('qudits', 0)
Layer #2
Layer #2
Duration undefined
Layer #3
Layer #3 (Data Transactions)
Duration 0 s
Transaction Source Measure on ('qudits', 0)
Transaction Destinations ('qudits', 0)
Layer #4
Layer #4
Duration undefined
Layer #5
Layer #5 (Data Transactions)
Duration 0 s
Transaction Source Measure on ('qudits', 0)
Transaction Destinations ('qudits', 0)
Layer #6
Layer #6
Duration undefined
qudits 0
Measure on ('qudits', 0)

Parameters
Dim 2
Register Register(name=qudits_results, num_bits=1, dim=2)
ConditionalOperation on ('qudits', 0)

Operations
Outcome 0
Gate ID

Matrix:
1 0
0 1
Outcome 1
Gate X

Matrix:
0 1
1 0

Register
Name qudits_results
Num Bits 1
Dim 2
Measure on ('qudits', 0)

Parameters
Dim 2
Register Register(name=qudits_results, num_bits=1, dim=2)
ConditionalOperation on ('qudits', 0)

Operations
Outcome 0
Gate ID

Matrix:
1 0
0 1
Outcome 1
Gate X

Matrix:
0 1
1 0

Register
Name qudits_results
Num Bits 1
Dim 2
Measure on ('qudits', 0)

Parameters
Dim 2
Register Register(name=qudits_results, num_bits=1, dim=2)
ConditionalOperation on ('qudits', 0)

Operations
Outcome 0
Gate ID

Matrix:
1 0
0 1
Outcome 1
Gate X

Matrix:
0 1
1 0

Register
Name qudits_results
Num Bits 1
Dim 2

Note

ConditionalOperation and qubit reset are currently incompatible with sweeping parameters of a IntegrationFilter.

Defining conditional operations directly#

Conditional operations are executed conditioned on an outcome stored in a Register. This register is written to by a Classifierspecified on an Acquisition.

Consider the following example program on a single channel:

[6]:
# define a classifier with a register
register = qcs.Register(name="results", num_outcomes=1)
classifier = qcs.Classifier([0, 0.1], register)

# define two channels for the readout waveform and acquisition
awg = qcs.Channels(0, "awg")
dig = qcs.Channels(0, "dig")

# define the readout pulse and the integration filter
frequency = 5e9
readout_pulse = qcs.RFWaveform(30e-9, qcs.GaussianEnvelope(), 1, frequency)
integration_filter = qcs.RFWaveform(30e-9, qcs.ConstantEnvelope(), 1, 0)

program = qcs.Program()

program.add_waveform(readout_pulse, awg)
program.add_acquisition(integration_filter, dig, classifier)
program.draw()
Program
keysight-logo-svg
Program Body
Program
Duration 30 ns
Layers 1
Targets 2
Layer #0
Layer #0
Duration 30 ns
awg 0
RFWaveform on ('awg', 0)

Parameters
Duration Scalar(name=_implicit, value=30 ns, dtype=float, unit=s)
Amplitude 1
Frequency 5 GHz
Envelope GaussianEnvelope(2.0)
Instantaneous Phase 0 rad
Post-phase 0 rad
dig 0
Acquisition on ('dig', 0)

Parameters
Duration Scalar(name=_implicit, value=30 ns, dtype=float, unit=s)
Integration Filter
RFWaveform

Parameters
Duration Scalar(name=_implicit, value=30 ns, dtype=float, unit=s)
Amplitude 1
Frequency 0 Hz
Envelope ConstantEnvelope()
Instantaneous Phase 0 rad
Post-phase 0 rad
Classifier Classifier(Array(name=_implicit, shape=(2,), dtype=complex, unit=none))

This program now contains a single RF waveform played on the AWG channel and a simultaneous acquisition on a digitizer channel. We can now add the conditional operation based on the register that is connected to this acquisition:

[7]:
# first, define two waveforms to be played for either outcome (0 or 1)
waveform_0 = qcs.RFWaveform(30e-9, qcs.GaussianEnvelope(), 1, frequency)
waveform_1 = qcs.RFWaveform(30e-9, qcs.GaussianEnvelope(), 0.5, frequency)

# add the conditional operation using those waveforms and targeting the awg
# channel
program.add_conditional_operation(
    operations=[waveform_0, waveform_1], target=awg, register=classifier.register
)

program.draw()
Program
keysight-logo-svg
Program Body
Program
Duration 60 ns
Layers 3
Targets 2
Layer #0
Layer #0
Duration 30 ns
Layer #1
Layer #1 (Data Transactions)
Duration 0 s
Transaction Source Acquisition on ('dig', 0)
Transaction Destinations ('awg', 0)
Layer #2
Layer #2
Duration 30 ns
awg 0
RFWaveform on ('awg', 0)

Parameters
Duration Scalar(name=_implicit, value=30 ns, dtype=float, unit=s)
Amplitude 1
Frequency 5 GHz
Envelope GaussianEnvelope(2.0)
Instantaneous Phase 0 rad
Post-phase 0 rad
ConditionalOperation on ('awg', 0)

Operations
Outcome 0
RFWaveform

Parameters
Duration Scalar(name=_implicit, value=30 ns, dtype=float, unit=s)
Amplitude 1
Frequency 5 GHz
Envelope GaussianEnvelope(2.0)
Instantaneous Phase 0 rad
Post-phase 0 rad
Outcome 1
RFWaveform

Parameters
Duration Scalar(name=_implicit, value=30 ns, dtype=float, unit=s)
Amplitude 0.5
Frequency 5 GHz
Envelope GaussianEnvelope(2.0)
Instantaneous Phase 0 rad
Post-phase 0 rad

Register
Name results
Num Bits 1
Dim 2
dig 0
Acquisition on ('dig', 0)

Parameters
Duration Scalar(name=_implicit, value=30 ns, dtype=float, unit=s)
Integration Filter
RFWaveform

Parameters
Duration Scalar(name=_implicit, value=30 ns, dtype=float, unit=s)
Amplitude 1
Frequency 0 Hz
Envelope ConstantEnvelope()
Instantaneous Phase 0 rad
Post-phase 0 rad
Classifier Classifier(Array(name=_implicit, shape=(2,), dtype=complex, unit=none))

This program contains a conditional operation with two waveforms that will be played depending on the outcome stored in the register. If the outcome is 0, the waveform at index 0 of the operations list will be played, and if it is 1, the waveform at the index 1 will be played.

Rendering programs with conditional operations#

Operations such as ConditionalOperation split the program into multiple group of layers. The render by default displays the last group of layers. To view a different layer, use the program_layer argument. Note that the render function displays all the layers in a group at a time.

Additionally, the user can specify the condition argument to select the measured state outcome to visualize (in the following example it can be 0 or 1).

[8]:
register = qcs.Register(name="results", num_outcomes=1)
classifier = qcs.Classifier([0, 0.1], register)

# define two channels for the readout waveform and acquisition
awg = qcs.Channels(0, "awg")
dig = qcs.Channels(0, "dig")

# define the readout pulse and the integration filter, and the waveforms to be played
# for either outcome (0 or 1)
frequency = 5e9
waveform_0 = qcs.RFWaveform(30e-9, qcs.GaussianEnvelope(), 1, frequency + 0.2e9)
waveform_1 = qcs.RFWaveform(30e-9, qcs.GaussianEnvelope(), -0.5, frequency)
readout_pulse = qcs.RFWaveform(30e-9, qcs.GaussianEnvelope(), 1, frequency)
integration_filter = qcs.RFWaveform(30e-9, qcs.ConstantEnvelope(), 1, 0)

program = qcs.Program()

program.add_waveform(readout_pulse, awg)
program.add_waveform(readout_pulse, awg, new_layer=True)
program.add_acquisition(integration_filter, dig, classifier)

# add the conditional operation using those waveforms and targeting the awg
# channel
program.add_conditional_operation(
    operations=[waveform_0, waveform_1], target=awg, register=classifier.register
)
program.add_waveform(readout_pulse, awg)
program.add_waveform(readout_pulse, awg)
[9]:
program.draw()
Program
keysight-logo-svg
Program Body
Program
Duration 150 ns
Layers 4
Targets 2
Layer #0
Layer #0
Duration 30 ns
Layer #1
Layer #1
Duration 30 ns
Layer #2
Layer #2 (Data Transactions)
Duration 0 s
Transaction Source Acquisition on ('dig', 0)
Transaction Destinations ('awg', 0)
Layer #3
Layer #3
Duration 90 ns
awg 0
RFWaveform on ('awg', 0)

Parameters
Duration Scalar(name=_implicit, value=30 ns, dtype=float, unit=s)
Amplitude 1
Frequency 5 GHz
Envelope GaussianEnvelope(2.0)
Instantaneous Phase 0 rad
Post-phase 0 rad
RFWaveform on ('awg', 0)

Parameters
Duration Scalar(name=_implicit, value=30 ns, dtype=float, unit=s)
Amplitude 1
Frequency 5 GHz
Envelope GaussianEnvelope(2.0)
Instantaneous Phase 0 rad
Post-phase 0 rad
ConditionalOperation on ('awg', 0)

Operations
Outcome 0
RFWaveform

Parameters
Duration Scalar(name=_implicit, value=30 ns, dtype=float, unit=s)
Amplitude 1
Frequency 5.2 GHz
Envelope GaussianEnvelope(2.0)
Instantaneous Phase 0 rad
Post-phase 0 rad
Outcome 1
RFWaveform

Parameters
Duration Scalar(name=_implicit, value=30 ns, dtype=float, unit=s)
Amplitude -0.5
Frequency 5 GHz
Envelope GaussianEnvelope(2.0)
Instantaneous Phase 0 rad
Post-phase 0 rad

Register
Name results
Num Bits 1
Dim 2
RFWaveform on ('awg', 0)

Parameters
Duration Scalar(name=_implicit, value=30 ns, dtype=float, unit=s)
Amplitude 1
Frequency 5 GHz
Envelope GaussianEnvelope(2.0)
Instantaneous Phase 0 rad
Post-phase 0 rad
RFWaveform on ('awg', 0)

Parameters
Duration Scalar(name=_implicit, value=30 ns, dtype=float, unit=s)
Amplitude 1
Frequency 5 GHz
Envelope GaussianEnvelope(2.0)
Instantaneous Phase 0 rad
Post-phase 0 rad
dig 0
Acquisition on ('dig', 0)

Parameters
Duration Scalar(name=_implicit, value=30 ns, dtype=float, unit=s)
Integration Filter
RFWaveform

Parameters
Duration Scalar(name=_implicit, value=30 ns, dtype=float, unit=s)
Amplitude 1
Frequency 0 Hz
Envelope ConstantEnvelope()
Instantaneous Phase 0 rad
Post-phase 0 rad
Classifier Classifier(Array(name=_implicit, shape=(2,), dtype=complex, unit=none))

Calling render without specifying program_layer will display the last group of layers which in this case are the layers after the last conditional operation. By default, condition is set to 0.

[10]:
program.render()

Let’s visualize the program when the measured state is 1.

[11]:
program.render(condition=1)

We can also display the layers before the ConditionalOperation. Note that in this case setting program_layer to either 0 or 1 will render the same waveforms as both the layers belong to the same group.

[12]:
program.render(program_layer=0)

Download

Download this file as Jupyter notebook: branching_logic.ipynb.