Skip to content

Commit f9a5c73

Browse files
Better RCS script
1 parent 59082b6 commit f9a5c73

2 files changed

Lines changed: 169 additions & 147 deletions

File tree

scripts/fc_mps_qrack_validation.py

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
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 pyqrack import QrackSimulator
13+
14+
from qiskit import QuantumCircuit
15+
16+
import quimb.tensor as tn
17+
from qiskit_quimb import quimb_circuit
18+
19+
import jax
20+
import jax.numpy as jnp
21+
22+
23+
# Function by Google search AI
24+
def int_to_bitstring(integer, length):
25+
return (bin(integer)[2:].zfill(length))[::-1]
26+
27+
28+
# Modified to use MPS by (Anthropic) Claude
29+
def bench_qrack(width, depth, sdrp, is_sparse):
30+
lcv_range = range(width)
31+
all_bits = list(lcv_range)
32+
retained = min(width ** 2, 1 << width)
33+
checked = min(width ** 2, 1 << width)
34+
35+
# chi controls approximation quality vs. speed
36+
# chi = width is cheap; chi = width**2 is closer to exact
37+
# for QV circuits at modest width, chi = 2*width is a reasonable start
38+
chi = min(width ** 3, 1 << width)
39+
40+
# CircuitMPS maintains state as MPS with bounded bond dimension
41+
# Gate application is O(chi^2 * width) per gate instead of exact
42+
quimb_rcs = tn.CircuitMPS(width, max_bond=chi, to_backend=jnp.array)
43+
44+
rcs = QuantumCircuit(width)
45+
46+
for d in range(depth):
47+
for i in lcv_range:
48+
th = random.uniform(0, 2 * math.pi)
49+
ph = random.uniform(0, 2 * math.pi)
50+
lm = random.uniform(0, 2 * math.pi)
51+
rcs.u(th, ph, lm, i)
52+
quimb_rcs.apply_gate('U3', th, ph, lm, i)
53+
54+
unused_bits = all_bits.copy()
55+
random.shuffle(unused_bits)
56+
while len(unused_bits) > 1:
57+
c = unused_bits.pop()
58+
t = unused_bits.pop()
59+
rcs.cx(c, t)
60+
quimb_rcs.apply_gate('CX', c, t)
61+
62+
# Run Qrack for heavy output sieve
63+
if is_sparse:
64+
experiment = QrackSimulator(width, isTensorNetwork=False, isOpenCL=False, isSparse=True)
65+
else:
66+
experiment = QrackSimulator(width)
67+
if sdrp > 0:
68+
experiment.set_sdrp(sdrp)
69+
experiment.run_qiskit_circuit(rcs, shots=0)
70+
highest_prob = experiment.highest_prob_perm()
71+
experiment_counts = dict(Counter(experiment.measure_shots(all_bits, checked)))
72+
experiment_counts[highest_prob] = checked
73+
experiment_counts = sorted(experiment_counts.items(), key=operator.itemgetter(1), reverse=True)
74+
experiment = None
75+
76+
# Approximate amplitude estimation via MPS
77+
# amplitude() on CircuitMPS is O(chi^2 * width) per call
78+
# vs. O(exp(treewidth)) for exact contraction
79+
n_pow = 1 << width
80+
u_u = 1 / n_pow
81+
ideal_amps = {}
82+
sum_probs = 0
83+
84+
for count_tuple in experiment_counts:
85+
if len(ideal_amps) >= retained and count_tuple[1] < 2:
86+
break
87+
key = count_tuple[0]
88+
bitstring = int_to_bitstring(key, width)
89+
# Approximate amplitude from MPS — cheap inner product
90+
amp = complex(quimb_rcs.amplitude(bitstring))
91+
prob = abs(amp) ** 2
92+
if prob <= u_u:
93+
continue
94+
ideal_amps[key] = amp
95+
sum_probs += prob
96+
97+
# Normalize
98+
ideal_probs = {k: abs(v)**2 / sum_probs for k, v in ideal_amps.items()}
99+
100+
# Qrack control for XEB reference
101+
control = QrackSimulator(width)
102+
control.run_qiskit_circuit(rcs, shots=0)
103+
control_probs = control.out_probs()
104+
105+
return calc_stats(control_probs, ideal_probs)
106+
107+
108+
def calc_stats(ideal_probs, exp_probs):
109+
# For QV, we compare probabilities of (ideal) "heavy outputs."
110+
# If the probability is above 2/3, the protocol certifies/passes the qubit width.
111+
n_pow = len(ideal_probs)
112+
n = int(round(math.log2(n_pow)))
113+
mean_guess = 1 / n_pow
114+
model = 1 / 2
115+
threshold = statistics.median(ideal_probs)
116+
u_u = statistics.mean(ideal_probs)
117+
numer = 0
118+
denom = 0
119+
hog_prob = 0
120+
sqr_diff = 0
121+
for i in range(n_pow):
122+
exp = (1 - model) * (exp_probs[i] if i in exp_probs else 0) + model * mean_guess
123+
ideal = ideal_probs[i]
124+
125+
# XEB / EPLG
126+
denom += (ideal - u_u) ** 2
127+
numer += (ideal - u_u) * (exp - u_u)
128+
129+
# L2 norm
130+
sqr_diff += (ideal - exp) ** 2
131+
132+
# QV / HOG
133+
if ideal > threshold:
134+
hog_prob += exp
135+
136+
xeb = numer / denom
137+
rss = math.sqrt(sqr_diff)
138+
139+
return {
140+
"qubits": n,
141+
"xeb": float(xeb),
142+
"hog_prob": float(hog_prob),
143+
"l2_diff": float(rss),
144+
}
145+
146+
147+
def main():
148+
if len(sys.argv) < 3:
149+
raise RuntimeError(
150+
"Usage: python3 fc_qiskit_validation.py [width] [depth] [sdrp] [is_sparse]"
151+
)
152+
153+
width = int(sys.argv[1])
154+
depth = int(sys.argv[2])
155+
sdrp = 0 # (1.0 - 1.0 / math.sqrt(2)) / 2.0
156+
is_sparse = False
157+
if len(sys.argv) > 3:
158+
sdrp = float(sys.argv[3])
159+
if len(sys.argv) > 4:
160+
is_sparse = sys.argv[4] not in ["False", "0"]
161+
162+
# Run the benchmarks
163+
print(bench_qrack(width, depth, sdrp, is_sparse))
164+
165+
return 0
166+
167+
168+
if __name__ == "__main__":
169+
sys.exit(main())

scripts/fc_tn_qiskit_validation.py

Lines changed: 0 additions & 147 deletions
This file was deleted.

0 commit comments

Comments
 (0)