Skip to content

Commit 7d286ee

Browse files
authored
Merge pull request #111 from BoxiLi/qutip-qip-0.2.X
Prepare the qutip-qip-0.2.0 release
2 parents 60fe50e + eda8e43 commit 7d286ee

25 files changed

+438
-253
lines changed

.github/workflows/test.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ on:
44
push:
55
branches: [ master ]
66
pull_request:
7-
branches: [ master ]
87

98
jobs:
109
test:

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.2.0.dev
1+
0.2.0

doc/pulse-paper/README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ The following table summarizes the sections in the paper and the corresponding c
88

99
| Section | Code example |
1010
| ----------- | ----------- |
11-
| Appendix A | `pulse_simulation.py` |
12-
| Appendix B | `dj_algorithm.py` |
11+
| Section 4 | `main_example.py`|
12+
| Appendix A | `dj_algorithm.py` |
1313
| Fig.4 and Appendix C | `customize.py` |
14-
| Fig.5 in Section 4 | `decoherence.py` |
15-
| Appendix D | `deutsch_jozsa.qasm` and `deutsch_jozsa-qasm.py` |
14+
| Fig.5 | `decoherence.py` |
15+
| Section 5 | `deutsch_jozsa.qasm` and `deutsch_jozsa-qasm.py` |
16+
| Appendix B | `qft.py` |

doc/pulse-paper/customize.py

Lines changed: 46 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -10,49 +10,37 @@
1010
global_setup(fontsize=10)
1111
except:
1212
pass
13+
plt.rcParams.update({"text.usetex": False, "font.size": 10})
1314
from joblib import Parallel, delayed # for parallel simulations
14-
1515
import numpy as np
1616
from qutip import sigmax, sigmay, sigmaz, basis, qeye, tensor, Qobj, fock_dm
1717
from qutip_qip.circuit import QubitCircuit, Gate
18-
from qutip_qip.device import ModelProcessor
18+
from qutip_qip.device import ModelProcessor, Model
1919
from qutip_qip.compiler import GateCompiler, Instruction
2020
from qutip import Options
2121
from qutip_qip.noise import Noise
2222

2323

24-
class MyProcessor(ModelProcessor):
25-
"""Custom processor built using ModelProcessor as the base class.
26-
27-
This custom processor will inherit all the methods of the base class
28-
such as setting up of the T1 and T2 decoherence rates in the simulations.
29-
30-
In addition, it is possible to write your own functions to add control
31-
pulses.
32-
33-
Args:
34-
num_qubits (int): Number of qubits in the processor.
35-
t1, t2 (float or list): The T1 and T2 decoherence rates for the
36-
qubit. If it is a list, then it is assumed that
37-
each element of the list corresponds to the rates
38-
for each of the qubits.
39-
"""
40-
41-
def __init__(self, num_qubits, t1=None, t2=None):
42-
super().__init__(num_qubits, t1=t1, t2=t2)
43-
self.pulse_mode = "discrete" # set the control pulse as discrete or continuous.
44-
self.set_up_ops() # set up the available Hamiltonians.
45-
self.dims = [2] * num_qubits # dimension of the quantum system.
46-
self.num_qubits = num_qubits
47-
self.native_gates = ["RX", "RY"]
24+
class MyModel(Model):
25+
"""A custom Hamiltonian model with sigmax and sigmay control."""
26+
def get_control(self, label):
27+
"""
28+
Get an avaliable control Hamiltonian.
29+
For instance, sigmax control on the zeroth qubits is labeld "sx0".
4830
49-
def set_up_ops(self):
50-
"""Sets up the single qubit control operators for each qubit."""
51-
for m in range(self.num_qubits):
52-
self.add_control(2 * np.pi * sigmax() / 2, m, label="sx" + str(m))
53-
for m in range(self.num_qubits):
54-
self.add_control(2 * np.pi * sigmay() / 2, m, label="sy" + str(m))
31+
Args:
32+
label (str): The label of the Hamiltonian
5533
34+
Returns:
35+
The Hamiltonian and target qubits as a tuple (qutip.Qobj, list).
36+
"""
37+
targets = int(label[2:])
38+
if label[:2] == "sx":
39+
return 2 * np.pi * sigmax() / 2, [targets]
40+
elif label[:2] == "sy":
41+
return 2 * np.pi * sigmax() / 2, [targets]
42+
else:
43+
raise NotImplementError("Unknown control.")
5644

