-
Notifications
You must be signed in to change notification settings - Fork 24
/
draw_qasm3.py
459 lines (366 loc) · 13.2 KB
/
draw_qasm3.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
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
# Copyright (C) 2024 qBraid
#
# This file is part of the qBraid-SDK
#
# The qBraid-SDK is free software released under the GNU General Public License v3
# or later. You can redistribute and/or modify it under the terms of the GPL v3.
# See the LICENSE file in the project root or <https://www.gnu.org/licenses/gpl-3.0.html>.
#
# THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3.
"""
Takes in OpenQASM 3 code and outputs an ASCII circuit representation
"""
import re
import numpy as np
controlled_gates = [
"cx",
"c3x",
"c3sx",
"c4x",
"ccx",
"ch",
"crx",
"cry",
"crz",
"cs",
"csdg",
"cswap",
"csx",
"cu",
"cu1",
"cu3",
"cx",
"cy",
"cz",
"ccz",
]
multi_qubit_gates = [
"cp",
"dcx",
"ecr",
"rccx",
"rc3x",
"rxx",
"ryy",
"rzz",
"rzx",
"xx_minus_yy",
"xx_plus_yy",
"iswap",
"swap",
]
single_qubit_gates = [
"h",
"id",
"p",
"r",
"rx",
"ry",
"rz",
"s",
"sdg",
"sx",
"sxdg",
"t",
"tdg",
"U",
"u1",
"u2",
"u3",
"x",
"y",
"z",
]
q_c_reg_gates = ["m"]
all_gates = single_qubit_gates + multi_qubit_gates + controlled_gates + q_c_reg_gates
def is_valid_gate(line):
"""Checks if input string line is a valid OPENQASM gate"""
if len(line) < 2:
return False
if len(line) > 2 and line[:2] == "//":
return False
if len(line) > 7 and line[:7] == "include":
return False
if len(line) > 8 and line[:8] == "OPENQASM":
return False
return True
def parse_gate_type(line, gates):
"""Returns gate type and params of a line"""
params = None
if "measure" in line:
return ("m", params)
gate_type = line.split(" ")[0]
if "(" in line:
gate_type = line.split("(")[0]
params = re.search(r"\(.*\)", line)[0][1:-1]
if gate_type not in gates:
return (None, None)
return (gate_type, params)
class Gate:
"""An individual gate in the circuit"""
def __init__(self, line, num_qregs, num_cregs):
"""Initializes a gate"""
assert is_valid_gate(line), "Invalid gate: " + str(line)
self.gate_type, self.params = parse_gate_type(line, all_gates)
assert self.gate_type is not None, "Gate is not found in list of possible gates"
self.num_qregs = num_qregs
self.num_cregs = num_cregs
self.qregs = self.parse_qregs(line)
self.cregs = self.parse_cregs(line)
def parse_qregs(self, line):
"""Gets the quantum registers associated with the gate"""
matches = re.findall(r"q\[.*?\]", line)
qregs = []
for match in matches:
qregs.append(int(match[2:-1]))
return qregs
def parse_cregs(self, line):
"""Gets the classical registers associated with the gate"""
matches = re.findall(r"b\[.*?\]", line)
cregs = []
for match in matches:
cregs.append(int(match[2:-1]))
return cregs
def get_height(self):
"""Gets the height of the gate"""
if self.gate_type in single_qubit_gates:
return 1
if self.gate_type in q_c_reg_gates:
return self.num_qregs - min(self.qregs) + max(self.cregs) + 1
return max(self.qregs) - min(self.qregs) + 1
def get_width(self):
"""Gets the width of the gate"""
gate_str = self.gate_type
if self.params is not None:
gate_str += "(" + self.params + ")"
if gate_str in ("swap", "cswap"):
return 3
return len(gate_str) + 4
def apply_gate_at_pos(self, gate_str, mat, pos):
"""Adds the gate at the given y coordinate in the matrix representation"""
mat[pos, :] = ["|"] + ["-"] * (len(gate_str) + 2) + ["|"]
mat[pos + 1, :] = ["|"] + [" "] + list(gate_str) + [" "] + ["|"]
mat[pos + 2, :] = ["|"] + ["-"] * (len(gate_str) + 2) + ["|"]
return mat
def mat(self):
"""Outputs a matrix representation of the gate"""
height = self.get_height()
gate_str = self.gate_type
mat = np.array([])
if self.params is not None:
gate_str += "(" + self.params + ")"
if height == 1:
mat = np.empty((3 * height, len(gate_str) + 4), dtype="str")
mat = self.apply_gate_at_pos(gate_str, mat, 0)
elif self.gate_type == "m":
mat = np.empty((3 * height, len(gate_str) + 4), dtype="str")
mat = self.apply_gate_at_pos(gate_str, mat, 0)
c_reg_line = (
[" "] * int((len(gate_str) + 4) / 2) + ["║"] + [" "] * int((len(gate_str) + 4) / 2)
)
mat[3 : 3 * height - 1, :] = [c_reg_line] * (3 * height - 4)
mat[3 * height - 1, :] = [" "] * (len(gate_str) + 4)
elif self.gate_type in controlled_gates:
if gate_str == "cswap":
mat = np.full((3 * height, 3), " ", dtype="str")
mat[1:-1, 1] = ["|"] * (mat.shape[1] - 2)
mat[(self.qregs[0] - min(self.qregs)) * 3 + 1, :2] = ["-", "■"]
mat[(self.qregs[1] - min(self.qregs)) * 3 + 1, :2] = ["-", "X"]
mat[(self.qregs[2] - min(self.qregs)) * 3 + 1, :2] = ["-", "X"]
return mat
mat = np.full((3 * height, len(gate_str) + 4), " ", dtype="str")
mid = int(mat.shape[1] / 2)
mat[1:-1, mid] = ["|"] * (mat.shape[0] - 2)
for qreg in range(len(self.qregs) - 1):
mat[(self.qregs[qreg] - min(self.qregs)) * 3 + 1, : mid + 1] = ["-"] * mid + ["■"]
for qreg in range(height):
if qreg != self.qregs[-1]:
mat[3 * qreg + 1, :mid] = ["-"] * mid
mat = self.apply_gate_at_pos(gate_str, mat, (self.qregs[-1] - min(self.qregs)) * 3)
elif self.gate_type in multi_qubit_gates:
if gate_str == "swap":
mat = np.empty((3 * height, 3), dtype="str")
mat[0, :] = [" "] * 3
mat[1, :] = ["-", "X", " "]
mat[2 : 3 * height - 2, :] = [[" ", "|", " "]] * (3 * height - 4)
mat[3 * height - 2, :] = ["-", "X", " "]
mat[3 * height - 1, :] = [" "] * 3
mat[range(1, 3 * height, 3), 0] = ["-"] * height
else:
mat = np.full((3 * height, len(gate_str) + 4), " ", dtype="str")
mat[0, :] = ["|"] + ["-"] * (mat.shape[1] - 2) + ["|"]
mat[1:-1, :] = ["|"] + [" "] * (mat.shape[1] - 2) + ["|"]
for qreg_idx, _ in enumerate(self.qregs):
mat[(self.qregs[qreg_idx] - min(self.qregs)) * 3 + 1, 1] = qreg_idx
mat[int(mat.shape[0] / 2), mat.shape[1] - len(gate_str) - 1 : -1] = list(gate_str)
mat[-1, :] = ["|"] + ["-"] * (mat.shape[1] - 2) + ["|"]
return mat
def get_collision(circuit, g):
"""Gets the collision position of a gate"""
pos = circuit.shape[1] - 1
collision = False
reg_idxes = range(3 * min(g.qregs), 3 * (g.get_height() + min(g.qregs)))
while not collision and pos > 0:
for reg_idx in reg_idxes:
if circuit[reg_idx][pos] != " ":
collision = True
break
if collision:
break
pos -= 1
if pos != 0:
pos += 3
return pos, reg_idxes
def add_gate(circuit, g, pos, reg_idxes):
"""Adds a gate to the circuit"""
while pos + g.get_width() >= circuit.shape[1]:
circuit = np.concatenate(
[circuit, np.full((circuit.shape[0], circuit.shape[1]), " ", dtype=str)], axis=1
)
circuit[reg_idxes, pos : pos + g.get_width()] = g.mat()
return circuit
def add_moment(circuit, moment):
"""Adds a moment to the circuit"""
collisions = [get_collision(circuit, g)[0] for g in moment]
pos = max(collisions)
widths = [g.get_width() for g in moment]
max_w = max(widths)
for g in moment:
_, reg_idxes = get_collision(circuit, g)
centered_moment_pos = pos + int((max_w - g.get_width()) / 2)
circuit = add_gate(circuit, g, centered_moment_pos, reg_idxes)
circuit = add_wires(circuit, g, g.num_qregs, centered_moment_pos)
return circuit
def get_circuit_height(qasm_str):
"""Gets the number of qubit and classical registers in the circuit"""
num_qregs = 0
num_cregs = 0
for line in qasm_str.split("\n"):
if re.match(r"qreg q\[.*\]", line):
num_qregs = int(re.match(r"qreg q\[.*\]", line)[0][7:-1])
continue
if re.match(r"creg q\[.*\]", line):
num_cregs = int(re.match(r"creg q\[.*\]", line)[0][7:-1])
continue
if re.match(r"qubit\[.*\]", line):
num_qregs = int(re.match(r"qubit\[.*\]", line)[0][6:-1])
continue
if re.match(r"bit\[.*\]", line):
num_cregs = int(re.match(r"bit\[.*\]", line)[0][4:-1])
continue
return num_qregs, num_cregs
def get_collision_before_pos(circuit, x, y):
"""Gets the position of nearest collision with another gate"""
while circuit[y, x] == " " and x >= 0:
x -= 1
return x
def add_wires(circuit, g, num_qregs, pos):
"""Adds wires to connect gates"""
if pos == 0:
return circuit
start_wire = min(g.qregs)
end_wire = max(g.qregs)
if g.gate_type == "m":
end_wire = num_qregs
for y in range(3 * start_wire + 1, 3 * end_wire + 4, 3):
while circuit[y, pos] == " ":
pos += 1
collision_pos = get_collision_before_pos(circuit, pos - 1, y)
circuit[y, collision_pos + 1 : pos] = ["-"] * (pos - collision_pos - 1)
return circuit
def add_cregs(circuit, num_cregs, end_pos):
"""Adds classical registers to the circuit"""
for bit in range(num_cregs):
y = -2 - (bit * 3)
circuit[y, :end_pos] = ["="] * end_pos
return circuit
def extend_qregs(circuit, num_qregs, end_pos):
"""Extends the quantum registers to the end of the circuit"""
for qreg in range(num_qregs):
eol_regex = re.search(r"(\S)\s*$", "".join(circuit[3 * qreg + 1]))
eol_pos = eol_regex.regs[1][1]
circuit[3 * qreg + 1, eol_pos:end_pos] = ["-"] * (end_pos - eol_pos)
return circuit
def can_add_gate(m_qregs, m_cregs, gate):
"""Checks if a gate can be added to a moment"""
if len(gate.qregs) != 0:
min_qreg = min(gate.qregs)
max_qreg = max(gate.qregs) if gate.gate_type not in q_c_reg_gates else gate.num_qregs
for qreg in range(min_qreg, max_qreg + 1):
if qreg in m_qregs:
return False
if len(gate.cregs) != 0:
min_creg = min(gate.cregs) if gate.gate_type not in q_c_reg_gates else 0
max_creg = max(gate.cregs)
for creg in range(min_creg, max_creg + 1):
if creg in m_cregs:
return False
return True
def get_moments(gates):
"""Splits up the circuit into moments"""
moments = []
while gates != []:
gate = gates.pop(0)
moment = [gate]
m_qregs = list(gate.qregs)
m_cregs = list(gate.cregs)
gate_idx = 0
while gate_idx < len(gates):
gate = gates[gate_idx]
if can_add_gate(m_qregs, m_cregs, gate):
moment.append(gates.pop(gate_idx))
gate_idx -= 1
for qreg in gate.qregs:
m_qregs.append(qreg)
for creg in gate.cregs:
m_cregs.append(creg)
gate_idx += 1
moments.append(moment)
return moments
def _qasm3_drawer(qasm_str: str) -> str:
"""Iterates over the input string and returns the gates"""
num_qregs, num_cregs = get_circuit_height(qasm_str)
height = (num_qregs + num_cregs) * 3
circuit = np.full((height, 50), " ", dtype=str)
gates = []
for line in qasm_str.split("\n"):
valid = is_valid_gate(line)
gate_type, _ = parse_gate_type(line, all_gates)
if valid and gate_type is not None:
g = Gate(line, num_qregs, num_cregs)
gates.append(g)
moments = get_moments(gates)
for moment in moments:
circuit = add_moment(circuit, moment)
# circuit, pos = add_gate(circuit, g)
# circuit = add_wires(circuit, g, num_qregs, pos)
end_pos = circuit.shape[1]
while np.all(circuit[:, end_pos - 1] == [" "] * circuit.shape[0]):
end_pos -= 1
end_pos += 1
circuit = add_cregs(circuit, num_cregs, end_pos)
circuit = extend_qregs(circuit, num_qregs, end_pos)
padding = 5
out = ""
reg_idx = 0
is_q_reg = True
for row_idx, _ in enumerate(circuit):
line = ""
if row_idx % 3 == 1:
if reg_idx < num_qregs and is_q_reg:
line += ("q" + str(reg_idx)).ljust(padding, "-")
elif is_q_reg:
reg_idx = 0
is_q_reg = False
if not is_q_reg:
line += ("c" + str(reg_idx)).ljust(padding, "=")
reg_idx += 1
else:
line += " " * padding
line += "".join(circuit[row_idx]).rstrip() + "\n"
if line.strip() != "":
out += line
return out[:-1]
def qasm3_drawer(qasm_str: str) -> None:
"""Draws the circuit from the input string"""
print(_qasm3_drawer(qasm_str))