Demo 2:Abel HSP 离散对数

第 10 章 隐藏结构与群算法 · 本章演示 · 代码演示总索引

可视化:双图 —— 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'}")

运行结果

Demo 2:Abel HSP 离散对数

运行方式

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

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