Note
Run interactively in jupyter notebook.
Fine Calibrations¶
The amplitude of a pulse can be precisely calibrated using error amplifying gate sequences. These gate sequences apply the same gate a variable number of times. Therefore, if each gate has a small error \(\delta\theta\) in the rotation angle then a sequence of \(n\) gates will have a rotation error of \(n\cdot\delta\theta\). We will work with ibmq_armonk
and compare our results to those reported by the backend.
[1]:
import numpy as np
from qiskit import IBMQ
from qiskit.pulse import InstructionScheduleMap
import qiskit.pulse as pulse
from qiskit_experiments.library import FineXAmplitude, FineSXAmplitude
[2]:
IBMQ.load_account()
provider = IBMQ.get_provider(hub='ibm-q', group='open', project='main')
backend = provider.get_backend('ibmq_armonk')
[3]:
qubit = 0
Instruction schedule map¶
We will run the fine calibration experiments with our own pulse schedules. To do this we create an instruction to schedule map which we populate with the schedules we wish to work with. This instruction schedule map is then given to the transpile options of the calibration experiments so that the Qiskit transpiler can attach the pulse schedules to the gates in the experiments. We will base all our pulses on the default X
pulse of Armonk.
[4]:
x_pulse = backend.defaults().instruction_schedule_map.get('x', (qubit,)).instructions[0][1].pulse
x_pulse
[4]:
Drag(duration=320, amp=(0.9017311554901297+0j), sigma=80, beta=-0.9639957979207219, name='Xp_d0')
[5]:
# create the schedules we need and add them to an instruction schedule map.
sx_pulse = pulse.Drag(x_pulse.duration, 0.5*x_pulse.amp, x_pulse.sigma, x_pulse.beta, name="SXp_d0")
y_pulse = pulse.Drag(x_pulse.duration, 1.0j*x_pulse.amp, x_pulse.sigma, x_pulse.beta, name="Yp_d0")
d0, inst_map = pulse.DriveChannel(qubit), InstructionScheduleMap()
for name, pulse_ in [("x", x_pulse), ("y", y_pulse), ("sx", sx_pulse)]:
with pulse.build(name=name) as sched:
pulse.play(pulse_, d0)
inst_map.add(name, (qubit,), sched)
Fine Amplitude Calibration¶
[6]:
ideal_amp = x_pulse.amp
print(f"The reported amplitude of the X pulse is {ideal_amp:.4f}.")
The reported amplitude of the X pulse is 0.9017+0.0000j.
Detecting an over-rotated pulse¶
We now take the x pulse reported by the backend and add a 2% overrotation to it by scaling the amplitude and see if the experiment can detect this overrotation. We replace the default X
pulse in the instruction schedule map with this overrotated pulse.
[7]:
pulse_amp = ideal_amp*1.02
target_angle = np.pi
with pulse.build(backend=backend, name="x") as x_over:
pulse.play(pulse.Drag(x_pulse.duration, pulse_amp, x_pulse.sigma, x_pulse.beta), d0)
inst_map.add("x", (qubit,), x_over)
[8]:
amp_cal = FineXAmplitude(qubit, backend=backend)
amp_cal.set_transpile_options(inst_map=inst_map)
Observe here that we added a square-root of X pulse before appyling the error amplifying sequence. This is done to be able to distinguish between over-rotated and under-rotated pulses.
[9]:
amp_cal.circuits()[5].draw(output="mpl")
[9]:

[10]:
data_over = amp_cal.run().block_for_results()
[11]:
data_over.figure(0)
[11]:

[12]:
print(data_over.analysis_results("d_theta"))
DbAnalysisResultV1
- name: d_theta
- value: 0.057015162697548215 ± 0.001116534957642551
- χ²: 10.326624587562133
- quality: bad
- device_components: ['Q0']
- verified: False
[13]:
dtheta = data_over.analysis_results("d_theta").value.value
scale = target_angle / (target_angle + dtheta)
print(f"The ideal angle is {target_angle:.2f} rad. We measured a deviation of {dtheta:.3f} rad.")
print(f"Thus, scale the {pulse_amp:.4f} pulse amplitude by {scale:.3f} to obtain {pulse_amp*scale:.5f}.")
print(f"Amplitude reported by the backend {ideal_amp:.4f}.")
The ideal angle is 3.14 rad. We measured a deviation of 0.057 rad.
Thus, scale the 0.9198+0.0000j pulse amplitude by 0.982 to obtain 0.90337+0.00000j.
Amplitude reported by the backend 0.9017+0.0000j.
Detecting an under-rotated pulse¶
[14]:
pulse_amp = ideal_amp*0.98
target_angle = np.pi
with pulse.build(backend=backend, name="xp") as x_under:
pulse.play(pulse.Drag(x_pulse.duration, pulse_amp, x_pulse.sigma, x_pulse.beta), d0)
inst_map.add("x", (qubit,), x_under)
[15]:
amp_cal = FineXAmplitude(qubit, backend=backend)
amp_cal.set_transpile_options(inst_map=inst_map)
[16]:
data_under = amp_cal.run().block_for_results()
[17]:
data_under.figure(0)
[17]:

