Rabi Drive & Ramsey Experiment (by nick brønn)

cahyati sangaji (cahya)
7 min readDec 8, 2020

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_multivector
excited = 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 IBMQ
IBMQ.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 = 1024
rabi_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_fit
fit_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 = 256
detuning_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 us
plt.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.

--

--