Demo 1:量子 PCA

第 15 章 梯度、谱方法与量子机器学习 · 本章演示 · 代码演示总索引

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

运行结果

Demo 1:量子 PCA

运行方式

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

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