Demo 3:字符串重写

第 13 章 拓扑、统计物理与采样 · 本章演示 · 代码演示总索引

可视化:双图 —— 经典步数 vs Grover 迭代

对应教程:第 13 章 拓扑、统计物理与采样 中相关小节 · 依赖安装:pip install "unified-quantum[visualization,simulation]"

实现要点

重写规则 (固定一组): a → bc b → a c → a 我们关心匹配查询: 给定起始串 S, 经典法必须逐步展开每条规则 (O(|S|·|R|) per step); 量子 Grover 匹配只测 √M 个候选 (M=|prefix|·|R|). 对比:

  • 经典 "单步展开" 时间 (模拟, 扫 4 个 |S| 长度)

  • 量子 Grover 迭代次数 (~ π/4 √M) 画柱图对比, 并对一组具体 query 演示 Grover 找到目标子串的命中概率.

完整源码

demos/ch13-topology-statistical-physics/03_string_rewriting.py
  1"""
  2ch13-topology demo 3: 字符串重写系统量子加速 (toy)
  3
  4对应教程: chenzhaoyun.com/quantum-tutorial/ch13-topology-statistical-physics/string-rewriting.html
  5依赖: pip install unified-quantum[visualization]
  6运行: python demos/ch13-topology-statistical-physics/03_string_rewriting.py
  7
  8重写规则 (固定一组):
  9  a → bc
 10  b → a
 11  c → a
 12我们关心匹配查询: 给定起始串 S, 经典法必须逐步展开每条规则
 13(O(|S|·|R|) per step); 量子 Grover 匹配只测 √M 个候选 (M=|prefix|·|R|).
 14对比:
 15  - 经典 "单步展开" 时间 (模拟, 扫 4 个 |S| 长度)
 16  - 量子 Grover 迭代次数 (~ π/4 √M)
 17画柱图对比, 并对一组具体 query 演示 Grover 找到目标子串的命中概率.
 18"""
 19
 20from __future__ import annotations
 21
 22from pathlib import Path
 23
 24import matplotlib
 25matplotlib.use("Agg")
 26import matplotlib.pyplot as plt
 27import numpy as np
 28
 29from uniqc import Circuit, grover_operator, grover_oracle, submit_task, wait_for_result
 30
 31OUT = Path(__file__).parent
 32SHOTS = 500
 33
 34
 35# ---------------------------------------------------------------------------
 36# 1) 玩具重写系统: a → bc, b → a, c → a
 37# ---------------------------------------------------------------------------
 38REWRITE = {"a": "bc", "b": "a", "c": "a"}
 39
 40
 41def one_step(s: str) -> str:
 42    """Apply one rewrite pass: scan s left-to-right, replace each char.
 43    To keep it deterministic & fast, replace leftmost match first then advance.
 44    """
 45    out = []
 46    i = 0
 47    while i < len(s):
 48        ch = s[i]
 49        if ch in REWRITE:
 50            out.append(REWRITE[ch])
 51            i += 1
 52        else:
 53            out.append(ch)
 54            i += 1
 55    return "".join(out)
 56
 57
 58def evolve(s: str, n_steps: int) -> str:
 59    for _ in range(n_steps):
 60        s = one_step(s)
 61    return s
 62
 63
 64# Build a few example strings and their trajectories
 65EXAMPLES = [
 66    ("a",      4),
 67    ("ab",     4),
 68    ("abc",    4),
 69    ("aabc",   5),
 70    ("ba",     5),
 71]
 72print("ch13-demo-3: classical vs quantum string rewriting")
 73for s, k in EXAMPLES:
 74    final = evolve(s, k)
 75    print(f"  start={s!r:>6}  after {k} steps -> {final!r}  "
 76          f"(length {len(final)})")
 77
 78
 79# ---------------------------------------------------------------------------
 80# 2) 经典 "单步展开" 复杂度: 对每个 |S|, 跑 k 步, 计总字符访问
 81# ---------------------------------------------------------------------------
 82def classical_cost(s: str, k: int) -> int:
 83    total_chars_scanned = 0
 84    cur = s
 85    for _ in range(k):
 86        for ch in cur:
 87            total_chars_scanned += 1
 88        cur = one_step(cur)
 89    return total_chars_scanned
 90
 91
 92# |S| ∈ {4, 8, 16, 32} 起始串用 'a' 重复, 跑 6 步
 93lengths = [4, 8, 16, 32]
 94classical_costs = []
 95final_lengths = []
 96for L in lengths:
 97    s = "a" * L
 98    cost = classical_cost(s, 6)
 99    final = evolve(s, 6)
