Demo 1:量子 PCA¶
可视化:单子图 —— 4×4 块编码+QPE 估前 2 特征值
对应教程:第 15 章 梯度、谱方法与量子机器学习 中相关小节 · 依赖安装:pip install "unified-quantum[visualization,simulation]"
实现要点¶
A = diag(7/8, 5/8, 3/8, 1/8); T_total = 2π ⇒ phase = λ, buckets hit midpoints exactly. uniqc 的 crz/cp/toffoli 均为 (target, control) 顺序;整体相位 φ_avg 不进测量,故先 strip。
完整源码¶
demos/ch15-learning-spectral/01_quantum_pca.py¶
1"""
2ch15 demo 1: quantum PCA via block-encoding + 2-bit QPE on 4x4 diagonal A.
3
4对应教程:chenzhaoyun.com/quantum-tutorial/ch15-learning-spectral/tensor-pca.html
5A = diag(7/8, 5/8, 3/8, 1/8); T_total = 2π ⇒ phase = λ, buckets hit midpoints exactly.
6uniqc 的 crz/cp/toffoli 均为 (target, control) 顺序;整体相位 φ_avg 不进测量,故先 strip。
7依赖:pip install unified-quantum[visualization]
8"""
9from __future__ import annotations
10
11from pathlib import Path
12
13import matplotlib
14matplotlib.use("Agg")
15import matplotlib.pyplot as plt
16import numpy as np
17
18from uniqc import Circuit, submit_task, wait_for_result
19
20OUT = Path(__file__).parent
21SHOTS = 2000
22
23# === A: 4x4 diagonal Hermitian ===
24# Eigenvalues chosen so that phase = λ lands exactly on 2-bit QPE bucket midpoints.
25# With T_total = 2π, bucket midpoints at (m+0.5)/4 for m=0..3 → λ ∈ {1/8, 3/8, 5/8, 7/8}.
26EIGS = np.array([7 / 8, 5 / 8, 3 / 8, 1 / 8])
27A = np.diag(EIGS)
28N_ANC = 2
29N_SYS = 2
30T = 2 * np.pi / 3 # 1+2 = 3 controlled-U applications, T_total = 3T = 2π
31N_TOTAL = N_ANC + N_SYS
32
33
34def diag_unitary_angles(phases):
35 """Decompose 2-qubit diagonal U = diag(exp(iφ_kk)) as e^{iα·Z⊗Z} · e^{iβ·(Z⊗I)} · e^{iγ·(I⊗Z)}.
36 Strip global phase φ_avg = Σφ/4 first; missing e^{iφ_avg} is a global phase.
37 """
38 phi00, phi01, phi10, phi11 = phases
39 avg = (phi00 + phi01 + phi10 + phi11) / 4.0
40 p00, p01, p10 = phi00 - avg, phi01 - avg, phi10 - avg
41 return (-(p01 + p10) / 2.0, (p00 + p01) / 2.0, (p00 + p10) / 2.0)
42
43
44def controlled_diag_unitary(c, anc, qs0, qs1, phases):
45 """Append controlled-U on (qs0, qs1) controlled by anc. uniqc crz uses (target, control)."""
46 alpha, beta, gamma = diag_unitary_angles(phases)
47 two_beta = float((2 * beta) % (2 * np.pi))
48 if abs(two_beta) > 1e-9:
49 c.crz(qs0, anc, two_beta)
50 two_gamma = float((2 * gamma) % (2 * np.pi))
51 if abs(two_gamma) > 1e-9:
52 c.crz(qs1, anc, two_gamma)
53 c.toffoli(anc, qs0, qs1)
54 two_alpha = float((2 * alpha) % (2 * np.pi))
55 if abs(two_alpha) > 1e-9:
56 c.crz(qs1, anc, two_alpha)
57 c.toffoli(anc, qs0, qs1)
58
59
60def build_qpe_circuit(eigs, t_param):
61 """Build N_ANC-bit QPE circuit for A = diag(eigs) on N_SYS system qubits."""
62 n_total = N_ANC + N_SYS
63 phases = list(eigs * t_param)
64 c = Circuit()
65 # H on all qubits: ancilla in |+⟩^N_ANC, system in |+⟩^N_SYS
66 for q in range(n_total):
67 c.h(q)
68 c.barrier()
69 # Controlled-U^(2^k) for k = 0, ..., N_ANC-1 (total evolution = 2^N_ANC - 1 calls)
70 for k in range(N_ANC):
71 anc = k
72 qs0, qs1 = N_ANC, N_ANC + 1
73 for _ in range(2 ** k):
74 controlled_diag_unitary(c, anc, qs0, qs1, phases)
75 c.barrier()
76 # Inverse QFT on ancilla (q0, ..., q_{N_ANC-1})
77 if N_ANC > 1:
78 c.swap(0, N_ANC - 1)
79 for j in range(N_ANC - 1, -1, -1):
80 c.h(j)
81 for m in range(j):
82 phase = -np.pi / (2 ** (j - m))
83 c.cp(m, j, phase)
84 c.barrier()
85 for q in range(n_total):
86 c.measure(q)
87 return c
88
89
90# === Build & submit ===
91print("ch15-learning-spectral demo 1: quantum PCA via block-encoding + QPE")
92print(f" A diagonal eigenvalues: {EIGS.tolist()}")
93T_total = (2 ** N_ANC - 1) * T
94print(f" evolution T = {T:.4f}, T_total = {T_total:.4f} = 2π (no phase wrap)")
95print(f" QPE ancilla = {N_ANC} bits, system = {N_SYS} qubits")
96print(f" total shots = {SHOTS}")
97
98c = build_qpe_circuit(EIGS, T)
99print(f" circuit depth = {c.depth}")
100
101result = wait_for_result(submit_task(circuit=c, backend="dummy:local:simulator", shots=SHOTS))
102counts = result.counts
103total = sum(max(0, v) for v in counts.values())
104
105# Aggregate ancilla buckets (top N_ANC chars of (N_ANC+N_SYS)-qubit bitstring, MSB-first)
106anc_counts = {}
107for bs, cnt in counts.items():
108 if cnt <= 0:
109 continue
110 anc_bs = bs[:N_ANC]
111 anc_counts[anc_bs] = anc_counts.get(anc_bs, 0) + cnt
112
113
114def bucket_to_eig(b):
115 """Recover λ from QPE bucket b: phase = (b+0.5)/2^N, λ = phase · 2π / T_total."""
116 phase = (b + 0.5) / (2 ** N_ANC)
117 eig = phase * 2 * np.pi / T_total
118 return ((eig + 1.0) % 2.0) - 1.0
119
120
121print(f" total shots: {total}")
122print(f" QPE bucket histogram:")
123quantum_eigs = []
124for k in range(2 ** N_ANC):
125 bs = format(k, f"0{N_ANC}b")
126 cnt = anc_counts.get(bs, 0)
127 eig = bucket_to_eig(k)
128 quantum_eigs.append(eig)
129 print(f" bucket {k} (anc={bs}): {cnt:5d} shots ({cnt / max(total, 1) * 100:5.1f}%) -> λ≈{eig:+.4f}")
130
131print(f" classical eigenvalues: {EIGS.tolist()}")
132print(f" quantum eigenvalues: {[round(e, 4) for e in quantum_eigs]}")
133
134# === matplotlib 可视化 ===
135fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12.5, 5.0))
136
137# Left: matrix A heatmap
138im = ax1.imshow(A, cmap="RdBu_r", vmin=0, vmax=1)
139for i in range(4):
140 for j in range(4):
141 ax1.text(j, i, f"{A[i, j]:.3f}", ha="center", va="center",
142 color="white" if A[i, j] > 0.5 else "black", fontsize=9)
143ax1.set_xticks(range(4))
144ax1.set_yticks(range(4))
145ax1.set_xticklabels([f"|{i:02b}⟩" for i in range(4)])
146ax1.set_yticklabels([f"|{i:02b}⟩" for i in range(4)])
147ax1.set_title("Hermitian A (4x4 diagonal)")
148plt.colorbar(im, ax=ax1, shrink=0.7)
149
150# Right: classical vs quantum eigenvalues
151x = np.arange(4)
152classical_sorted = np.sort(EIGS)[::-1]
153quantum_sorted = sorted(quantum_eigs, reverse=True)
154ax2.bar(x - 0.2, classical_sorted, 0.4, color="#1f77b4", ec="black", label="classical (numpy)")
155ax2.bar(x + 0.2, quantum_sorted, 0.4, color="#ff7f0e", ec="black", label="quantum (QPE)")
156for i in range(4):
157 ax2.text(i - 0.2, classical_sorted[i] + 0.025, f"{classical_sorted[i]:.3f}",
158 ha="center", fontsize=8)
159 ax2.text(i + 0.2, quantum_sorted[i] + 0.025, f"{quantum_sorted[i]:.3f}",
160 ha="center", fontsize=8)
161ax2.set_xticks(x)
162ax2.set_xticklabels([f"$\\lambda_{i + 1}$" for i in range(4)])
163ax2.set_ylabel("eigenvalue")
164ax2.axhline(0, color="gray", ls="--", lw=0.7)
165ax2.set_title("classical vs quantum eigenvalues")
166ax2.set_ylim(-0.05, 1.05)
167ax2.legend(loc="upper right")
168ax2.grid(alpha=0.3, axis="y")
169
170plt.suptitle("ch15 demo 1: quantum PCA via block-encoding + QPE", y=1.02)
171plt.tight_layout()
172out_path = OUT / "01_quantum_pca.png"
173plt.savefig(out_path, dpi=120, bbox_inches="tight")
174plt.close()
175
176# === stdout key stats ===
177qarr = np.array(quantum_eigs)
178diffs = []
179for true_e in EIGS:
180 nearest = qarr[np.argmin(np.abs(qarr - true_e))]
181 diffs.append(abs(nearest - true_e))
182max_err = float(max(diffs)) if diffs else 0.0
183
184print()
185print(f" classical eigenvalues: {np.round(EIGS, 4).tolist()}")
186print(f" quantum eigenvalues: {[round(e, 4) for e in quantum_eigs]}")
187print(f" max |quantum - classical|: {max_err:.4f}")
188print(f" all 4 buckets resolved: {sum(1 for k in range(2 ** N_ANC) if anc_counts.get(format(k, f'0{N_ANC}b'), 0) > 0) == 4}")
189print(f" PNG -> {out_path}")
运行结果¶

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