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 HardwareOperation
s.
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
Array
s 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
Array
s:
[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.