Demo 1:VQE H₂

第 8 章 量子机器学习 · 本章演示 · 代码演示总索引

可视化:双子图(收敛+vs FCI) —— UCCSD + COBYLA 估 H₂ 基态 ≈ -2.016 Ha

对应教程:第 8 章 量子机器学习 中相关小节 · 依赖安装:pip install "unified-quantum[visualization,simulation]"

实现要点

核心:用 2-比特 Bravyi-Kitaev 映射的 H2 哈密顿量 H = -1.0523732 I + 0.3979374 Z0 - 0.3979374 Z1 + 0.08972914 Z0Z1 + 0.1809313 X0X1 + 0.1809313 Y0Y1 FCI 由 numpy.linalg.eigh 解 H 矩阵直接给出(≈ -2.0164 Ha)。 配合 4 参数硬件高效拟设 U(θ) = [Ry(θ2) ⊗ Ry(θ3)] · CNOT · [Ry(θ0) ⊗ Ry(θ1)] 在 scipy.optimize (COBYLA) 下做经典变分优化, 每步分三个测量基(Z、Y、X)估计 5 条泡利字符串, 绘制能量收敛曲线 + 末态能量 vs FCI 对比柱图。

完整源码

demos/ch08-qml/01_vqe_h2.py
  1"""
  2ch08-qml demo 1: VQE for the H2 molecule ground state
  3
  4对应教程:chenzhaoyun.com/quantum-tutorial/ch08-qml/vqe-tutorial.html
  5核心:用 2-比特 Bravyi-Kitaev 映射的 H2 哈密顿量
  6        H = -1.0523732 I + 0.3979374 Z0 - 0.3979374 Z1
  7            + 0.08972914 Z0Z1 + 0.1809313 X0X1 + 0.1809313 Y0Y1
  8     FCI 由 numpy.linalg.eigh 解 H 矩阵直接给出(≈ -2.0164 Ha)。
  9     配合 4 参数硬件高效拟设
 10        U(θ) = [Ry(θ2) ⊗ Ry(θ3)] · CNOT · [Ry(θ0) ⊗ Ry(θ1)]
 11     在 scipy.optimize (COBYLA) 下做经典变分优化,
 12     每步分三个测量基(Z、Y、X)估计 5 条泡利字符串,
 13     绘制能量收敛曲线 + 末态能量 vs FCI 对比柱图。
 14
 15依赖:pip install unified-quantum[visualization] scipy
 16运行:python demos/ch08-qml/01_vqe_h2.py
 17"""
 18from __future__ import annotations
 19
 20from pathlib import Path
 21
 22import matplotlib
 23matplotlib.use("Agg")
 24import matplotlib.pyplot as plt
 25import numpy as np
 26
 27from scipy.optimize import minimize
 28
 29from uniqc import (
 30    Circuit, submit_task, wait_for_result,
 31    calculate_expectation,
 32)
 33
 34OUT = Path(__file__).parent
 35
 36# H2 / STO-3G in the Bravyi-Kitaev 2-qubit mapping
 37# Reference: O'Malley et al. (2016), qiskit nature tutorials.
 38H_CONST, C_Z0, C_Z1, C_Z0Z1, C_XX, C_YY = (
 39    -1.0523732, 0.3979374, -0.3979374, 0.08972914, 0.1809313, 0.1809313,
 40)
 41I2 = np.eye(2, dtype=complex); Z = np.array([[1, 0], [0, -1]], dtype=complex)
 42X = np.array([[0, 1], [1, 0]], dtype=complex); Y = np.array([[0, -1j], [1j, 0]], dtype=complex)
 43H_MAT = (
 44    H_CONST * np.kron(I2, I2) + C_Z0 * np.kron(Z, I2) + C_Z1 * np.kron(I2, Z)
 45    + C_Z0Z1 * np.kron(Z, Z) + C_XX * np.kron(X, X) + C_YY * np.kron(Y, Y)
 46)
 47H_FCI = float(np.linalg.eigvalsh(H_MAT)[0].real)
 48
 49SHOTS = 600
 50
 51
 52def ansatz(theta0: float, theta1: float, theta2: float, theta3: float) -> Circuit:
 53    """2-layer hardware-efficient ansatz: 4 free parameters."""
 54    c = Circuit()
 55    c.ry(0, float(theta0))
 56    c.ry(1, float(theta1))
 57    c.cnot(0, 1)
 58    c.ry(0, float(theta2))
 59    c.ry(1, float(theta3))
 60    return c
 61
 62
 63def measure_in_basis(circuit: Circuit, basis: str) -> dict:
 64    """Clone the state-prep circuit, append basis rotation, measure."""
 65    c = Circuit()
 66    c.add_circuit(circuit)
 67    if basis == "Z":
 68        pass  # measure directly
 69    elif basis == "X":
 70        c.h(0); c.h(1)
 71    elif basis == "Y":
 72        c.sdg(0); c.h(0)
 73        c.sdg(1); c.h(1)
 74    else:
 75        raise ValueError(basis)
 76    c.measure(0)
 77    c.measure(1)
 78    r = wait_for_result(submit_task(circuit=c, backend="dummy:local:simulator", shots=SHOTS))
 79    return r.probabilities
 80
 81
 82def energy_of(theta) -> float:
 83    """Estimate ⟨ψ(θ)|H|ψ(θ)⟩ over the 5 non-constant Pauli strings."""
 84    state_prep = ansatz(*theta)
 85
 86    z_probs = measure_in_basis(state_prep, "Z")
 87    x_probs = measure_in_basis(state_prep, "X")
 88    y_probs = measure_in_basis(state_prep, "Y")
 89    e_z0   = calculate_expectation(z_probs, "ZI")
 90    e_z1   = calculate_expectation(z_probs, "IZ")
 91    e_z0z1 = calculate_expectation(z_probs, "ZZ")
 92    e_xx   = calculate_expectation(x_probs, "ZZ")  # X⊗X after H⊗H
 93    e_yy   = calculate_expectation(y_probs, "ZZ")  # Y⊗Y after (S†H)⊗(S†H)
 94    return (
 95        H_CONST
 96        + C_Z0 * e_z0
 97        + C_Z1 * e_z1
 98        + C_Z0Z1 * e_z0z1
 99        + C_XX * e_xx
100        + C_YY * e_yy
101    )
102
103
104# === 1. warm-up: evaluate energy at a few initial points to scope the search ===
105print("ch08-qml-demo-1: VQE on H2 (STO-3G, BK 2-qubit reduction)")
106print(f"  FCI reference energy: {H_FCI:.6f} Hartree")
107print(f"  shots per basis measurement: {SHOTS}")
108
109# === 2. COBYLA optimization with a callback recording the trajectory ===
110trajectory: list[tuple[int, float, np.ndarray]] = []
111
112
113def cb(xk: np.ndarray) -> None:
114    e = energy_of(xk)
115    trajectory.append((len(trajectory), e, xk.copy()))
116
117
118rng = np.random.default_rng(seed=0)
119x0 = rng.uniform(-np.pi, np.pi, size=4)
120
121# initial evaluation
122trajectory.append((0, energy_of(x0), x0.copy()))
123
124result = minimize(
125    fun=lambda th: energy_of(th),
126    x0=x0,
127    method="COBYLA",
128    options={"maxiter": 60, "rhobeg": 0.6, "disp": False},
129    callback=cb,
130)
131theta_opt = np.asarray(result.x, dtype=float)
132energy_opt = float(result.fun)
133
134iters = np.array([t[0] for t in trajectory])
135energies = np.array([t[1] for t in trajectory])
136
137# === 3. matplotlib 可视化 ===
138fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12.5, 4.6))
139
140# Left: energy convergence
141ax1.plot(iters, energies, "-o", color="#d62728", ms=4, lw=1.4,
142         label=r"VQE $\langle H\rangle$ trajectory")
143ax1.axhline(H_FCI, color="#1f77b4", ls="--", lw=2.0,
144            label=fr"FCI reference $E_0 = {H_FCI:.4f}$ Ha")
145ax1.fill_between(iters, H_FCI, energies,
146                 where=(energies > H_FCI), color="#d62728", alpha=0.12,
147                 label="variational gap")
148ax1.set_xlabel("COBYLA iteration")
149ax1.set_ylabel(r"energy $\langle H\rangle$ (Hartree)")
150ax1.set_title("VQE convergence on H2/STO-3G (2-qubit BK)")
151ax1.grid(alpha=0.3)
152ax1.legend(loc="upper right", fontsize=9)
153
154# Right: bar chart of final energy vs FCI
155bar_x = np.array([0, 1])
156bar_y = np.array([energy_opt, H_FCI])
157colors = ["#d62728", "#1f77b4"]
158labels = [fr"VQE $E(\theta^*) = {energy_opt:.4f}$", fr"FCI $E_0 = {H_FCI:.4f}$"]
159bars = ax2.bar(bar_x, bar_y, color=colors, ec="black", lw=0.6, width=0.6)
160for b, y in zip(bars, bar_y):
161    ax2.text(b.get_x() + b.get_width() / 2, y + 0.002, f"{y:.4f}",
162             ha="center", va="bottom", fontsize=10, fontweight="bold")
163err = abs(energy_opt - H_FCI)
164ax2.set_xticks(bar_x)
165ax2.set_xticklabels(labels, fontsize=9)
166ax2.set_ylabel(r"energy (Hartree)")
167ax2.set_title(fr"final energy gap = ${err:.2e}$ Ha ($\approx {err*627.5:.1f}$ kcal/mol)")
168ax2.set_ylim(min(energy_opt, H_FCI) - 0.01, max(energy_opt, H_FCI) + 0.025)
169ax2.grid(alpha=0.3, axis="y")
170ax2.axhline(H_FCI, color="#1f77b4", ls=":", lw=1.0, alpha=0.6)
171
172plt.suptitle("ch08-qml demo 1: VQE on the H2 molecule ground state", y=1.02)
173plt.tight_layout()
174out_path = OUT / "01_vqe_h2.png"
175plt.savefig(out_path, dpi=120, bbox_inches="tight")
176plt.close()
177
178# === 4. stdout 关键统计 ===
179print(f"  total circuit evaluations: {len(trajectory)}")
180print(f"  initial energy            : {energies[0]:.6f} Ha")
181print(f"  final energy E(theta*)    : {energy_opt:.6f} Ha")
182print(f"  FCI energy                : {H_FCI:.6f} Ha")
183print(f"  absolute error            : {abs(energy_opt - H_FCI):.3e} Ha")
184print(f"  theta* (rad)              : {theta_opt.tolist()}")
185print(f"  PNG -> {out_path}")

运行结果

Demo 1:VQE H₂

运行方式

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

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