Simulating Qiskit Pulse Schedules with Qiskit Dynamics#
This tutorial shows how to use Qiskit Dynamics to simulate a Pulse schedule with a simple model of a qubit. The qubit is modeled by the drift hamiltonian
to which we apply the drive
Here, sx
gate followed by a phase shift on the drive so that the following
pulse creates a sy
rotation. Therefore, if the qubit begins in the
ground state we expect that this second pulse will not have any effect
on the qubit. This situation is simulated with the following steps:
Create the pulse schedule
Converting pulse schedules to a
Signal
Create the system model, configured to simulate pulse schedules
Simulate the pulse schedule using the model
1. Create the pulse schedule#
First, we use the pulse module in Qiskit to create a pulse schedule.
import numpy as np
import qiskit.pulse as pulse
# Strength of the Rabi-rate in GHz.
r = 0.1
# Frequency of the qubit transition in GHz.
w = 5.
# Sample rate of the backend in ns.
dt = 1 / 4.5
# Define gaussian envelope function to approximately implement an sx gate.
amp = 1. / 1.75
sig = 0.6985/r/amp
T = 4*sig
duration = int(T / dt)
beta = 2.0
with pulse.build(name="sx-sy schedule") as sxp:
pulse.play(pulse.Drag(duration, amp, sig / dt, beta), pulse.DriveChannel(0))
pulse.shift_phase(np.pi/2, pulse.DriveChannel(0))
pulse.play(pulse.Drag(duration, amp, sig / dt, beta), pulse.DriveChannel(0))
sxp.draw()

2. Convert the pulse schedule to a Signal
#
Qiskit Dynamics has functionality for converting pulse schedule to instances
of Signal
. This is done using the pulse instruction to signal
converter InstructionToSignals
. This converter needs to know the
sample rate of the arbitrary waveform generators creating the signals,
i.e. dt
, as well as the carrier frequency of the signals,
i.e. w
. The plot below shows the envelopes and the signals resulting
from this conversion. The dashed line shows the time at which the
virtual Z
gate is applied.
from matplotlib import pyplot as plt
from qiskit_dynamics.pulse import InstructionToSignals
plt.rcParams["font.size"] = 16
converter = InstructionToSignals(dt, carriers={"d0": w})
signals = converter.get_signals(sxp)
fig, axs = plt.subplots(1, 2, figsize=(14, 4.5))
for ax, title in zip(axs, ["envelope", "signal"]):
signals[0].draw(0, 2*T, 2000, title, axis=ax)
ax.set_xlabel("Time (ns)")
ax.set_ylabel("Amplitude")
ax.set_title(title)
ax.vlines(T, ax.get_ylim()[0], ax.get_ylim()[1], "k", linestyle="dashed")

3. Create the system model#
We now setup a Solver
instance with the desired Hamiltonian information,
and configure it to simulate pulse schedules. This requires specifying
which channels act on which operators, channel carrier frequencies, and sample width dt
.
Additionally, we setup this solver in the rotating frame and perform the
rotating wave approximation.
from qiskit.quantum_info.operators import Operator
from qiskit_dynamics import Solver
# construct operators
X = Operator.from_label('X')
Z = Operator.from_label('Z')
drift = 2 * np.pi * w * Z/2
operators = [2 * np.pi * r * X/2]
# construct the solver
hamiltonian_solver = Solver(
static_hamiltonian=drift,
hamiltonian_operators=operators,
rotating_frame=drift,
rwa_cutoff_freq=2 * 5.0,
hamiltonian_channels=['d0'],
channel_carrier_freqs={'d0': w},
dt=dt
)
4. Simulate the pulse schedule using the model#
In the last step we perform the simulation and plot the results. Note that, as we have
configured hamiltonian_solver
to simulate pulse schedules, we pass the schedule xp
directly to the signals
argument of the solve
method. Equivalently, signals
generated by converter.get_signals
above can also be passed to the signals
argument
and in this case should produce identical behavior.
from qiskit.quantum_info.states import Statevector
# Start the qubit in its ground state.
y0 = Statevector([1., 0.])
%time sol = hamiltonian_solver.solve(t_span=[0., 2*T], y0=y0, signals=sxp, atol=1e-8, rtol=1e-8)
CPU times: user 32.5 s, sys: 20.1 ms, total: 32.5 s
Wall time: 32.5 s
def plot_populations(sol):
pop0 = [psi.probabilities()[0] for psi in sol.y]
pop1 = [psi.probabilities()[1] for psi in sol.y]
fig = plt.figure(figsize=(8, 5))
plt.plot(sol.t, pop0, lw=3, label="Population in |0>")
plt.plot(sol.t, pop1, lw=3, label="Population in |1>")
plt.xlabel("Time (ns)")
plt.ylabel("Population")
plt.legend(frameon=False)
plt.ylim([0, 1.05])
plt.xlim([0, 2*T])
plt.vlines(T, 0, 1.05, "k", linestyle="dashed")
The plot below shows the population of the qubit as it evolves during
the pulses. The vertical dashed line shows the time of the virtual Z
rotation which was induced by the shift_phase
instruction in the
pulse schedule. As expected, the first pulse moves the qubit to an
eigenstate of the Y
operator. Therefore, the second pulse, which
drives around the Y
-axis due to the phase shift, has hardley any
influence on the populations of the qubit.
plot_populations(sol)
