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 target qudit at the frequency of the control qudits, and a waveform with attenuated amplitude on the control qudits. We map our first two qudits (indexed 0 and 1) to targets and the latter two to controls.

[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 target waveform with the frequency of the control qudits cr_target_waveform = qcs.RFWaveform( dur, qcs.GaussianEnvelope(), amps * angles, qudit_frequencies[2:] ) # play a compensating waveform on the control qudits with smaller amplitude cr_control_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", cr_target_waveform, control_waveform=cr_control_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()
keysight-logo-svg
Program
Program
Duration undefined
Layers 3
Targets 4
Repetitions
Layer #0
Layer #0
Duration undefined
Layer #1
Layer #1
Duration undefined
Layer #2
Layer #2
Duration undefined
qudits 0
X
Gate X on ('qudits', 0)

Matrix:
0 1
1 0
MULTIGATE
ParameterizedGate on (('qudits', 0), ('qudits', 2))

Parameters beta
Values ScalarRef(name=beta, value=0.3, dtype=float, unit=none)

Matrices
0 1 0 0
1 0 0 0
0 0 0 -1
0 0 -1 0
Measure on ('qudits', 0)

Parameters
Dim 2
1
X
Gate X on ('qudits', 1)

Matrix:
0 1
1 0
MULTIGATE
ParameterizedGate on (('qudits', 1), ('qudits', 3))

Parameters beta
Values ScalarRef(name=beta, value=0.3, dtype=float, unit=none)

Matrices
0 1 0 0
1 0 0 0
0 0 0 -1
0 0 -1 0
Measure on ('qudits', 1)

Parameters
Dim 2
2
MULTIGATE
ParameterizedGate on (('qudits', 2), ('qudits', 0))

Parameters beta
Values ScalarRef(name=beta, value=0.3, dtype=float, unit=none)

Matrices
0 1 0 0
1 0 0 0
0 0 0 -1
0 0 -1 0
Measure on ('qudits', 2)

Parameters
Dim 2
3
MULTIGATE
ParameterizedGate on (('qudits', 3), ('qudits', 1))

Parameters beta
Values ScalarRef(name=beta, value=0.3, dtype=float, unit=none)

Matrices
0 1 0 0
1 0 0 0
0 0 0 -1
0 0 -1 0
Measure on ('qudits', 3)

Parameters
Dim 2

We can now compile this program using our calibration set.

[12]:
compiled_program = qcs.LinkerPass(*calset.linkers.values()).apply(program)

compiled_program.draw()
keysight-logo-svg
Program
Program
Duration 410 ns
Layers 3
Targets 8
Repetitions
Layer #0
Layer #0
Duration 50 ns
Layer #1
Layer #1
Duration 60 ns
Layer #2
Layer #2
Duration 300 ns
control 0
RFWaveform on ('control', 0)

Parameters
Duration ScalarRef(name=x_duration, value=50 ns, dtype=float, unit=s)
Amplitude ScalarRef(name=x_amplitude, value=0.5, dtype=float, unit=none)
Frequency ScalarRef(name=qudit_freqs, value=4.91 GHz, dtype=float, unit=Hz)
Envelope GaussianEnvelope(2.0)
Instantaneous Phase ScalarRef(name=x_phase, value=0 rad, dtype=float, unit=rad)
Post-phase ScalarRef(name=x_post_phase, value=0 rad, dtype=float, unit=rad)
RFWaveform on ('control', 0)

Parameters
Duration ScalarRef(name=multi_qubit_duration, value=50 ns, dtype=float, unit=s)
Amplitude ScalarRef(name=_implicit, value=0.06, dtype=float, unit=none)
Frequency ScalarRef(name=qudit_freqs, value=4.93 GHz, dtype=float, unit=Hz)
Envelope GaussianEnvelope(2.0)
Instantaneous Phase ScalarRef(name=CR_control_phase, value=0 rad, dtype=float, unit=rad)
Post-phase ScalarRef(name=CR_control_post_phase, value=0 rad, dtype=float, unit=rad)
RFWaveform on ('control', 0)

Parameters
Duration ArraySlice(name=measure_duration, shape=(4,), dtype=float, unit=s, value=[300 ns, 300 ns, 300 ns, 300 ns])
Amplitude ArraySlice(name=measure_amplitude, shape=(4,), dtype=float, unit=none, value=[0.5, 0.5, 0.5, 0.5])
Frequency ArraySlice(name=readout_freqs, shape=(4,), dtype=float, unit=Hz, value=[5.91 GHz, 5.92 GHz, 5.93 GHz, 5.94 GHz])
Envelope GaussianEnvelope(2.0)
Instantaneous Phase ArraySlice(name=measure_phase, shape=(4,), dtype=float, unit=rad, value=[0 rad, 0 rad, 0 rad, 0 rad])
Post-phase ArraySlice(name=measure_post_phase, shape=(4,), dtype=float, unit=rad, value=[0 rad, 0 rad, 0 rad, 0 rad])
1
RFWaveform on ('control', 1)

Parameters
Duration ScalarRef(name=x_duration, value=50 ns, dtype=float, unit=s)
Amplitude ScalarRef(name=x_amplitude, value=0.5, dtype=float, unit=none)
Frequency ScalarRef(name=qudit_freqs, value=4.92 GHz, dtype=float, unit=Hz)
Envelope GaussianEnvelope(2.0)
Instantaneous Phase ScalarRef(name=x_phase, value=0 rad, dtype=float, unit=rad)
Post-phase ScalarRef(name=x_post_phase, value=0 rad, dtype=float, unit=rad)
RFWaveform on ('control', 1)

Parameters
Duration ScalarRef(name=multi_qubit_duration, value=60 ns, dtype=float, unit=s)
Amplitude ScalarRef(name=_implicit, value=0.06, dtype=float, unit=none)
Frequency ScalarRef(name=qudit_freqs, value=4.94 GHz, dtype=float, unit=Hz)
Envelope GaussianEnvelope(2.0)
Instantaneous Phase ScalarRef(name=CR_control_phase, value=0 rad, dtype=float, unit=rad)
Post-phase ScalarRef(name=CR_control_post_phase, value=0 rad, dtype=float, unit=rad)
RFWaveform on ('control', 1)

Parameters
Duration ArraySlice(name=measure_duration, shape=(4,), dtype=float, unit=s, value=[300 ns, 300 ns, 300 ns, 300 ns])
Amplitude ArraySlice(name=measure_amplitude, shape=(4,), dtype=float, unit=none, value=[0.5, 0.5, 0.5, 0.5])
Frequency ArraySlice(name=readout_freqs, shape=(4,), dtype=float, unit=Hz, value=[5.91 GHz, 5.92 GHz, 5.93 GHz, 5.94 GHz])
Envelope GaussianEnvelope(2.0)
Instantaneous Phase ArraySlice(name=measure_phase, shape=(4,), dtype=float, unit=rad, value=[0 rad, 0 rad, 0 rad, 0 rad])
Post-phase ArraySlice(name=measure_post_phase, shape=(4,), dtype=float, unit=rad, value=[0 rad, 0 rad, 0 rad, 0 rad])
2
RFWaveform on ('control', 2)

Parameters
Duration ScalarRef(name=multi_qubit_duration, value=50 ns, dtype=float, unit=s)
Amplitude ScalarRef(name=_implicit, value=0.3, dtype=float, unit=none)
Frequency ScalarRef(name=qudit_freqs, value=4.93 GHz, dtype=float, unit=Hz)
Envelope GaussianEnvelope(2.0)
Instantaneous Phase ScalarRef(name=CR_target_phase, value=0 rad, dtype=float, unit=rad)
Post-phase ScalarRef(name=CR_target_post_phase, value=0 rad, dtype=float, unit=rad)
RFWaveform on ('control', 2)

Parameters
Duration ArraySlice(name=measure_duration, shape=(4,), dtype=float, unit=s, value=[300 ns, 300 ns, 300 ns, 300 ns])
Amplitude ArraySlice(name=measure_amplitude, shape=(4,), dtype=float, unit=none, value=[0.5, 0.5, 0.5, 0.5])
Frequency ArraySlice(name=readout_freqs, shape=(4,), dtype=float, unit=Hz, value=[5.91 GHz, 5.92 GHz, 5.93 GHz, 5.94 GHz])
Envelope GaussianEnvelope(2.0)
Instantaneous Phase ArraySlice(name=measure_phase, shape=(4,), dtype=float, unit=rad, value=[0 rad, 0 rad, 0 rad, 0 rad])
Post-phase ArraySlice(name=measure_post_phase, shape=(4,), dtype=float, unit=rad, value=[0 rad, 0 rad, 0 rad, 0 rad])
3
RFWaveform on ('control', 3)

Parameters
Duration ScalarRef(name=multi_qubit_duration, value=60 ns, dtype=float, unit=s)
Amplitude ScalarRef(name=_implicit, value=0.3, dtype=float, unit=none)
Frequency ScalarRef(name=qudit_freqs, value=4.94 GHz, dtype=float, unit=Hz)
Envelope GaussianEnvelope(2.0)
Instantaneous Phase ScalarRef(name=CR_target_phase, value=0 rad, dtype=float, unit=rad)
Post-phase ScalarRef(name=CR_target_post_phase, value=0 rad, dtype=float, unit=rad)
RFWaveform on ('control', 3)

Parameters
Duration ArraySlice(name=measure_duration, shape=(4,), dtype=float, unit=s, value=[300 ns, 300 ns, 300 ns, 300 ns])
Amplitude ArraySlice(name=measure_amplitude, shape=(4,), dtype=float, unit=none, value=[0.5, 0.5, 0.5, 0.5])
Frequency ArraySlice(name=readout_freqs, shape=(4,), dtype=float, unit=Hz, value=[5.91 GHz, 5.92 GHz, 5.93 GHz, 5.94 GHz])
Envelope GaussianEnvelope(2.0)
Instantaneous Phase ArraySlice(name=measure_phase, shape=(4,), dtype=float, unit=rad, value=[0 rad, 0 rad, 0 rad, 0 rad])
Post-phase ArraySlice(name=measure_post_phase, shape=(4,), dtype=float, unit=rad, value=[0 rad, 0 rad, 0 rad, 0 rad])
readout 0
Acquisition on ('readout', 0)

Parameters
Duration ArraySlice(name=measure_duration, shape=(4,), dtype=float, unit=s, value=[300 ns, 300 ns, 300 ns, 300 ns])
1
Acquisition on ('readout', 1)

Parameters
Duration ArraySlice(name=measure_duration, shape=(4,), dtype=float, unit=s, value=[300 ns, 300 ns, 300 ns, 300 ns])
2
Acquisition on ('readout', 2)

Parameters
Duration ArraySlice(name=measure_duration, shape=(4,), dtype=float, unit=s, value=[300 ns, 300 ns, 300 ns, 300 ns])
3
Acquisition on ('readout', 3)

Parameters
Duration ArraySlice(name=measure_duration, shape=(4,), dtype=float, unit=s, value=[300 ns, 300 ns, 300 ns, 300 ns])

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_target_phase, shape=(2,), dtype=float, unit=rad),
 Array(name=CR_target_post_phase, shape=(2,), dtype=float, unit=rad),
 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=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.

On this page