Download

Download this file as Jupyter notebook: parameterized_linker_native_gates_sq.ipynb.

Compiling single-qubit native gates to waveforms

A ParameterizedLinker can be used to map a continuous family of gates, described by a ParameterizedGate, onto a continuous family of HardwareOperations. This is useful for compiling gates into their physical implementation as pulse sequences.

In this guide, we show how to compile a commonly-used native single-qubit gate into a typical pulse sequence. The gate is an arbitrary \(XY\) rotation; that is, a rotation about an arbitrary axis on the equator of the Bloch sphere. There are two independent parameters: theta, the rotation angle, and phi, the angle about which to rotate. For any qubit with an electric or magnetic dipole, this is natively implemented with a single pulse at the frequency of the transition between the states \(\ket{0}\) and \(\ket{1}\).

Creating the Linker

We first define all the targets in the system.

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

n_targets = 4
qubits = qcs.Qudits(range(n_targets))
drive_channels = qcs.Channels(range(n_targets), "xy_channels")

Next, we define the instruction we would like to be able to compile.

[3]:
zxz = qcs.ParametricGate(
    [-qcs.PAULIS.sigma_z, qcs.PAULIS.sigma_x, qcs.PAULIS.sigma_z],
    ["phi", "theta", "phi"],
)
phis = qcs.Array(name="phis", shape=len(qubits), dtype=float)
thetas = qcs.Array(name="thetas", shape=len(qubits), dtype=float)
zxz_param_gate = qcs.ParameterizedGate(zxz, [phis, thetas])

We also define the set of pulse parameters needed to implement the gate, with some initial guesses for their values.

[4]:
freq_values = [5e9 + 100e6 * n for n in range(n_targets)]
frequency_cals = qcs.Array("single_qubit_frequencies", value=freq_values, dtype=float)
amplitude_cals = qcs.Array("single_qubit_amplitudes", value=[0.5] * n_targets)
pulse_duration = qcs.Scalar("single_qubit_duration", value=30e-9, dtype=float)

Now we define the routine that is used to replace the gate, which is a small program specifying the physical implementation of the gate. We define this program to perform the operation on all Qudits in parallel on the system using the compact broadcasting rules of Array.

[5]:
replacement_program = qcs.Program()

# each channel can have an independent amplitude, encoded as
# the product of the intended angle of rotation and the calibrated amplitude scale

envelope = qcs.GaussianEnvelope()
# waveform phases are equal to intended axis of rotation
waveforms = qcs.RFWaveform(
    pulse_duration,
    envelope,
    amplitude_cals * thetas,
    rf_frequency=frequency_cals,
    instantaneous_phase=phis,
)

# add the waveforms to all channels
node = replacement_program.add_waveform(waveforms, drive_channels)

With the compilation target gate_instruction and the compilation output replacement_program defined, we can create a ParameterizedLinker to encapsulate this set of replacement rules.

[6]:
linker = qcs.ParameterizedLinker(zxz_param_gate, qubits, replacement_program)

Using the Linker

We can now use our linker to replace any ParameterizedGate that uses our native gate, with any subset of targets.

[7]:
program = qcs.Program()

some_qubits = qcs.Qudits([0, 2, 3])
some_thetas = qcs.Array("some_thetas", value=[np.pi / 2, np.pi / 3, np.pi / 4])
some_phis = qcs.Array("some_phis", value=[0.0, 0.0, np.pi / 2])
program.add_parametric_gate(zxz, [some_phis, some_thetas], some_qubits)

linker_pass = qcs.LinkerPass(linker)
program_compiled = linker_pass.apply(program)

We can visualize the resulting program at the waveform level:

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

The ParameterizedGate has been compiled into waveforms that effect a native microwave-driven single-qubit gates on each qubit. The waveforms have frequencies that directly come from the sq_freq_cals Array used by the ParameterizedLinker, and amplitudes that depend on the sq_amp_cals and the some_thetas defined in the program.

Finally, the phase of each of these waveforms depends only on the values of the Array some_phis. Namely, the waveform for label 3 is 90 degrees out of phase with the waveform for label 2.

Updating the Linker

Now we see how the ParameterizedLinker can serve as a store for dynamic calibration data. We update some of the calibration Arrays that reside in the linker, and re-plot the waveforms. Since parameters of the compiled programs are defined as a relation, they are responsive to changes in the ParameterizedLinker’s Arrays:

[9]:
linker.program.variables.single_qubit_frequencies[0].value += 50e6
linker.program.variables.single_qubit_amplitudes[2].value *= 0.5

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

Download

Download this file as Jupyter notebook: parameterized_linker_native_gates_sq.ipynb.

On this page