qoptct 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qoptct/__init__.py +28 -0
- qoptct/bench_real.py +212 -0
- qoptct/block_synth.py +265 -0
- qoptct/circuit_opt.py +127 -0
- qoptct/contrib/__init__.py +0 -0
- qoptct/contrib/c1/__init__.py +0 -0
- qoptct/contrib/c1/best_clean/MANIFEST.json +13 -0
- qoptct/contrib/c1/best_clean/csla_mux_3.qasm +156 -0
- qoptct/contrib/c1/best_clean/gf2^4_mult.qasm +184 -0
- qoptct/contrib/c1/best_clean/gf2^5_mult.qasm +288 -0
- qoptct/contrib/c1/best_clean/mod5_4.qasm +23 -0
- qoptct/contrib/c1/best_clean/mod_mult_55.qasm +71 -0
- qoptct/contrib/c1/best_clean/mod_red_21.qasm +118 -0
- qoptct/contrib/c1/best_clean/rc_adder_6.qasm +156 -0
- qoptct/contrib/c1/best_clean/tof_10.qasm +122 -0
- qoptct/contrib/c1/best_clean/tof_3.qasm +31 -0
- qoptct/contrib/c1/best_clean/tof_4.qasm +44 -0
- qoptct/contrib/c1/best_clean/tof_5.qasm +57 -0
- qoptct/contrib/c1/csyn.py +265 -0
- qoptct/contrib/c1/resyn.py +122 -0
- qoptct/contrib/c1/resyn2.py +136 -0
- qoptct/contrib/c1/resyn3.py +262 -0
- qoptct/contrib/c1/resyn4.py +97 -0
- qoptct/contrib/c1/run.py +71 -0
- qoptct/contrib/c10/__init__.py +0 -0
- qoptct/contrib/c10/pathsum.py +191 -0
- qoptct/contrib/c10/verify_big.py +251 -0
- qoptct/contrib/c12/__init__.py +0 -0
- qoptct/contrib/c12/mcx.py +271 -0
- qoptct/contrib/c2/__init__.py +0 -0
- qoptct/contrib/c2/andc.py +521 -0
- qoptct/contrib/c2/peep.py +59 -0
- qoptct/contrib/c2/run.py +51 -0
- qoptct/contrib/c2/sweep2.py +72 -0
- qoptct/contrib/c8/__init__.py +0 -0
- qoptct/contrib/c8/hyb.py +159 -0
- qoptct/contrib/eng1/__init__.py +0 -0
- qoptct/contrib/eng1/det.py +333 -0
- qoptct/contrib/eng1/det2.py +386 -0
- qoptct/contrib/eng1/equiv.py +88 -0
- qoptct/contrib/eng1/fasttodd.py +144 -0
- qoptct/contrib/eng1/gad.py +272 -0
- qoptct/contrib/eng1/gad2.py +80 -0
- qoptct/contrib/eng1/ours/adder_8.json +1 -0
- qoptct/contrib/eng1/ours/barenco_tof_10.json +1 -0
- qoptct/contrib/eng1/ours/barenco_tof_3.json +1 -0
- qoptct/contrib/eng1/ours/barenco_tof_4.json +1 -0
- qoptct/contrib/eng1/ours/barenco_tof_5.json +1 -0
- qoptct/contrib/eng1/ours/csla_mux_3.json +1 -0
- qoptct/contrib/eng1/ours/csum_mux_9.json +1 -0
- qoptct/contrib/eng1/ours/gf2^4_mult.json +1 -0
- qoptct/contrib/eng1/ours/gf2^5_mult.json +1 -0
- qoptct/contrib/eng1/ours/grover_5.json +1 -0
- qoptct/contrib/eng1/ours/ham15-low.json +1 -0
- qoptct/contrib/eng1/ours/mod5_4.json +1 -0
- qoptct/contrib/eng1/ours/mod_mult_55.json +1 -0
- qoptct/contrib/eng1/ours/mod_red_21.json +1 -0
- qoptct/contrib/eng1/ours/qcla_com_7.json +1 -0
- qoptct/contrib/eng1/ours/qft_4.json +1 -0
- qoptct/contrib/eng1/ours/rc_adder_6.json +1 -0
- qoptct/contrib/eng1/ours/tof_10.json +1 -0
- qoptct/contrib/eng1/ours/tof_3.json +1 -0
- qoptct/contrib/eng1/ours/tof_4.json +1 -0
- qoptct/contrib/eng1/ours/tof_5.json +1 -0
- qoptct/contrib/eng1/ours/vbe_adder_3.json +1 -0
- qoptct/contrib/eng2/__init__.py +0 -0
- qoptct/contrib/eng2/improve.py +79 -0
- qoptct/contrib/eng2/regions.py +69 -0
- qoptct/contrib/eng2/synth.py +216 -0
- qoptct/contrib/tg2/__init__.py +0 -0
- qoptct/contrib/tg2/sel.py +293 -0
- qoptct/contrib/tg3/__init__.py +0 -0
- qoptct/contrib/tg3/core.py +229 -0
- qoptct/contrib/tg3/post.py +102 -0
- qoptct/contrib/tg3/search.py +145 -0
- qoptct/contrib/tg3/search2.py +73 -0
- qoptct/contrib/tg3/search_fin.py +32 -0
- qoptct/contrib/tg3/tg3_opt.py +69 -0
- qoptct/ei2.py +553 -0
- qoptct/expert_iter.py +230 -0
- qoptct/fast_gates.py +31 -0
- qoptct/gidney.py +190 -0
- qoptct/global_cancel.py +36 -0
- qoptct/meas_portfolio.py +54 -0
- qoptct/models/mx_4_4q.pt +0 -0
- qoptct/models/rt_3_si2_3q.pt +0 -0
- qoptct/models/u_2_best_2q.pt +0 -0
- qoptct/models/value_u4q.pt +0 -0
- qoptct/portfolio/__init__.py +0 -0
- qoptct/portfolio/bench_hook.py +47 -0
- qoptct/portfolio/evo_lib.py +373 -0
- qoptct/portfolio/programs/__init__.py +0 -0
- qoptct/portfolio/programs/c0001_combo2.py +10 -0
- qoptct/portfolio/programs/c0002_vphase_push.py +198 -0
- qoptct/portfolio/programs/c0005_phasepoly_graysynth.py +299 -0
- qoptct/portfolio/programs/c0006_combo2_permpair.py +150 -0
- qoptct/portfolio/programs/c0007_conj_rtof.py +203 -0
- qoptct/portfolio/programs/c0008_cla_collapse.py +292 -0
- qoptct/portfolio/programs/c0009_phasepoly_rtof4.py +298 -0
- qoptct/portfolio/programs/c0010_combo2_majcarry.py +208 -0
- qoptct/portfolio/programs/c0011_rtof3_vphase.py +248 -0
- qoptct/portfolio/programs/c0013_unify_permpair_vphase.py +300 -0
- qoptct/portfolio/programs/c0014_majcarry_ppresynth.py +298 -0
- qoptct/portfolio/programs/c0015_mcx_resynth.py +151 -0
- qoptct/portfolio/programs/c0017_rtof3_esop.py +297 -0
- qoptct/proto2/__init__.py +0 -0
- qoptct/proto2/phase_fold.py +67 -0
- qoptct/proto2/rtof.py +246 -0
- qoptct/proto2/verify.py +62 -0
- qoptct/qenv.py +147 -0
- qoptct/qfast.py +182 -0
- qoptct/qgnn.py +305 -0
- qoptct/qiskit_qopt.py +309 -0
- qoptct/qopt_core.py +466 -0
- qoptct/qopt_metrics.py +107 -0
- qoptct/tpoly.py +165 -0
- qoptct/train_agent.py +153 -0
- qoptct/vartodd_bridge.py +73 -0
- qoptct/verify_qasm.py +69 -0
- qoptct-0.3.0.dist-info/METADATA +282 -0
- qoptct-0.3.0.dist-info/RECORD +126 -0
- qoptct-0.3.0.dist-info/WHEEL +5 -0
- qoptct-0.3.0.dist-info/entry_points.txt +8 -0
- qoptct-0.3.0.dist-info/licenses/LICENSE +202 -0
- qoptct-0.3.0.dist-info/licenses/NOTICE +11 -0
- qoptct-0.3.0.dist-info/top_level.txt +1 -0
qoptct/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""qoptct -- a verified Clifford+T circuit optimizer (T count and CX count). Project name: qopt.
|
|
2
|
+
|
|
3
|
+
The distribution and the import package are called ``qoptct`` (the name ``qopt`` on PyPI belongs to an
|
|
4
|
+
unrelated project): ``pip install qoptct`` / ``import qoptct``.
|
|
5
|
+
|
|
6
|
+
Command line: qoptct optimize INPUT.qc [-o OUT] [--mode cx|balanced|t] [--clean] [--meas] [--net] [--post] ...
|
|
7
|
+
Python: import qoptct; names, ref, best, how = qoptct.optimize("circuit.qc")
|
|
8
|
+
Qiskit: from qoptct.qiskit_qopt import QoptPass
|
|
9
|
+
transpile(qc, optimization_method="qoptct") # transpiler stage plugin
|
|
10
|
+
|
|
11
|
+
PyTorch is optional and only needed for the neural mode (``--net``): pip install "qoptct[net]".
|
|
12
|
+
|
|
13
|
+
Internal modules use flat imports (``import evo_lib``) and ``import qoptct`` for the core functions; the package
|
|
14
|
+
directory is therefore put on sys.path when qoptct is imported, and the public names of ``qoptct.qopt_core``
|
|
15
|
+
(load_qc, save_qc, prove, optimize, ... and the time limits PROG_SEC, PROVE_SEC, FOLD_SEC, PYZX_SEC) are
|
|
16
|
+
re-exported here. Caches go to $QOPT_CACHE (default ~/.cache/qopt).
|
|
17
|
+
"""
|
|
18
|
+
import os as _os
|
|
19
|
+
import sys as _sys
|
|
20
|
+
|
|
21
|
+
__version__ = "0.3.0"
|
|
22
|
+
_PKG = _os.path.dirname(_os.path.abspath(__file__))
|
|
23
|
+
if _PKG not in _sys.path:
|
|
24
|
+
_sys.path.insert(0, _PKG)
|
|
25
|
+
|
|
26
|
+
from . import qopt_core as _core # noqa: E402
|
|
27
|
+
|
|
28
|
+
globals().update({k: v for k, v in vars(_core).items() if not k.startswith("__") and k not in ("_os", "_sys")})
|
qoptct/bench_real.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""Этап 12, пункт 2+3 плана автора: реальные алгоритмы (набор Feynman/Amy–Maslov–Mosca, Clifford+T).
|
|
2
|
+
Сравнение CX и времени: исходная, Qiskit opt3, PyZX full_optimize, наш блочный (с сетью / без сети),
|
|
3
|
+
и наш блочный поверх результата PyZX. Проверка равенства — PyZX verify_equality (+ Operator для n<=10)."""
|
|
4
|
+
import glob, json, os, sys, time, warnings
|
|
5
|
+
from multiprocessing import Pool
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import numpy as np
|
|
8
|
+
warnings.filterwarnings("ignore")
|
|
9
|
+
ROOT = Path(__file__).resolve().parent
|
|
10
|
+
sys.setrecursionlimit(100000)
|
|
11
|
+
RESTARTS = int(os.environ.get("RESTARTS", "0")); SKIP_P = float(os.environ.get("SKIP_P", "0.15"))
|
|
12
|
+
VARIANTS = os.environ.get("VARIANTS", "ours,nonet,pz")
|
|
13
|
+
OUT = os.environ.get("OUT", "bench_real")
|
|
14
|
+
KS = (5, 4, 3) if os.environ.get("MODEL5") else (4, 3)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def zx_to_ours(c):
|
|
18
|
+
out = []
|
|
19
|
+
for g in c.to_basic_gates().gates:
|
|
20
|
+
nm = type(g).__name__
|
|
21
|
+
if nm == "HAD": out.append(("h", (g.target,)))
|
|
22
|
+
elif nm == "NOT": out.append(("x", (g.target,)))
|
|
23
|
+
elif nm == "CNOT": out.append(("cx", (g.control, g.target)))
|
|
24
|
+
elif nm == "CZ": out += [("h", (g.target,)), ("cx", (g.control, g.target)), ("h", (g.target,))]
|
|
25
|
+
elif nm in ("T", "S", "Z", "ZPhase"):
|
|
26
|
+
if abs(float(g.phase) * 4 - round(float(g.phase) * 4)) > 1e-9:
|
|
27
|
+
raise ValueError("фаза не кратна pi/4")
|
|
28
|
+
k = int(round(float(g.phase) * 4)) % 8 # фаза в единицах pi/4
|
|
29
|
+
seq = {0: [], 1: ["t"], 2: ["s"], 3: ["s", "t"], 4: ["s", "s"], 5: ["sdg", "tdg"], 6: ["sdg"], 7: ["tdg"]}[k]
|
|
30
|
+
out += [(s, (g.target,)) for s in seq]
|
|
31
|
+
elif nm == "XPhase":
|
|
32
|
+
k = int(round(float(g.phase) * 4)) % 8
|
|
33
|
+
seq = {0: [], 1: ["t"], 2: ["s"], 3: ["s", "t"], 4: ["s", "s"], 5: ["sdg", "tdg"], 6: ["sdg"], 7: ["tdg"]}[k]
|
|
34
|
+
out += [("h", (g.target,))] + [(s, (g.target,)) for s in seq] + [("h", (g.target,))]
|
|
35
|
+
else:
|
|
36
|
+
raise ValueError("команда " + nm)
|
|
37
|
+
return out
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def ours_to_zx(c, n):
|
|
41
|
+
import pyzx as zx
|
|
42
|
+
z = zx.Circuit(n)
|
|
43
|
+
for nm, qb in c:
|
|
44
|
+
if nm == "h": z.add_gate("HAD", qb[0])
|
|
45
|
+
elif nm == "x": z.add_gate("NOT", qb[0])
|
|
46
|
+
elif nm == "cx": z.add_gate("CNOT", qb[0], qb[1])
|
|
47
|
+
elif nm in ("t", "tdg"): z.add_gate("T", qb[0], adjoint=(nm == "tdg"))
|
|
48
|
+
elif nm in ("s", "sdg"): z.add_gate("S", qb[0], adjoint=(nm == "sdg"))
|
|
49
|
+
return z
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def check(c1, c0, n):
|
|
53
|
+
"""Сквозная проверка: n<=10 — матрицы целиком; n<=22 — 3 случайных состояния (с общей фазой);
|
|
54
|
+
больше — 'blocks' (каждый заменённый блок уже проверен точно при замене)."""
|
|
55
|
+
from qiskit.quantum_info import Operator, Statevector, random_statevector
|
|
56
|
+
import block_synth as bs
|
|
57
|
+
if n <= 10:
|
|
58
|
+
return bool(Operator(bs.to_qc(c1, n)).equiv(Operator(bs.to_qc(c0, n))))
|
|
59
|
+
if n > 22: # PyZX: True — доказано; False/неудача — НЕ доказано (None), а не «верно»
|
|
60
|
+
try:
|
|
61
|
+
r = ours_to_zx(c1, n).verify_equality(ours_to_zx(c0, n))
|
|
62
|
+
except Exception:
|
|
63
|
+
r = False
|
|
64
|
+
if r:
|
|
65
|
+
return True
|
|
66
|
+
if n <= 26: # PyZX не доказал — численно (complex128, 2 случайных состояния, общая фаза), как у агента
|
|
67
|
+
sys.path.insert(0, str(ROOT / "proto2")); from verify import numeric_equal
|
|
68
|
+
return True if numeric_equal(c1, c0, n) else False
|
|
69
|
+
return None
|
|
70
|
+
q1, q0 = bs.to_qc(c1, n), bs.to_qc(c0, n)
|
|
71
|
+
ph = None
|
|
72
|
+
for seed in range(3):
|
|
73
|
+
v = random_statevector(2 ** n, seed=seed)
|
|
74
|
+
o = np.vdot(v.evolve(q0).data, v.evolve(q1).data)
|
|
75
|
+
if abs(abs(o) - 1) > 1e-11 or (ph is not None and abs(o - ph) > 1e-11): # Г1: 1e-6 пропускал ошибки при n=20–22
|
|
76
|
+
return False
|
|
77
|
+
ph = o
|
|
78
|
+
return True
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def cx(c):
|
|
82
|
+
return sum(g[0] == "cx" for g in c)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _init(model4):
|
|
86
|
+
import block_synth as bs
|
|
87
|
+
bs._init(64, 256, model4, False, 0.5, False)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def job(args):
|
|
91
|
+
"""PORTFOLIO=1: несколько нарезок (без/с перестановками, разные зерна), берётся лучшая — нарезка неустойчива."""
|
|
92
|
+
name, n, circ, no_net, cycles = args
|
|
93
|
+
if not os.environ.get("PORTFOLIO"):
|
|
94
|
+
return job1(args, 0)
|
|
95
|
+
t0 = time.time(); best = None
|
|
96
|
+
import block_synth as _bs
|
|
97
|
+
_bs.CACHE.clear(); os.environ["KEEP_CACHE"] = "1"
|
|
98
|
+
variants = ((False, 0), (True, 0), (False, 1)) if RESTARTS else ((False, 0), (True, 0))
|
|
99
|
+
for commute, seed in variants:
|
|
100
|
+
if commute: os.environ["COMMUTE"] = "1"
|
|
101
|
+
else: os.environ.pop("COMMUTE", None)
|
|
102
|
+
r = job1(args, seed)
|
|
103
|
+
if best is None or r[3] and __import__("block_synth").key(r[3]) < __import__("block_synth").key(best[3]):
|
|
104
|
+
best = r
|
|
105
|
+
os.environ.pop("COMMUTE", None); os.environ.pop("KEEP_CACHE", None)
|
|
106
|
+
return best[0], best[1], best[2], best[3], time.time() - t0
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def job1(args, seed):
|
|
110
|
+
name, n, circ, no_net, cycles = args
|
|
111
|
+
import block_synth as bs
|
|
112
|
+
bs.W["no_net"] = no_net
|
|
113
|
+
if not os.environ.get("KEEP_CACHE"): bs.CACHE.clear() # память только внутри схемы — результат не зависит от порядка схем по процессам (шум ±25)
|
|
114
|
+
t0 = time.time(); best = circ
|
|
115
|
+
GC = bool(os.environ.get("GCANCEL"))
|
|
116
|
+
if GC:
|
|
117
|
+
from global_cancel import global_cancel
|
|
118
|
+
if os.environ.get("RTOF"): # свёртка фаз по всей схеме (агент «нестандартные идеи»)
|
|
119
|
+
sys.path.insert(0, str(ROOT / "proto2")); from phase_fold import fold_cancel
|
|
120
|
+
_gc = global_cancel
|
|
121
|
+
global_cancel = lambda c: _gc(fold_cancel(c, n))
|
|
122
|
+
best = global_cancel(best)
|
|
123
|
+
for _ in range(cycles):
|
|
124
|
+
improved = False
|
|
125
|
+
if GC:
|
|
126
|
+
c2 = global_cancel(best)
|
|
127
|
+
if bs.key(c2) < bs.key(best):
|
|
128
|
+
best, improved = c2, True
|
|
129
|
+
for k in KS:
|
|
130
|
+
for flip in (0, 1):
|
|
131
|
+
c = bs.inv_circ(best) if flip else best
|
|
132
|
+
c2 = bs.one_pass(c, min(k, n))
|
|
133
|
+
c2 = bs.inv_circ(c2) if flip else c2
|
|
134
|
+
if bs.key(c2) < bs.key(best):
|
|
135
|
+
best, improved = c2, True
|
|
136
|
+
if not improved:
|
|
137
|
+
break
|
|
138
|
+
rng = np.random.default_rng(seed); fails = 0
|
|
139
|
+
while fails < RESTARTS: # случайные перезапуски нарезки, пока RESTARTS подряд без выигрыша
|
|
140
|
+
k = KS[int(rng.integers(len(KS)))]; flip = int(rng.integers(2))
|
|
141
|
+
c = bs.inv_circ(best) if flip else best
|
|
142
|
+
c2 = bs.one_pass(c, min(k, n), rng, SKIP_P)
|
|
143
|
+
c2 = bs.inv_circ(c2) if flip else c2
|
|
144
|
+
if bs.key(c2) < bs.key(best):
|
|
145
|
+
best, fails = c2, 0
|
|
146
|
+
else:
|
|
147
|
+
fails += 1
|
|
148
|
+
return name, no_net, len(circ), best, time.time() - t0
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def main():
|
|
152
|
+
import pyzx as zx
|
|
153
|
+
from qiskit import QuantumCircuit, transpile
|
|
154
|
+
model4 = sys.argv[1] if len(sys.argv) > 1 else ""
|
|
155
|
+
rows, jobs, base = {}, [], {}
|
|
156
|
+
for f in sorted(glob.glob(str(ROOT / "bench_qc" / "*.qc"))):
|
|
157
|
+
name = os.path.basename(f)[:-3]
|
|
158
|
+
if os.environ.get("ONLY") and name not in os.environ["ONLY"].split(","): continue
|
|
159
|
+
z = zx.Circuit.load(f).to_basic_gates(); n = z.qubits
|
|
160
|
+
orig = zx_to_ours(z)
|
|
161
|
+
t = time.time(); zo = zx.optimize.full_optimize(z.copy()); tz = time.time() - t
|
|
162
|
+
pz = zx_to_ours(zo)
|
|
163
|
+
qc = QuantumCircuit(n)
|
|
164
|
+
for nm, qb in orig: getattr(qc, nm)(*qb)
|
|
165
|
+
t = time.time(); qcx = transpile(qc, basis_gates=["u", "cx"], optimization_level=3, seed_transpiler=0).count_ops().get("cx", 0); tq = time.time() - t
|
|
166
|
+
base[name] = {"n": n, "orig": cx(orig), "qiskit": qcx, "t_qiskit": tq, "pyzx": cx(pz), "t_pyzx": tz, "z": z, "orig_c": orig}
|
|
167
|
+
V = VARIANTS.split(",")
|
|
168
|
+
jobs += ([(name, n, orig, False, 4)] if "ours" in V else []) + ([(name, n, orig, True, 4)] if "nonet" in V else []) \
|
|
169
|
+
+ ([(name + "+pz", n, pz, False, 4)] if "pz" in V else [])
|
|
170
|
+
ep = ROOT / "results" / ("evo_starts_local.json" if os.environ.get("EVO_STARTS") == "local" else "evo_starts.json")
|
|
171
|
+
if os.environ.get("EVO") and ep.exists():
|
|
172
|
+
_evo = json.loads(ep.read_text())
|
|
173
|
+
if name in _evo:
|
|
174
|
+
ev = [(g, tuple(q)) for g, q in _evo[name]]
|
|
175
|
+
base[name]["evo_start"] = cx(ev)
|
|
176
|
+
jobs.append((name + "+evo", n, ev, False, 4))
|
|
177
|
+
rp = ROOT / "proto2" / f"combo2_{name}.pkl"
|
|
178
|
+
if os.environ.get("RTOF") and rp.exists():
|
|
179
|
+
import pickle
|
|
180
|
+
_n, _raw, rt = pickle.load(open(rp, "rb"))
|
|
181
|
+
base[name]["rt_start"] = cx(rt)
|
|
182
|
+
jobs.append((name + "+rt", n, rt, False, 4))
|
|
183
|
+
print(f"{name}: n={n} CX {cx(orig)} | Qiskit {qcx} ({tq:.1f} с) | PyZX {cx(pz)} ({tz:.1f} с)", flush=True)
|
|
184
|
+
jobs.sort(key=lambda j: -len(j[2]))
|
|
185
|
+
with Pool(8, initializer=_init, initargs=(model4,)) as pool:
|
|
186
|
+
for name, no_net, ln, best, sec in pool.imap_unordered(job, jobs):
|
|
187
|
+
b = name.replace("+pz", "").replace("+rt", "").replace("+evo", "")
|
|
188
|
+
ok = check(best, base[b]["orig_c"], base[b]["n"])
|
|
189
|
+
key = "pz_ours" if name.endswith("+pz") else ("rt_ours" if name.endswith("+rt") else ("evo_ours" if name.endswith("+evo") else ("nonet" if no_net else "ours")))
|
|
190
|
+
base[b][key], base[b]["t_" + key], base[b]["ok_" + key] = cx(best), sec, ok
|
|
191
|
+
base[b]["circ_" + key] = [[g, list(q)] for g, q in best] # сохранить схему для перепроверки
|
|
192
|
+
print(f" {name} {'без сети' if no_net else 'с сетью'}: CX {cx(best)} за {sec:.0f} с, проверка {ok}", flush=True)
|
|
193
|
+
out = {k: {kk: vv for kk, vv in v.items() if kk not in ("z", "orig_c")} for k, v in base.items()}
|
|
194
|
+
(ROOT / "results" / f"{OUT}.json").write_text(json.dumps(out, indent=1))
|
|
195
|
+
print("ИТОГ таблица:", flush=True)
|
|
196
|
+
print("схема n | исх | Qiskit | PyZX | наш(сеть) | наш(без сети) | PyZX+наш | время наш/без/Qiskit/PyZX | проверка", flush=True)
|
|
197
|
+
for k, v in out.items():
|
|
198
|
+
print(f"{k} {v['n']} | {v['orig']} | {v['qiskit']} | {v['pyzx']} | {v.get('ours')} | {v.get('nonet')} | {v.get('pz_ours')} | "
|
|
199
|
+
f"{v.get('t_ours',0):.0f}/{v.get('t_nonet',0):.0f}/{v['t_qiskit']:.1f}/{v['t_pyzx']:.1f} | "
|
|
200
|
+
f"{v.get('ok_ours')}/{v.get('ok_nonet')}/{v.get('ok_pz_ours')}", flush=True)
|
|
201
|
+
tot = lambda key: sum(v.get(key, 0) or 0 for v in out.values())
|
|
202
|
+
for v in out.values(): # лучшее из проверенных стартов
|
|
203
|
+
cands = [v[k] for k in ("ours", "rt_ours", "evo_ours") if v.get(k) is not None and v.get("ok_" + k) is True]
|
|
204
|
+
v["best_ours"] = min(cands) if cands else None
|
|
205
|
+
print("СУММА лучшего из стартов (только проверенные):", tot("best_ours"),
|
|
206
|
+
"| rt-старт с сетью:", tot("rt_ours"), "| без проверки:", [k for k, v in out.items() if v["best_ours"] is None], flush=True)
|
|
207
|
+
(ROOT / "results" / f"{OUT}.json").write_text(json.dumps(out, indent=1))
|
|
208
|
+
print(f"СУММА CX: наш {tot('ours')}, Qiskit {tot('qiskit')}, исходные {tot('orig')}; время наш {tot('t_ours'):.0f} с", flush=True)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
if __name__ == "__main__":
|
|
212
|
+
main()
|
qoptct/block_synth.py
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""Этап 11: блочный синтез. Схему на n кубитах режем на блоки по <=k (4/3) кубитов,
|
|
2
|
+
каждый блок пересобирают обученные агенты синтеза 2q/3q/4q (exp/u_k_best), берём, если CX меньше.
|
|
3
|
+
Проходы повторяются (k=4, k=3, и на обращённой схеме — другие границы блоков), пока есть выигрыш.
|
|
4
|
+
Итог проверяется точным сравнением матриц (Qiskit Operator.equiv)."""
|
|
5
|
+
import argparse, json, os, time, warnings
|
|
6
|
+
from multiprocessing import Pool
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import numpy as np
|
|
9
|
+
torch = None # optional; imported lazily by _torch() -- only neural block resynthesis (--net) needs it
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _torch():
|
|
13
|
+
"""Import torch on first use (pip install "qoptct[net]"); the other modes never import it."""
|
|
14
|
+
global torch
|
|
15
|
+
if torch is None:
|
|
16
|
+
import qoptct
|
|
17
|
+
qoptct.require_torch()
|
|
18
|
+
import torch as _t
|
|
19
|
+
torch = _t
|
|
20
|
+
return torch
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
warnings.filterwarnings("ignore")
|
|
24
|
+
ROOT = Path(__file__).resolve().parent
|
|
25
|
+
INV = {"h": "h", "x": "x", "s": "sdg", "sdg": "s", "t": "tdg", "tdg": "t", "cx": "cx"}
|
|
26
|
+
CTX = {}
|
|
27
|
+
CACHE = {}
|
|
28
|
+
W = {}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _init(width, widen, model4='', no_net=False, opt_time=0.5, uniform=False):
|
|
32
|
+
_torch()
|
|
33
|
+
import qgnn
|
|
34
|
+
from qfast import ChoiEngine
|
|
35
|
+
torch.set_num_threads(1)
|
|
36
|
+
W["w"], W["widen"], W["no_net"], W["opt"], W["uniform"] = width, widen, no_net, opt_time, uniform
|
|
37
|
+
for k in (2, 3, 4) + ((5,) if os.environ.get("MODEL5") else ()):
|
|
38
|
+
mp = {4: model4, 3: os.environ.get("MODEL3", ""), 5: os.environ.get("MODEL5", "")}.get(k, "")
|
|
39
|
+
ck = torch.load((ROOT / mp) if mp else (ROOT / "models" / f"u_{k}_best_{k}q.pt"), map_location="cpu", weights_only=False)
|
|
40
|
+
net = {"gnnu": qgnn.QGNNU, "gnnh": qgnn.QGNNH, "gnnhp": qgnn.QGNNHP}[ck.get("arch", "gnnu")](**ck["gnn_kw"])
|
|
41
|
+
net.load_state_dict(ck["state_dict"]); net.eval()
|
|
42
|
+
eng = ChoiEngine(k)
|
|
43
|
+
cfg = {"beta": 3.0, "gamma": 0.0, "max_steps": ck["max_steps"], "extra": 3, "task": "unitary"}
|
|
44
|
+
val = None
|
|
45
|
+
vp = ROOT / "models" / f"value_u{k}q.pt"
|
|
46
|
+
if vp.exists():
|
|
47
|
+
vck = torch.load(vp, map_location="cpu", weights_only=False)
|
|
48
|
+
val = qgnn.QValueU(**vck["kw"]); val.load_state_dict(vck["state_dict"]); val.eval(); cfg["lam"] = 0.5
|
|
49
|
+
gi = {(nm, tuple(qb)): i for i, (nm, qb) in enumerate(eng.gates)}
|
|
50
|
+
CTX[k] = (net, eng, cfg, val, gi)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def key(c):
|
|
54
|
+
return (sum(g[0] == "cx" for g in c), len(c))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def inv_circ(c):
|
|
58
|
+
return [(INV[nm], qb) for nm, qb in reversed(c)]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
DIAG = {"s", "sdg", "t", "tdg"}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def commutes(g1, g2):
|
|
65
|
+
"""Точные правила перестановки команд Clifford+T (И4): диагональные — через управляющий CX, X — через цель,
|
|
66
|
+
CX с общими только управляющими или только целевыми кубитами."""
|
|
67
|
+
(n1, q1), (n2, q2) = g1, g2
|
|
68
|
+
if not set(q1) & set(q2):
|
|
69
|
+
return True
|
|
70
|
+
if n1 != "cx" and n2 != "cx":
|
|
71
|
+
return (n1 in DIAG and n2 in DIAG) or (n1 == n2 == "x") or (n1 == n2 == "h")
|
|
72
|
+
if n1 == "cx" and n2 == "cx":
|
|
73
|
+
return q1[0] != q2[1] and q2[0] != q1[1]
|
|
74
|
+
one, cx = (g1, g2) if n2 == "cx" else (g2, g1)
|
|
75
|
+
return (one[0] in DIAG and one[1][0] == cx[1][0]) or (one[0] == "x" and one[1][0] == cx[1][1])
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def take_block_commute(rem, k, rng=None, p=0.0):
|
|
79
|
+
"""Как take_block, но команда может «перепрыгнуть» пропущенные, если коммутирует со всеми пропущенными на своих кубитах."""
|
|
80
|
+
Q, take, skip = set(), [], []
|
|
81
|
+
for g in rem:
|
|
82
|
+
qs = set(g[1])
|
|
83
|
+
if rng is not None and Q and rng.random() < p:
|
|
84
|
+
skip.append(g); continue
|
|
85
|
+
if len(Q | qs) <= k and all(commutes(g, s) for s in skip if set(s[1]) & qs):
|
|
86
|
+
take.append(g); Q |= qs
|
|
87
|
+
else:
|
|
88
|
+
skip.append(g)
|
|
89
|
+
return sorted(Q), take, skip
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def take_block(rem, k, rng=None, p=0.0):
|
|
93
|
+
if os.environ.get("COMMUTE"):
|
|
94
|
+
return take_block_commute(rem, k, rng, p)
|
|
95
|
+
Q, B, take, skip = set(), set(), [], []
|
|
96
|
+
for g in rem:
|
|
97
|
+
qs = set(g[1])
|
|
98
|
+
if rng is not None and Q and rng.random() < p: # случайный пропуск — другие границы блоков
|
|
99
|
+
skip.append(g); B |= qs; continue
|
|
100
|
+
if not (qs & B) and len(Q | qs) <= k:
|
|
101
|
+
take.append(g); Q |= qs
|
|
102
|
+
else:
|
|
103
|
+
skip.append(g); B |= qs
|
|
104
|
+
return sorted(Q), take, skip
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def resynth(qs, gates):
|
|
108
|
+
import ei2
|
|
109
|
+
from circuit_opt import optimize
|
|
110
|
+
k = len(qs)
|
|
111
|
+
if k < 2 or not any(g[0] == "cx" for g in gates):
|
|
112
|
+
return gates
|
|
113
|
+
net, eng, cfg, val, gi = CTX[k]
|
|
114
|
+
m = {q: i for i, q in enumerate(qs)}
|
|
115
|
+
prog = [gi[(nm, tuple(m[q] for q in qb))] for nm, qb in gates]
|
|
116
|
+
target = eng.choi(prog)
|
|
117
|
+
from qfast import state_sig
|
|
118
|
+
sig = (k, state_sig(target))
|
|
119
|
+
if W.get("use_cache", True) and sig in CACHE: # память: этот блок уже решали
|
|
120
|
+
W["hits"] = W.get("hits", 0) + 1
|
|
121
|
+
best = CACHE[sig]
|
|
122
|
+
return [(eng.gates[eng.inverse_action[a]][0], tuple(qs[i] for i in eng.gates[eng.inverse_action[a]][1]))
|
|
123
|
+
for a in reversed(best)]
|
|
124
|
+
raw = [eng.inverse_action[a] for a in reversed(prog)] # известная размотка (точная)
|
|
125
|
+
best = optimize(eng, target, raw, time_limit=W["opt"])
|
|
126
|
+
if not exact(eng, target, best):
|
|
127
|
+
best = raw
|
|
128
|
+
ei2._net, ei2._eng, ei2._cfg, ei2._val = net, eng, cfg, val
|
|
129
|
+
if W.get("uniform"): # перебор без сети: равные вероятности всех команд, без оценщика
|
|
130
|
+
ei2._net, ei2._val = Uniform(eng.n_actions), None
|
|
131
|
+
for w in (() if W["no_net"] else (W["w"], W["widen"])):
|
|
132
|
+
if not w:
|
|
133
|
+
continue
|
|
134
|
+
res = ei2.beam(target, w, bound=len(best))
|
|
135
|
+
if res is not None:
|
|
136
|
+
acts = optimize(eng, target, res[1], time_limit=1.0)
|
|
137
|
+
if exact(eng, target, acts) and key([eng.gates[a] for a in acts]) < key([eng.gates[a] for a in best]):
|
|
138
|
+
best = acts
|
|
139
|
+
break
|
|
140
|
+
CACHE[sig] = list(best)
|
|
141
|
+
return [(eng.gates[eng.inverse_action[a]][0], tuple(qs[i] for i in eng.gates[eng.inverse_action[a]][1]))
|
|
142
|
+
for a in reversed(best)]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def Uniform(na):
|
|
146
|
+
"""Network stand-in with equal probabilities for all actions (search without a network)."""
|
|
147
|
+
t = _torch()
|
|
148
|
+
|
|
149
|
+
class _Uniform(t.nn.Module):
|
|
150
|
+
def __init__(self, na):
|
|
151
|
+
super().__init__(); self.na = na
|
|
152
|
+
|
|
153
|
+
def forward(self, x):
|
|
154
|
+
return t.zeros(x.shape[0], self.na)
|
|
155
|
+
|
|
156
|
+
return _Uniform(na)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def exact(eng, target, acts):
|
|
160
|
+
"""Точная проверка блока: размотка должна дать тождество (совпадение 1 с точностью 1e-9), а не >= 0,99."""
|
|
161
|
+
s = target
|
|
162
|
+
for a in acts:
|
|
163
|
+
s = eng.apply1(s, a)
|
|
164
|
+
return abs(eng.amp0(s)) ** 2 >= 1 - 1e-9
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def one_pass(circ, k, rng=None, p=0.0):
|
|
168
|
+
rem, out = circ, []
|
|
169
|
+
while rem:
|
|
170
|
+
qs, take, rem = take_block(rem, k, rng, p)
|
|
171
|
+
out += resynth(qs, take)
|
|
172
|
+
return out
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def to_qc(c, n):
|
|
176
|
+
from qiskit import QuantumCircuit
|
|
177
|
+
qc = QuantumCircuit(n)
|
|
178
|
+
for nm, qb in c:
|
|
179
|
+
getattr(qc, nm)(*qb)
|
|
180
|
+
return qc
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def solve(args):
|
|
184
|
+
idx, n, circ, max_cycles = args
|
|
185
|
+
from qiskit.quantum_info import Operator
|
|
186
|
+
t0 = time.time()
|
|
187
|
+
best = circ
|
|
188
|
+
for _ in range(max_cycles):
|
|
189
|
+
improved = False
|
|
190
|
+
for k in (4, 3):
|
|
191
|
+
for flip in (0, 1):
|
|
192
|
+
c = inv_circ(best) if flip else best
|
|
193
|
+
c2 = one_pass(c, min(k, n))
|
|
194
|
+
c2 = inv_circ(c2) if flip else c2
|
|
195
|
+
if key(c2) < key(best):
|
|
196
|
+
best, improved = c2, True
|
|
197
|
+
if not improved:
|
|
198
|
+
break
|
|
199
|
+
ok = Operator(to_qc(best, n)).equiv(Operator(to_qc(circ, n)))
|
|
200
|
+
return idx, {"cx": key(best)[0], "len": len(best), "equiv": bool(ok), "sec": time.time() - t0, "circ": best}
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def gen(n, count, seed0=9_000_000):
|
|
204
|
+
from qiskit import transpile
|
|
205
|
+
from qfast import Engine, ChoiEngine
|
|
206
|
+
eng = Engine(n)
|
|
207
|
+
ce = ChoiEngine(n) if n <= 5 else None
|
|
208
|
+
one = [i for i, (nm, _) in enumerate(eng.gates) if nm != "cx"]
|
|
209
|
+
cxs = [i for i, (nm, _) in enumerate(eng.gates) if nm == "cx"]
|
|
210
|
+
out, seed = [], seed0
|
|
211
|
+
while len(out) < count:
|
|
212
|
+
rng = np.random.default_rng(seed); seed += 1
|
|
213
|
+
prog = []
|
|
214
|
+
for _ in range(int(rng.integers(n, 4 * n + 1))):
|
|
215
|
+
for _ in range(int(rng.integers(1, n + 1))):
|
|
216
|
+
prog.append(int(rng.choice(one)))
|
|
217
|
+
prog.append(int(rng.choice(cxs)))
|
|
218
|
+
if ce is not None and abs(ce.amp0(ce.choi(prog))) ** 2 >= 0.99:
|
|
219
|
+
continue
|
|
220
|
+
c = [(eng.gates[a][0], tuple(eng.gates[a][1])) for a in prog]
|
|
221
|
+
qcx = transpile(to_qc(c, n), basis_gates=["u", "cx"], optimization_level=3,
|
|
222
|
+
seed_transpiler=0).count_ops().get("cx", 0)
|
|
223
|
+
if qcx == 0:
|
|
224
|
+
continue
|
|
225
|
+
out.append((c, qcx))
|
|
226
|
+
return out
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def main():
|
|
230
|
+
p = argparse.ArgumentParser()
|
|
231
|
+
p.add_argument("--qubits", type=int, default=5)
|
|
232
|
+
p.add_argument("--count", type=int, default=300)
|
|
233
|
+
p.add_argument("--width", type=int, default=64)
|
|
234
|
+
p.add_argument("--widen", type=int, default=0)
|
|
235
|
+
p.add_argument("--cycles", type=int, default=3)
|
|
236
|
+
p.add_argument("--workers", type=int, default=8)
|
|
237
|
+
p.add_argument("--model4", default="")
|
|
238
|
+
p.add_argument("--no-net", action="store_true")
|
|
239
|
+
p.add_argument("--uniform", action="store_true", help="тот же поиск, но без сети (равные вероятности)")
|
|
240
|
+
p.add_argument("--opt-time", type=float, default=0.5)
|
|
241
|
+
p.add_argument("--tag", default="")
|
|
242
|
+
a = p.parse_args()
|
|
243
|
+
n = a.qubits
|
|
244
|
+
t0 = time.time()
|
|
245
|
+
data = gen(n, a.count)
|
|
246
|
+
print(f"{n}q: {len(data)} схем, CX в исходной {np.mean([key(c)[0] for c, _ in data]):.1f}, "
|
|
247
|
+
f"Qiskit opt3 {np.mean([q for _, q in data]):.1f} ({time.time()-t0:.0f} с)", flush=True)
|
|
248
|
+
with Pool(a.workers, initializer=_init, initargs=(a.width, a.widen, a.model4, a.no_net, a.opt_time, a.uniform)) as pool:
|
|
249
|
+
res = dict(pool.imap_unordered(solve, [(i, n, c, a.cycles) for i, (c, _) in enumerate(data)]))
|
|
250
|
+
orig = np.array([key(c)[0] for c, _ in data]); qk = np.array([q for _, q in data])
|
|
251
|
+
ours = np.array([res[i]["cx"] for i in range(len(data))])
|
|
252
|
+
eq = np.mean([res[i]["equiv"] for i in range(len(data))])
|
|
253
|
+
out = {"uniform": a.uniform, "tag": a.tag, "no_net": a.no_net, "opt_time": a.opt_time, "model4": a.model4, "qubits": n, "count": len(data), "width": a.width, "widen": a.widen, "orig_cx": orig.mean(),
|
|
254
|
+
"qiskit_opt3_cx": qk.mean(), "block_cx": ours.mean(), "equiv": eq,
|
|
255
|
+
"better_than_qiskit": float(np.mean(ours < qk)), "equal": float(np.mean(ours == qk)),
|
|
256
|
+
"worse": float(np.mean(ours > qk)), "minutes": (time.time() - t0) / 60,
|
|
257
|
+
"sec_per_circuit": float(np.mean([res[i]["sec"] for i in range(len(data))]))}
|
|
258
|
+
print("ИТОГ:", json.dumps(out), flush=True)
|
|
259
|
+
(ROOT / "results").mkdir(exist_ok=True)
|
|
260
|
+
(ROOT / "results" / f"block{a.tag}_{n}q.json").write_text(json.dumps(
|
|
261
|
+
{"summary": out, "circuits": [{"orig": data[i][0], "qiskit_cx": int(data[i][1]), **res[i]} for i in range(len(data))]}))
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
if __name__ == "__main__":
|
|
265
|
+
main()
|
qoptct/circuit_opt.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Оптимизатор небольших схем: чистит найденные решения перед тем, как они попадут в учебник.
|
|
2
|
+
|
|
3
|
+
Решение хранится как список ходов, которые «разматывают» цель обратно в |0...0>.
|
|
4
|
+
Каждое изменение проверяется ТОЧНО: прогоняем цель через новый список ходов на быстром
|
|
5
|
+
моторе qfast и требуем, чтобы вероятность |0...0> осталась >= 0.99. Цель улучшения:
|
|
6
|
+
меньше CX, при равенстве — короче.
|
|
7
|
+
|
|
8
|
+
Проходы (повторяются, пока что-то улучшается; 2б — удаление пары «CX + любая команда»):
|
|
9
|
+
1. удаление одной команды (убирает «мусор»: отменяющие друг друга пары после сдвига,
|
|
10
|
+
фазовые команды на кубитах, где они ничего не меняют, и т.п.);
|
|
11
|
+
2. удаление двух соседних команд;
|
|
12
|
+
3. замена окна из 2–3 команд одной командой (например, T,T -> S).
|
|
13
|
+
Проверка: .venv/bin/python circuit_opt.py --qubits 6 (на учебнике, без записи)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import json
|
|
18
|
+
import time
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
|
|
22
|
+
SUCCESS = 0.99
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _ok(eng, state, actions):
|
|
26
|
+
s = state
|
|
27
|
+
for a in actions:
|
|
28
|
+
s = eng.apply1(s, a)
|
|
29
|
+
from qfast import amp0
|
|
30
|
+
import numpy as _np
|
|
31
|
+
s = _np.asarray(s, complex)
|
|
32
|
+
return abs(amp0(eng, s)) ** 2 / _np.vdot(s, s).real >= 1 - 1e-6 # точно (Г1 Н3), а не >= 0,99
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _key(eng, actions):
|
|
36
|
+
return (int(eng.is_cx[actions].sum()) if actions else 0, len(actions))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def optimize(eng, target, actions, max_passes=4, time_limit=2.0):
|
|
40
|
+
"""Возвращает улучшенный список ходов (или исходный). Всегда проверено точно."""
|
|
41
|
+
target = np.asarray(target, complex)
|
|
42
|
+
best = list(actions)
|
|
43
|
+
if not _ok(eng, target, best):
|
|
44
|
+
return best
|
|
45
|
+
t0 = time.time()
|
|
46
|
+
n_act = eng.n_actions
|
|
47
|
+
for _ in range(max_passes):
|
|
48
|
+
improved = False
|
|
49
|
+
# 1) и 2): удаление одной или двух соседних команд
|
|
50
|
+
for width in (1, 2):
|
|
51
|
+
i = 0
|
|
52
|
+
while i + width <= len(best):
|
|
53
|
+
cand = best[:i] + best[i + width:]
|
|
54
|
+
if _ok(eng, target, cand):
|
|
55
|
+
best = cand; improved = True
|
|
56
|
+
else:
|
|
57
|
+
i += 1
|
|
58
|
+
if time.time() - t0 > time_limit:
|
|
59
|
+
return best
|
|
60
|
+
# 2б) удаление любой пары команд, где первая — CX (например, две CX, разделённые
|
|
61
|
+
# командами, которые с ними коммутируют) — главный источник экономии CX
|
|
62
|
+
cx_pos = [i for i, a in enumerate(best) if eng.is_cx[a]]
|
|
63
|
+
found = False
|
|
64
|
+
for i in cx_pos:
|
|
65
|
+
for j in range(len(best)):
|
|
66
|
+
if j == i:
|
|
67
|
+
continue
|
|
68
|
+
cand = [a for k, a in enumerate(best) if k not in (i, j)]
|
|
69
|
+
if _ok(eng, target, cand):
|
|
70
|
+
best = cand; improved = found = True
|
|
71
|
+
break
|
|
72
|
+
if found or time.time() - t0 > time_limit:
|
|
73
|
+
break
|
|
74
|
+
# 3) замена окна из 2–3 команд одной командой (с проверкой, что стало лучше)
|
|
75
|
+
for width in (2, 3):
|
|
76
|
+
i = 0
|
|
77
|
+
while i + width <= len(best):
|
|
78
|
+
window_key = _key(eng, best[i:i + width])
|
|
79
|
+
pre = target
|
|
80
|
+
for a in best[:i]:
|
|
81
|
+
pre = eng.apply1(pre, a)
|
|
82
|
+
done = False
|
|
83
|
+
for r in range(n_act):
|
|
84
|
+
if (int(eng.is_cx[r]), 1) >= window_key:
|
|
85
|
+
continue
|
|
86
|
+
cand = best[:i] + [r] + best[i + width:]
|
|
87
|
+
if _ok(eng, pre, [r] + best[i + width:]):
|
|
88
|
+
best = cand; improved = done = True
|
|
89
|
+
break
|
|
90
|
+
if not done:
|
|
91
|
+
i += 1
|
|
92
|
+
if time.time() - t0 > time_limit:
|
|
93
|
+
return best
|
|
94
|
+
if not improved:
|
|
95
|
+
break
|
|
96
|
+
return best
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def main():
|
|
100
|
+
from qfast import Engine
|
|
101
|
+
p = argparse.ArgumentParser()
|
|
102
|
+
p.add_argument("--qubits", type=int, default=6)
|
|
103
|
+
p.add_argument("--solutions", default=None)
|
|
104
|
+
p.add_argument("--limit", type=int, default=300)
|
|
105
|
+
a = p.parse_args()
|
|
106
|
+
n = a.qubits
|
|
107
|
+
eng = Engine(n)
|
|
108
|
+
T = np.load(f"data/train_{n}q.npz")["targets"]
|
|
109
|
+
sol = json.loads(open(a.solutions or f"data/ei_solutions_{n}q.json").read())
|
|
110
|
+
keys = list(sol)[:a.limit]
|
|
111
|
+
cx0 = l0 = cx1 = l1 = better = 0
|
|
112
|
+
t = time.time()
|
|
113
|
+
for k in keys:
|
|
114
|
+
acts = sol[k][1]
|
|
115
|
+
new = optimize(eng, T[int(k)], acts)
|
|
116
|
+
assert _ok(eng, np.asarray(T[int(k)], complex), new)
|
|
117
|
+
c0, c1 = _key(eng, acts), _key(eng, new)
|
|
118
|
+
cx0 += c0[0]; l0 += c0[1]; cx1 += c1[0]; l1 += c1[1]
|
|
119
|
+
better += c1 < c0
|
|
120
|
+
m = len(keys)
|
|
121
|
+
print(f"{n} кубитов, {m} решений учебника: CX {cx0 / m:.2f} -> {cx1 / m:.2f}, "
|
|
122
|
+
f"длина {l0 / m:.1f} -> {l1 / m:.1f}; улучшено {better} ({better / m:.0%}); "
|
|
123
|
+
f"{(time.time() - t) / m * 1000:.0f} мс на решение")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
if __name__ == "__main__":
|
|
127
|
+
main()
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"csla_mux_3": "9d52e8a719c86b757b13c4bd0001c6e69be4afb16acfb773ce8b4428957d2d5d",
|
|
3
|
+
"gf2^4_mult": "1f0b3f6637d1d73d0c6e69030e77198ed89b9111aa0a5334458084f97e5fce33",
|
|
4
|
+
"gf2^5_mult": "6ae44de8a94f4998dfb44bf33330cd0106b9f508e63f205ab7b49e8905a21e0a",
|
|
5
|
+
"mod5_4": "9d32b6df9264366676c3e259211d43c8686b7b3cbcb215e0bfa9d81c3919b3a6",
|
|
6
|
+
"mod_mult_55": "6740ed207932ae523291aaca0f0d38e5a0fbe903abd10871b2b1923fbbb4b92c",
|
|
7
|
+
"mod_red_21": "de0573cbb25fbca8b9546e485627901a9fd4135cc2ddfb59abc0e288f29f6656",
|
|
8
|
+
"rc_adder_6": "337176687452af345b8c078bbe63fe91a8306451c603fe9e627dc4a9395df70b",
|
|
9
|
+
"tof_10": "8958838674a8025346f3eed2219497c61039f012beef7ec22c4afcfd72980ae0",
|
|
10
|
+
"tof_3": "10caf87ef6ab65b8f798fa002fa4e2ebb5b44d1e3f2c95a21540121a9613ba7d",
|
|
11
|
+
"tof_4": "5778913a5c34ef226c9496f0fb8bb772b119b7c3455b31a83492e16913fd85f4",
|
|
12
|
+
"tof_5": "fde7a805c93176d6db2d2eb93d9aa1f81d58b6537c62ec5f4c37e27a4d25977f"
|
|
13
|
+
}
|