Demo 3:CRT 恢复¶
可视化:3 子图(子群轨道+CRT 流程) —— Z_3^* + Z_5^* 拆解后合并
对应教程:第 9 章 高等代数与数论量子算法 中相关小节 · 依赖安装:pip install "unified-quantum[visualization,simulation]"
实现要点¶
模型: N=15 = 3 * 5, a=2, 目标 g=8. Shor DLP 在 N 为合数时, 可拆成在 p_i = 3, 5 上的子 DLP (量子周期查找), 然后用 CRT 拼回 x (mod ord_N(a)). 这里: Z_3^: a=2, ord_3(2)=2, 轨道 1 -> 2 -> 1; 8 mod 3 = 2, 故 x_1 = 1. Z_5^: a=2, ord_5(2)=4, 轨道 1 -> 2 -> 4 -> 3 -> 1; 8 mod 5 = 3, 故 x_2 = 3. CRT (n_1=2, n_2=4): x = 3 满足 x mod 2 = 1, x mod 4 = 3, 与期望 x=3 一致.
量子部分用 1-qubit S/T 门做"简化路径" eigenstate QPE, 对应各子群的阶: ord_3(2) = 2 -> S eigenstate phase 1/4 (对应 r=4 in S gate, 或换 T 适配 r=2) ord_5(2) = 4 -> S eigenstate phase 1/4.
完整源码¶
demos/ch09-algebra-number-theory/03_crt_recovery.py¶
1"""
2ch09 演示 3: 用中国剩余定理 (CRT) 拼接各素数子群上的 Shor 离散对数结果.
3
4模型: N=15 = 3 * 5, a=2, 目标 g=8.
5Shor DLP 在 N 为合数时, 可拆成在 p_i = 3, 5 上的子 DLP (量子周期查找),
6然后用 CRT 拼回 x (mod ord_N(a)). 这里:
7 Z_3^*: a=2, ord_3(2)=2, 轨道 1 -> 2 -> 1; 8 mod 3 = 2, 故 x_1 = 1.
8 Z_5^*: a=2, ord_5(2)=4, 轨道 1 -> 2 -> 4 -> 3 -> 1; 8 mod 5 = 3, 故 x_2 = 3.
9 CRT (n_1=2, n_2=4): x = 3 满足 x mod 2 = 1, x mod 4 = 3, 与期望 x=3 一致.
10
11量子部分用 1-qubit S/T 门做"简化路径" eigenstate QPE, 对应各子群的阶:
12 ord_3(2) = 2 -> S eigenstate phase 1/4 (对应 r=4 in S gate, 或换 T 适配 r=2)
13 ord_5(2) = 4 -> S eigenstate phase 1/4.
14
15对应教程: chenzhaoyun.com/quantum-tutorial/ch09-algebra-number-theory/finite-rings-ideals.html
16依赖: pip install unified-quantum[visualization]
17运行: python demos/ch09-algebra-number-theory/03_crt_recovery.py
18"""
19from uniqc import Circuit, submit_task, wait_for_result
20from uniqc.algorithms.core.circuits import qpe_circuit
21import matplotlib
22matplotlib.use("Agg")
23import matplotlib.pyplot as plt
24import numpy as np
25from pathlib import Path
26
27OUT = Path(__file__).parent
28SHOTS = 2000
29N_PREC = 4
30
31
32def orbit_mult(a, n_mod, start, max_steps=20):
33 seen = {start: 0}
34 state = start
35 cycle = [state]
36 while True:
37 state = (a * state) % n_mod
38 if state in seen:
39 break
40 seen[state] = len(cycle)
41 cycle.append(state)
42 return cycle, len(cycle) - seen[state]
43
44
45def classical_dlog(a, g, n_mod):
46 """返回最小 x 使 a^x ≡ g (mod n_mod), 找不到返回 -1."""
47 for x in range(n_mod):
48 if pow(a, x, n_mod) == g % n_mod:
49 return x
50 return -1
51
52
53def crt_pair(x1, n1, x2, n2):
54 """对两个未必互素的模 n_1, n_2, 解 x ≡ x_1 (mod n_1), x ≡ x_2 (mod n_2).
55 返回 (x, n) 使 x 是该线性同余方程在模 n 下的唯一解.
56 若 x_1, x_2 不一致 (mod gcd(n_1, n_2)) 则返回 None."""
57 from math import gcd
58 g = gcd(n1, n2)
59 if (x1 - x2) % g != 0:
60 return None
61 n = n1 * n2 // g
62 # 用扩展欧几里得合并
63 def egcd(a, b):
64 if b == 0:
65 return (a, 1, 0)
66 d, u, v = egcd(b, a % b)
67 return (d, v, u - (a // b) * v)
68 _, u1, _ = egcd(n1, n2 // g)
69 x = (x1 * (n2 // g) * u1 + x2 * (n1 // g)) % n
70 return x, n
71
72
73# ---- 1. 经典前置 ----
74N = 15
75A = 2
76G = 8 # 目标: 找 x 使 2^x ≡ 8 (mod 15)
77P1, P2 = 3, 5
78
79ord_3 = 2
80ord_5 = 4
81cycle_p1, r_p1 = orbit_mult(A, P1, 1)
82cycle_p2, r_p2 = orbit_mult(A, P2, 1)
83
84g1 = G % P1 # 8 mod 3 = 2
85g2 = G % P2 # 8 mod 5 = 3
86x1 = classical_dlog(A, g1, P1) # log_2(2) mod 3 → 1
87x2 = classical_dlog(A, g2, P2) # log_2(3) mod 5 → 3
88
89crt_res = crt_pair(x1, ord_3, x2, ord_5)
90x_crt, mod_crt = crt_res # x = 3 (mod lcm(2, 4)=4)
91
92# 验证
93x_true = classical_dlog(A, G, N) # log_2(8) mod 15 → 3
94
95
96# ---- 2. QPE on S eigenstate for both subgroups (period proxy) ----
97def run_qpe_phase(unitary, prep, n_prec=N_PREC, shots=SHOTS):
98 c = qpe_circuit(n_precision=n_prec, unitary_circuit=unitary,
99 state_prep=prep, measure=True)
100 tid = submit_task(circuit=c, backend="dummy:local:simulator", shots=shots)
101 res = wait_for_result(tid)
102 counts = res.counts
103 n_states = 2 ** n_prec
104 dist = np.zeros(n_states, dtype=float)
105 for bs, cnt in counts.items():
106 # uniqc qpe bitstring length = n_prec + 1 (末位是未测量 eigenstate),
107 # 末 n_prec 字符直接读为二进制整数.
108 k = int(bs[-n_prec:], 2)
109 dist[k] += cnt
110 dist /= dist.sum()
111 return dist
112
113
114unitary_s = Circuit()
115unitary_s.s(0)
116prep_eigen = Circuit()
117prep_eigen.x(0)
118phase_dist = run_qpe_phase(unitary_s, prep_eigen, n_prec=N_PREC, shots=SHOTS)
119peak_k = int(np.argmax(phase_dist))
120peak_phase = peak_k / (2 ** N_PREC)
121
122
123# ---- 3. matplotlib 可视化: 两个子群轨道 + QPE + CRT 流程 ----
124fig, axes = plt.subplots(1, 3, figsize=(16.5, 4.8))
125
126ax = axes[0]
127labels_p1 = [f"|{x:02b}>" for x in cycle_p1]
128ax.bar(range(len(cycle_p1)), [1.0] * len(cycle_p1), color="#1f77b4", edgecolor="black")
129ax.set_xticks(range(len(cycle_p1)))
130ax.set_xticklabels(labels_p1, fontsize=10)
131for i in range(len(cycle_p1)):
132 ax.text(i, 1.02, f"k={i}", ha="center", va="bottom", fontsize=9, color="dimgray")
133ax.set_ylim(0, 1.3)
134ax.set_xlabel(f"U_a^k |1> in Z_{P1}^* (a={A})")
135ax.set_ylabel("probability")
136ax.set_title(
137 f"Subgroup Z_{P1}^*: order r={r_p1}\n"
138 f"g mod {P1} = {g1}, log_{A}({g1}) = x_{P1} = {x1}"
139)
140ax.grid(axis="y", alpha=0.3)
141
142ax = axes[1]
143labels_p2 = [f"|{x:03b}>" for x in cycle_p2]
144ax.bar(range(len(cycle_p2)), [1.0] * len(cycle_p2), color="#1f77b4", edgecolor="black")
145ax.set_xticks(range(len(cycle_p2)))
146ax.set_xticklabels(labels_p2, fontsize=10)
147for i in range(len(cycle_p2)):
148 ax.text(i, 1.02, f"k={i}\nx={i}", ha="center", va="bottom", fontsize=8, color="dimgray")
149ax.set_ylim(0, 1.4)
150ax.set_xlabel(f"U_a^k |1> in Z_{P2}^* (a={A})")
151ax.set_ylabel("probability")
152ax.set_title(
153 f"Subgroup Z_{P2}^*: order r={r_p2}\n"
154 f"g mod {P2} = {g2}, log_{A}({g2}) = x_{P2} = {x2}"
155)
156ax.grid(axis="y", alpha=0.3)
157
158ax = axes[2]
159xs = np.arange(2 ** N_PREC)
160probs = phase_dist * 100
161bar_colors = ["#d62728" if k in (0, 4, 8, 12) else "#1f77b4" for k in xs]
162ax.bar(xs, probs, color=bar_colors, edgecolor="black")
163ax.set_xticks(xs)
164ax.set_xticklabels([f"{k:04b}" for k in xs], rotation=0, fontsize=7)
165ax.set_xlabel("counting register (k) -> phase = k / 16")
166ax.set_ylabel("probability (%)")
167ax.set_title(
168 f"QPE on S eigenstate |1> (proxy for r=4)\n"
169 f"peak k={peak_k}, phase={peak_phase:.3f} -> r ~ 4 (matches ord_{P2})"
170)
171for k in (0, 4, 8, 12):
172 ax.text(k, probs[k] + 1.5, f"k={k}\nphase={k/(2**N_PREC):.3f}",
173 ha="center", va="bottom", fontsize=7, color="darkred")
174ax.set_ylim(0, max(probs.max() * 1.25, 30))
175ax.grid(axis="y", alpha=0.3)
176
177fig.suptitle(
178 f"CRT recovery of discrete log: N={N}={P1}*{P2}, a={A}, g={G} "
179 f"(QPE shots={SHOTS})",
180 fontsize=12, y=1.02,
181)
182plt.tight_layout()
183plt.savefig(OUT / "03_crt_recovery.png", dpi=120, bbox_inches="tight")
184plt.close()
185
186
187# ---- 4. stdout 关键统计 ----
188print("ch09-algebra-number-theory demo 3: CRT recovery of Shor DLP")
189print(f" N = {N} = {P1} * {P2}, base a = {A}, target g = {G}")
190print(f" subgroup Z_{P1}^*: order r1 = {r_p1}, g mod {P1} = {g1}, x_{P1} = {x1}")
191print(f" subgroup Z_{P2}^*: order r2 = {r_p2}, g mod {P2} = {g2}, x_{P2} = {x2}")
192print(f" CRT (x ≡ {x1} mod {r_p1}, x ≡ {x2} mod {r_p2}): x = {x_crt} (mod {mod_crt})")
193print(f" true x by brute force (mod {r_p1 * r_p2}): {x_true}")
194print(f" agreement (CRT vs classical brute-force): {x_crt == x_true}")
195print(f" verify: {A}^{x_crt} mod {N} = {pow(A, x_crt, N)} (target g = {G})")
196print(f" QPE phase histogram peak: k = {peak_k} (phase = {peak_phase:.4f})")
197print(f" QPE peak probability: {phase_dist[peak_k]*100:.2f}%")
198print(f" saved: {OUT / '03_crt_recovery.png'}")
运行结果¶

运行方式¶
# 仓库根目录执行;输出 PNG 到 demos/ch09-algebra-number-theory/03_crt_recovery.png,关键统计到 stdout
.venv/bin/python3 demos/ch09-algebra-number-theory/03_crt_recovery.py