Download

Download this file as Jupyter notebook: calibration_set.ipynb.

Managing calibration data and linkers#

Note

This guide requires the asset calibration set.

In this guide, we show how to use a CalibrationSet to manage calibration data and the operation definitions provided by Linkerss. The CalibrationSet object provides a way to store and update calibration data across multiple objects simultaneously. The CalibrationSet used in this and other guides has the parameters listed below.

../../_images/calibration_set_variables.png

We work with Linkers for single-qubit \(XY\) rotations and multi-qubit cross-resonance gates, both common in superconducting systems. See Compiling single-qubit native gates to waveforms, Compiling multi-qubit native gates to waveforms, and Compiling measurements to waveforms and acquisitions to learn how to create these objects in detail.

Native gates to compile#

We first define the native gates we want to compile, represented by the following ParametricGates:

[2]:
import keysight.qcs as qcs
import numpy as np

# euler decomposition for XY rotations
layers = [-qcs.PAULIS.sigma_z, qcs.PAULIS.sigma_x, qcs.PAULIS.sigma_z]
zxz = qcs.ParametricGate(layers, ["phi", "theta", "phi"])

# cross-resonance multi-qubit gate
zx_ham = qcs.PAULIS.sigma_z & qcs.PAULIS.sigma_x
cr = qcs.ParametricGate([zx_ham], ["beta"])

Anatomy of a CalibrationSet#

A CalibrationSet contains a VariableSet and a dictionary of Linkerss. We load one with single- and multi-qubit linkers for our native gates.

[3]:
calibration_set = qcs.load("../../assets/calibration.qcs")

Variables#

The calibration variables can be accessed via variables.

[4]:
calibration_set.variable_list
[4]:
[Array(name=single_qubit_frequencies, shape=(4,), dtype=float, unit=Hz),
 Array(name=single_qubit_amplitudes, shape=(4,), dtype=float, unit=none),
 Array(name=multi_qubit_amplitudes, shape=(2,), dtype=float, unit=none),
 Scalar(name=single_qubit_duration, value=3e-08, dtype=float, unit=s),
 Scalar(name=multi_qubit_duration, value=3e-08, dtype=float, unit=s),
 Array(name=readout_frequencies, shape=(4,), dtype=float, unit=Hz),
 Scalar(name=readout_frequency_detuning, value=0.0, dtype=float, unit=Hz),
 Array(name=readout_amplitudes, shape=(4,), dtype=float, unit=none),
 Array(name=readout_phases, shape=(4,), dtype=float, unit=rad),
 Scalar(name=readout_plateau_time, value=1e-07, dtype=float, unit=s),
 Scalar(name=readout_rise_time, value=3e-08, dtype=float, unit=s),
 Scalar(name=readout_pulse_delay, value=4e-08, dtype=float, unit=s),
 Scalar(name=acquisition_duration, value=2e-07, dtype=float, unit=s),
 Array(name=ref_pts, shape=(4, 2), dtype=complex, unit=none),
 Array(name=phis, shape=(4,), dtype=float, unit=rad),
 Array(name=thetas, shape=(4,), dtype=float, unit=none),
 Array(name=beta, shape=(2,), dtype=float, unit=none),
 Scalar(name=alpha, value=0.33, dtype=float, unit=none)]

While it is convenient to save the full calibration set with variables and linkers in a single file, it is also possible to save the variables separately using the export_values() method, which creates a json file that can easily be edited.

This can be integrated into a workflow where calibration variables can be saved at certain times to track their history.

[5]:
# calibration_set.export_values("calibration_values_Jan_01_2024.qcs")

