Skip to content

Commit f33d28c

Browse files
committed
circuit drawer mvp
1 parent 7864289 commit f33d28c

File tree

1 file changed

+90
-1
lines changed

1 file changed

+90
-1
lines changed

qubit_simulator/simulator.py

+90-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import numpy as np
22
import matplotlib.pyplot as plt
33
from matplotlib.colors import hsv_to_rgb
4+
from matplotlib.patches import Circle, Rectangle
45
from .gates import Gates
56

67

@@ -15,9 +16,10 @@ def __init__(self, num_qubits: int):
1516
# Statevector of length 2^n, start in |0...0>
1617
self.state = np.zeros(2**num_qubits, dtype=complex)
1718
self.state[0] = 1.0
19+
self._circuit = []
1820

1921
def __sizeof__(self):
20-
return self.state.nbytes + 8 # 8 bytes for the int n
22+
return self.state.nbytes + sum(op.__sizeof__() for op in self._circuit) + 8
2123

2224
def reset(self):
2325
self.state = np.zeros(2**self.n, dtype=complex)
@@ -43,46 +45,59 @@ def _apply_gate(self, U: np.ndarray, qubits: list):
4345
# Single-qubit gates
4446
def x(self, q: int):
4547
self._apply_gate(Gates.X, [q])
48+
self._circuit.append(("X", [q]))
4649

4750
def y(self, q: int):
4851
self._apply_gate(Gates.Y, [q])
52+
self._circuit.append(("Y", [q]))
4953

5054
def z(self, q: int):
5155
self._apply_gate(Gates.Z, [q])
56+
self._circuit.append(("Z", [q]))
5257

5358
def h(self, q: int):
5459
self._apply_gate(Gates.H, [q])
60+
self._circuit.append(("H", [q]))
5561

5662
def s(self, q: int):
5763
self._apply_gate(Gates.S, [q])
64+
self._circuit.append(("S", [q]))
5865

5966
def t(self, q: int):
6067
self._apply_gate(Gates.T, [q])
68+
self._circuit.append(("T", [q]))
6169

6270
def u(self, theta: float, phi: float, lam: float, q: int):
6371
self._apply_gate(Gates.U(theta, phi, lam), [q])
72+
self._circuit.append(("U", [q], (theta, phi, lam)))
6473

6574
# Two-qubit gates
6675
def cx(self, control: int, target: int):
6776
self._apply_gate(Gates.controlled_gate(Gates.X), [control, target])
77+
self._circuit.append(("CX", [control, target]))
6878

6979
def cu(self, theta: float, phi: float, lam: float, control: int, target: int):
7080
self._apply_gate(
7181
Gates.controlled_gate(Gates.U(theta, phi, lam)), [control, target]
7282
)
83+
self._circuit.append(("CU", [control, target], (theta, phi, lam)))
7384

7485
def swap(self, q1: int, q2: int):
7586
self._apply_gate(Gates.SWAP_matrix(), [q1, q2])
87+
self._circuit.append(("SWAP", [q1, q2]))
7688

7789
def iswap(self, q1: int, q2: int):
7890
self._apply_gate(Gates.iSWAP_matrix(), [q1, q2])
91+
self._circuit.append(("iSWAP", [q1, q2]))
7992

8093
# Three-qubit gates
8194
def toffoli(self, c1: int, c2: int, t: int):
8295
self._apply_gate(Gates.Toffoli_matrix(), [c1, c2, t])
96+
self._circuit.append(("TOFFOLI", [c1, c2, t]))
8397

8498
def fredkin(self, c: int, t1: int, t2: int):
8599
self._apply_gate(Gates.Fredkin_matrix(), [c, t1, t2])
100+
self._circuit.append(("FREDKIN", [c, t1, t2]))
86101

87102
# Simulation & measurement
88103
def run(self, shots: int = 100) -> dict[str, int]:
@@ -114,3 +129,77 @@ def plot_state(self):
114129
cb = plt.colorbar(plt.cm.ScalarMappable(cmap="hsv"), ax=ax)
115130
cb.set_label("Phase (radians mod 2π)")
116131
plt.show()
132+
133+
def draw(self, ax: plt.Axes = None, figsize: tuple[int, int] = None):
134+
if ax is None:
135+
if not figsize:
136+
figsize = (max(8, len(self._circuit)), self.n + 1)
137+
fig, ax = plt.subplots(figsize=figsize)
138+
for q in range(self.n):
139+
ax.hlines(q, -0.5, len(self._circuit) - 0.5, color="k")
140+
cC = lambda x, y: ax.add_patch(Circle((x, y), 0.08, fc="k", zorder=3))
141+
xT = lambda x, y: (
142+
ax.add_patch(Circle((x, y), 0.18, fc="w", ec="k", zorder=3)),
143+
ax.plot(
144+
[x - 0.1, x + 0.1],
145+
[y - 0.1, y + 0.1],
146+
"k",
147+
[x - 0.1, x + 0.1],
148+
[y + 0.1, y - 0.1],
149+
"k",
150+
zorder=4,
151+
),
152+
)
153+
box = lambda x, y, t: (
154+
ax.add_patch(
155+
Rectangle(
156+
(x - 0.3, y - 0.3), 0.6, 0.6, fc="lightblue", ec="k", zorder=3
157+
)
158+
),
159+
ax.text(x, y, t, ha="center", va="center", zorder=4),
160+
)
161+
for i, (g, qs, *pars) in enumerate(self._circuit):
162+
if g in "XYZHST":
163+
box(i, qs[0], g)
164+
elif g == "U":
165+
box(
166+
i, qs[0], f"U\n({pars[0][0]:.2g},{pars[0][1]:.2g},{pars[0][2]:.2g})"
167+
)
168+
elif g in ("CX", "CU"):
169+
ax.vlines(i, *sorted(qs), color="k")
170+
cC(i, qs[0])
171+
if g == "CX":
172+
xT(i, qs[1])
173+
else:
174+
box(
175+
i,
176+
qs[1],
177+
f"U\n({pars[0][0]:.2g},{pars[0][1]:.2g},{pars[0][2]:.2g})",
178+
)
179+
elif g in ("SWAP", "iSWAP"):
180+
ax.vlines(i, *sorted(qs), color="k")
181+
xT(i, qs[0])
182+
xT(i, qs[1])
183+
if g == "iSWAP":
184+
ax.text(i, sum(qs) / 2, "i", ha="center", va="center", zorder=4)
185+
elif g == "TOFFOLI":
186+
ax.vlines(i, min(qs), max(qs), color="k")
187+
cC(i, qs[0])
188+
cC(i, qs[1])
189+
xT(i, qs[2])
190+
elif g == "FREDKIN":
191+
ax.vlines(i, min(qs), max(qs), color="k")
192+
cC(i, qs[0])
193+
xT(i, qs[1])
194+
xT(i, qs[2])
195+
else:
196+
box(i, qs[0], g)
197+
for q in range(self.n):
198+
ax.text(-1, q, f"q{q}", ha="right", va="center")
199+
ax.set_xlim(-1, len(self._circuit))
200+
ax.set_ylim(-0.5, self.n - 0.5)
201+
ax.invert_yaxis()
202+
ax.axis("off")
203+
ax.set_title("Circuit Diagram")
204+
plt.tight_layout()
205+
plt.show()

0 commit comments

Comments
 (0)