[18]:
print(data_under.analysis_results("d_theta"))
DbAnalysisResultV1
- name: d_theta
- value: -0.04063457298918617 ± 0.0008703716859582015
- χ²: 9.476715071582424
- quality: bad
- device_components: ['Q0']
- verified: False
[19]:
dtheta = data_under.analysis_results("d_theta").value.value
scale = target_angle / (target_angle + dtheta)
print(f"The ideal angle is {target_angle:.2f} rad. We measured a deviation of {dtheta:.3f} rad.")
print(f"Thus, scale the {pulse_amp:.4f} pulse amplitude by {scale:.3f} to obtain {pulse_amp*scale:.5f}.")
print(f"Amplitude reported by the backend {ideal_amp:.4f}.")
The ideal angle is 3.14 rad. We measured a deviation of -0.041 rad.
Thus, scale the 0.8837+0.0000j pulse amplitude by 1.013 to obtain 0.89528+0.00000j.
Amplitude reported by the backend 0.9017+0.0000j.
Analyzing a \(\frac{\pi}{2}\) pulse¶
We now consider the \(\frac{\pi}{2}\) rotation. Note that in this case we do not need to add a \(\frac{\pi}{2}\) rotation to the circuits.
[20]:
# restor the x_pulse
inst_map.add("x", (qubit,), backend.defaults().instruction_schedule_map.get('x', (qubit,)))
[21]:
amp_cal = FineSXAmplitude(qubit, backend)
amp_cal.set_transpile_options(inst_map=inst_map)
[22]:
amp_cal.circuits()[5].draw(output="mpl")
[22]:

[23]:
data_x90p = amp_cal.run().block_for_results()
[24]:
data_x90p.figure(0)
[24]:

[25]:
print(data_x90p.analysis_results("d_theta"))
DbAnalysisResultV1
- name: d_theta
- value: 0.11453288392453519 ± 0.0004978518661405299
- χ²: 0.9575406908660448
- quality: good
- device_components: ['Q0']
- verified: False
[26]:
sx = backend.defaults().instruction_schedule_map.get('sx', (qubit,))
sx_ideal_amp = sx.instructions[0][1].pulse.amp
target_angle = np.pi / 2
dtheta = data_x90p.analysis_results("d_theta").value.value
scale = target_angle / (target_angle + dtheta)
print(f"The ideal angle is {target_angle:.2f} rad. We measured a deviation of {dtheta:.3f} rad.")
print(f"Thus, scale the {sx_pulse.amp:.4f} pulse amplitude by {scale:.3f} to obtain {sx_pulse.amp*scale:.5f}.")
print(f"Amplitude reported by the backend {sx_ideal_amp:.4f}.")
The ideal angle is 1.57 rad. We measured a deviation of 0.115 rad.
Thus, scale the 0.4509+0.0000j pulse amplitude by 0.932 to obtain 0.42023+0.00000j.
Amplitude reported by the backend 0.4062-0.1000j.
Let’s rerun this calibration using the updated value of the amplitude of the \(\frac{\pi}{2}\) pulse.
[27]:
pulse_amp = sx_pulse.amp*scale
with pulse.build(backend=backend, name="sx") as sx_new:
pulse.play(pulse.Drag(x_pulse.duration, pulse_amp, x_pulse.sigma, x_pulse.beta), d0)
inst_map.add("sx", (qubit,), sx_new)
[28]:
data_x90p = amp_cal.run().block_for_results()
[29]:
data_x90p.figure(0)
[29]:

