forked from zxcalc/pyzx
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtest_phasepoly.py
299 lines (275 loc) · 11.9 KB
/
test_phasepoly.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
from typing import List
import unittest
import sys, os
if __name__ == "__main__":
sys.path.append("..")
sys.path.append(".")
import numpy as np
from fractions import Fraction
from pyzx.routing import architecture
from pyzx.routing.architecture import create_architecture
from pyzx.routing.cnot_mapper import ElimMode
from pyzx.routing.parity_maps import CNOT_tracker, Parity
from pyzx.linalg import Mat2
from pyzx.routing.phase_poly import PhasePoly
from pyzx.circuit import Circuit, HAD, CNOT, ZPhase, XPhase
from pyzx.tensor import compare_tensors
from pyzx.extract import permutation_as_swaps
SEED = 42
class TestPhasePoly(unittest.TestCase):
def setUp(self):
np.random.seed(SEED)
self.n_tests = 1
self.n_qubits = 5
name = architecture.LINE
self.architecture = create_architecture(name, n_qubits=self.n_qubits)
# Define some circuits to work with
folder = "circuits/steiner/" + str(self.n_qubits) + "qubits/"
try:
cnot_options = next(os.walk(folder))
except StopIteration:
# There are no circuits to test with
return
n_cnots = cnot_options[1]
self.circuit = []
n_phase_layers = 5
self.n_phase_layers = n_phase_layers
def filename():
return os.path.join(
*[
folder,
n_cnots[np.random.choice(len(n_cnots))],
"Original" + str(np.random.choice(20)) + ".qasm",
]
)
for _ in range(self.n_tests):
c = Circuit.from_qasm_file(filename())
for _ in range(n_phase_layers):
for i in range(self.n_qubits):
if (
np.random.choice(2, p=[0.2, 0.8]) == 1
): # Pick H gate with chance .2
phase = np.random.choice([1, -1]) * Fraction(
1, int(np.random.choice([1, 2, 4]))
)
c.add_gate(ZPhase(target=i, phase=phase))
else:
c.add_gate(HAD(target=i))
c.add_circuit(Circuit.from_qasm_file(filename()))
self.circuit.append(c)
def assertGates(self, circuit):
for gate in circuit.gates:
if hasattr(gate, "name") and gate.name == "CNOT":
edge = (gate.control, gate.target)
self.assertTrue(
edge in self.architecture.graph.edges()
or tuple(reversed(edge)) in self.architecture.graph.edges()
)
def assertMat2Equal(self, m1, m2, triangle=False):
if triangle:
self.assertListEqual(
*[
[
m.data[i, j]
for i in range(self.n_qubits)
for j in range(self.n_qubits)
if i >= j
]
for m in [m1, m2]
]
)
else:
self.assertNdArrEqual(m1.data, m2.data)
def assertNdArrEqual(self, a1, a2):
if isinstance(a1, list):
if isinstance(a2, list):
self.assertListEqual(a1, a2)
else:
self.assertListEqual(a1, a2.tolist())
else:
if isinstance(a2, list):
self.assertListEqual(a1.tolist(), a2)
else:
self.assertListEqual(a1.tolist(), a2.tolist())
def assertCircuitEquivalent(self, c1, c2):
the_same = compare_tensors(c1, c2)
self.assertTrue(the_same)
def assertPhasePolyEqual(self, p1, p2):
# self.assertDictEqual(p1.xphases, p2.xphases)
self.assertDictEqual(p1.zphases, p2.zphases)
self.assertListEqual(p1.out_par, p2.out_par)
def assertPartitionEqual(self, sets, partitions: List[List[Parity]]):
flat_partition = [p for subset in partitions for p in subset]
# Are all sets partitioned?
for s in sets:
self.assertIn(s, flat_partition)
extra_parities = []
for p in flat_partition:
if p not in sets:
extra_parities.append(p)
for p in extra_parities:
self.assertTrue(p.count() == 1)
# self.assertCountEqual(extra_parities, list(set(extra_parities))) # No duplicates
# [self.assertIn(p, sets) for p in flat_partition]
# Is every partition a set of independent parities?
for pt in partitions:
self.assertTrue(PhasePoly._independent(pt))
def assertFinalParityEqual(self, c1, c2):
old_cnots = CNOT_tracker(c1.qubits)
new_cnots = CNOT_tracker(c1.qubits)
for gate in c1.gates:
if isinstance(gate, CNOT):
old_cnots.row_add(gate.control, gate.target)
for gate in c2.gates:
if isinstance(gate, CNOT):
new_cnots.row_add(gate.control, gate.target)
self.assertCircuitEquivalent(old_cnots, new_cnots)
return old_cnots, new_cnots
""" Matroid partitioning is not a good algorithm
def test_partitions(self):
for i in range(self.n_tests):
with self.subTest(i=i):
circuit = Circuit(self.circuit[i].qubits)
for gate in self.circuit[i].gates:
if isinstance(gate, HAD):
break
circuit.add_gate(gate)
# Check if the phase poly is created properly
phase_poly = PhasePoly.fromCircuit(circuit)
# Check the partitions
partitions = phase_poly.partition(skip_output_parities=False)
self.assertPartitionEqual(phase_poly.all_parities, partitions)
"""
def test_phase_poly_parity_creation(self):
for i in range(self.n_tests):
with self.subTest(i=i):
n_qubits = self.circuit[i].qubits
circuit = Circuit(n_qubits)
for gate in self.circuit[i].gates:
if isinstance(gate, HAD):
break
circuit.add_gate(gate)
# Check if the phase poly is created properly
phase_poly = PhasePoly.fromCircuit(circuit)
old_cnots = CNOT_tracker(n_qubits)
for gate in circuit.gates:
if isinstance(gate, CNOT):
old_cnots.row_add(gate.control, gate.target)
original_out_par = [
Parity(row) for row in old_cnots.matrix.data
]
# Check if the output parities are the same
self.assertListEqual(original_out_par, phase_poly.out_par)
def test_phase_poly_phase_creation(self):
for i in range(self.n_tests):
with self.subTest(i=i):
n_qubits = self.circuit[i].qubits
circuit = Circuit(n_qubits)
had_phase = False
for gate in self.circuit[i].gates:
if isinstance(gate, HAD):
break
elif isinstance(gate, ZPhase) or isinstance(gate, XPhase):
had_phase = True
elif had_phase and isinstance(gate, CNOT):
break
circuit.add_gate(gate)
# Check if the phase poly is created properly
phase_poly = PhasePoly.fromCircuit(circuit)
old_cnots = CNOT_tracker(n_qubits)
for gate in circuit.gates:
if isinstance(gate, CNOT):
old_cnots.row_add(gate.control, gate.target)
original_out_par = [
"".join([str(v) for v in row]) for row in old_cnots.matrix.data
]
# Check if the output parities are the same
self.assertListEqual(original_out_par, phase_poly.out_par)
# Check if the original phase poly is the same
phase_poly_parities = list(
phase_poly.zphases.keys()
) # + list(phase_poly.xphases.keys())
for parity in phase_poly_parities:
# Check if all phase poly parities are in the original
self.assertIn(parity, original_out_par)
def test_phase_poly_routed(self):
for i in range(self.n_tests):
with self.subTest(i=i):
c = Circuit(self.circuit[i].qubits)
for gate in self.circuit[i].gates:
if isinstance(gate, HAD):
break
c.add_gate(gate)
self.do_phase_poly(
c.copy(), ElimMode.STEINER_MODE, self.architecture, routed=True
)
def do_phase_poly(
self, circuit, mode, architecture, routed=False, tensor_compare=False
):
phase_poly = PhasePoly.fromCircuit(circuit)
# TODO: test the other synthesis functions
# for func in [phase_poly.matroid_synth, phase_poly.gray_synth, phase_poly.rec_gray_synth, phase_poly.Ariannes_synth]:
for func in [phase_poly.Ariannes_synth]:
with self.subTest(i=func):
# Check the synthesized circuit
new_circuit, initial_perm, output_perm = func(
mode=mode, architecture=architecture, full_reduce=True
)
if routed:
self.assertGates(new_circuit)
adjusted_circuit = self.apply_perms(
new_circuit, initial_perm, output_perm
)
new_phase_poly = PhasePoly.fromCircuit(adjusted_circuit)
# Check if the phasepolys are the same
self.assertPhasePolyEqual(phase_poly, new_phase_poly)
self.assertFinalParityEqual(circuit, adjusted_circuit)
# Check if the circuits are the same
self.assertCircuitEquivalent(adjusted_circuit, circuit)
def test_tensor_compare(self):
n_qubits = 2
old_qubits = self.n_qubits
self.n_qubits = n_qubits
circuit = Circuit(n_qubits)
n_cnots = 1
for _ in range(n_cnots):
circuit.add_gate("CNOT", *np.random.choice(n_qubits, 2, False))
for i in range(self.n_qubits):
phase = np.random.choice([1, -1]) * Fraction(
1, int(np.random.choice([1, 2, 4]))
)
circuit.add_gate(ZPhase(target=i, phase=phase))
for _ in range(n_cnots):
circuit.add_gate("CNOT", *np.random.choice(n_qubits, 2, False))
# for i in range(self.n_qubits):
# phase = np.random.choice([1,-1])*Fraction(1, int(np.random.choice([1,2,4])))
# circuit.add_gate(ZPhase(target=i, phase=phase))
# for _ in range(n_cnots):
# circuit.add_gate("CNOT", *np.random.choice(n_qubits, 2, False))
self.do_phase_poly(
circuit.copy(),
ElimMode.STEINER_MODE,
None,
routed=False,
tensor_compare=True,
)
self.n_qubits = old_qubits
def apply_perms(self, circuit, initial_perm, output_perm):
adjusted_circuit = Circuit(self.n_qubits)
# Undo the initial permutation
for q1, q2 in permutation_as_swaps({v: k for k, v in enumerate(initial_perm)}):
adjusted_circuit.add_gate(CNOT(q1, q2))
adjusted_circuit.add_gate(CNOT(q2, q1))
adjusted_circuit.add_gate(CNOT(q1, q2))
# Do the circuit
for gate in circuit.gates:
adjusted_circuit.add_gate(gate)
# Realise the output permutation
for q1, q2 in permutation_as_swaps({k: v for k, v in enumerate(output_perm)}):
adjusted_circuit.add_gate(CNOT(q1, q2))
adjusted_circuit.add_gate(CNOT(q2, q1))
adjusted_circuit.add_gate(CNOT(q1, q2))
return adjusted_circuit
if __name__ == "__main__":
print("Testing PhasePoly")
unittest.main()