5745
class MyCompiler(GateCompiler):
5846
"""Custom compiler for generating pulses from gates using the base class
@@ -87,10 +75,11 @@ def generate_pulse(self, gate, tlist, coeff, phase=0.0):
8775
to implement a gate containing the control pulses.
8876
"""
8977
pulse_info = [
78+
# (control label, coeff)
9079
("sx" + str(gate.targets[0]), np.cos(phase) * coeff),
9180
("sy" + str(gate.targets[0]), np.sin(phase) * coeff),
9281
]
93-
return [Instruction(gate, tlist, pulse_info)]
82+
return [Instruction(gate, tlist=tlist, pulse_info=pulse_info)]
9483

9584
def single_qubit_gate_compiler(self, gate, args):
9685
"""Compiles single qubit gates to pulses.
@@ -133,17 +122,19 @@ def rotation_with_phase_compiler(self, gate, args):
133122
circuit.add_gate("RX", targets=0, arg_value=np.pi / 2)
134123
circuit.add_gate("Z", targets=0)
135124

136-
myprocessor = MyProcessor(1)
125+
myprocessor = ModelProcessor(model=MyModel(num_qubits))
126+
myprocessor.native_gates = ["RX", "RY"]
137127

138128
mycompiler = MyCompiler(num_qubits, {"pulse_amplitude": 0.02})
139129

140130
myprocessor.load_circuit(circuit, compiler=mycompiler)
141131
result = myprocessor.run_state(basis(2, 0))
142132

143133
fig, ax = myprocessor.plot_pulses(
144-
figsize=(LINEWIDTH * 0.7, LINEWIDTH / 2 * 0.7), dpi=200
134+
figsize=(LINEWIDTH * 0.7, LINEWIDTH / 2 * 0.7), dpi=200,
135+
use_control_latex=False
145136
)
146-
ax[-1].set_xlabel("Time")
137+
ax[-1].set_xlabel("$t$")
147138
fig.tight_layout()
148139
fig.savefig("custom_compiler_pulse.pdf")
149140
fig.show()
@@ -166,8 +157,8 @@ def get_noisy_dynamics(self, dims=None, pulses=None, systematic_noise=None):
166157
systematic_noise: A Pulse object (not used in this example).
167158
"""
168159
for i, pulse in enumerate(pulses):
169-
if "sx" or "sy" not in pulse.label:
170-
pass # filter out other pulses, e.g. drift
160+
if "sx" not in pulse.label and "sy" not in pulse.label:
161+
continue # filter out other pulses, e.g. drift
171162
target = pulse.targets[0]
172163
if target != 0: # add pulse to the left neighbour
173164
pulses[i].add_control_noise(
@@ -197,7 +188,7 @@ def single_crosstalk_simulation(num_gates):
197188
solver methods such as mesolve.
198189
"""
199190
num_qubits = 2 # Qubit-0 is the target qubit. Qubit-1 suffers from crosstalk.
200-
myprocessor = MyProcessor(num_qubits)
191+
myprocessor = ModelProcessor(model=MyModel(num_qubits))
201192
# Add qubit frequency detuning 1.852MHz for the second qubit.
202193
myprocessor.add_drift(2 * np.pi * (sigmaz() + 1) / 2 * 1.852, targets=1)
203194
myprocessor.native_gates = None # Remove the native gates
@@ -228,7 +219,6 @@ def single_crosstalk_simulation(num_gates):
228219
return result
229220

230221

231-
num_qubits = 2
232222
num_sample = 2
233223
# num_sample = 1600
234224
fidelity = []
@@ -247,31 +237,32 @@ def single_crosstalk_simulation(num_gates):
247237
# Recorded result
248238
num_gates_list = [250, 500, 750, 1000, 1250, 1500]
249239
data_y = [
250-
0.9577285560461476,
251-
0.9384849070716464,
252-
0.9230217713086177,
253-
0.9062344084919285,
254-
0.889009550855518,
255-
0.8749290612064392,
240+
0.9566768747558925,
241+
0.9388905075892828,
242+
0.9229470389282218,
243+
0.9075513000339529,
244+
0.8941659320508855,
245+
0.8756519016627652
256246
]
247+
257248
data_y_error = [
258-
0.000431399017208067,
259-
0.0008622091914303468,
260-
0.0012216267555118497,
261-
0.001537120153687202,
262-
0.0018528957172559654,
263-
0.0020169257334183596,
249+
0.00042992029265330223,
250+
0.0008339882813741004,
251+
0.0012606632769758602,
252+
0.0014643550337816722,
253+
0.0017695604671714809,
254+
0.0020964978542167617
264255
]
265256

266257

267-
def linear(x, a):
258+
def rb_curve(x, a):
268259
return (1 / 2 + np.exp(-2 * a * x) / 2) * 0.975
269260

270261

271-
pos, cov = curve_fit(linear, num_gates_list, data_y, p0=[0.001])
262+
pos, cov = curve_fit(rb_curve, num_gates_list, data_y, p0=[0.001])
272263

273264
xline = np.linspace(0, 1700, 200)
274-
yline = linear(xline, *pos)
265+
yline = rb_curve(xline, *pos)
275266

276267
fig, ax = plt.subplots(figsize=(LINEWIDTH, 0.65 * LINEWIDTH), dpi=200)
277268
ax.errorbar(

doc/pulse-paper/decoherence.py

Lines changed: 82 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,99 @@
11
TEXTWIDTH = 7.1398920714
22
LINEWIDTH = 3.48692403487
3-
import matplotlib as mpl
43
import matplotlib.pyplot as plt
4+
import matplotlib.gridspec as gridspec
55
try:
66
from quantum_plots import global_setup
77
global_setup(fontsize = 10)
88
except:
99
pass
10-
11-
import matplotlib.pyplot as plt
12-
10+
plt.rcParams.update({"text.usetex": False, "font.size": 10})
1311
import numpy as np
14-
from qutip import sigmaz, basis
12+
import scipy
13+
from qutip import sigmaz, basis, sigmax, fidelity
1514
from qutip_qip.operations import hadamard_transform
1615
from qutip_qip.pulse import Pulse
1716
from qutip_qip.device import LinearSpinChain
1817
from qutip_qip.circuit import QubitCircuit
19-
20-
# define a circuit with only idling
21-
t = 30.
22-
circuit = QubitCircuit(1)
23-
circuit.add_gate("IDLE", 0, arg_value=t)
24-
# define a processor with t2 relaxation
18+
pi = np.pi
19+
num_samples = 500
20+
amp = 0.1
2521
f = 0.5
2622
t2 = 10 / f
27-
processor = LinearSpinChain(1, t2=t2)
28-
processor.add_drift(
29-
2*np.pi*sigmaz()/2*f, targets=[0])
30-
processor.load_circuit(circuit)
31-
# Record the expectation value
32-
plus_state = \
33-
(basis(2,1) + basis(2,0)).unit()
34-
result = processor.run_state(
35-
init_state=plus_state,
36-
tlist = np.linspace(0., t, 1000),
37-
# observable
38-
e_ops=[plus_state*plus_state.dag()])
39-
40-
tlist = np.linspace(0., t, 1000)
41-
fig, ax = plt.subplots(figsize = (LINEWIDTH, LINEWIDTH*0.65), dpi=200)
42-
# detail about lenght of tlist needs to be fixed
43-
ax.plot(tlist[:-1], result.expect[0][:-1], '-', label="Simulation", color="slategray")
44-
ax.plot(tlist[:-1], np.exp(-1./t2 * tlist[:-1])*0.5 + 0.5, '--', label="Theory", color="slategray")
45-
ax.set_xlabel(r"Time [$\mu$s]")
46-
ax.set_ylabel("Ramsey signal")
47-
ax.legend()
23+
24+
# Define a processor.
25+
proc = LinearSpinChain(
26+
num_qubits=1, sx=amp/2, t2=t2)
27+
ham_idle = 2*pi * sigmaz()/2 * f
28+
resonant_sx = 2*pi * sigmax() - \
29+
ham_idle / (amp/2)
30+
proc.add_drift(ham_idle, targets=0)
31+
proc.add_control(
32+
resonant_sx, targets=0, label="sx0")
33+
34+
# Define a Ramsey experiment.
35+
def ramsey(t, proc):
36+
qc = QubitCircuit(1)
37+
qc.add_gate("RX", 0, arg_value=pi/2)
38+
qc.add_gate("IDLE", 0, arg_value=t)
39+
qc.add_gate("RX", 0, arg_value=pi/2)
40+
proc.load_circuit(qc)
41+
result = proc.run_state(
42+
init_state=basis(2, 0),
43+
e_ops = sigmaz()
44+
)
45+
return result.expect[0][-1]
46+
47+
idle_tlist = np.linspace(0., 30., num_samples)
48+
measurements = np.asarray([ramsey(t, proc) for t in idle_tlist])
49+
50+
fig, ax = plt.subplots(figsize = (LINEWIDTH, LINEWIDTH*0.60), dpi=200)
51+
52+
rx_gate_time = 1/4/amp # pi/2
53+
total_time = 2*rx_gate_time + idle_tlist[-1]
54+
55+
tlist = np.linspace(0., total_time, num_samples)
56+
ax.plot(idle_tlist[:], measurements[:], '-', label="Simulation", color="slategray")
57+
58+
peak_ind = scipy.signal.find_peaks(measurements)[0]
59+
decay_func = lambda t, t2, f0: f0 * np.exp(-1./t2 * t)
60+
(t2_fit, f0_fit), _ = scipy.optimize.curve_fit(decay_func, idle_tlist[peak_ind], measurements[peak_ind])
61+
print("T2:", t2)
62+
print("Fitted T2:", t2_fit)
63+
64+
ax.plot(idle_tlist, decay_func(idle_tlist, t2_fit, f0_fit), '--', label="Theory", color="slategray")
65+
ax.set_xlabel(r"Idling time $t$ [$\mu$s]")
66+
ax.set_ylabel("Ramsey signal", labelpad=2)
67+
ax.set_ylim((ax.get_ylim()[0], ax.get_ylim()[1]))
68+
ax.set_position([0.18, 0.2, 0.75, 0.75])
4869
ax.grid()
49-
fig.tight_layout()
70+
5071
fig.savefig("fig5_decoherence.pdf")
51-
fig.show()
72+
fig.show()
73+
74+
75+
circuit = QubitCircuit(1)
76+
circuit.add_gate("RX", 0, arg_value=pi/2)
77+
circuit.add_gate("IDLE", 0, arg_value=15.)
78+
circuit.add_gate("RX", 0, arg_value=pi/2)
79+
proc.load_circuit(circuit)
80+
fig2, axis = proc.plot_pulses(figsize=(LINEWIDTH, LINEWIDTH*0.15), use_control_latex=False, dpi=200)
81+
axis[0].set_ylim((0. - axis[0].get_ylim()[1] * 0.05, axis[0].get_ylim()[1]))
82+
axis[0].set_position([0.18, 0.39, 0.75, 0.60])
83+
axis[0].set_ylabel("sx0", labelpad=25)
84+
axis[0].yaxis.set_label_coords(-0.13, 0.25)
85+
axis[0].set_xlabel("Ramsey pulse")
86+
fig2.savefig("fig5_decoherence_pulse.pdf")
87+
fig2.show()
88+
89+
# Test for time-dependent decoherence
90+
from qutip_qip.noise import DecoherenceNoise
91+
from qutip import sigmam
92+
tlist = np.linspace(0, 30., 100)
93+
coeff = tlist * 0.01
94+
proc.add_noise(
95+
DecoherenceNoise(sigmam(), targets=0, coeff=coeff, tlist=tlist))
96+
result = proc.run_state(
97+
init_state=basis(2, 0),
98+
e_ops = sigmaz()
99+
)
Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
from qutip_qip.qasm import read_qasm
2-
32
qc = read_qasm("deutsch-jozsa.qasm")
43

54
from qutip_qip.qasm import save_qasm
6-
75
save_qasm(qc, "deutsch-jozsa-qutip.qasm")

0 commit comments

Comments
 (0)