Demo 2:块编码¶
可视化:3 子图(U_A+A+A/α+测量) —— 2×2 厄米矩阵 A 的 4-qubit 块编码 U_A 验证
对应教程:第 5 章 哈密顿量模拟与 QSP 中相关小节 · 依赖安装:pip install "unified-quantum[visualization,simulation]"
实现要点¶
模型: A = [[1, 0.5], [0.5, 1]] (Hermitian, 对称正定) 操作数范数: alpha = ||A||op = 1.5, 归一化后 A/alpha = [[2/3, 1/3], [1/3, 2/3]] 特征值 (A/alpha): 1, 1/3; 特征向量 (descending): v+ = (1, 1)/sqrt(2), v_- = (1, -1)/sqrt(2) 特征基旋转: U_eig = H (按 descending eigvec 列排列)
uniqc 不支持 add_gate('U', matrix=...) 自定义矩阵入口. 采用「数学构造 + 物理电路 + get_matrix 校验」三步法:
用 numpy 显式构造 4x4 块编码酉阵 U_A = (U_eig ⊗ I_a) · U_D · (U_eig^dag ⊗ I_a), 其中 U_D 是 multiplexed Ry 块编码 diag(cos theta_0, cos theta_1), theta_s = arccos(eigval_s).
用 H (本征基旋转) + CR_y (对角块编码) 分解得到电路.
比对 c.get_matrix() 与理论 U_A; 在模拟器上验证 P(ancilla=0|s) = |(A/alpha)_{s,s}|^2.
完整源码¶
demos/ch05-hamiltonian-qsp/02_block_encoding_hermitian.py¶
1"""
2ch05 演示 2: 2x2 厄米矩阵的块编码 (Block Encoding) 演示.
3
4模型: A = [[1, 0.5], [0.5, 1]] (Hermitian, 对称正定)
5操作数范数: alpha = ||A||_op = 1.5, 归一化后 A/alpha = [[2/3, 1/3], [1/3, 2/3]]
6特征值 (A/alpha): 1, 1/3; 特征向量 (descending): v_+ = (1, 1)/sqrt(2), v_- = (1, -1)/sqrt(2)
7特征基旋转: U_eig = H (按 descending eigvec 列排列)
8
9uniqc 不支持 add_gate('U', matrix=...) 自定义矩阵入口.
10采用「数学构造 + 物理电路 + get_matrix 校验」三步法:
11 1. 用 numpy 显式构造 4x4 块编码酉阵 U_A = (U_eig ⊗ I_a) · U_D · (U_eig^dag ⊗ I_a),
12 其中 U_D 是 multiplexed Ry 块编码 diag(cos theta_0, cos theta_1), theta_s = arccos(eigval_s).
13 2. 用 H (本征基旋转) + CR_y (对角块编码) 分解得到电路.
14 3. 比对 c.get_matrix() 与理论 U_A; 在模拟器上验证 P(ancilla=0|s) = |(A/alpha)_{s,s}|^2.
15
16对应教程: chenzhaoyun.com/quantum-tutorial/ch05-hamiltonian-qsp/block-encoding-tutorial.html
17依赖: pip install unified-quantum[visualization]
18运行: python demos/ch05-hamiltonian-qsp/02_block_encoding_hermitian.py
19"""
20from uniqc import Circuit, submit_task, wait_for_result
21import matplotlib
22matplotlib.use("Agg")
23import matplotlib.pyplot as plt
24import numpy as np
25from pathlib import Path
26
27OUT = Path(__file__).parent
28SHOTS = 4000
29
30A = np.array([[1.0, 0.5], [0.5, 1.0]], dtype=complex)
31ALPHA = float(np.linalg.norm(A, ord=2))
32A_NORM = A / ALPHA
33
34eigvals_N, eigvecs_N = np.linalg.eigh(A_NORM)
35order = np.argsort(eigvals_N)[::-1]
36EIGVALS = eigvals_N[order]
37U_EIG = eigvecs_N[:, order]
38THETA = np.array([float(np.arccos(max(-1.0, min(1.0, v)))) for v in EIGVALS])
39
40N_A = 2
41DIM = 4
42I2 = np.eye(2, dtype=complex)
43
44
45def build_block_encoding_unitary() -> np.ndarray:
46 """Construct 4x4 U_A explicitly via eigenbasis-rotation + multiplexed-Ry.
47
48 Returns a 4x4 matrix matching uniqc's convention: q[0] is the LSB and
49 the inner (right) tensor factor; q[1] is the MSB and the outer (left) factor.
50 The block-encoding upper-left 2x2 block (projecting q[0]=q_a=0) equals A/alpha.
51 """
52 U_D = np.zeros((DIM, DIM), dtype=complex)
53 for s in range(N_A):
54 c, si = np.cos(THETA[s]), np.sin(THETA[s])
55 ry_block = np.array([[c, -si], [si, c]], dtype=complex)
56 base = 2 * s
57 U_D[base:base + 2, base:base + 2] = ry_block
58 left = np.kron(U_EIG, I2)
59 right = np.kron(U_EIG.conj().T, I2)
60 return left @ U_D @ right
61
62
63U_A_theory = build_block_encoding_unitary()
64upper_left_theory = U_A_theory[::2, ::2]
65
66
67def build_block_encoding_circuit() -> Circuit:
68 """Eigenbasis rotation H + multiplexed-Ry on q_a=LSB controlled by q_s=MSB.
69
70 Note: c.h(1) is the Hadamard gate (different from c.ry(1, pi/2) which is R_y(pi/2)).
71 """
72 c = Circuit()
73 c.h(1)
74 c.ry(0, 2 * THETA[0])
75 c.ry(0, THETA[1] - THETA[0])
76 c.cnot(1, 0)
77 c.ry(0, -(THETA[1] - THETA[0]))
78 c.cnot(1, 0)
79 c.h(1)
80 return c
81
82
83c_block = build_block_encoding_circuit()
84U_A_circuit = c_block.get_matrix()
85upper_left_circuit = U_A_circuit[::2, ::2]
86match_err = float(np.linalg.norm(U_A_circuit - U_A_theory, ord="fro"))
87be_err = float(np.linalg.norm(upper_left_circuit - A_NORM, ord="fro"))
88
89
90def measure_block_encoding(input_q_s: int) -> dict:
91 """Run block-encoding circuit initialized with |input_q_s⟩ and return
92 joint probability table over (q_s, q_a). LE bitstring 'ab' has a=q_s, b=q_a.
93 """
94 c = build_block_encoding_circuit()
95 if input_q_s == 1:
96 c.x(1)
97 c.measure(0)
98 c.measure(1)
99 res = wait_for_result(submit_task(circuit=c, backend="dummy:local:simulator", shots=SHOTS))
100 counts = res.counts
101 joint = {(s, a): 0.0 for s in range(2) for a in range(2)}
102 for bs, cnt in counts.items():
103 q_s_val = int(bs[0])
104 q_a_val = int(bs[1])
105 joint[(q_s_val, q_a_val)] = cnt / SHOTS
106 return joint
107
108
109joint_s0 = measure_block_encoding(0)
110joint_s1 = measure_block_encoding(1)
111p0_s0 = joint_s0[(0, 0)] + joint_s0[(1, 0)]
112p0_s1 = joint_s1[(0, 0)] + joint_s1[(1, 0)]
113theory_p0_s0_row = [float(abs(A_NORM[s, 0]) ** 2) for s in range(2)]
114theory_p0_s1_row = [float(abs(A_NORM[s, 1]) ** 2) for s in range(2)]
115
116
117fig, axes = plt.subplots(1, 3, figsize=(16.0, 5.2))
118
119ax = axes[0]
120vmax = float(np.max(np.abs(U_A_theory)))
121im = ax.imshow(np.abs(U_A_theory), cmap="viridis", vmin=0, vmax=vmax)
122ax.set_title(f"Block-encoding U_A (4x4)\nupper-left 2x2 = A/alpha (alpha = {ALPHA:.3f})")
123ax.set_xticks(range(4))
124ax.set_yticks(range(4))
125labels_idx = ["|00>", "|01>", "|10>", "|11>"]
126ax.set_xticklabels(labels_idx, fontsize=10)
127ax.set_yticklabels(labels_idx, fontsize=10)
128ax.set_xlabel("col index (LE: q_a q_s)")
129ax.set_ylabel("row index (LE: q_a q_s)")
130for i in range(4):
131 for j in range(4):
132 v = U_A_theory[i, j]
133 if abs(v) > 0.05:
134 ax.text(j, i, f"{abs(v):.2f}", ha="center", va="center",
135 fontsize=8, color="white" if abs(v) > 0.4 else "black")
136plt.colorbar(im, ax=ax, fraction=0.046, pad=0.04)
137
138ax = axes[1]
139panels = [
140 ("A\n(original 2x2)", np.abs(A)),
141 ("A/alpha\n(normalized)", np.abs(A_NORM)),
142 ("circuit block\n(q_a=0)", np.abs(upper_left_circuit)),
143 ("theory block\n(q_a=0)", np.abs(upper_left_theory)),
144]
145combined = np.zeros((2, 2 * len(panels)), dtype=float)
146for k, (_, mat) in enumerate(panels):
147 combined[:, 2 * k:2 * (k + 1)] = mat
148im2 = ax.imshow(combined, cmap="Blues", vmin=0, vmax=1, aspect="auto")
149ax.set_title("2x2 blocks compared (right two should match A/alpha)")
150ax.set_yticks([0, 1])
151ax.set_yticklabels(["row 0", "row 1"], fontsize=9)
152ax.set_xticks([0.5 + 2 * k for k in range(len(panels))])
153ax.set_xticklabels([p[0] for p in panels], fontsize=8)
154for k, (_, mat) in enumerate(panels):
155 for i in range(2):
156 for j in range(2):
157 v = mat[i, j]
158 if v > 0.01:
159 ax.text(2 * k + j, i, f"{v:.3f}", ha="center", va="center",
160 fontsize=8, color="white" if v > 0.5 else "black")
161plt.colorbar(im2, ax=ax, fraction=0.046, pad=0.04)
162
163ax = axes[2]
164system_states = ["|0_s>", "|1_s>"]
165x = np.arange(2)
166w = 0.18
167for j, q_s_target in enumerate([0, 1]):
168 measured_anc0 = [joint_s0[(q_s_target, 0)], joint_s1[(q_s_target, 0)]]
169 theory_anc0 = [theory_p0_s0_row[q_s_target], theory_p0_s1_row[q_s_target]]
170 offset = (j - 0.5) * w
171 ax.bar(x + offset, measured_anc0, width=w * 0.9,
172 color="#1f77b4" if j == 0 else "#aec7e8",
173 edgecolor="black", label=f"measured q_s={q_s_target}")
174 ax.bar(x + offset + w, theory_anc0, width=w * 0.9,
175 color="#d62728" if j == 0 else "#ff9896",
176 edgecolor="black", label=f"theory |(A/alpha)[:, {q_s_target}]|^2" if j == 0 else None)
177 for i, (pm, pt) in enumerate(zip(measured_anc0, theory_anc0)):
178 ax.text(x[i] + offset, pm + 0.012, f"{pm:.3f}", ha="center", va="bottom",
179 fontsize=8, color="darkblue")
180 ax.text(x[i] + offset + w, pt + 0.012, f"{pt:.3f}", ha="center", va="bottom",
181 fontsize=8, color="darkred")
182ax.set_xticks(x)
183ax.set_xticklabels(system_states)
184ax.set_xlabel("input system state |s_s>")
185ax.set_ylabel("P(ancilla = 0 AND q_s = s')")
186ax.set_title(
187 "Block-encoding: P(anc=0, q_s=s' | sys=s) should equal |(A/alpha)_{s', s}|^2\n"
188 "(two columns per system: q_s=0 outcome first, then q_s=1)"
189)
190ax.set_ylim(0, 0.55)
191ax.legend(loc="upper right", fontsize=7, ncol=2)
192ax.grid(axis="y", alpha=0.3)
193
194fig.suptitle(
195 f"Block encoding of A = [[1, 0.5], [0.5, 1]] "
196 f"(alpha = {ALPHA:.3f}, theta = [{THETA[0]:.3f}, {THETA[1]:.3f}])",
197 fontsize=13, y=1.02,
198)
199plt.tight_layout()
200plt.savefig(OUT / "02_block_encoding_hermitian.png", dpi=120, bbox_inches="tight")
201plt.close()
202
203
204unit_err = float(np.linalg.norm(U_A_circuit @ U_A_circuit.conj().T - np.eye(DIM)))
205print("ch05-hamiltonian-qsp demo 2: Block encoding of 2x2 Hermitian")
206print(f" A = [[1, 0.5], [0.5, 1]], alpha = ||A||_op = {ALPHA:.6f}")
207print(f" A/alpha =\n {A_NORM}")
208print(f" normalized eigenvalues (descending): {EIGVALS.tolist()}")
209print(f" theta_0 = {THETA[0]:.6f}, theta_1 = {THETA[1]:.6f}")
210print(f" ||circuit U_A - theory U_A||_F = {match_err:.2e}")
211print(f" ||circuit upper-left block - A/alpha||_F = {be_err:.2e}")
212print(f" ||U_A U_A^dag - I||_F (unitarity) = {unit_err:.2e}")
213print(f" measured P(anc=0, q_s=0 | sys=0_s) = {joint_s0[(0, 0)]:.4f} (theory |A/alpha[0,0]|^2 = {theory_p0_s0_row[0]:.4f})")
214print(f" measured P(anc=0, q_s=1 | sys=0_s) = {joint_s0[(1, 0)]:.4f} (theory |A/alpha[1,0]|^2 = {theory_p0_s0_row[1]:.4f})")
215print(f" measured P(anc=0, q_s=0 | sys=1_s) = {joint_s1[(0, 0)]:.4f} (theory |A/alpha[0,1]|^2 = {theory_p0_s1_row[0]:.4f})")
216print(f" measured P(anc=0, q_s=1 | sys=1_s) = {joint_s1[(1, 0)]:.4f} (theory |A/alpha[1,1]|^2 = {theory_p0_s1_row[1]:.4f})")
217print(f" saved: {OUT / '02_block_encoding_hermitian.png'}")
运行结果¶

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