# 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, \(\Omega(t)\) is the drive signal which we will create using Qiskit pulse. The factor
\(r\) is the strength with which the drive signal drives the qubit. We begin by creating a pulse
schedule with a `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 15.3 s, sys: 6.9 ms, total: 15.3 s
Wall time: 15.3 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)
```