Rabi Drive & Ramsey Experiment (by nick brønn)
Cahyati Supriyati Sangaji (My Note)
Treat the transmon as a qubit for simplicity
Then we can describe the dynamics of the qubit with the Pauli Matrices:
They obey the commutator relations
On its own, the qubit Hamiltonian is
Ground state of the qubit (|0⟩): points in the +𝑧̂ direction of the Bloch sphere
Excited state of the qubit (|1⟩): points in the −𝑧̂ direction of the Bloch sphere
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_bloch_multivectorexcited = Statevector.from_int(1, 2)
plot_bloch_multivector(excited.data)
The Pauli matrices also let us define raising and lowering operators
They raise and lower qubit states
Electric Dipole Interaction
The qubit behaves as an electric dipole
The drive behaves as an electric field
The drive Hamiltonian is then
And now, some math…
Rotating Wave Approximation
Move the Hamiltonian to the interaction picture
with
Calculate the operator terms
The transformed Hamiltonian is
Rotating Wave Approximation
Transform Hamiltonian back to the Schrödinger picture
And the total qubit Hamiltonian is
Qubit Drive Example
In a similar calculation to earlier, the effective Hamiltonian is
What this means
Qiskit Pulse: On-resonant Drive (Rabi)
Import Necessary Libraries
from qiskit.tools.jupyter import *
from qiskit import IBMQIBMQ.save_account('<Your Token>')
IBMQ.load_account()
provider = IBMQ.get_provider(hub='ibm-q', group='open', project='main')
backend = provider.get_backend('ibmq_armonk')
Verify Backend is Pulse-enabled
backend_config = backend.configuration()
assert backend_config.open_pulse, "Backend doesn't support Pulse"
Qiskit Pulse: On-resonant Drive (Rabi)
Take care of some other things
dt = backend_config.dt
print(f"Sampling time: {dt*1e9} ns")backend_defaults = backend.defaults()
Out:
Sampling time: 0.2222222222222222 ns
Qiskit Pulse: On-resonant Drive (Rabi)
import numpy as np# unit conversion factors -> all backend properties
# returned in SI (Hz, sec, etc)
GHz = 1.0e9 # Gigahertz
MHz = 1.0e6 # Megahertz
us = 1.0e-6 # Microseconds
ns = 1.0e-9 # Nanoseconds# We will find the qubit frequency for the following qubit.
qubit = 0
# The Rabi sweep will be at the given qubit frequency.
# The default frequency is given in Hz
center_frequency_Hz = backend_defaults.qubit_freq_est[qubit]
# warning: this will change in a future release
print(f"Qubit {qubit} has an estimated frequency of {center_frequency_Hz / GHz} GHz.")
Out:
Qubit 0 has an estimated frequency of 4.974445862780936 GHz.
Qiskit Pulse: On-resonant Drive (Rabi)
# This is where we access all of our Pulse features!
from qiskit import pulse, assemble
from qiskit.pulse import Play
# This Pulse module helps us build sampled
# pulses for common pulse shapes
from qiskit.pulse import pulse_lib### Collect the necessary channels
drive_chan = pulse.DriveChannel(qubit)
meas_chan = pulse.MeasureChannel(qubit)
acq_chan = pulse.AcquireChannel(qubit)inst_sched_map = backend_defaults.instruction_schedule_map
measure = inst_sched_map.get('measure', qubits=[0])
Qiskit Pulse: On-resonant Drive (Rabi)
# Rabi experiment parameters
# Drive amplitude values to iterate over:
# 50 amplitudes evenly spaced from 0 to 0.75
num_rabi_points = 50
drive_amp_min = 0
drive_amp_max = 0.75
drive_amps = np.linspace(drive_amp_min, drive_amp_max, num_rabi_points)# drive waveforms mush be in units of 16
drive_sigma = 80 # in dt
drive_samples = 8*drive_sigma # in dt
Qiskit Pulse: On-resonant Drive (Rabi)
# Build the Rabi experiments:
# A drive pulse at the qubit frequency, followed by a measurement,
# where we vary the drive amplitude each time.
rabi_schedules = []
for drive_amp in drive_amps:
rabi_pulse = pulse_lib.gaussian(duration=drive_samples, amp=drive_amp,
sigma=drive_sigma, name=f"Rabi drive amplitude = {drive_amp}")
this_schedule = pulse.Schedule(name=f"Rabi drive amplitude = {drive_amp}")
this_schedule += Play(rabi_pulse, drive_chan)
# The left shift `<<` is special syntax meaning to
# shift the start time of the schedule by some duration
this_schedule += measure << this_schedule.duration
rabi_schedules.append(this_schedule)rabi_schedules[-1].draw(label=True, scaling=1.0)
Qiskit Pulse: On-resonant Drive (Rabi)
# assemble the schedules into a Qobj
num_shots_per_point = 1024rabi_experiment_program = assemble(rabi_schedules,
backend=backend,
meas_level=1,
meas_return='avg',
shots=num_shots_per_point,
schedule_los=[{drive_chan:
center_frequency_Hz}]
* num_rabi_points)
Sintax:
# RUN the job on a real device
job = backend.run(rabi_experiment_program)
print(job.job_id())
from qiskit.tools.monitor import job_monitor
job_monitor(job)# OR retreive result from previous run
job = backend.retrieve_job("<Job ID>")
Out:
<Your Job ID>
Job Status: job has successfully run
Qiskit Pulse: On-resonant Drive (Rabi)
rabi_results = job.result()import matplotlib.pyplot as plt
plt.style.use('dark_background')scale_factor = 1e-14# center data around 0
def baseline_remove(values):
return np.array(values) - np.mean(values)
Qiskit Pulse: On-resonant Drive (Rabi)
rabi_values = []
for i in range(num_rabi_points):
# Get the results for `qubit` from the ith experiment
rabi_values.append(rabi_results.get_memory(i)[qubit]*scale_factor)rabi_values = np.real(baseline_remove(rabi_values))plt.xlabel("Drive amp [a.u.]")
plt.ylabel("Measured signal [a.u.]")
plt.scatter(drive_amps, rabi_values, color='white') # plot real part of Rabi values
plt.show()
Qiskit Pulse: On-resonant Drive (Rabi)
Define Rabi curve-fitting function
from scipy.optimize import curve_fitdef fit_function(x_values, y_values, function, init_params):
fitparams, conv = curve_fit(function, x_values, y_values, init_params)
y_fit = function(x_values, *fitparams)
return fitparams, y_fitfit_params, y_fit = fit_function(drive_amps,
rabi_values,
lambda x, A, B, drive_period, phi:
(A*np.cos(2*np.pi*x/drive_period -
phi) + B),
[10, 0.1, 0.6, 0])
Qiskit Pulse: On-resonant Drive (Rabi)
plt.scatter(drive_amps, rabi_values, color='white')
plt.plot(drive_amps, y_fit, color='red')drive_period = fit_params[2] # get period of rabi oscillationplt.axvline(drive_period/2, color='red', linestyle='--')
plt.axvline(drive_period, color='red', linestyle='--')
plt.annotate("", xy=(drive_period, 0), xytext=(drive_period/2,0), arrowprops=dict(arrowstyle="<->", color='red'))plt.xlabel("Drive amp [a.u.]", fontsize=15)
plt.ylabel("Measured signal [a.u.]", fontsize=15)
plt.show()
Save 𝜋/2 pulse for later
pi_amp = abs(drive_period / 2)
print(f"Pi Amplitude = {pi_amp}")
Out:
Pi Amplitude = 0.9344596890678515
Sintax:
# Drive parameters
# The drive amplitude for pi/2 is simply half the amplitude of the pi pulse
drive_amp = pi_amp / 2
# x_90 is a concise way to say pi_over_2; i.e., an X rotation of 90 degrees
x90_pulse = pulse_lib.gaussian(duration=drive_samples,
amp=drive_amp,
sigma=drive_sigma,
name='x90_pulse')
Qiskit Pulse: Off-resonant Drive (Ramsey)
# Ramsey experiment parameters
time_max_us = 1.8
time_step_us = 0.025
times_us = np.arange(0.1, time_max_us, time_step_us)
# Convert to units of dt
delay_times_dt = times_us * us / dt# create schedules for Ramsey experiment
ramsey_schedules = []for delay in delay_times_dt:
this_schedule = pulse.Schedule(name=f"Ramsey delay = {delay * dt / us} us")
this_schedule += Play(x90_pulse, drive_chan)
this_schedule += Play(x90_pulse, drive_chan) << this_schedule.duration + int(delay)
this_schedule += measure << this_schedule.duration
ramsey_schedules.append(this_schedule)ramsey_schedules[-1].draw(label=True, scaling=1.0)
Qiskit Pulse: Off-resonant Drive (Ramsey)
# Execution settings
num_shots = 256detuning_MHz = 2
ramsey_frequency = round(center_frequency_Hz + detuning_MHz * MHz, 6) # need ramsey freq in Hz
ramsey_program = assemble(ramsey_schedules,
backend=backend,
meas_level=1,
meas_return='avg',
shots=num_shots,
schedule_los=[{drive_chan: ramsey_frequency}]*len(ramsey_schedules)
)# RUN the job on a real device
job = backend.run(ramsey_program)
print(job.job_id())
from qiskit.tools.monitor import job_monitor
job_monitor(job)# OR retreive job from previous run
job = backend.retrieve_job('<Job ID>')
Out:
<Your Job>
Job Status: job has successfully run
Qiskit Pulse: Off-resonant Drive (Ramsey)
Ramsey curve-fitting function
ramsey_results = job.result()
ramsey_values = []
for i in range(len(times_us)):
ramsey_values.append(ramsey_results.get_memory(i)[qubit]*scale_factor)fit_params, y_fit = fit_function(times_us, np.real(ramsey_values),
lambda x, A, del_f_MHz, C, B: (
A * np.cos(2*np.pi*
del_f_MHz*x - C) + B
),
[5, 1./0.4, 0, 0.25]
)
Qiskit Pulse: Off-resonant Drive (Ramsey)
# Off-resonance component
_, del_f_MHz, _, _, = fit_params # freq is MHz since times in usplt.scatter(times_us, np.real(ramsey_values), color='white')
plt.plot(times_us, y_fit, color='red', label=f"df = {del_f_MHz:.2f} MHz")
plt.xlim(0, np.max(times_us))
plt.xlabel('Delay between X90 pulses [$\mu$s]', fontsize=15)
plt.ylabel('Measured Signal [a.u.]', fontsize=15)
plt.title('Ramsey Experiment', fontsize=15)
plt.legend()
plt.show()
References:
Qiskit (Global Summer School), Introduction to Quantum Computing and Quantum Hardware — Lab 6.