Skip to content

Commit e07f066

Browse files
authored
Merge pull request #335 Pulse paper relocation
- Move the pulse-paper codes to the root (previously was under `/doc`) - Separate the test in the github workflows (previously was under doctest), also fix the bug which didn't test the pulse-paper code in the CI. - Minor fixes to the pulse-paper code based on the changes in gate-refactor.
2 parents ac53148 + 440d8d9 commit e07f066

File tree

14 files changed

+214
-202
lines changed

14 files changed

+214
-202
lines changed

.github/workflows/test.yml

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,27 @@ jobs:
9999
run: |
100100
cd doc
101101
make doctest
102+
103+
pulse-paper-code:
104+
runs-on: ubuntu-latest
105+
106+
steps:
107+
- uses: actions/checkout@v6
108+
- name: Set up Python
109+
uses: actions/setup-python@v6
110+
with:
111+
python-version: 3.12
112+
113+
- name: Install dependencies
114+
run: |
115+
python -m pip install --upgrade pip
116+
pip install .[graphics,control]
117+
102118
- name: Test code examples for the pulse paper
103119
run: |
104-
python -m pip install joblib pytest pytest-custom_exit_code
105-
cd doc/pulse-paper
106-
pytest *.py --suppress-no-test-exit-code
120+
cd pulse-paper
121+
for f in *.py; do python - W error "$f"; done
122+
# '-W error' will throw an error in case any warning is raised
107123
108124
finalise:
109125
name: Finalise coverage reporting

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ __pycache__/
77
*.html
88
*.htm
99

