Demo 1:碰撞 BHT¶
可视化:双图 —— N=8 标记命中 + log-log 查询数对比
对应教程:第 11 章 查询复杂度 中相关小节 · 依赖安装:pip install "unified-quantum[visualization,simulation]"
实现要点¶
核心:在 N=8 的二对一承诺函数 f=[0,0,1,1,2,2,3,3] 上演示 Brassard-Hoyer-Tapp 抽样表 + Grover 算法的 r = N^(1/3) 平衡点。画三幅图: (左) hash 函数结构 + 抽样表 + "另一半"原像标记 (中) 经典生日攻击 sqrt(N) vs 量子 BHT N^(1/3) 查询数 log-log 对比 (右) N=8 实际跑 Grover: 命中 marked 状态的概率分布
完整源码¶
demos/ch11-query-complexity/01_collision_bht.py¶
1"""
2ch11 演示 1:碰撞问题查询复杂度对比 + BHT 量子算法小实例
3
4对应教程:chenzhaoyun.com/quantum-tutorial/ch11-query-complexity/collision-element-distinctness.html
5核心:在 N=8 的二对一承诺函数 f=[0,0,1,1,2,2,3,3] 上演示 Brassard-Hoyer-Tapp
6 抽样表 + Grover 算法的 r = N^(1/3) 平衡点。画三幅图:
7 (左) hash 函数结构 + 抽样表 + "另一半"原像标记
8 (中) 经典生日攻击 sqrt(N) vs 量子 BHT N^(1/3) 查询数 log-log 对比
9 (右) N=8 实际跑 Grover: 命中 marked 状态的概率分布
10
11依赖:pip install unified-quantum[visualization]
12运行:python demos/ch11-query-complexity/01_collision_bht.py
13"""
14from pathlib import Path
15
16import matplotlib
17matplotlib.use("Agg")
18import matplotlib.pyplot as plt
19import numpy as np
20
21from uniqc import Circuit, submit_task, wait_for_result
22
23OUT = Path(__file__).parent
24SHOTS = 2000
25N_DOMAIN = 8
26HASH_TABLE = np.array([0, 0, 1, 1, 2, 2, 3, 3], dtype=int)
27
28
29def mark_state(c: Circuit, ms: int, n_qubits: int = 3) -> None:
30 """对 n-qubit 寄存器的 |ms> 加相位翻转 (ms 用 little-endian 二进制)."""
31 bits = [(ms >> (n_qubits - 1 - b)) & 1 for b in range(n_qubits)]
32 for q in range(n_qubits):
33 if bits[q] == 0:
34 c.x(q)
35 c.h(n_qubits - 1)
36 c.toffoli(0, 1, n_qubits - 1)
37 c.h(n_qubits - 1)
38 for q in range(n_qubits):
39 if bits[q] == 0:
40 c.x(q)
41
42
43def diffusion(c: Circuit, n_qubits: int = 3) -> None:
44 for q in range(n_qubits):
45 c.h(q); c.x(q)
46 c.h(n_qubits - 1)
47 c.toffoli(0, 1, n_qubits - 1)
48 c.h(n_qubits - 1)
49 for q in range(n_qubits):
50 c.x(q); c.h(q)
51
52
53def find_partners(sample: list[int]) -> list[int]:
54 """给定抽样 index, 返回表外 '另一半' 原像 (sorted, dedup)."""
55 s_set = set(sample)
56 marked = []
57 for i in sample:
58 partner = int(np.where(HASH_TABLE == HASH_TABLE[i])[0][
59 np.where(HASH_TABLE == HASH_TABLE[i])[0] != i][0])
60 if partner not in s_set:
61 marked.append(partner)
62 return sorted(set(marked))
63
64
65def run_grover(marked: list[int], iters: int = 2) -> dict:
66 c = Circuit()
67 for q in range(3):
68 c.h(q)
69 for _ in range(iters):
70 for ms in marked:
71 mark_state(c, ms)
72 diffusion(c)
73 for q in range(3):
74 c.measure(q)
75 r = wait_for_result(submit_task(circuit=c, backend="dummy:local:simulator", shots=SHOTS))
76 return dict(r.counts)
77
78
79print("ch11-query-complexity demo 1: collision problem + BHT")
80print(f" domain N = {N_DOMAIN}, hash f = {HASH_TABLE.tolist()} (two-to-one), shots = {SHOTS}")
81
82# === 复杂度对比表 ===
83N_LIST = [8, 27, 64, 216, 1000, 8000, 27000, 100000, 1000000]
84classical_queries = [int(np.ceil(np.sqrt(2 * np.log(2) * n))) for n in N_LIST]
85quantum_queries = [2 * int(round(n ** (1.0 / 3.0))) for n in N_LIST]
86logN = np.log10(N_LIST)
87logC, logQ = np.log10(classical_queries), np.log10(quantum_queries)
88
89# === N=8 上跑实际 Grover ===
90sample_indices = [0, 2]
91sample_outputs = [int(HASH_TABLE[i]) for i in sample_indices]
92marked_states = find_partners(sample_indices)
93grover_counts = run_grover(marked_states, iters=2)
94total = sum(grover_counts.values())
95mark_total = sum(v for k, v in grover_counts.items() if int(k, 2) in marked_states)
96n_labels = [f"{i:03b}" for i in range(N_DOMAIN)]
97grover_probs = np.array([grover_counts.get(lbl, 0) / total * 100 for lbl in n_labels])
98
99# === 画图 ===
100fig, axes = plt.subplots(1, 3, figsize=(16.0, 5.0))
101partner_map = {i: [j for j in range(N_DOMAIN) if j != i and HASH_TABLE[j] == HASH_TABLE[i]][0]
102 for i in range(N_DOMAIN)}
103
104# (左) hash 结构
105ax = axes[0]
106colors = ["#1f77b4" if i not in sample_indices else "#ffd700" for i in range(N_DOMAIN)]
107bars = ax.bar(np.arange(N_DOMAIN), HASH_TABLE, color=colors, edgecolor="black")
108for i, b in enumerate(bars):
109 star = "*" if i in sample_indices else ""
110 ax.text(b.get_x() + b.get_width() / 2, b.get_height() + 0.06,
111 f"f({i})={HASH_TABLE[i]}\npart={partner_map[i]}{star}",
112 ha="center", va="bottom", fontsize=7)
113ax.set_xticks(np.arange(N_DOMAIN))
114ax.set_xticklabels([f"in {i}" for i in range(N_DOMAIN)], fontsize=8)
115ax.set_xlabel("input index")
116ax.set_ylabel("hash value")
117ax.set_title(f"two-to-one hash f (gold = sampled)\n"
118 f"sample = {sample_indices} outputs = {sample_outputs}")
119ax.set_ylim(0, HASH_TABLE.max() + 0.6)
120ax.grid(axis="y", alpha=0.3)
121
122# (中) 经典 vs 量子 log-log
123ax = axes[1]
124ax.plot(logN, logC, "o-", color="#d62728", lw=2, ms=7, label=r"classical birthday $O(\sqrt{N})$")
125ax.plot(logN, logQ, "s-", color="#1f77b4", lw=2, ms=7, label=r"quantum BHT $O(N^{1/3})$")
126ref = np.array([logN[0], logN[-1]])
127ax.plot(ref, 0.5 * ref + logC[0] - 0.5 * logN[0], "--", color="#d62728", alpha=0.4, label="slope 1/2")
128ax.plot(ref, (1.0 / 3.0) * ref + logQ[0] - (1.0 / 3.0) * logN[0], "--", color="#1f77b4", alpha=0.4, label="slope 1/3")
129for x, yc, yq in zip(logN, logC, logQ):
130 ax.annotate(f"{10**yc:.0f}", (x, yc), textcoords="offset points", xytext=(0, 6),
131 ha="center", fontsize=6.5, color="#d62728")
132 ax.annotate(f"{10**yq:.0f}", (x, yq), textcoords="offset points", xytext=(0, -12),
133 ha="center", fontsize=6.5, color="#1f77b4")
134ax.set_xlabel(r"$\log_{10} N$")
135ax.set_ylabel(r"$\log_{10}$ # queries")
136ax.set_title("query complexity: classical vs quantum\n"
137 r"$Q_{\mathrm{cl}} = O(\sqrt{N})$ vs $Q_{\mathrm{qu}} = O(N^{1/3})$")
138ax.legend(loc="upper left", fontsize=8)
139ax.grid(alpha=0.3)
140
141# (右) Grover 分布
142ax = axes[2]
143colors = ["#2ca02c" if i in marked_states else "#9467bd" for i in range(N_DOMAIN)]
144bars = ax.bar(np.arange(N_DOMAIN), grover_probs, color=colors, edgecolor="black")
145for i, b in enumerate(bars):
146 if grover_probs[i] > 0.5:
147 ax.text(b.get_x() + b.get_width() / 2, b.get_height() + 1.0,
148 f"{grover_probs[i]:.1f}%", ha="center", va="bottom", fontsize=8, fontweight="bold")
149ax.set_xticks(np.arange(N_DOMAIN))
150ax.set_xticklabels(n_labels, fontsize=9)
151ax.axhline(100.0 / N_DOMAIN, color="gray", ls=":", lw=1.5, label=f"uniform {100/N_DOMAIN:.1f}%")
152ax.set_xlabel("Grover measurement |xyz>")
153ax.set_ylabel("probability (%)")
154ax.set_title(f"BHT Grover (sampled r={len(sample_indices)}, "
155 f"marked = {marked_states})\n"
156 fr"$P(\mathrm{{mark}}) = {mark_total/total*100:.1f}$%")
157ax.legend(loc="upper right", fontsize=8)
158ax.set_ylim(0, max(50, grover_probs.max() + 8))
159ax.grid(axis="y", alpha=0.3)
160
161fig.suptitle("ch11 demo 1: collision problem — BHT sampling + Grover beats classical birthday",
162 y=1.01, fontsize=12)
163plt.tight_layout()
164out_path = OUT / "01_collision_bht.png"
165plt.savefig(out_path, dpi=120, bbox_inches="tight")
166plt.close()
167
168print(f" sample table: {sample_indices} -> f-values {sample_outputs}")
169print(f" marked 'other halves' (Grover targets): {marked_states}")
170print(f" Grover P(marked) measured = {mark_total/total:.3f} "
171 f"(uniform = {1.0/N_DOMAIN:.3f}, "
172 f"speedup = {(mark_total/total)/(1.0/N_DOMAIN):.2f}x)")
173print(f" complexity scaling:")
174for n, c, q in zip(N_LIST, classical_queries, quantum_queries):
175 print(f" N = {n:>7d} Q_classical = {c:>5d} Q_quantum = {q:>4d} speedup = {c/max(q,1):>4.1f}x")
176print(f" saved: {out_path}")
运行结果¶

运行方式¶
# 仓库根目录执行;输出 PNG 到 demos/ch11-query-complexity/01_collision_bht.png,关键统计到 stdout
.venv/bin/python3 demos/ch11-query-complexity/01_collision_bht.py