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.
Files changed (126) hide show
  1. qoptct/__init__.py +28 -0
  2. qoptct/bench_real.py +212 -0
  3. qoptct/block_synth.py +265 -0
  4. qoptct/circuit_opt.py +127 -0
  5. qoptct/contrib/__init__.py +0 -0
  6. qoptct/contrib/c1/__init__.py +0 -0
  7. qoptct/contrib/c1/best_clean/MANIFEST.json +13 -0
  8. qoptct/contrib/c1/best_clean/csla_mux_3.qasm +156 -0
  9. qoptct/contrib/c1/best_clean/gf2^4_mult.qasm +184 -0
  10. qoptct/contrib/c1/best_clean/gf2^5_mult.qasm +288 -0
  11. qoptct/contrib/c1/best_clean/mod5_4.qasm +23 -0
  12. qoptct/contrib/c1/best_clean/mod_mult_55.qasm +71 -0
  13. qoptct/contrib/c1/best_clean/mod_red_21.qasm +118 -0
  14. qoptct/contrib/c1/best_clean/rc_adder_6.qasm +156 -0
  15. qoptct/contrib/c1/best_clean/tof_10.qasm +122 -0
  16. qoptct/contrib/c1/best_clean/tof_3.qasm +31 -0
  17. qoptct/contrib/c1/best_clean/tof_4.qasm +44 -0
  18. qoptct/contrib/c1/best_clean/tof_5.qasm +57 -0
  19. qoptct/contrib/c1/csyn.py +265 -0
  20. qoptct/contrib/c1/resyn.py +122 -0
  21. qoptct/contrib/c1/resyn2.py +136 -0
  22. qoptct/contrib/c1/resyn3.py +262 -0
  23. qoptct/contrib/c1/resyn4.py +97 -0
  24. qoptct/contrib/c1/run.py +71 -0
  25. qoptct/contrib/c10/__init__.py +0 -0
  26. qoptct/contrib/c10/pathsum.py +191 -0
  27. qoptct/contrib/c10/verify_big.py +251 -0
  28. qoptct/contrib/c12/__init__.py +0 -0
  29. qoptct/contrib/c12/mcx.py +271 -0
  30. qoptct/contrib/c2/__init__.py +0 -0
  31. qoptct/contrib/c2/andc.py +521 -0
  32. qoptct/contrib/c2/peep.py +59 -0
  33. qoptct/contrib/c2/run.py +51 -0
  34. qoptct/contrib/c2/sweep2.py +72 -0
  35. qoptct/contrib/c8/__init__.py +0 -0
  36. qoptct/contrib/c8/hyb.py +159 -0
  37. qoptct/contrib/eng1/__init__.py +0 -0
  38. qoptct/contrib/eng1/det.py +333 -0
  39. qoptct/contrib/eng1/det2.py +386 -0
  40. qoptct/contrib/eng1/equiv.py +88 -0
  41. qoptct/contrib/eng1/fasttodd.py +144 -0
  42. qoptct/contrib/eng1/gad.py +272 -0
  43. qoptct/contrib/eng1/gad2.py +80 -0
  44. qoptct/contrib/eng1/ours/adder_8.json +1 -0
  45. qoptct/contrib/eng1/ours/barenco_tof_10.json +1 -0
  46. qoptct/contrib/eng1/ours/barenco_tof_3.json +1 -0
  47. qoptct/contrib/eng1/ours/barenco_tof_4.json +1 -0
  48. qoptct/contrib/eng1/ours/barenco_tof_5.json +1 -0
  49. qoptct/contrib/eng1/ours/csla_mux_3.json +1 -0
  50. qoptct/contrib/eng1/ours/csum_mux_9.json +1 -0
  51. qoptct/contrib/eng1/ours/gf2^4_mult.json +1 -0
  52. qoptct/contrib/eng1/ours/gf2^5_mult.json +1 -0
  53. qoptct/contrib/eng1/ours/grover_5.json +1 -0
  54. qoptct/contrib/eng1/ours/ham15-low.json +1 -0
  55. qoptct/contrib/eng1/ours/mod5_4.json +1 -0
  56. qoptct/contrib/eng1/ours/mod_mult_55.json +1 -0
  57. qoptct/contrib/eng1/ours/mod_red_21.json +1 -0
  58. qoptct/contrib/eng1/ours/qcla_com_7.json +1 -0
  59. qoptct/contrib/eng1/ours/qft_4.json +1 -0
  60. qoptct/contrib/eng1/ours/rc_adder_6.json +1 -0
  61. qoptct/contrib/eng1/ours/tof_10.json +1 -0
  62. qoptct/contrib/eng1/ours/tof_3.json +1 -0
  63. qoptct/contrib/eng1/ours/tof_4.json +1 -0
  64. qoptct/contrib/eng1/ours/tof_5.json +1 -0
  65. qoptct/contrib/eng1/ours/vbe_adder_3.json +1 -0
  66. qoptct/contrib/eng2/__init__.py +0 -0
  67. qoptct/contrib/eng2/improve.py +79 -0
  68. qoptct/contrib/eng2/regions.py +69 -0
  69. qoptct/contrib/eng2/synth.py +216 -0
  70. qoptct/contrib/tg2/__init__.py +0 -0
  71. qoptct/contrib/tg2/sel.py +293 -0
  72. qoptct/contrib/tg3/__init__.py +0 -0
  73. qoptct/contrib/tg3/core.py +229 -0
  74. qoptct/contrib/tg3/post.py +102 -0
  75. qoptct/contrib/tg3/search.py +145 -0
  76. qoptct/contrib/tg3/search2.py +73 -0
  77. qoptct/contrib/tg3/search_fin.py +32 -0
  78. qoptct/contrib/tg3/tg3_opt.py +69 -0
  79. qoptct/ei2.py +553 -0
  80. qoptct/expert_iter.py +230 -0
  81. qoptct/fast_gates.py +31 -0
  82. qoptct/gidney.py +190 -0
  83. qoptct/global_cancel.py +36 -0
  84. qoptct/meas_portfolio.py +54 -0
  85. qoptct/models/mx_4_4q.pt +0 -0
  86. qoptct/models/rt_3_si2_3q.pt +0 -0
  87. qoptct/models/u_2_best_2q.pt +0 -0
  88. qoptct/models/value_u4q.pt +0 -0
  89. qoptct/portfolio/__init__.py +0 -0
  90. qoptct/portfolio/bench_hook.py +47 -0
  91. qoptct/portfolio/evo_lib.py +373 -0
  92. qoptct/portfolio/programs/__init__.py +0 -0
  93. qoptct/portfolio/programs/c0001_combo2.py +10 -0
  94. qoptct/portfolio/programs/c0002_vphase_push.py +198 -0
  95. qoptct/portfolio/programs/c0005_phasepoly_graysynth.py +299 -0
  96. qoptct/portfolio/programs/c0006_combo2_permpair.py +150 -0
  97. qoptct/portfolio/programs/c0007_conj_rtof.py +203 -0
  98. qoptct/portfolio/programs/c0008_cla_collapse.py +292 -0
  99. qoptct/portfolio/programs/c0009_phasepoly_rtof4.py +298 -0
  100. qoptct/portfolio/programs/c0010_combo2_majcarry.py +208 -0
  101. qoptct/portfolio/programs/c0011_rtof3_vphase.py +248 -0
  102. qoptct/portfolio/programs/c0013_unify_permpair_vphase.py +300 -0
  103. qoptct/portfolio/programs/c0014_majcarry_ppresynth.py +298 -0
  104. qoptct/portfolio/programs/c0015_mcx_resynth.py +151 -0
  105. qoptct/portfolio/programs/c0017_rtof3_esop.py +297 -0
  106. qoptct/proto2/__init__.py +0 -0
  107. qoptct/proto2/phase_fold.py +67 -0
  108. qoptct/proto2/rtof.py +246 -0
  109. qoptct/proto2/verify.py +62 -0
  110. qoptct/qenv.py +147 -0
  111. qoptct/qfast.py +182 -0
  112. qoptct/qgnn.py +305 -0
  113. qoptct/qiskit_qopt.py +309 -0
  114. qoptct/qopt_core.py +466 -0
  115. qoptct/qopt_metrics.py +107 -0
  116. qoptct/tpoly.py +165 -0
  117. qoptct/train_agent.py +153 -0
  118. qoptct/vartodd_bridge.py +73 -0
  119. qoptct/verify_qasm.py +69 -0
  120. qoptct-0.3.0.dist-info/METADATA +282 -0
  121. qoptct-0.3.0.dist-info/RECORD +126 -0
  122. qoptct-0.3.0.dist-info/WHEEL +5 -0
  123. qoptct-0.3.0.dist-info/entry_points.txt +8 -0
  124. qoptct-0.3.0.dist-info/licenses/LICENSE +202 -0
  125. qoptct-0.3.0.dist-info/licenses/NOTICE +11 -0
  126. 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
+ }