# the variables stored in this format are sorted by qudit:
print(open("calibration_values_Jan_01_2024.qcs").read())
{
  "qudits_0": {
    "phis": 0.0,
    "thetas": 0.0,
    "single_qubit_duration": 3E-08,
    "single_qubit_amplitudes": 0.1,
    "single_qubit_frequencies": 5000000000.0,
    "readout_pulse_delay": 4E-08,
    "readout_rise_time": 3E-08,
    "readout_amplitudes": 0.1,
    "readout_frequencies": 5800000000.0,
    "readout_frequency_detuning": 0.0,
    "readout_phases": 0.0,
    "readout_plateau_time": 1E-07,
    "acquisition_duration": 2E-07,
    "ref_pts": {
      "real": [
        1.0,
        1.0
      ],
      "imag": [
        1.0,
        -1.0
      ]
    }
  },
  "qudits_1": {
    "phis": 0.0,
    "thetas": 0.0,
    "single_qubit_duration": 3E-08,
    "single_qubit_amplitudes": 0.1,
    "single_qubit_frequencies": 5100000000.0,
    "readout_pulse_delay": 4E-08,
    "readout_rise_time": 3E-08,
    "readout_amplitudes": 0.1,
    "readout_frequencies": 5850000000.0,
    "readout_frequency_detuning": 0.0,
    "readout_phases": 0.0,
    "readout_plateau_time": 1E-07,
    "acquisition_duration": 2E-07,
    "ref_pts": {
      "real": [
        1.0,
        1.0
      ],
      "imag": [
        1.0,
        -1.0
      ]
    }
  },
  "qudits_2": {
    "phis": 0.0,
    "thetas": 0.0,
    "single_qubit_duration": 3E-08,
    "single_qubit_amplitudes": 0.1,
    "single_qubit_frequencies": 5200000000.0,
    "readout_pulse_delay": 4E-08,
    "readout_rise_time": 3E-08,
    "readout_amplitudes": 0.1,
    "readout_frequencies": 5900000000.0,
    "readout_frequency_detuning": 0.0,
    "readout_phases": 0.0,
    "readout_plateau_time": 1E-07,
    "acquisition_duration": 2E-07,
    "ref_pts": {
      "real": [
        1.0,
        1.0
      ],
      "imag": [
        1.0,
        -1.0
      ]
    }
  },
  "qudits_3": {
    "phis": 0.0,
    "thetas": 0.0,
    "single_qubit_duration": 3E-08,
    "single_qubit_amplitudes": 0.1,
    "single_qubit_frequencies": 5300000000.0,
    "readout_pulse_delay": 4E-08,
    "readout_rise_time": 3E-08,
    "readout_amplitudes": 0.1,
    "readout_frequencies": 5950000000.0,
    "readout_frequency_detuning": 0.0,
    "readout_phases": 0.0,
    "readout_plateau_time": 1E-07,
    "acquisition_duration": 2E-07,
    "ref_pts": {
      "real": [
        1.0,
        1.0
      ],
      "imag": [
        1.0,
        -1.0
      ]
    }
  },
  "couplings_0": {
    "beta": 0.0,
    "multi_qubit_duration": 3E-08,
    "multi_qubit_amplitudes": 0.1,
    "single_qubit_frequencies": 5000000000.0,
    "alpha": 0.33
  },
  "couplings_1": {
    "beta": 0.0,
    "multi_qubit_duration": 3E-08,
    "multi_qubit_amplitudes": 0.1,
    "single_qubit_frequencies": 5100000000.0,
    "alpha": 0.33
  }
}

We can edit the exported file and load the edits back in using the import_values() method:

[6]:
calibration_set.import_values("calibration_values_Jan_01_2024.qcs")

Linkers#

The dictionary of linkers can be accessed via linkers().

[7]:
list(calibration_set.linkers.keys())
[7]:
['single_qubit_linker', 'multi_qubit_linker_suppression', 'measurement_linker']

A CalibrationSet also has an active_linkers property that can be used to specify a subset of all available linkers. By default, all linkers are active and can be set using their names.

[8]:
calibration_set.active_linkers = ["single_qubit_linker", "measurement_linker"]
list(calibration_set.active_linkers.keys())
[8]:
['single_qubit_linker', 'measurement_linker']

Using the Linkers#

Now, using the linkers within, we can define gate-level programs and compile them:

[9]:
qubits = qcs.Qudits(range(2))

# define a sample program
program_to_compile = qcs.Program()

# apply X90 on qubit 0 and an X on qubit 1
phis = qcs.Array("phis", value=[0.0, 0.0])
thetas = qcs.Array("thetas", value=[np.pi / 2, np.pi])
program_to_compile.add_parametric_gate(zxz, [phis, thetas], qubits)

# apply (ZX)^1/2 on qubits (0, 1)
beta = qcs.Array("beta", value=[np.pi / 2])
multiqudits = qcs.MultiQudits.from_qudits((qubits[0], qubits[1]))
program_to_compile.add_parametric_gate(cr, [beta], multiqudits)

program_to_compile.draw()
Program
keysight-logo-svg
Program Body
Program
Duration undefined
Layers 2
Targets 2
Layer #0
Layer #0
Duration undefined
Layer #1
Layer #1
Duration undefined
qudits 0
PGATE
ParameterizedGate on ('qudits', 0)

Parameters phi, theta, phi
Values Array(name=phis, shape=(2,), dtype=float, unit=none)
Array(name=thetas, shape=(2,), dtype=float, unit=none)

Matrices
-1 0
0 1
0 1
1 0
1 0
0 -1
MULTIGATE
ParameterizedGate on (('qudits', 0), ('qudits', 1))

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

Matrices
0 1 0 0
1 0 0 0
0 0 0 -1
0 0 -1 0
1
PGATE
ParameterizedGate on ('qudits', 1)

Parameters phi, theta, phi
Values Array(name=phis, shape=(2,), dtype=float, unit=none)
Array(name=thetas, shape=(2,), dtype=float, unit=none)

Matrices
-1 0
0 1
0 1
1 0
1 0
0 -1
MULTIGATE
ParameterizedGate on (('qudits', 1), ('qudits', 0))

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

