Sequences
In some cases, one may want to intersperse ideal unitary gates within a sequence of time-dependent operations. This is possible using an object called a Sequence. A Sequence
is essentially a list containing PulseSequences, Operations, and unitary operators. When Sequence.run(init_state)
is called, the Sequence
iterates over its constituent PulseSequences
, Operations
,
and unitaries, applying each to the resulting state of the last.
Sequence
is designed to behave like a Python list, so it has the following methods defined:
append()
extend()
insert()
pop()
clear()
__len__()
__getitem__()
__iter__()
Notes:
Just like a
PulseSequence
orCompiledPulseSequence
, aSequence
must be associated with aSystem
.Whereas
PulseSequence.run()
andCompiledPulseSequence.run()
return an instance ofqutip.solver.Result
,Sequence.run()
returns a SequenceResult object, which behaves just likequtip.solver.Result
.SequenceResult.states
stores the quantumstates
after each stage of the simulation (states[0]
isinit_state
andstates[-1]
is the final state of the system).
[1]:
%config InlineBackend.figure_formats = ['svg']
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import qutip
from sequencing import Transmon, Cavity, System, Sequence
[2]:
qubit = Transmon('qubit', levels=3, kerr=-200e-3)
cavity = Cavity('cavity', levels=10, kerr=-10e-6)
system = System('system', modes=[qubit, cavity])
system.set_cross_kerr(cavity, qubit, chi=-2e-3)
qubit.gaussian_pulse.drag = 5
Interleave pulses and unitaries
Here we perform a “\(\pi\)-pulse” composed of \(20\) interleaved \(\frac{\pi}{40}\)-pulses and unitary rotations.
[3]:
init_state = system.ground_state()
# calculate expectation value of |qubit=1, cavity=0>
e_ops = [system.fock_dm(qubit=1)]
n_rotations = 20
theta = np.pi / n_rotations
seq = Sequence(system)
for _ in range(n_rotations):
# Capture a PulseSequence
qubit.rotate_x(theta/2)
# # Alternatively, we can append an Operation
# operation = qubit.rotate_x(theta/2, capture=False)
# seq.append(operation)
# Append a unitary
seq.append(qubit.Rx(theta/2))
result = seq.run(init_state, e_ops=e_ops, full_evolution=True, progress_bar=True)
states = result.states
100%|██████████| 40/40 [00:00<00:00, 77.66it/s]
Inspect the sequence
Sequence.plot_coefficients()
plots Hamiltonian coefficients vs. time. Instantaneous unitary operations are represented by dashed vertical lines. If multiple unitaries occur at the same time, only a single dashed line is drawn.
[4]:
fig, ax = seq.plot_coefficients(subplots=False)
ax.set_xlabel('Time [ns]')
ax.set_ylabel('Hamiltonian coefficient [GHz]')
fig.set_size_inches(8,4)
fig.tight_layout()
fig.subplots_adjust(top=0.9)
[5]:
print('len(states):', len(states))
print(f'state fidelity: {qutip.fidelity(states[-1], qubit.Rx(np.pi) * init_state)**2:.4f}')
len(states): 821
state fidelity: 0.9996
Plot the results
[6]:
e_pops = result.expect[0] # probability of measuring the state |qubit=1, cavity=0>
fig, ax = plt.subplots(figsize=(8,4))
ax.plot(result.times, e_pops, '.')
ax.scatter(result.times[:1], e_pops[:1], marker='s', color='k', label='init_state')
# draw vertical lines at the location of each unitary rotation
for i in range(1, result.times.size // (2*n_rotations) + 1):
t = 2 * n_rotations * i - 1
label = 'unitaries' if i == 1 else None
ax.axvline(t, color='k', alpha=0.25, ls='--', lw=1.5, label=label)
ax.axhline(0, color='k', lw=1)
ax.axhline(1, color='k', lw=1)
ax.set_ylabel('$P(|e\\rangle)$')
ax.set_xlabel('Times [ns]')
ax.set_title('Interleaved pulses and unitaries')
ax.legend(loc=0);
[7]:
print(result)
SequenceResult
--------------
Number of times: 821
states = True
expect = True, num_expect = 1
num_collapse = 0
[8]:
from qutip.ipynbtools import version_table
version_table()
[8]:
Software | Version |
---|---|
QuTiP | 4.7.0 |
Numpy | 1.23.2 |
SciPy | 1.9.1 |
matplotlib | 3.5.3 |
Cython | 0.29.32 |
Number of CPUs | 2 |
BLAS Info | OPENBLAS |
IPython | 8.4.0 |
Python | 3.8.6 (default, Oct 19 2020, 15:10:29) [GCC 7.5.0] |
OS | posix [linux] |
Tue Aug 30 19:27:03 2022 UTC |
[ ]: