Download
Download this file as Jupyter notebook: intro.ipynb.
Simple Compilation Workflow
This guide illustrates how users can translate between programs expressed through high-level quantum circuits and waveform programs that can be run on hardware.
Simple translation tasks for single- and multi-qudit gate and measurements can be
automated using a CalibrationSet
, as shown in this
guide. More complex translations require the explicit definition of
Linkers
, which is demonstrated in
Introduction to linkers and the following guides.
Setting up
To start with, we define our qudit topology through a
QuditGraph
and instantiate a new
CalibrationSet
based on this configuration.
[2]:
import keysight.qcs as qcs
# create a topology consisting of 4 qudits with couplings between (0, 2) and (1, 3)
n_qudits = 4
qudits = qcs.Qudits(range(n_qudits))
pairs = qudits.make_connectivity([(0, 2), (1, 3)])
# instantiate a qudit graph with the above couplings
topology = qcs.QuditGraph(qudits, pairs)
# instantiate a new calibration set with this topology
calset = qcs.CalibrationSet(topology)
Next, we define a few basic variables as well as virtual channels for our qudits.
[3]:
qudit_frequencies = qcs.Array(
"qudit_freqs", value=[4.91e9, 4.92e9, 4.93e9, 4.94e9], dtype=float
)
readout_frequencies = qcs.Array(
"readout_freqs", value=[5.91e9, 5.92e9, 5.93e9, 5.94e9], dtype=float
)
durations = qcs.Array("multi_qubit_duration", value=[50e-9, 60e-9], dtype=float)
amplitudes = qcs.Array("multi_qubit_amplitudes", value=[0.5, 0.6], dtype=float)
# define virtual channels to represent our qudit channels
channels = qcs.Channels(range(n_qudits), "control")
readout_channels = qcs.Channels(range(n_qudits), "readout")
The virtual control and readout channels that we defined in the last step can later
be mapped to physical channels on the hardware using a
ChannelMapper
as shown in
Channel mappers.
Single-qudit gates
Now we specify how single-qudit gates are to be handled. For this example, we will use
a simple X gate. We define a waveform to replace this gate with when encountered
in a program and call the add_sq_gate()
method to add this gate to our calibration set.
[4]:
# define the matching waveform for an X gate
x_waveform = qcs.RFWaveform(50e-9, qcs.GaussianEnvelope(), 0.5, qudit_frequencies)
# add to calibration set
calset.add_sq_gate("x", qcs.GATES.x, x_waveform, channels=channels)
The variables within our x_waveform
are automatically added to our calibration
set and broadcasted to match the number of single qudits in our topology. Inspecting
the variables
shows all the variables
that were added:
[5]:
list(calset.variables.variables)
[5]:
[Array(name=x_duration, shape=(4,), dtype=float, unit=s),
Array(name=x_amplitude, shape=(4,), dtype=float, unit=none),
Array(name=qudit_freqs, shape=(4,), dtype=float, unit=Hz),
Array(name=x_phase, shape=(4,), dtype=float, unit=rad),
Array(name=x_post_phase, shape=(4,), dtype=float, unit=rad)]
We can see the qudit_freqs
variable that we created above, and in addition the
calibration set generated variables for all the other waveform parameters, all with
shape (4,)
. However, since we initialized our waveforms with only a single value
for the duration, amplitude and phases, all these arrays will hold those values.
For example, notice how the duration has been broadcasted four times:
[6]:
calset.variables.x_duration.value
[6]:
array([5.e-08, 5.e-08, 5.e-08, 5.e-08])
This allows for individual calibration of each of these parameters for each qudits.
The values stored in a calibration set can also be saved on disk using the
export_values()
method:
[7]:
# calset.export_values("calibration_values.qcs")
loaded_file = open("calibration_values.qcs").read()
print(loaded_file)
{
"qudits_0": {
"x_duration": 5E-08,
"x_amplitude": 0.5,
"qudit_freqs": 4910000000.0,
"x_phase": 0.0,
"x_post_phase": 0.0
},
"qudits_1": {
"x_duration": 5E-08,
"x_amplitude": 0.5,
"qudit_freqs": 4920000000.0,
"x_phase": 0.0,
"x_post_phase": 0.0
},
"qudits_2": {
"x_duration": 5E-08,
"x_amplitude": 0.5,
"qudit_freqs": 4930000000.0,
"x_phase": 0.0,
"x_post_phase": 0.0
},
"qudits_3": {
"x_duration": 5E-08,
"x_amplitude": 0.5,
"qudit_freqs": 4940000000.0,
"x_phase": 0.0,
"x_post_phase": 0.0
}
}
The values are stored per qudit and can be edited and loaded back in using the
import_values()
method:
[8]:
calset.import_values("calibration_values.qcs")
Cross-resonance gate
Next up, we define a parametric gate that represents our cross-resonance gate, which
we define here through a parameterized ZX Hamiltonian.
We replace this operation with a waveform on the control qudit at the frequency of
the target qudits, and a waveform with attenuated amplitude on the target qudits.
We map our first two qudits (indexed 0
and 1
) to controls and the latter two
to targets.
[9]:
# Define the gate operation through a ZX Hamiltonian - the & operator represents
# the Kronecker product
cr_gate = qcs.ParametricGate([qcs.PAULIS.sigma_z & qcs.PAULIS.sigma_x], ["beta"])
angles = qcs.Array(name="beta", value=[0.3, 0.4], dtype=float)
cr_param_gate = qcs.ParameterizedGate(cr_gate, angles)
dur = qcs.Array("multi_qubit_duration", value=[50e-9, 60e-9], dtype=float)
amps = qcs.Array("multi_qubit_amplitudes", value=[1, 1], dtype=float)
# define the waveforms to replace the gate with
# play the control waveform with the frequency of the target qudits
cr_control_waveform = qcs.RFWaveform(
dur, qcs.GaussianEnvelope(), amps * angles, qudit_frequencies[2:]
)
# play a compensating waveform on the control qudits with smaller amplitude
cr_target_waveform = qcs.RFWaveform(
dur, qcs.GaussianEnvelope(), amps * angles * 0.2, qudit_frequencies[2:]
)
# add to calibration set
calset.add_cr_gate(
cr_param_gate,
pairs,
"x",
control_waveform=cr_control_waveform,
target_waveform=cr_target_waveform,
name="CR",
)
More details on how to implement multi-qudit linkers can be found in Compiling multi-qubit native gates to waveforms.
Measurements
Lastly, we define how measurements are to be handled. To keep it simple, we only instantiate a single readout drive pulse that is played on the control channels simultaneously to an acquisition. For a more elaborate example of measurement translation, see Compiling measurements to waveforms and acquisitions.
[10]:
readout_drive_pulse = qcs.RFWaveform(
300e-9, qcs.GaussianEnvelope(), 0.5, readout_frequencies
)
# add to the calibration set
calset.add_measurement(
readout_drive_pulse,
name="measure",
readout_channels=channels,
acquire_channels=readout_channels,
)
Apply to a quantum circuit
Now we are ready to define our programs in terms of quantum circuit and translate them down to the hardware layer. We create a simple program that contains all the operations used above:
[11]:
program = qcs.Program()
program.add_gate(qcs.GATES.x, qudits[0])
program.add_gate(qcs.GATES.x, qudits[1])
program.add_parametric_gate(cr_gate, angles[:1], (qudits[0], qudits[2]))
program.add_parametric_gate(cr_gate, angles[:1], (qudits[1], qudits[3]))
program.add_measurement(qudits)
program.draw()
Program
Program
|
||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Layer #0
Layer #0
|
Layer #1
Layer #1
|
Layer #2
Layer #2
|
||||||||||||||||||||||||||||
|
|
X
Gate X on ('qudits', 0)
Matrix:
|
MULTIGATE
ParameterizedGate on (('qudits', 0), ('qudits', 2))
Matrices
|
Measure on ('qudits', 0)
Parameters
|
||||||||||||||||||||||||||
|
X
Gate X on ('qudits', 1)
Matrix:
|
MULTIGATE
ParameterizedGate on (('qudits', 1), ('qudits', 3))
Matrices
|
Measure on ('qudits', 1)
Parameters
|
|||||||||||||||||||||||||||
|
MULTIGATE
ParameterizedGate on (('qudits', 2), ('qudits', 0))
Matrices
|
Measure on ('qudits', 2)
Parameters
|
||||||||||||||||||||||||||||
|
MULTIGATE
ParameterizedGate on (('qudits', 3), ('qudits', 1))
Matrices
|
Measure on ('qudits', 3)
Parameters
|
We can now compile this program using our calibration set.
[12]:
compiled_program = qcs.LinkerPass(*calset.linkers.values()).apply(program)
compiled_program.draw()
Program
Program
|
||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Layer #0
Layer #0
|
Layer #1
Layer #1
|
Layer #2
Layer #2
|
||||||||||||||||||||||||||||||||||||||
|
|
RFWaveform on ('control', 0)
Parameters
|
RFWaveform on ('control', 0)
Parameters
|
RFWaveform on ('control', 0)
Parameters
|
||||||||||||||||||||||||||||||||||||
|
RFWaveform on ('control', 1)
Parameters
|
RFWaveform on ('control', 1)
Parameters
|
RFWaveform on ('control', 1)
Parameters
|
|||||||||||||||||||||||||||||||||||||
|
RFWaveform on ('control', 2)
Parameters
|
RFWaveform on ('control', 2)
Parameters
|
||||||||||||||||||||||||||||||||||||||
|
RFWaveform on ('control', 3)
Parameters
|
RFWaveform on ('control', 3)
Parameters
|
||||||||||||||||||||||||||||||||||||||
|
|
Acquisition on ('readout', 0)
Parameters
|
||||||||||||||||||||||||||||||||||||||
|
Acquisition on ('readout', 1)
Parameters
|
|||||||||||||||||||||||||||||||||||||||
|
Acquisition on ('readout', 2)
Parameters
|
|||||||||||||||||||||||||||||||||||||||
|
Acquisition on ('readout', 3)
Parameters
|
And we can see that all the explicit and implicit variables used by the operations in this program have been added to the calibration set:
[13]:
list(calset.variables.variables)
[13]:
[Array(name=x_duration, shape=(4,), dtype=float, unit=s),
Array(name=x_amplitude, shape=(4,), dtype=float, unit=none),
Array(name=qudit_freqs, shape=(4,), dtype=float, unit=Hz),
Array(name=x_phase, shape=(4,), dtype=float, unit=rad),
Array(name=x_post_phase, shape=(4,), dtype=float, unit=rad),
Array(name=beta, shape=(2,), dtype=float, unit=none),
Array(name=multi_qubit_duration, shape=(2,), dtype=float, unit=s),
Array(name=multi_qubit_amplitudes, shape=(2,), dtype=float, unit=none),
Array(name=CR_control_phase, shape=(2,), dtype=float, unit=rad),
Array(name=CR_control_post_phase, shape=(2,), dtype=float, unit=rad),
Array(name=CR_target_phase, shape=(2,), dtype=float, unit=rad),
Array(name=CR_target_post_phase, shape=(2,), dtype=float, unit=rad),
Array(name=measure_duration, shape=(4,), dtype=float, unit=s),
Array(name=measure_amplitude, shape=(4,), dtype=float, unit=none),
Array(name=readout_freqs, shape=(4,), dtype=float, unit=Hz),
Array(name=measure_phase, shape=(4,), dtype=float, unit=rad),
Array(name=measure_post_phase, shape=(4,), dtype=float, unit=rad)]
Download
Download this file as Jupyter notebook: intro.ipynb.