10+
# Pdf files (from plots in /pulse-paper)
11+
*.pdf
12+
/pulse-paper/deutsch-jozsa-qutip.qasm
13+
1014
# C extensions
1115
*.so
1216

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
This folder contains code examples used in the publication [*Pulse-level noisy quantum circuits with QuTiP*](https://quantum-journal.org/papers/q-2022-01-24-630). To run the examples, please first install the software package qutip-qip
22
```
3-
pip install qutip_qip[full] joblib
3+
pip install qutip-qip[full]
44
```
55
All examples are self-contained and running the code should reproduce the plots used in the paper.
66

@@ -9,8 +9,8 @@ The following table summarizes the sections in the paper and the corresponding c
99
| Section | Code example |
1010
| ----------- | ----------- |
1111
| Section 4 | `main_example.py`|
12-
| Appendix A | `dj_algorithm.py` |
1312
| Fig.4 and Appendix C | `customize.py` |
1413
| Fig.5 | `decoherence.py` |
1514
| Section 5 | `deutsch_jozsa.qasm` and `deutsch_jozsa-qasm.py` |
15+
| Appendix A | `dj_algorithm.py` |
1616
| Appendix B | `qft.py` |
Lines changed: 115 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
1-
TEXTWIDTH = 7.1398920714
2-
LINEWIDTH = 3.48692403487
1+
from concurrent.futures import ProcessPoolExecutor # for parallel simulations
32

4-
import matplotlib as mpl
3+
import numpy as np
54
import matplotlib.pyplot as plt
6-
75
from scipy.optimize import curve_fit
86

9-
try:
10-
global_setup(fontsize=10)
11-
except:
12-
pass
13-
plt.rcParams.update({"text.usetex": False, "font.size": 10})
14-
from joblib import Parallel, delayed # for parallel simulations
15-
import numpy as np
167
from qutip import (
178
fidelity,
189
sigmax,
@@ -25,11 +16,29 @@
2516
fock_dm,
2617
)
2718
from qutip_qip.circuit import QubitCircuit
28-
from qutip_qip.operations import Gate
19+
from qutip_qip.operations import AngleParametricGate
20+
from qutip_qip.operations.gates import RX, RY, Z
2921
from qutip_qip.device import ModelProcessor, Model
3022
from qutip_qip.compiler import GateCompiler, PulseInstruction
3123
from qutip_qip.noise import Noise
3224

25+
plt.rcParams.update({"text.usetex": False, "font.size": 10})
26+
LINEWIDTH = 3.48692403487
27+
TEXTWIDTH = 7.1398920714
28+
29+
30+
class ROT(AngleParametricGate):
31+
num_qubits = 1
32+
num_params = 1
33+
34+
def __init__(self, arg_value):
35+
super().__init__(arg_value)
36+
37+
def compute_qobj(args, dtype) -> Qobj:
38+
# This is not required, because Pules levele implementation
39+
# of this gate is already provided.
40+
pass
41+
3342

3443
class MyModel(Model):
3544
"""A custom Hamiltonian model with sigmax and sigmay control."""
@@ -68,16 +77,16 @@ def __init__(self, num_qubits, params):
6877
super().__init__(num_qubits, params=params)
6978
self.params = params
7079
self.gate_compiler = {
71-
"ROT": self.rotation_with_phase_compiler,
72-
"RX": self.single_qubit_gate_compiler,
73-
"RY": self.single_qubit_gate_compiler,
80+
ROT: self.rotation_with_phase_compiler,
81+
RX: self.single_qubit_gate_compiler,
82+
RY: self.single_qubit_gate_compiler,
7483
}
7584

76-
def generate_pulse(self, gate, tlist, coeff, phase=0.0):
85+
def generate_pulse(self, circ_op, tlist, coeff, phase=0.0):
7786
"""Generates the pulses.
7887
7988
Args:
80-
gate (qutip_qip.circuit.Gate): A qutip Gate object.
89+
circ_op (qutip_qip.circuit.GateInstruction): A GateInstruction object.
8190
tlist (array): A list of times for the evolution.
8291
coeff (array): An array of coefficients for the gate pulses
8392
phase (float): The value of the phase for the gate.
@@ -89,61 +98,65 @@ def generate_pulse(self, gate, tlist, coeff, phase=0.0):
8998

9099
pulse_info = [
91100
# (control label, coeff)
92-
("sx" + str(gate.targets[0]), np.cos(phase) * coeff),
93-
("sy" + str(gate.targets[0]), np.sin(phase) * coeff),
101+
("sx" + str(circ_op.targets[0]), np.cos(phase) * coeff),
102+
("sy" + str(circ_op.targets[0]), np.sin(phase) * coeff),
94103
]
95-
return [PulseInstruction(gate, tlist=tlist, pulse_info=pulse_info)]
104+
return [PulseInstruction(circ_op, tlist=tlist, pulse_info=pulse_info)]
96105

97-
def single_qubit_gate_compiler(self, gate, args):
106+
def single_qubit_gate_compiler(self, circ_op, args):
98107
"""Compiles single qubit gates to pulses.
99108
100109
Args:
101-
gate (qutip_qip.circuit.Gate): A qutip Gate object.
110+
circ_op (qutip_qip.circuit.GateInstruction)
102111
103112
Returns:
104113
PulseInstruction (qutip_qip.compiler.instruction.PulseInstruction):
105114
A pulse instruction to implement a gate containing the control pulses.
106115
"""
107116
# gate.arg_value is the rotation angle
108-
tlist = np.abs(gate.arg_value) / self.params["pulse_amplitude"]
109-
coeff = self.params["pulse_amplitude"] * np.sign(gate.arg_value)
117+
gate = circ_op.operation
118+
theta = gate.arg_value[0]
119+
120+
tlist = np.abs(theta) / self.params["pulse_amplitude"]
121+
coeff = self.params["pulse_amplitude"] * np.sign(theta)
110122
coeff /= 2 * np.pi
111123
if gate.name == "RX":
112-
return self.generate_pulse(gate, tlist, coeff, phase=0.0)
124+
return self.generate_pulse(circ_op, tlist, coeff, phase=0.0)
113125
elif gate.name == "RY":
114-
return self.generate_pulse(gate, tlist, coeff, phase=np.pi / 2)
126+
return self.generate_pulse(circ_op, tlist, coeff, phase=np.pi / 2)
115127

116-
def rotation_with_phase_compiler(self, gate, args):
128+
def rotation_with_phase_compiler(self, circ_op, args):
117129
"""Compiles gates with a phase term.
118130
119131
Args:
120-
gate (qutip_qip.circuit.Gate): A qutip Gate object.
132+
circ_op (qutip_qip.circuit.GateInstruction):
121133
122134
Returns:
123135
PulseInstruction (qutip_qip.compiler.instruction.PulseInstruction):
124136
A pulse instruction to implement a gate containing the control pulses.
125137
"""
126138
# gate.arg_value is the pulse phase
139+
phase = circ_op.operation.arg_value[0]
127140
tlist = self.params["duration"]
128141
coeff = self.params["pulse_amplitude"]
129142
coeff /= 2 * np.pi
130-
return self.generate_pulse(gate, tlist, coeff, phase=gate.arg_value)
143+
return self.generate_pulse(circ_op, tlist, coeff, phase=phase)
131144

132145

133146
# Define a circuit and run the simulation
134147
num_qubits = 1
135148

136149
circuit = QubitCircuit(1)
137-
circuit.add_gate("RX", targets=0, arg_value=np.pi / 2)
138-
circuit.add_gate("Z", targets=0)
150+
circuit.add_gate(RX(np.pi / 2), targets=0)
151+
circuit.add_gate(Z, targets=0)
139152
result1 = circuit.run(basis(2, 0))
140153

141-
myprocessor = ModelProcessor(model=MyModel(num_qubits))
142-
myprocessor.native_gates = ["RX", "RY"]
143-
144154
mycompiler = MyCompiler(num_qubits, {"pulse_amplitude": 0.02})
145155

156+
myprocessor = ModelProcessor(model=MyModel(num_qubits))
157+
myprocessor.native_gates = ["RX", "RY"]
146158
myprocessor.load_circuit(circuit, compiler=mycompiler)
159+
147160
result2 = myprocessor.run_state(basis(2, 0)).states[-1]
148161
assert abs(fidelity(result1, result2) - 1) < 1.0e-5
149162

@@ -221,14 +234,15 @@ def single_crosstalk_simulation(num_gates):
221234

222235
# Define a randome circuit.
223236
gates_set = [
224-
Gate(name="ROT", arg_value=0),
225-
Gate(name="ROT", arg_value=np.pi / 2),
226-
Gate(name="ROT", arg_value=np.pi),
227-
Gate(name="ROT", arg_value=np.pi / 2 * 3),
237+
ROT(arg_value=0),
238+
ROT(np.pi / 2),
239+
ROT(np.pi),
240+
ROT(np.pi / 2 * 3),
228241
]
229242
circuit = QubitCircuit(num_qubits)
230243
for ind in np.random.randint(0, 4, num_gates):
231244
circuit.add_gate(gates_set[ind], targets=0)
245+
232246
# Simulate the circuit.
233247
myprocessor.load_circuit(circuit, compiler=mycompiler)
234248
init_state = tensor(
@@ -245,68 +259,68 @@ def single_crosstalk_simulation(num_gates):
245259
return result
246260

247261

248-
num_sample = 2
249-
# num_sample = 1600
250-
fidelity = []
251-
fidelity_error = []
252262
init_fid = 0.975
253-
num_gates_list = [250]
254-
# num_gates_list = [250, 500, 750, 1000, 1250, 1500]
255-
256-
# The full simulation may take several hours
257-
# so we just choose num_sample=2 and num_gates=250 as a test
258-
for num_gates in num_gates_list:
259-
expect = Parallel(n_jobs=1)(
260-
delayed(single_crosstalk_simulation)(num_gates)
261-
for i in range(num_sample)
262-
)
263-
fidelity.append(np.mean(expect))
264-
fidelity_error.append(np.std(expect) / np.sqrt(num_sample))
265-
266-
# Recorded result
267-
num_gates_list = [250, 500, 750, 1000, 1250, 1500]
268-
data_y = [
269-
0.9566768747558925,
270-
0.9388905075892828,
271-
0.9229470389282218,
272-
0.9075513000339529,
273-
0.8941659320508855,
274-
0.8756519016627652,
275-
]
276-
277-
data_y_error = [
278-
0.00042992029265330223,
279-
0.0008339882813741004,
280-
0.0012606632769758602,
281-
0.0014643550337816722,
282-
0.0017695604671714809,
283-
0.0020964978542167617,
284-
]
285-
286-
287-
def rb_curve(x, a):
288-
return (1 / 2 + np.exp(-2 * a * x) / 2) * 0.975
289-
290-
291-
pos, cov = curve_fit(rb_curve, num_gates_list, data_y, p0=[0.001])
292-
293-
xline = np.linspace(0, 1700, 200)
294-
yline = rb_curve(xline, *pos)
295-
296-
fig, ax = plt.subplots(figsize=(LINEWIDTH, 0.65 * LINEWIDTH), dpi=200)
297-
ax.errorbar(
298-
num_gates_list,
299-
data_y,
300-
yerr=data_y_error,
301-
fmt=".",
302-
capsize=2,
303-
color="slategrey",
304-
)
305-
ax.plot(xline, yline, color="slategrey")
306-
ax.set_ylabel("Average fidelity")
307-
ax.set_xlabel(r"Number of $\pi$ rotations")
308-
ax.set_xlim((0, 1700))
309263

310-
fig.tight_layout()
311-
fig.savefig("fig4_cross_talk.pdf")
312-
fig.show()
264+
if __name__ == "__main__":
265+
num_sample = 2
266+
# num_sample = 1600
267+
fidelity = []
268+
fidelity_error = []
269+
num_gates_list = [250]
270+
# num_gates_list = [250, 500, 750, 1000, 1250, 1500]
271+
272+
# The full simulation may take several hours
273+
# so we just choose num_sample=2 and num_gates=250 as a test
274+
for num_gates in num_gates_list:
275+
args = [num_gates] * num_sample
276+
with ProcessPoolExecutor() as executor:
277+
expect = list(executor.map(single_crosstalk_simulation, args))
278+
279+
fidelity.append(np.mean(expect))
280+
fidelity_error.append(np.std(expect) / np.sqrt(num_sample))
281+
282+
# Recorded result
283+
num_gates_list = [250, 500, 750, 1000, 1250, 1500]
284+
data_y = [
285+
0.9566768747558925,
286+
0.9388905075892828,
287+
0.9229470389282218,
288+
0.9075513000339529,
289+
0.8941659320508855,
290+
0.8756519016627652,
291+
]
292+
293+
data_y_error = [
294+
0.00042992029265330223,
295+
0.0008339882813741004,
296+
0.0012606632769758602,
297+
0.0014643550337816722,
298+
0.0017695604671714809,
299+
0.0020964978542167617,
300+
]
301+
302+
def rb_curve(x, a):
303+
return (1 / 2 + np.exp(-2 * a * x) / 2) * 0.975
304+
305+
pos, cov = curve_fit(rb_curve, num_gates_list, data_y, p0=[0.001])
306+
307+
xline = np.linspace(0, 1700, 200)
308+
yline = rb_curve(xline, *pos)
309+
310+
fig, ax = plt.subplots(figsize=(LINEWIDTH, 0.65 * LINEWIDTH), dpi=200)
311+
ax.errorbar(
312+
num_gates_list,
313+
data_y,
314+
yerr=data_y_error,
315+
fmt=".",
316+
capsize=2,
317+
color="slategrey",
318+
)
319+
ax.plot(xline, yline, color="slategrey")
320+
ax.set_ylabel("Average fidelity")
321+
ax.set_xlabel(r"Number of $\pi$ rotations")
322+
ax.set_xlim((0, 1700))
323+
324+
fig.tight_layout()
325+
fig.savefig("fig4_cross_talk.pdf")
326+
fig.show()

0 commit comments

Comments
 (0)