100    classical_costs.append(cost)
101    final_lengths.append(len(final))
102    print(f"  |S|={L}: classical cost over 6 steps = {cost} chars, "
103          f"final length = {len(final)}")
104
105
106# ---------------------------------------------------------------------------
107# 3) 量子 Grover 匹配: 字符串长度 N, 字符表大小 M, 总候选数 M^N
108#    这里做 "子串包含查询": 是否包含 "bc" 子串, 用 1 次 oracle.
109#    量子代价 ~ π/4 √(M^N); 经典代价 ~ N (字面扫).
110# ---------------------------------------------------------------------------
111def grover_iters(M: int, N: int) -> float:
112    """Iter count ≈ π/4 · √(M^N) for unique solution."""
113    return np.pi / 4.0 * np.sqrt(M ** N)
114
115
116# 子串包含查询 N = 4 chars, M = 2  ({b,c}); 迭代数 = π/4 · √16 ≈ 1.57
117# 经典扫 4 chars ≈ 4 操作
118# 量子 Grover 用 1 次 oracle (~1 iter) 就能找到 ~1 个解
119
120# For quantum demo: 2-bit search space, oracle marks "11" (substring "cc"
121# in alphabet {a,b,c} is too complex; we abstract to a 2-qubit marker)
122# Treat 2-qubit input as 4 candidates; oracle marks |11⟩; show Grover finds it.
123
124def run_grover_2q(n_iter: int) -> dict:
125    """Run n_iter Grover iterations targeting |11⟩ on 2 qubits.
126    The oracle adds an ancilla (qubit 2); we extract system bits via last 2 chars."""
127    # Oracle: marks |11⟩ (= 3) by flipping its phase
128    oracle = grover_oracle(marked_state=3, n_qubits=2)
129    # Diffusion + oracle
130    diffusion = grover_diffusion(n_qubits=2)
131    c = Circuit()
132    # Initial superposition
133    c.h(0); c.h(1)
134    for _ in range(n_iter):
135        c.add_circuit(oracle)
136        c.add_circuit(diffusion)
137    c.measure(0); c.measure(1)
138    tid = submit_task(circuit=c, backend="dummy:local:simulator", shots=SHOTS)
139    r = wait_for_result(tid)
140    counts = r.counts
141    total = sum(counts.values())
142    # Extract q0 q1 = last 2 chars of bit string (uniqc MSB-first → qN-1..q0)
143    sys_probs = {"00": 0.0, "01": 0.0, "10": 0.0, "11": 0.0}
144    if total > 0:
145        for k, v in counts.items():
146            sys_bs = k[-2:] if len(k) >= 2 else k.zfill(2)
147            if sys_bs in sys_probs:
148                sys_probs[sys_bs] += v / total
149    return sys_probs
150
151
152from uniqc import grover_diffusion  # noqa: E402
153
154print("\n  Grover iterations on 2-qubit search (target |11⟩):")
155grover_probs = []
156for n_iter in range(0, 5):
157    probs = run_grover_2q(n_iter)
158    p11 = probs["11"]
159    grover_probs.append((n_iter, probs, p11))
160    print(f"    iter {n_iter}: P(|11>) = {p11:.3f}  full = "
161          f"{ {k: f'{v:.2f}' for k, v in probs.items()} }")
162
163
164# ---------------------------------------------------------------------------
165# 4) matplotlib 可视化
166# ---------------------------------------------------------------------------
167fig, axes = plt.subplots(1, 3, figsize=(16.0, 4.8))
168
169ax = axes[0]
170bars = ax.bar(range(len(grover_probs)),
171              [p[2] for p in grover_probs],
172              color="#d62728", alpha=0.85, ec="black", lw=0.6)
173for b, p in zip(bars, grover_probs):
174    ax.text(b.get_x() + b.get_width() / 2, p[2] + 0.015,
175            f"{p[2]:.2f}", ha="center", va="bottom", fontsize=10)
176ax.set_xticks(range(len(grover_probs)))
177ax.set_xticklabels([f"iter {p[0]}" for p in grover_probs])
178ax.set_xlabel("Grover iteration")
179ax.set_ylabel(r"success probability $P(|11\rangle)$")
180ax.set_title("Quantum string-pattern matcher\n"
181             "Grover iterates on 2-qubit search, target = |11⟩")
182ax.set_ylim(0, 1.1)
183ax.grid(axis="y", alpha=0.3)
184
185ax = axes[1]
186xpos = np.arange(len(lengths))
187bars1 = ax.bar(xpos - 0.18, classical_costs, 0.35,
188               color="#1f77b4", alpha=0.85, ec="black", lw=0.6,
189               label="classical char-scans")
190ax.set_yscale("log")
191ax.set_xticks(xpos)
192ax.set_xticklabels([f"|S|={L}" for L in lengths])
193ax.set_xlabel("start-string length |S|, 6 rewrite steps")
194ax.set_ylabel("classical work (log)")
195ax.set_title("Classical cost of 6 rewrite steps\n"
196             "linear in evolving |S| — work grows polynomially")
197ax.grid(axis="y", alpha=0.3, which="both")
198ax.legend(fontsize=9, loc="upper left")
199
200ax2 = ax.twinx()
201bars2 = ax2.bar(xpos + 0.18, final_lengths, 0.35,
202                color="#ff7f0e", alpha=0.85, ec="black", lw=0.6,
203                label="final |S|")
204ax2.set_ylabel("final |S| after 6 steps", color="#ff7f0e")
205ax2.tick_params(axis="y", labelcolor="#ff7f0e")
206ax2.legend(fontsize=9, loc="upper right")
207
208ax = axes[2]
209M_list = [2, 3, 4]
210N_list = [4, 8, 12, 16, 20]
211classical_curve = []
212quantum_curve = []
213for M in M_list:
214    cs, qs = [], []
215    for N in N_list:
216        cs.append(N)
217        qs.append(grover_iters(M, N))
218    classical_curve.append(cs)
219    quantum_curve.append(qs)
220
221ax.plot(N_list, classical_curve[0], "o-", color="#1f77b4", lw=2, ms=7,
222        label=r"classical  $|S|=N$ scans")
223ax.plot(N_list, quantum_curve[0], "s--", color="#d62728", lw=2, ms=7,
224        label=r"quantum  Grover iters, $M=2$")
225ax.plot(N_list, quantum_curve[1], "^--", color="#9467bd", lw=2, ms=7,
226        label=r"quantum  Grover iters, $M=3$")
227ax.plot(N_list, quantum_curve[2], "v--", color="#2ca02c", lw=2, ms=7,
228        label=r"quantum  Grover iters, $M=4$")
229ax.set_yscale("log")
230ax.set_xlabel(r"string length $N$")
231ax.set_ylabel(r"work units (log)")
232ax.set_title("Substring-match complexity\n"
233             r"classical $O(N)$ vs Grover $O(\sqrt{M^N})$")
234ax.grid(alpha=0.3, which="both")
235ax.legend(fontsize=8)
236
237plt.suptitle("ch13-topology demo 3: classical vs quantum "
238             "string rewriting", y=1.02)
239plt.tight_layout()
240out_path = OUT / "03_string_rewriting.png"
241plt.savefig(out_path, dpi=120, bbox_inches="tight")
242plt.close()
243
244
245# ---------------------------------------------------------------------------
246# 5) stdout 关键统计
247# ---------------------------------------------------------------------------
248print("\n  summary:")
249print(f"  shots per Grover circuit: {SHOTS}, iters tested: 0..4")
250print(f"  P(|11>) after 1 Grover iter: {grover_probs[1][2]:.3f}  "
251      f"(theory π/4·sin(2·1·arcsin(1/2)) ≈ 1.000 — capped by shots)")
252print(f"  P(|11>) after 4 Grover iters: {grover_probs[4][2]:.3f}")
253print(f"  classical cost (|S|=32, 6 steps): {classical_costs[-1]} chars")
254print(f"  total quantum circuits run: {len(grover_probs)}")
255print(f"  total quantum shots: {len(grover_probs) * SHOTS}")
256print(f"  PNG -> {out_path}")

运行结果

Demo 3:字符串重写

运行方式

# 仓库根目录执行;输出 PNG 到 demos/ch13-topology-statistical-physics/03_string_rewriting.png,关键统计到 stdout
.venv/bin/python3 demos/ch13-topology-statistical-physics/03_string_rewriting.py

← 本章演示 · ↑ 代码演示总索引 · ⌂ 教程门户