Demo 2:Abel HSP 离散对数¶
可视化:双图 —— N=15 拆 p1, p2 + CRT
对应教程:第 10 章 隐藏结构与群算法 中相关小节 · 依赖安装:pip install "unified-quantum[visualization,simulation]"
实现要点¶
问题:求 x 使 2^x ≡ 8 (mod 15),期望 x=3。 Shor 把阶查找拆成对 N 的每个素因子 p 分别查找,再 CRT (lcm) 拼合:
p=3: ord_3(2) = 2 - p=5: ord_5(2) = 4 - lcm(2, 4) = 4 = r ✓ QPE 估相位:U|a⟩ = |2a mod p⟩ 的本征相位是 j/r_p。 实操:用 numpy 模拟 QPE 输出(直接构造 U 所需门代价过高),CRT 与恢复 x 经典完成。
完整源码¶
demos/ch10-hidden-structures/02_abel_hsp.py¶
1"""
2ch10 演示 2:Abel HSP / 离散对数 + CRT period finding(N=15, a=2)
3
4对应教程:chenzhaoyun.com/quantum-tutorial/ch10-hidden-structures/abelian-hidden-subgroup.html
5依赖:pip install unified-quantum[visualization]
6运行:python demos/ch10-hidden-structures/02_abel_hsp.py
7
8问题:求 x 使 2^x ≡ 8 (mod 15),期望 x=3。
9Shor 把阶查找拆成对 N 的每个素因子 p 分别查找,再 CRT (lcm) 拼合:
10 - p=3: ord_3(2) = 2 - p=5: ord_5(2) = 4 - lcm(2, 4) = 4 = r ✓
11QPE 估相位:U|a⟩ = |2a mod p⟩ 的本征相位是 j/r_p。
12实操:用 numpy 模拟 QPE 输出(直接构造 U 所需门代价过高),CRT 与恢复 x 经典完成。
13"""
14from uniqc import Circuit, submit_task, wait_for_result
15import matplotlib
16matplotlib.use("Agg")
17import matplotlib.pyplot as plt
18import numpy as np
19from pathlib import Path
20from math import gcd
21from fractions import Fraction
22
23OUT = Path(__file__).parent
24N, A, H, SHOTS = 15, 2, 8, 1500
25
26
27def simulate_qpe_phase(period: int, n_precision: int, shots: int, rng) -> np.ndarray:
28 """模拟 QPE 估相位:j/period → k ≈ j·Q/period + 高斯噪声。"""
29 Q = 1 << n_precision
30 js = rng.integers(0, period, size=shots)
31 sigma = max(0.5, np.sqrt(Q) / 4.0)
32 ks = np.round(js * Q / period + rng.normal(0, sigma, size=shots)).astype(int) % Q
33 return ks
34
35
36def recover_period_via_cf(k: int, Q: int, max_period: int) -> int:
37 """连分数:从 k/Q 恢复候选 period。"""
38 if k == 0:
39 return Q
40 frac = Fraction(k, Q)
41 for d in range(1, max_period + 1):
42 if abs(frac - Fraction(round(frac * d), d)) < 1 / (2 * Q):
43 return d
44 return Q
45
46
47def order_mod(a, p):
48 for r in range(1, p + 1):
49 if pow(a, r, p) == 1:
50 return r
51 return -1
52
53
54def factor_via_order(a, N, r):
55 """已知 a 的阶 r → gcd(a^(r/2) ± 1, N) 给出非平凡因子。"""
56 if r % 2 != 0:
57 return None
58 for delta in (-1, 1):
59 g = gcd(pow(a, r // 2, N) + delta, N)
60 if 1 < g < N:
61 return g, N // g
62 return None
63
64
65# === 1. 经典检查:列出 2^x mod 15 与部分周期 ===
66r_full = order_mod(A, N)
67r1 = order_mod(A, 3)
68r2 = order_mod(A, 5)
69print(f"target: {A}^x ≡ {H} (mod {N}); true x = 3, full period r = {r_full}")
70print(f"CRT split: r_1 = ord_3({A}) = {r1}, r_2 = ord_5({A}) = {r2}")
71print(f"classical powers: {[pow(A, x, N) for x in range(r_full + 1)]}")
72print()
73
74# === 2. 模拟 QPE 输出 ===
75rng = np.random.default_rng(seed=2025)
76samples_p1 = simulate_qpe_phase(r1, n_precision=3, shots=SHOTS, rng=rng)
77samples_p2 = simulate_qpe_phase(r2, n_precision=3, shots=SHOTS, rng=rng)
78
79# === 3. 连分数恢复 r_1, r_2,lcm 拼合 ===
80def top_two_peaks(xs):
81 vals, counts = np.unique(xs, return_counts=True)
82 order = np.argsort(-counts)
83 out = [int(vals[order[0]])]
84 if len(order) > 1:
85 out.append(int(vals[order[1]]))
86 return out
87
88
89k1 = next((k for k in top_two_peaks(samples_p1) if k != 0), 0)
90k2 = next((k for k in top_two_peaks(samples_p2) if k != 0), 0)
91recovered_r1 = recover_period_via_cf(k1, 8, max_period=N)
92recovered_r2 = recover_period_via_cf(k2, 8, max_period=N)
93r_recovered = recovered_r1 * recovered_r2 // gcd(recovered_r1, recovered_r2)
94
95# === 4. 因子分解与离散对数恢复 ===
96factors = factor_via_order(A, N, r_recovered)
97x_recovered = next((x for x in range(r_recovered) if pow(A, x, N) == H), None)
98
99# === 5. matplotlib 可视化 ===
100fig, axes = plt.subplots(1, 2, figsize=(13, 5))
101
102# 左图:QPE 输出相位直方图(p=3 与 p=5 并列)
103ax = axes[0]
104bins = np.arange(0, 9)
105h1, _ = np.histogram(samples_p1, bins=bins)
106h2, _ = np.histogram(samples_p2, bins=bins)
107w = 0.4
108ax.bar(np.arange(8) - w / 2, h1 / SHOTS * 100, width=w, color="#1f77b4",
109 edgecolor="black", label=f"p=3 (r={r1}): k = j·Q/r")
110ax.bar(np.arange(8) + w / 2, h2 / SHOTS * 100, width=w, color="#d62728",
111 edgecolor="black", label=f"p=5 (r={r2}): k = j·Q/r")
112for r in (r1, r2):
113 for j in range(r):
114 k = round(j * 8 / r)
115 ax.axvline(k, color="gray", linestyle=":", alpha=0.4)
116 ax.annotate(f"j={j}", xy=(k, 50), xytext=(k, 52),
117 ha="center", fontsize=7, color="gray")
118ax.set_xticks(np.arange(8))
119ax.set_xticklabels([f"k={k}" for k in range(8)])
120ax.set_xlabel("Measured precision register k (Q = 2^3 = 8)")
121ax.set_ylabel("Probability (%)")
122ax.set_title(
123 f"Simulated QPE phase estimation for p=3 and p=5\n"
124 f"({SHOTS} shots each, peaks at k = j·Q/r_p)",
125 fontsize=10,
126)
127ax.set_ylim(0, 60)
128ax.grid(axis="y", alpha=0.3)
129ax.legend(loc="upper right", fontsize=9)
130
131# 右图:CRT 拼合流程 + 离散对数恢复(纯文本)
132ax = axes[1]
133ax.axis("off")
134sep = "─" * 48
135txt = f"CRT combination of partial periods\n{sep}\n\n"
136txt += f" p_1 = 3: ord_3({A}) = {r1}\n"
137txt += f" QPE sample k_1 = {k1}/Q → r_1 = {recovered_r1} {'✓' if recovered_r1 == r1 else '✗'}\n\n"
138txt += f" p_2 = 5: ord_5({A}) = {r2}\n"
139txt += f" QPE sample k_2 = {k2}/Q → r_2 = {recovered_r2} {'✓' if recovered_r2 == r2 else '✗'}\n\n"
140txt += f" full period: lcm(r_1, r_2) = lcm({r1}, {r2}) = {r_recovered}\n\n"
141txt += f"Factoring via order\n{sep}\n\n"
142if factors is not None:
143 f1, f2 = factors
144 txt += f" r = {r_recovered} (even) → gcd({A}^(r/2) ± 1, {N})\n"
145 txt += f" factors of {N} = ({f1}, {f2}) ✓\n\n"
146else:
147 txt += f" r = {r_recovered} (odd or trivial) → try other branch\n\n"
148txt += f"Discrete log recovery\n{sep}\n\n"
149txt += f" find x with {A}^x ≡ {H} (mod {N}); search x ∈ [0, r={r_recovered}):\n"
150for x_try in range(r_recovered):
151 val = pow(A, x_try, N)
152 marker = " ← MATCH" if val == H else ""
153 txt += f" {A}^{x_try} mod {N} = {val}{marker}\n"
154txt += f"\n recovered x = {x_recovered} (true x = 3, match = {x_recovered == 3})"
155ax.text(0.02, 0.98, txt, family="monospace", fontsize=9,
156 verticalalignment="top", transform=ax.transAxes)
157
158fig.suptitle(
159 f"Abelian HSP discrete log on Z_{N}: a={A}, h={H} "
160 f"({SHOTS} QPE shots per prime, dummy:local:simulator)",
161 fontsize=11, y=1.02,
162)
163plt.tight_layout()
164plt.savefig(OUT / "02_abel_hsp.png", dpi=120, bbox_inches="tight")
165plt.close()
166
167# === 6. stdout 关键统计 ===
168print("ch10-hidden-structures demo 2: Abel HSP discrete log + CRT period finding")
169print(f" QPE sample k_1 = {k1} → r_1 recovered = {recovered_r1} (true {r1})")
170print(f" QPE sample k_2 = {k2} → r_2 recovered = {recovered_r2} (true {r2})")
171print(f" combined r = lcm(r_1, r_2) = {r_recovered}")
172print(f" factorization of {N} = {factors}")
173print(f" discrete log: recovered x = {x_recovered} (true x = 3, match = {x_recovered == 3})")
174print(f" saved: {OUT / '02_abel_hsp.png'}")
运行结果¶

运行方式¶
# 仓库根目录执行;输出 PNG 到 demos/ch10-hidden-structures/02_abel_hsp.png,关键统计到 stdout
.venv/bin/python3 demos/ch10-hidden-structures/02_abel_hsp.py