Skip to content

Commit 605f89e

Browse files
Sparse fully-connected XEB hacking
1 parent 450e0d4 commit 605f89e

File tree

1 file changed

+161
-0
lines changed

1 file changed

+161
-0
lines changed

scripts/fc_tn_qiskit_validation.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
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 operator
6+
import random
7+
import statistics
8+
import sys
9+
10+
from collections import Counter
11+
12+
from scipy.stats import binom
13+
14+
from pyqrack import QrackSimulator
15+
16+
from qiskit import QuantumCircuit
17+
from qiskit_aer.backends import AerSimulator
18+
from qiskit.quantum_info import Statevector
19+
20+
import quimb.tensor as tn
21+
from qiskit_quimb import quimb_circuit
22+
23+
24+
# Function by Google search AI
25+
def int_to_bitstring(integer, length):
26+
return (bin(integer)[2:].zfill(length))[::-1]
27+
28+
29+
def bench_qrack(width, depth, sdrp):
30+
lcv_range = range(width)
31+
all_bits = list(lcv_range)
32+
shots = 1 << (width + 2)
33+
retained = width * width
34+
35+
rcs = QuantumCircuit(width)
36+
for d in range(depth):
37+
# Single-qubit gates
38+
for i in lcv_range:
39+
for b in range(3):
40+
rcs.h(i)
41+
rcs.rz(random.uniform(0, 2 * math.pi), i)
42+
43+
# 2-qubit couplers
44+
unused_bits = all_bits.copy()
45+
random.shuffle(unused_bits)
46+
while len(unused_bits) > 1:
47+
c = unused_bits.pop()
48+
t = unused_bits.pop()
49+
rcs.cx(c, t)
50+
51+
experiment = QrackSimulator(width, isTensorNetwork=False, isSparse=True, isOpenCL=False)
52+
if sdrp > 0:
53+
experiment.set_sdrp(sdrp)
54+
experiment.run_qiskit_circuit(rcs)
55+
experiment_counts = dict(Counter(experiment.measure_shots(all_bits, shots)))
56+
57+
sorted_counts = sorted(experiment_counts.items(), key=operator.itemgetter(1))
58+
59+
quimb_rcs = quimb_circuit(rcs)
60+
n_pow = 1 << width
61+
u_u = 1 / n_pow
62+
idx = 0
63+
ideal_probs = {}
64+
sum_probs = 0
65+
for idx in range(n_pow):
66+
count_tuple = sorted_counts[idx]
67+
key = count_tuple[0]
68+
prob = abs(quimb_rcs.amplitude(int_to_bitstring(key, width))) ** 2
69+
if prob <= u_u:
70+
continue
71+
val = count_tuple[1]
72+
ideal_probs[key] = val
73+
sum_probs += val
74+
if len(ideal_probs) >= retained:
75+
break
76+
77+
numer = 0
78+
denom = 0
79+
for key in ideal_probs.keys():
80+
ideal = ideal_probs[key]
81+
adj = ideal / sum_probs
82+
ideal_probs[key] = adj
83+
denom += (ideal - u_u) ** 2
84+
numer += (ideal - u_u) * (adj - u_u)
85+
86+
adj_xeb = numer / denom
87+
88+
rcs.save_statevector()
89+
control = AerSimulator(method="statevector")
90+
job = control.run(rcs)
91+
control_probs = Statevector(job.result().get_statevector()).probabilities()
92+
93+
return calc_stats(control_probs, ideal_probs, adj_xeb, shots, depth)
94+
95+
96+
def calc_stats(ideal_probs, exp_probs, adj_xeb, shots, depth):
97+
# For QV, we compare probabilities of (ideal) "heavy outputs."
98+
# If the probability is above 2/3, the protocol certifies/passes the qubit width.
99+
n_pow = len(ideal_probs)
100+
mean_guess = 1 / n_pow
101+
n = int(round(math.log2(n_pow)))
102+
threshold = statistics.median(ideal_probs)
103+
u_u = statistics.mean(ideal_probs)
104+
model = min(1.0, 1 / (adj_xeb * n))
105+
numer = 0
106+
denom = 0
107+
sum_hog_counts = 0
108+
sqr_diff = 0
109+
for i in range(n_pow):
110+
exp = model * (exp_probs[i] if i in exp_probs else 0) + (1.0 - model) * mean_guess
111+
ideal = ideal_probs[i]
112+
113+
# XEB / EPLG
114+
denom += (ideal - u_u) ** 2
115+
numer += (ideal - u_u) * (exp - u_u)
116+
117+
# L2 norm
118+
sqr_diff += (ideal - exp) ** 2
119+
120+
# QV / HOG
121+
if ideal > threshold:
122+
sum_hog_counts += exp * shots
123+
124+
hog_prob = sum_hog_counts / shots
125+
xeb = numer / denom
126+
# p-value of heavy output count, if method were actually 50/50 chance of guessing
127+
p_val = (
128+
(1 - binom.cdf(sum_hog_counts - 1, shots, 1 / 2)) if sum_hog_counts > 0 else 1
129+
)
130+
rss = math.sqrt(sqr_diff)
131+
132+
return {
133+
"qubits": n,
134+
"depth": depth,
135+
"xeb": float(xeb),
136+
"hog_prob": float(hog_prob),
137+
"l2_diff": float(rss),
138+
"p-value": float(p_val),
139+
}
140+
141+
142+
def main():
143+
if len(sys.argv) < 3:
144+
raise RuntimeError(
145+
"Usage: python3 fc_qiskit_validation.py [width] [depth] [trials]"
146+
)
147+
148+
width = int(sys.argv[1])
149+
depth = int(sys.argv[2])
150+
sdrp = 0
151+
if len(sys.argv) > 3:
152+
sdrp = float(sys.argv[3])
153+
154+
# Run the benchmarks
155+
print(bench_qrack(width, depth, sdrp))
156+
157+
return 0
158+
159+
160+
if __name__ == "__main__":
161+
sys.exit(main())

0 commit comments

Comments
 (0)