Demo 2:主特征向量¶
可视化:双图 —— 经典 power iter + 量子态制备
对应教程:第 15 章 梯度、谱方法与量子机器学习 中相关小节 · 依赖安装:pip install "unified-quantum[visualization,simulation]"
实现要点¶
核心: A 是 path graph P_4 的 adjacency matrix,归一化到 ||A||=1,主特征值 λ_max = 1.000。 经典 power iteration: x_t = A^t x_0 / ||...||,从 x_0 = (|00⟩+|01⟩)/√2 出发, Rayleigh 商 R(x_t) = ⟨x_t|A|x_t⟩ 单调收敛到 λ_max,|⟨v_1|x_t⟩|² 也 → 1。 量子端: 把收敛态 |v_1⟩ 用 5 个量子门 (H + 2·CNOT + RY + CNOT) 制备,再用 uniqc 测量 4 个基矢上的概率,验证与 numpy 算出的 v_1² 一致。这个 5-gate 分解依赖于 v_1 的对称性 (a=d, b=c),所以中间连续两次 CNOT 等价于 swap-and-restore, 最后一个 CNOT 再 swap 一次即可得到目标。 uniqc 坑: get_matrix 不能在 measure 之后调用,所以验证在 measure 之前做。
完整源码¶
demos/ch15-learning-spectral/02_principal_eigenvector.py¶
1"""
2ch15 demo 2: principal eigenvector via (quantum) power iteration on path graph P_4
3
4对应教程:chenzhaoyun.com/quantum-tutorial/ch15-learning-spectral/principal-eigenvector.html
5核心: A 是 path graph P_4 的 adjacency matrix,归一化到 ||A||=1,主特征值 λ_max = 1.000。
6 经典 power iteration: x_t = A^t x_0 / ||...||,从 x_0 = (|00⟩+|01⟩)/√2 出发,
7 Rayleigh 商 R(x_t) = ⟨x_t|A|x_t⟩ 单调收敛到 λ_max,|⟨v_1|x_t⟩|² 也 → 1。
8 量子端: 把收敛态 |v_1⟩ 用 5 个量子门 (H + 2·CNOT + RY + CNOT) 制备,再用 uniqc
9 测量 4 个基矢上的概率,验证与 numpy 算出的 v_1² 一致。这个 5-gate 分解依赖于
10 v_1 的对称性 (a=d, b=c),所以中间连续两次 CNOT 等价于 swap-and-restore,
11 最后一个 CNOT 再 swap 一次即可得到目标。
12 uniqc 坑: get_matrix 不能在 measure 之后调用,所以验证在 measure 之前做。
13
14依赖:pip install unified-quantum[visualization]
15运行:python demos/ch15-learning-spectral/02_principal_eigenvector.py
16"""
17from __future__ import annotations
18
19from pathlib import Path
20
21import matplotlib
22matplotlib.use("Agg")
23import matplotlib.pyplot as plt
24import numpy as np
25
26from uniqc import Circuit, submit_task, wait_for_result
27
28OUT = Path(__file__).parent
29SHOTS = 1500
30
31# === A: path graph P_4 adjacency + 0.5·I, normalized to spectral norm = 1 ===
32# Adding 0.5·I shifts eigenvalues from {±1.618, ±0.618}/1.618 → {2.118, 1.118, -0.118, -1.118}/2.118
33# = {1.0, 0.528, -0.056, -0.528}. λ_max is now unique and = 1, so power iteration
34# converges cleanly to v_1 (no oscillation between λ_0 and λ_3 as in the bare path graph).
35A_raw = np.array([
36 [0.5, 1.0, 0.0, 0.0],
37 [1.0, 0.5, 1.0, 0.0],
38 [0.0, 1.0, 0.5, 1.0],
39 [0.0, 0.0, 1.0, 0.5],
40])
41LAM_MAX_RAW = float(np.max(np.linalg.eigvalsh(A_raw)))
42A = A_raw / LAM_MAX_RAW
43LAM_MAX_NORM = 1.0
44
45eigvals_full, eigvecs_full = np.linalg.eigh(A)
46eigvals_sorted = eigvals_full[::-1]
47V1 = eigvecs_full[:, -1].copy()
48if V1[0] < 0:
49 V1 = -V1
50
51print("ch15-learning-spectral demo 2: principal eigenvector via power iteration")
52print(f" raw eigenvalues (path graph): {np.round(np.sort(np.linalg.eigvalsh(A_raw))[::-1], 4).tolist()}")
53print(f" normalized eigenvalues: {np.round(eigvals_sorted, 4).tolist()}")
54print(f" λ_max (normalized) = {LAM_MAX_NORM:.4f}")
55print(f" v_1 = {np.round(V1, 4).tolist()}")
56
57# === Classical power iteration from x_0 = (|00⟩ + |01⟩)/√2 ===
58N_ITER = 10
59x = np.array([1.0, 1.0, 0.0, 0.0], dtype=float) / np.sqrt(2.0)
60rayleigh = []
61overlap_v1 = []
62for t in range(N_ITER + 1):
63 norm_sq = float(np.dot(x, x))
64 r = float(x @ A @ x) / norm_sq if norm_sq > 1e-12 else 0.0
65 rayleigh.append(r)
66 x_norm = x / np.sqrt(norm_sq) if norm_sq > 1e-12 else x
67 overlap_v1.append(float(np.dot(V1, x_norm) ** 2))
68 x = A @ x
69 n = np.linalg.norm(x)
70 if n < 1e-12:
71 x = np.array([1.0, 1.0, 0.0, 0.0]) / np.sqrt(2.0)
72 else:
73 x = x / n
74
75print(f" Rayleigh R(x_t) over {N_ITER + 1} iterations:")
76for t, (r, ov) in enumerate(zip(rayleigh, overlap_v1)):
77 print(f" t={t:2d}: R = {r:+.4f} |⟨v_1|x_t⟩|² = {ov:.4f}")
78
79# === Quantum: prepare |v_1⟩ via 5-gate decomposition and measure ===
80# Use the symmetric structure: a=d, b=c, so 5-gate H+CNOT+CNOT+RY(α_1)+CNOT works
81a, b, c, d = V1
82alpha_1 = 2.0 * np.arctan2(b, a) # RY on q1 to make q1 in (a|0⟩+b|1⟩)/√(a²+b²)
83print(f" state-prep RY angle: α_1 = {alpha_1:.4f} (≈ 2·atan({b:.4f}/{a:.4f}))")
84
85c_q = Circuit()
86c_q.h(0)
87c_q.cnot(0, 1)
88c_q.cnot(0, 1)
89c_q.ry(1, alpha_1)
90c_q.cnot(0, 1)
91c_q.barrier()
92c_q.measure(0)
93c_q.measure(1)
94
95# Verify state prep before measure
96c_check = Circuit()
97c_check.h(0)
98c_check.cnot(0, 1)
99c_check.cnot(0, 1)
100c_check.ry(1, alpha_1)
101c_check.cnot(0, 1)
102ref = np.array(c_check.get_matrix())
103prepared = ref[:, 0]
104err_prep = float(np.max(np.abs(np.abs(prepared) - V1)))
105print(f" state-prep max amplitude error: {err_prep:.4f}")
106print(f" circuit depth (with measure) = {c_q.depth}")
107
108result = wait_for_result(submit_task(circuit=c_q, backend="dummy:local:simulator", shots=SHOTS))
109counts = result.counts
110total_shots = sum(max(0, v) for v in counts.values())
111
112print(f" measurement of |v_1⟩ ({total_shots} shots):")
113quantum_probs = {}
114for k in range(4):
115 bs = format(k, "02b")
116 cnt = counts.get(bs, 0)
117 p = cnt / max(total_shots, 1)
118 quantum_probs[k] = p
119 print(f" |{bs}⟩: {cnt:5d} shots p = {p:.3f} (classical |v_1[{k}]|² = {V1[k] ** 2:.3f})")
120
121# === matplotlib 可视化 ===
122fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12.5, 5.0))
123
124# Left: convergence curves
125t_arr = np.arange(N_ITER + 1)
126ax1.plot(t_arr, rayleigh, "-o", color="#1f77b4", lw=2.2, ms=7,
127 label=r"Rayleigh $R(x_t)$")
128ax1.axhline(LAM_MAX_NORM, color="#d62728", ls="--", lw=1.5,
129 label=r"$\lambda_{\max}=1$")
130ax1.plot(t_arr, overlap_v1, "-s", color="#2ca02c", lw=1.8, ms=6,
131 label=r"$|\langle v_1|x_t\rangle|^2$")
132ax1.set_xticks(t_arr)
133ax1.set_xlabel("iteration t")
134ax1.set_ylabel("value")
135ax1.set_ylim(0, 1.15)
136ax1.set_title("power iteration on path graph P_4")
137ax1.legend(loc="center right", fontsize=9)
138ax1.grid(alpha=0.3)
139
140# Right: |v_1⟩ verification (classical vs quantum)
141x_idx = np.arange(4)
142classical_v1_sq = V1 ** 2
143quantum_v1_sq = np.array([quantum_probs[k] for k in range(4)])
144ax2.bar(x_idx - 0.2, classical_v1_sq, 0.4, color="#1f77b4", ec="black",
145 label=r"classical $|v_1[k]|^2$")
146ax2.bar(x_idx + 0.2, quantum_v1_sq, 0.4, color="#ff7f0e", ec="black",
147 label=r"quantum $P(|k\rangle)$")
148for i in range(4):
149 ax2.text(i - 0.2, classical_v1_sq[i] + 0.015, f"{classical_v1_sq[i]:.3f}",
150 ha="center", fontsize=8)
151 ax2.text(i + 0.2, quantum_v1_sq[i] + 0.015, f"{quantum_v1_sq[i]:.3f}",
152 ha="center", fontsize=8)
153ax2.set_xticks(x_idx)
154ax2.set_xticklabels([f"|{k:02b}⟩" for k in range(4)])
155ax2.set_ylabel("probability")
156ax2.set_title(r"principal eigenvector $|v_1\rangle$ state verification")
157ax2.set_ylim(0, 0.5)
158ax2.legend(loc="upper right", fontsize=9)
159ax2.grid(alpha=0.3, axis="y")
160
161plt.suptitle("ch15 demo 2: principal eigenvector via power iteration", y=1.02)
162plt.tight_layout()
163out_path = OUT / "02_principal_eigenvector.png"
164plt.savefig(out_path, dpi=120, bbox_inches="tight")
165plt.close()
166
167print()
168print(f" R(x_0) = {rayleigh[0]:.4f}, R(x_{N_ITER}) = {rayleigh[-1]:.4f}")
169print(f" |⟨v_1|x_0⟩|² = {overlap_v1[0]:.4f}, |⟨v_1|x_{N_ITER}⟩|² = {overlap_v1[-1]:.4f}")
170print(f" state-prep max error: {err_prep:.4f}")
171print(f" PNG -> {out_path}")
运行结果¶

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