Customize gate calibrations
The pulse implementation of a gate is called “gate calibration”. Gaining access to gate calibrations empowers you to experiment with quantum gate designs and execute quantum programs with customized, improved gate calibrations. This notebook shows you how to access the native gate calibrations provided by quantum hardware providers, and shows you how to define custom gate calibrations for your quantum programs.
We start with basic imports, and initialize the Rigetti Cepheus-1-108Q device which is the targeted device for this example notebook.
[1]:
# general imports
import numpy as np
# AutoQASM imports
import autoqasm as aq
from autoqasm import pulse
from autoqasm.instructions import measure, rx, rz
# AWS imports: Import Braket SDK modules
from braket.aws import AwsDevice
from braket.circuits import FreeParameter, Gate, QubitSet
from braket.devices import Devices
from braket.pulse import DragGaussianWaveform
[2]:
device = AwsDevice(Devices.Rigetti.Cepheus1108Q)
Gate calibrations from hardware providers
To customize a gate calibration, you often need to be intimately familiar with how quantum gates are implemented with pulses. It is often a good place to start by viewing the native gate calibrations supplied by hardware providers. To do so, we first retrieve the information from the gate_calibrations attribute of a device.
[3]:
provider_calibrations = device.gate_calibrations
Even for the same gate, different physical qubits and angles usually have a different pulse implementation. Because of this, a gate calibration may be associated with specific physical qubits and angles. In the code snippet below, we retrieve the gate calibration of the Rx gate for physical qubit $0 and angle=pi/2.
[4]:
pulse_sequence_rx_pi_2_q0 = provider_calibrations.pulse_sequences[(Gate.Rx(np.pi / 2), QubitSet(0))]
print(pulse_sequence_rx_pi_2_q0.to_ir())
OPENQASM 3.0;
cal {
waveform wf_drag_gaussian_109 = drag_gaussian(32.0ns, 6.794574402304ns, 1.392566621453956e-09, 0.39105220711257643, false);
barrier $0;
play(Transmon_0_charge_tx, wf_drag_gaussian_109);
barrier $0;
}
Some gate calibrations are defined not with fixed values of angles, but rather with variables. In this case, these gate calibrations can be retrieved with angles of FreeParameter type. For example, the code snippet below retrieves the gate calibration of the Rz gate on the qubit $1 with a variable angle theta.
[5]:
theta = FreeParameter("theta")
pulse_sequence_rz_theta_q0 = provider_calibrations.pulse_sequences[(Gate.Rz(theta), QubitSet(1))]
print(pulse_sequence_rz_theta_q0.to_ir())
OPENQASM 3.0;
cal {
input float theta;
shift_phase(Transmon_1_charge_tx, -1.0 * theta);
}
Compose custom gate calibrations with AutoQASM
In this section, we show you how to customize a gate calibration and use it in a quantum program. To create a custom gate calibration, you need to
Find the definition of the gate in AutoQASM.
Decide the qubits and angles you want to define the calibration.
Compose a pulse program that defines the calibration.
As an example, let’s define the gate calibration of rx on qubit $1 and at angle pi/2. We first inspect the definition of the rx gate. You can find the definition of gate instructions in the gate module of AutoQASM. You can also use the Python built-in help function to retrieve the definition. A gate calibration for the rx gate must fully specify the inputs of the gate, target
and theta.
[6]:
help(rx)
Help on function rx in module autoqasm.instructions.gates:
rx(
target: int | str | braket.registers.qubit.Qubit | oqpy.classical_types._ClassicalVar | oqpy.base.OQPyExpression | oqpy.quantum_types.Qubit,
theta: float | braket.parametric.free_parameter_expression.FreeParameterExpression | oqpy.classical_types._ClassicalVar,
**kwargs
) -> None
X-axis rotation gate.
Args:
target (QubitIdentifierType): Target qubit.
theta (GateParameterType): Rotation angle in radians.
To specify fixed values for the parameters target and theta, we set the values through keyword arguments of the decorator. The pulse implementation for the gate calibration is in the body of the my_rx_cal function decorated by @aq.gate_calibration. In the example in the next cell, we want to experiment with offsetting the frequency of the pulse by 100 Hz away from the hardware provider’s implementation. The gate calibration my_rx_cal recreates the hardware provider’s
implementation but with an offset in the frequency.
[7]:
wf = DragGaussianWaveform(
24e-9, 2.547965400864e-9, 2.370235498840002e-10, 0.293350447987059, False, "wf_drag_gaussian_1"
)
q0_control_frame = device.frames["Transmon_0_charge_tx"]
@aq.gate_calibration(implements=rx, target="$0", theta=np.pi / 2)
def my_rx_cal():
pulse.barrier("$0")
pulse.shift_frequency(q0_control_frame, -321047.14178613486 - 100)
pulse.play(q0_control_frame, wf)
pulse.shift_frequency(q0_control_frame, 321047.14178613486 + 100)
pulse.barrier("$0")
Next, we will demonstrate how to attach my_rx_cal to the main quantum program my_program. The program is defined in the code block below.
[8]:
@aq.main
def my_program():
rx("$0", np.pi / 2)
rz("$1", 0.123)
measure("$0")
print(my_program.build().to_ir())
OPENQASM 3.0;
rx(1.5707963267948966) $0;
rz(0.123) $1;
bit __bit_0__;
__bit_0__ = measure $0;
To attach gate calibrations to the program, call the with_calibrations method on the built program to create a new program with my_rx_cal attached, leaving the original intact. This allows you to experiment with different gate calibrations on the same program without the need to redefine the main program every time.
In the printed OpenQASM script of the program, the gate definition for the rx gate becomes a defcal block.
[9]:
custom_program = my_program.build().with_calibrations(my_rx_cal)
print(custom_program.to_ir())
OPENQASM 3.0;
cal {
waveform wf_drag_gaussian_1 = drag_gaussian(24.0ns, 2.547965400864ns, 2.370235498840002e-10, 0.293350447987059, false);
}
defcal rx(1.5707963267948966) $0 {
barrier $0;
shift_frequency(Transmon_0_charge_tx, -321147.14178613486);
play(Transmon_0_charge_tx, wf_drag_gaussian_1);
shift_frequency(Transmon_0_charge_tx, 321147.14178613486);
barrier $0;
}
rx(1.5707963267948966) $0;
rz(0.123) $1;
bit __bit_0__;
__bit_0__ = measure $0;
You can also define a gate calibration with variable parameters. Variable parameters are used in the body of the decorated function. The variable parameters must be arguments of the decorated Python function, instead of being arguments to the decorator @aq.gate_calibration. The variable parameters must have a type hint of either aq.Qubit for qubits or float for angles. Let’s define another gate calibration with a variable parameter. This time, we define a calibration for the rz
gate on qubit $1 and a variable angle theta.
[10]:
q1_control_frame = device.frames["Transmon_1_charge_tx"]
@aq.gate_calibration(implements=rz, target="$1")
def my_rz_cal(theta: float):
pulse.barrier("$1")
pulse.shift_frequency(q1_control_frame, -1.0 * theta)
pulse.barrier("$1")
We then attach this gate calibration, my_rz_cal, to the main program we composed previously, together with the other gate calibration, my_rx_cal.
[11]:
custom_program = my_program.build().with_calibrations([my_rx_cal, my_rz_cal])
print(custom_program.to_ir())
OPENQASM 3.0;
cal {
waveform wf_drag_gaussian_1 = drag_gaussian(24.0ns, 2.547965400864ns, 2.370235498840002e-10, 0.293350447987059, false);
}
defcal rx(1.5707963267948966) $0 {
barrier $0;
shift_frequency(Transmon_0_charge_tx, -321147.14178613486);
play(Transmon_0_charge_tx, wf_drag_gaussian_1);
shift_frequency(Transmon_0_charge_tx, 321147.14178613486);
barrier $0;
}
defcal rz(angle[32] theta) $1 {
barrier $1;
shift_frequency(Transmon_1_charge_tx, -1.0 * theta);
barrier $1;
}
rx(1.5707963267948966) $0;
rz(0.123) $1;
bit __bit_0__;
__bit_0__ = measure $0;
In the printed OpenQASM script of the program, the gate definitions for the rx gate and the rz become defcal blocks of the program. The variable parameter angle for the rz gate is captured.
Summary
This example notebook shows you how to retrieve and view the gate calibrations supplied by hardware providers. With AutoQASM, you can customize gate calibrations to explore different pulse implementations of gates. You can define gate calibrations against fixed values of gate parameters, as well as using parametric definitions for variable parameters. Multiple gate calibrations can be attached to a program to create a new program. This makes it easy to experiment with different gate calibrations on a single program.