Matrices
0 1 0 0
1 0 0 0
0 0 0 -1
0 0 -1 0

Next, we define a LinkerPass to compile the program:

[10]:
linker_pass = qcs.LinkerPass(*calibration_set.linkers.values())
program_compiled = linker_pass.apply(program_to_compile)
program_compiled.draw()
Program
keysight-logo-svg
Program Body
Program
Duration 60 ns
Layers 2
Targets 2
Layer #0
Layer #0
Duration 30 ns
Layer #1
Layer #1
Duration 30 ns
xy_channels 0
RFWaveform on ('xy_channels', 0)

Parameters
Duration Scalar(name=single_qubit_duration, value=30 ns, dtype=float, unit=s)
Amplitude ArraySlice(name=_implicit, shape=(2,), dtype=float, unit=none, value=[0.1571, 0.3142])
Frequency ArraySlice(name=single_qubit_frequencies, shape=(2,), dtype=float, unit=Hz, value=[5 GHz, 5.1 GHz])
Envelope GaussianEnvelope(2.0)
Instantaneous Phase ArraySlice(name=_implicit, shape=(2,), dtype=float, unit=rad, value=[0 rad, 0 rad])
Post-phase 0 rad
RFWaveform on ('xy_channels', 0)

Parameters
Duration Scalar(name=multi_qubit_duration, value=30 ns, dtype=float, unit=s)
Amplitude ScalarRef(name=_implicit, value=0.1571, dtype=float, unit=none)
Frequency ScalarRef(name=single_qubit_frequencies, value=5.1 GHz, dtype=float, unit=Hz)
Envelope GaussianEnvelope(2.0)
Instantaneous Phase 0 rad
Post-phase 0 rad
1
RFWaveform on ('xy_channels', 1)

Parameters
Duration Scalar(name=single_qubit_duration, value=30 ns, dtype=float, unit=s)
Amplitude ArraySlice(name=_implicit, shape=(2,), dtype=float, unit=none, value=[0.1571, 0.3142])
Frequency ArraySlice(name=single_qubit_frequencies, shape=(2,), dtype=float, unit=Hz, value=[5 GHz, 5.1 GHz])
Envelope GaussianEnvelope(2.0)
Instantaneous Phase ArraySlice(name=_implicit, shape=(2,), dtype=float, unit=rad, value=[0 rad, 0 rad])
Post-phase 0 rad
RFWaveform on ('xy_channels', 1)

Parameters
Duration Scalar(name=multi_qubit_duration, value=30 ns, dtype=float, unit=s)
Amplitude ScalarRef(name=_implicit, value=0.0518, dtype=float, unit=none)
Frequency ScalarRef(name=single_qubit_frequencies, value=5.1 GHz, dtype=float, unit=Hz)
Envelope GaussianEnvelope(2.0)
Instantaneous Phase 0 rad
Post-phase 0 rad

As this program now only contains waveforms, we render it:

[11]:
program_compiled.render(
    channel_subplots=False,
    lo_frequency=5e9,
    sample_rate=qcs.SAMPLE_RATES[qcs.InstrumentEnum.M5300AWG],
)

Updating calibration data#

We can update the values of the variables in the CalibrationSet to update all the variables in Linkerss that use them. Since the cross-resonance gate uses the single-qubit frequencies, both waveforms are updated to reflect the new value.

[12]:
calibration_set.variables.single_qubit_frequencies.value[1] += 100e6

program_compiled.render(
    channel_subplots=False,
    lo_frequency=5e9,
    sample_rate=qcs.SAMPLE_RATES[qcs.InstrumentEnum.M5300AWG],
)

Adding new linkers#

Finally, we can create new linkers with the variables in and add new variables to the calibration set, then use the add_linker() method to so it can be saved, loaded, and updated across multiple sessions.

[13]:
operation = calibration_set.linkers["multi_qubit_linker_suppression"].operation
targets = calibration_set.linkers["multi_qubit_linker_suppression"].targets

mq_amp_cals = calibration_set.variables.multi_qubit_amplitudes
frequency_cals = calibration_set.variables.single_qubit_frequencies

# # define a new duration for this gate
mq_dur = qcs.Scalar("multi_qubit_duration_new", value=20e-9, dtype=float)
calibration_set.variables.declare(mq_dur)

gamma = qcs.Array(name="gamma", shape=mq_amp_cals.shape, dtype=float)

prog = qcs.Program()
channels = qcs.Channels([0, 2], "xy_channels")
envelope = qcs.GaussianEnvelope()
pulse = qcs.RFWaveform(mq_dur, envelope, gamma * mq_amp_cals, frequency_cals[1::2])

prog.add_waveform(pulse, channels)

calibration_set.add_linker(
    "multi_qubit_linker", qcs.ParameterizedLinker(operation, targets, prog)
)

Download

Download this file as Jupyter notebook: calibration_set.ipynb.