Skip to content

Commit afac9ed

Browse files
Qrack RCS OTOC
1 parent 40c0c33 commit afac9ed

File tree

1 file changed

+242
-0
lines changed

1 file changed

+242
-0
lines changed
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
# How good are Google's own "patch circuits" and "elided circuits" as a direct XEB approximation to full Sycamore circuits?
2+
# (Are they better than the 2019 Sycamore hardware?)
3+
4+
import math
5+
import random
6+
import statistics
7+
import sys
8+
9+
from collections import Counter
10+
11+
from scipy.stats import binom
12+
13+
from pyqrack import QrackSimulator
14+
15+
from qiskit import QuantumCircuit
16+
from qiskit_aer.backends import AerSimulator
17+
from qiskit.quantum_info import Statevector
18+
19+
20+
def factor_width(width):
21+
col_len = math.floor(math.sqrt(width))
22+
while ((width // col_len) * col_len) != width:
23+
col_len -= 1
24+
row_len = width // col_len
25+
if col_len == 1:
26+
raise Exception("ERROR: Can't simulate prime number width!")
27+
28+
return (row_len, col_len)
29+
30+
31+
def cx(sim, q1, q2):
32+
sim.cx(q1, q2)
33+
34+
35+
def cy(sim, q1, q2):
36+
sim.cy(q1, q2)
37+
38+
39+
def cz(sim, q1, q2):
40+
sim.cz(q1, q2)
41+
42+
43+
def acx(sim, q1, q2):
44+
sim.x(q1)
45+
sim.cx(q1, q2)
46+
sim.x(q1)
47+
48+
49+
def acy(sim, q1, q2):
50+
sim.x(q1)
51+
sim.cy(q1, q2)
52+
sim.x(q1)
53+
54+
55+
def acz(sim, q1, q2):
56+
sim.x(q1)
57+
sim.cz(q1, q2)
58+
sim.x(q1)
59+
60+
61+
def swap(sim, q1, q2):
62+
sim.swap(q1, q2)
63+
64+
65+
def iswap(sim, q1, q2):
66+
sim.swap(q1, q2)
67+
sim.cz(q1, q2)
68+
sim.s(q1)
69+
sim.s(q2)
70+
71+
72+
def iiswap(sim, q1, q2):
73+
sim.sdg(q2)
74+
sim.sdg(q1)
75+
sim.cz(q1, q2)
76+
sim.swap(q1, q2)
77+
78+
79+
def pswap(sim, q1, q2):
80+
sim.cz(q1, q2)
81+
sim.swap(q1, q2)
82+
83+
84+
def mswap(sim, q1, q2):
85+
sim.swap(q1, q2)
86+
sim.cz(q1, q2)
87+
88+
89+
def nswap(sim, q1, q2):
90+
sim.cz(q1, q2)
91+
sim.swap(q1, q2)
92+
sim.cz(q1, q2)
93+
94+
95+
def bench_qrack(width, depth, cycles):
96+
# This is a "nearest-neighbor" coupler random circuit.
97+
98+
lcv_range = range(width)
99+
all_bits = list(lcv_range)
100+
101+
# Nearest-neighbor couplers:
102+
gateSequence = [0, 3, 2, 1, 2, 1, 0, 3]
103+
two_bit_gates = swap, pswap, mswap, nswap, iswap, iiswap, cx, cy, cz, acx, acy, acz
104+
105+
row_len, col_len = factor_width(width)
106+
107+
rcs = QuantumCircuit(width)
108+
for d in range(depth):
109+
# Single-qubit gates
110+
for i in lcv_range:
111+
th = random.uniform(0, 2 * math.pi)
112+
ph = random.uniform(0, 2 * math.pi)
113+
lm = random.uniform(0, 2 * math.pi)
114+
rcs.u(th, ph, lm, i)
115+
116+
# Nearest-neighbor couplers:
117+
############################
118+
gate = gateSequence.pop(0)
119+
gateSequence.append(gate)
120+
for row in range(1, row_len, 2):
121+
for col in range(col_len):
122+
temp_row = row
123+
temp_col = col
124+
temp_row = temp_row + (1 if (gate & 2) else -1)
125+
temp_col = temp_col + (1 if (gate & 1) else 0)
126+
127+
if temp_row < 0:
128+
temp_row = temp_row + row_len
129+
if temp_col < 0:
130+
temp_col = temp_col + col_len
131+
if temp_row >= row_len:
132+
temp_row = temp_row - row_len
133+
if temp_col >= col_len:
134+
temp_col = temp_col - col_len
135+
136+
b1 = row * row_len + col
137+
b2 = temp_row * row_len + temp_col
138+
139+
if (b1 >= width) or (b2 >= width):
140+
continue
141+
142+
g = random.choice(two_bit_gates)
143+
g(rcs, b1, b2)
144+
145+
ops = ['I', 'X', 'Y', 'Z']
146+
pauli_strings = []
147+
148+
otoc = QuantumCircuit(width)
149+
for cycle in range(cycles):
150+
otoc &= rcs
151+
string = []
152+
for b in range(width):
153+
string.append(random.choice(ops))
154+
pauli_strings.append("".join(string))
155+
act_string(otoc, string)
156+
otoc &= rcs.inverse()
157+
158+
159+
experiment = QrackSimulator(width)
160+
experiment.run_qiskit_circuit(otoc)
161+
162+
otoc_aer = otoc.copy()
163+
otoc_aer.save_statevector()
164+
control = AerSimulator(method="statevector")
165+
job = control.run(otoc_aer)
166+
167+
shots = 1 << (width + 2)
168+
experiment_counts = dict(Counter(experiment.measure_shots(all_bits, shots)))
169+
control_probs = Statevector(job.result().get_statevector()).probabilities()
170+
171+
return calc_stats(control_probs, experiment_counts, d + 1, shots), pauli_strings
172+
173+
174+
def act_string(otoc, string):
175+
for i in range(len(string)):
176+
match string[i]:
177+
case 'X':
178+
otoc.x(i)
179+
case 'Y':
180+
otoc.y(i)
181+
case 'Z':
182+
otoc.z(i)
183+
case _:
184+
pass
185+
186+
187+
def calc_stats(ideal_probs, counts, depth, shots):
188+
# For QV, we compare probabilities of (ideal) "heavy outputs."
189+
# If the probability is above 2/3, the protocol certifies/passes the qubit width.
190+
n_pow = len(ideal_probs)
191+
n = int(round(math.log2(n_pow)))
192+
threshold = statistics.median(ideal_probs)
193+
u_u = statistics.mean(ideal_probs)
194+
numer = 0
195+
denom = 0
196+
sum_hog_counts = 0
197+
for i in range(n_pow):
198+
count = counts[i] if i in counts else 0
199+
ideal = ideal_probs[i]
200+
201+
# XEB / EPLG
202+
denom += (ideal - u_u) ** 2
203+
numer += (ideal - u_u) * ((count / shots) - u_u)
204+
205+
# QV / HOG
206+
if ideal > threshold:
207+
sum_hog_counts += count
208+
209+
hog_prob = sum_hog_counts / shots
210+
xeb = numer / denom
211+
# p-value of heavy output count, if method were actually 50/50 chance of guessing
212+
p_val = (
213+
(1 - binom.cdf(sum_hog_counts - 1, shots, 1 / 2)) if sum_hog_counts > 0 else 1
214+
)
215+
216+
return {
217+
"qubits": n,
218+
"depth": depth,
219+
"xeb": float(xeb),
220+
"hog_prob": float(hog_prob),
221+
"p-value": float(p_val),
222+
}
223+
224+
225+
def main():
226+
if len(sys.argv) < 4:
227+
raise RuntimeError(
228+
"Usage: python3 fc_qiskit_validation.py [width] [depth] [cycles]"
229+
)
230+
231+
width = int(sys.argv[1])
232+
depth = int(sys.argv[2])
233+
cycles = int(sys.argv[3])
234+
235+
# Run the benchmarks
236+
print(bench_qrack(width, depth, cycles))
237+
238+
return 0
239+
240+
241+
if __name__ == "__main__":
242+
sys.exit(main())

0 commit comments

Comments
 (0)