[30]:
print(data_x90p.analysis_results("d_theta"))
DbAnalysisResultV1
- name: d_theta
- value: 0.0035757081871945428 ± 0.0003946947710710705
- χ²: 1.2646181222690043
- quality: good
- device_components: ['Q0']
- verified: False
[31]:
dtheta = data_x90p.analysis_results("d_theta").value.value
scale = target_angle / (target_angle + dtheta)
print(f"The ideal angle is {target_angle:.2f} rad. We measured a deviation of {dtheta:.3f} rad.")
print(f"Thus, scale the {pulse_amp:.4f} pulse amplitude by {scale:.3f} to obtain {pulse_amp*scale:.5f}.")
print(f"Amplitude reported by the backend {sx_ideal_amp:.4f}.")
The ideal angle is 1.57 rad. We measured a deviation of 0.004 rad.
Thus, scale the 0.4202+0.0000j pulse amplitude by 0.998 to obtain 0.41927+0.00000j.
Amplitude reported by the backend 0.4062-0.1000j.
Fine DRAG Calibrations¶
[32]:
from qiskit_experiments.library import FineXDrag
[33]:
ideal_beta = x_pulse.beta
print(f"The reported beta of the X pulse is {ideal_beta:.4f}.")
The reported beta of the X pulse is -0.9640.
[34]:
pulse_beta = ideal_beta*1.25
target_angle = np.pi
with pulse.build(backend=backend, name="x") as x_over:
pulse.play(pulse.Drag(x_pulse.duration, x_pulse.amp, x_pulse.sigma, pulse_beta), d0)
inst_map.add("x", (qubit,), x_over)
[35]:
drag_cal = FineXDrag(qubit, backend)
drag_cal.set_transpile_options(inst_map=inst_map)
[36]:
drag_cal.circuits()[2].draw("mpl")
[36]:

[37]:
data_drag_x = drag_cal.run().block_for_results()
[38]:
data_drag_x.figure(0)
[38]:

[39]:
print(data_drag_x.analysis_results(0))
DbAnalysisResultV1
- name: @Parameters_FineDragAnalysis
- value: [-0.01416042 0.48915581] ± [0.00062282 0.0034207 ]
- χ²: 1.0082917682360732
- quality: good
- extra: <4 items>
- device_components: ['Q0']
- verified: False
[40]:
data_drag_x.analysis_results("d_theta").value.value
[40]:
-0.014160419499688572
[41]:
dtheta = data_drag_x.analysis_results("d_theta").value.value
ddelta = -0.25 * np.sqrt(np.pi) * dtheta * x_pulse.sigma / ((target_angle**2) / 4)
print(f"Adjust β={pulse_beta:.3f} by ddelta={ddelta:.3f} to get {ddelta + pulse_beta:.3f} as new β.")
print(f"The backend reports β={x_pulse.beta:.3f}")
Adjust β=-1.205 by ddelta=0.203 to get -1.002 as new β.
The backend reports β=-0.964
Half angle calibrations¶
Phase errors imply that it is possible for the sx
and x
pulse to be misaligned. This can occure, for example, due to non-linearities in the mixer skew. The half angle experiment allows us to measure such issues.
[42]:
from qiskit_experiments.library import HalfAngle
[43]:
hac = HalfAngle(qubit, backend)
hac.set_transpile_options(inst_map=inst_map)
[44]:
hac.circuits()[5].draw("mpl")
[44]:

[45]:
exp_data = hac.run().block_for_results()
[46]:
exp_data.figure(0)
[46]:

[47]:
print(exp_data.analysis_results(0))
DbAnalysisResultV1
- name: @Parameters_FineHalfAngleAnalysis
- value: [0.48127945 0.49289139] ± [0.00058405 0.00163032]
- χ²: 180.10770235913844
- quality: bad
- extra: <4 items>
- device_components: ['Q0']
- verified: False
[48]:
dhac = exp_data.analysis_results("d_hac").value.value
[49]:
sx = backend.defaults().instruction_schedule_map.get('sx', (qubit,))
sx_amp = sx.instructions[0][1].pulse.amp
print(f"Adjust the phase of {np.angle(sx_pulse.amp)} of the sx pulse by {-dhac/2:.3f} rad.")
print(f"The backend reports an angle of {np.angle(sx_amp):.3f} for the sx pulse.")
Adjust the phase of 0.0 of the sx pulse by -0.241 rad.
The backend reports an angle of -0.241 for the sx pulse.
[50]:
import qiskit.tools.jupyter
%qiskit_copyright
This code is a part of Qiskit
© Copyright IBM 2017, 2021.
This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.
[ ]: