2. 从零搭建 VQE:先准备好你的'量子算盘'
这里我默认你已经会写最基本的量子电路了——知道RX、RY、CNOT这些门怎么摆,也知道 Bloch 球大概是怎么回事。如果这些还有点儿模糊,建议先用手边的框架跑几个小例子热热身,不然直接上 VQE 会被各种符号砸晕。另外,Python 环境最好准备干净一点,建议用 Anaconda 或者 venv 单独建一个虚拟环境,避免依赖冲突。
2.1 量子态制备:用参数化电路'捏'出一个试探态
VQE 的核心思路是:先猜一个量子态,然后不断调整它的形状,直到它对应的能量最低。这个'猜'的过程,就是用量子电路来实现的。我们把这种电路叫做参数化量子电路(Parameterized Quantum Circuit, PQC),也有人叫它变分电路。
为什么非得用参数化电路?因为量子态是个极其复杂的高维对象,你不能凭空手写一个任意态出来——你是通过施加一系列量子门,从一个初始态(通常是 |0...0>)开始,慢慢把它'调制'成目标态。而这些量子门的旋转角度,就是我们要优化的参数。
拿 H2 分子举例,它只有 4 个自旋轨道,所以量子比特数只需要 4 个。但现实中的分子动不动就几十上百个轨道,你不能真的拿这么多量子比特去做实验。这时候就要用到一种叫'映射'的技巧,把化学中的轨道空间映射到量子比特空间。最常用的映射有 Jordan-Wigner 变换和 Bravyi-Kitaev 变换,前者实现简单、概念清晰,后者更节省量子比特资源但电路复杂度更高。初学者建议先搞懂 Jordan-Wigner,看代码的时候会轻松很多。
所谓制备试探态,字面意思就是:用一组参数(θ1, θ2, ...)去控制量子门的旋转角度,然后反复调节这组参数,直到输出的量子态让能量期望值最小。就像捏橡皮泥,你每捏一下,形状就变一点,直到捏出一个最满意的形状为止。
2.2 Hamiltonian 的构造:把分子的能量'翻译'成量子比特的语言
物理学家早就告诉我们,一个多电子体系的能量,可以写成一个叫 Hamiltonian(哈密顿量)的算符,它的本征值就是体系允许的能量。VQE 的目标就是找到这个 Hamiltonian 的最小本征值,也就是体系的基态能量。
但问题来了——Hamiltonian 是作用在电子上的,电子是费米子,它的数学描述用的是产生湮灭算符(a†, a),这套语言没法直接在量子比特上实现。所以你得把它'翻译'成泡利算符(X, Y, Z)的组合。翻译完之后,你会得到一串这样的东西:
H = -0.5 * (Z0 + Z1) + 0.2 * (X0 X1 + Y0 Y1)每个项都对应量子线路中的一个测量任务。
这个翻译过程非常关键,因为 Hamiltonian 的项数直接决定了后续测量的复杂度。比如 H2 分子的 Hamiltonian 只有 15 项左右,而更大的分子,比如 LiH 或 BeH2,项数会膨胀到几十上百项。每个项都要单独跑一次电路测量,然后加权求和,所以项数越多,计算开销越大。这也是为什么 VQE 在 NISQ(含噪声中等规模量子)时代只能处理小分子的原因之一。
2.3 优化器选择:经典世界怎么'指挥'量子电路
VQE 是个量子-经典混合算法,意味着量子计算机负责测量,经典计算机负责优化。你得有一个经典优化器,根据测量结果反馈给量子电路,告诉它下一步该往哪个方向调参数。这就是所谓的变分参数优化。
最简单但也最经典的优化器是 COBYLA,它不需要计算梯度,只靠比较不同参数点的目标函数值来寻优。用在 VQE 上,它的好处是抗噪声能力强,因为每次评估都是个带统计误差的测量值,COBYLA 这种直接比较型算法相对稳健。另一个常用的是 SPSA,它是一种基于随机梯度估计的方法,每次只做两次目标函数评估就能估算梯度,特别适合量子测量这种高成本场景。
至于 Adam 和 muon 这种经典机器学习优化器,VQE 也能用,但有个前提——你得能拿到梯度。要么通过参数平移规则(parameter shift rule)计算梯度,要么用有限差分法近似。参数平移规则是量子计算里的一大法宝,对某些门,比如 RX、RY,可以通过两侧平移 π/2 来精确估计梯度,代价是要跑两倍电路。这个方法在理论上是精确的,但在 NISQ 设备上会受到噪声干扰,所以实际使用时还要结合去噪手段。
注意:优化器不是越先进越好。在噪声大的设备上,花里胡哨的优化器往往不如简单的 COBYLA 实用。以小分子 VQE 为例,COBYLA 在几十步内就能收敛到化学精度(约千分之一 Hartree),而 Adam 常常需要上百步,耗时更长。
3. 用代码撸一个 H2 分子的 VQE:完整实操
下面我就带你从零开始跑一个 H2 分子的基态能量计算。我这里用 Qiskit 这个框架,主要是因为生态成熟、文档全面,而且对初学者友好。当然,你也完全可以改用 PennyLane 或其他框架,原理完全一样。
3.1 环境准备与安装
这里我默认你已经装了 Python 3.9 以上版本,然后一通操作:
pip install qiskit qiskit-natureqiskit-nature 是专门处理量子化学问题的库,里面内置了分子数据库,可以直接拿 H2、LiH 这些小分子来用而不用自己去推导 Hamiltonian。如果你不想用这个库,那你就得手动做 Hartree-Fock 计算,再手动算积分、做映射,那过程会痛苦很多。作为从零开始的教程,我们直接用现成工具把重点放在理解流程上。
建议顺手装上 matplotlib 和 numpy,因为后面要画收敛曲线:
pip install matplotlib numpy3.2 第一步:构建分子的电子结构问题
我们从最基础的 H2 分子开始。先定义它的几何构型——两个氢原子间距 0.735 埃(这个距离大约是平衡键长附近)。在 qiskit-nature 里,这一步的代码非常简洁:
from qiskit_nature.units import DistanceUnit from qiskit_nature.second_q.drivers import PySCFDriver # 定义 H2 分子:两个 H 原子,距离 0.735 Å driver = PySCFDriver( atom='H 0.0 0.0 0.0; H 0.0 0.0 0.735', basis='sto3g', charge=0, spin=0, unit=DistanceUnit.ANGSTROM ) # 生成电子结构问题 problem = driver.run()这里选的是 STO-3G 基组,是最小的基组。你可能会问,为什么要用这么小的基组?因为对教程来说,最重要的不是化学精度,而是把这个流程跑通。STO-3G 下的 H2 基态能量大约是 -1.85 Hartree 左右,和实验值有偏差,但完全够我们用来说明 VQE 算法的逻辑。等你自己理解了流程,再换成更大的基组(比如 6-31G)也不迟。
3.3 第二步:获取 Hamiltonian 和初始态
有了分子对象之后,我们需要把它的电子结构问题转化成量子比特 Hamiltonian。这里涉及两步:先做 HF 计算得到参考态,再做费米子到比特的映射:
from qiskit_nature.second_q.mappers import JordanWignerMapper from qiskit_nature.second_q.transformers import ActiveSpaceTransformer # 定义活性空间:保留 2 个空间轨道、2 个电子 transformer = ActiveSpaceTransformer(num_electrons=2, num_spatial_orbitals=2) problem = transformer.transform(problem) # 用 Jordan-Wigner 映射将费米子 Hamiltonian 转成泡利字符串 mapper = JordanWignerMapper() hamiltonian = mapper.map(problem.second_q_ops()[0]) # 获取 HF 初态(参考态) initial_state = problem.reference_occupied_occupations # 例如 [1, 1, 0, 0]这里的活性空间是一个非常工程化的概念:你不必把所有轨道都纳入量子计算,只需要选出一部分关键的轨道,把其他轨道'冻结'掉。这既减少了量子比特数,又不会损失太多精度。对于 H2 这种双电子体系,两个空间轨道(一个成键、一个反键)足够了,量子比特数被压缩到了 4 个。
3.4 第三步:设计参数化试探电路
接下来是重头戏,设计 PQC。我最常用的试探电路有两种:一种是 Ry 旋转层 + CX 纠缠层的交替结构(硬件高效电路),另一种是从化学直觉出发的 UCCSD 拟设(酉耦合簇)。前者电路简单、参数少,适合跑通流程;后者物理意义更强,参数更多,但表达能力更强。
这里先用一个最简单的硬件高效电路:
from qiskit import QuantumCircuit import numpy as np # 量子比特数 = 4 num_qubits = 4 params = [] qc = QuantumCircuit(num_qubits) # 第一层:Ry 旋转 for i in range(num_qubits): theta = Parameter(f'θ_{i}_0') params.append(theta) qc.ry(theta, i) # 纠缠层:相邻比特用 CX 门纠缠 for i in range(num_qubits - 1): qc.cx(i, i + 1) # 第二层:Ry 旋转 for i in range(num_qubits): theta = Parameter(f'θ_{i}_1') params.append(theta) qc.ry(theta, i) # 第三层:再一组 CX 门,增加表达力 for i in range(num_qubits - 1): qc.cx(i, i + 1)这段代码的思路是:先用旋转门把每个比特翻转到合适的位置,再用纠缠门让它们产生量子关联,最后再用旋转门做精细调整。这个结构在学术界有个名字叫'硬件高效拟设'(Hardware Efficient Ansatz),因为它在真实量子芯片上容易实现——只需要单比特旋转门和最近的 CNOT 门就能搭出来。
注意:这个电路只是众多选择之一。如果你在博客里看到 VQE 代码,用 UCCSD 拟设也别觉得奇怪,那是一种从量子化学理论出发、更'化学友好'的拟设,只是电路深了很多,在噪声设备上反而更难跑。
3.5 第四步:定义能量评估函数
有了 Hamiltonian 和试探电路,下一步就是定义一个函数:给定一组参数 θ,把电路跑一遍,测量各个泡利项的期望值,加权求和得到总能量。
在 Qiskit 里,有两种做法。初学者可以直接用 Estimator(估计器)这个 API,它会封装好所有的测量和期望值计算:
from qiskit.primitives import Estimator estimator = Estimator() def evaluate_energy(theta_values): param_dict = dict(zip(params, theta_values)) # 测量 Hamiltonian 期望值 energy = estimator.run(qc, hamiltonian, parameter_values=param_dict).result().values[0] return energy注意,这里我没有额外加测量电路,因为 Estimator 自动帮我们处理了测量。如果你用老版的 Qiskit(0.x),你就得手动对每个泡利项做基变换测量,非常繁琐。新版确实香了不少。
有一点必须提醒:Estimator 默认使用理想无噪声模拟器,所以结果很干净。如果你想模拟真实设备上的采样噪声,就需要指定带噪声的后端,或者限制 shots 数量。后面我会讲到这个坑。
3.6 第五步:优化器迭代跑起来
现在到了整个 VQE 的心脏——优化循环。我用 scipy 里的 COBYLA 来当优化器,因为它在评估次数少的情况下表现稳定:
from scipy.optimize import minimize import matplotlib.pyplot as plt history = [] def callback(theta_values): energy = evaluate_energy(theta_values) history.append(energy) print(f'参数: {theta_values}, 能量: {energy:.6f} Hartree') # 初始参数:全零(其实就是没有旋转,等于初始 HF 态) init_params = np.zeros(len(params)) result = minimize( evaluate_energy, init_params, method='COBYLA', callback=callback, options={'maxiter': 200, 'tol': 1e-4} ) # 输出最终结果 print(f'优化完成,最小能量: {result.fun:.6f} Hartree') # 画收敛曲线 plt.plot(history) plt.xlabel('迭代次数') plt.ylabel('能量 (Hartree)') plt.title('VQE 收敛曲线') plt.show()跑完这个代码,如果一切正常,你会看到能量曲线从大约 -1.0 附近快速下降,然后在一个平台期徘徊,最终稳定在 -1.85 Hartree 左右。这个值就非常接近 FCI(全组态相互作用)的精确解了。
这里有个细节:为什么初始点是全零?因为全零参数对应没有任何旋转,此时电路输出的就是初始引用的 HF 态。从这个点出发,优化器会让试探态逐渐由 HF 态演化,加入电子关联效应,从而把能量降下来。这非常直观——你只是把 HF 态当作起点,然后在它周围探索更优的态。
3.7 第四步(真·第四步)常见问题速查与避坑指南
这一部分我把它单独拎出来,因为 VQE 的报错和反直觉行为实在太多了,几乎每个新人在跑通之前都会卡住一次。我把自己踩过的坑,加上看别人踩过的坑,整理成了一张速查表:
| 问题 | 特征 | 排查方向 |
|---|---|---|
| 能量不收敛 | 曲线上下震荡,怎么都不下降 | 优化器步长太大;参数初始值不合适;测量误差太大 |
| 能量收敛到错误值 | 曲线平滑但最终值和理论值差很远 | 试探电路表达能力不足;映射选择有误;活性空间太小 |
| 程序报错:参数不匹配 | Parameter数量对不上 | 参数化电路里 Parameter 对象和实际传入的数值数量不一致 |
| 测量结果波动大 | 同样的参数两次结果差距很大 | shots 太小,噪声干扰;建议增大 shots 或改用 Estimator 默认理想模拟 |
| 优化器达到 maxiter | 还没收敛就停了 | 增大 maxiter;或改用带梯度的优化器,收敛更快 |
其中最常见的还是能量不收敛,尤其是你一开始用 Adam 优化器的时候。Adam 是个特爱调学习率的优化器,但在 VQE 这种采样有噪声的目标函数上,它很容易被噪声带偏。我试验过很多次,Adam 在无噪声模拟器上表现不错,在带噪声的模拟器上就很容易炸,相比之下 COBYLA 稳如老狗。
3.8 再进一步:从模拟器走向真实硬件前,你必须知道的事
如果你只是想在笔记本上体验一下 VQE,那用模拟器足够了。但如果你真的打算把它跑在真实量子芯片上,我建议你先冷静一下,因为现实世界比模拟残酷得多。真实硬件上的噪声来源多种多样,包括但不限于:
- 门误差:每次量子门操作都可能有误差,单比特门误差通常在 1e-3 量级,双比特门在 1e-2 量级以上。
- 读取误差:测量比特时可能读错,导致结果偏差。
- 退相干:量子比特的相干时间有限,通常在几十微秒量级,电路越长误差越大。
这些误差积累起来,会让 VQE 的能量评估非常不稳定。你在模拟器上测得的圆润收敛曲线,放到真机上可能变成一坨毛刺。要缓解这个问题,有两类思路:
一类是误差缓解(error mitigation)。最常用的包括 Zero-Noise Extrapolation(ZNE)和测量误差修正。Qiskit 里有 Mitiq 库可以做 ZNE,思路是对电路做噪声放大,然后外推回零噪声极限。这个方法在中小规模电路上效果还行,但也不是万能的。
另一类是降低电路深度。与其用深层的化学拟设,不如用浅层的硬件高效拟设,牺牲一部分表达能力,换取更长的相干时间内能完成测量。在 NISQ 时代,浅电路往往比深电路跑出来的结果更接近理论值,因为门误差和退相干都更小。
4. 让 VQE 更聪明的三种进阶技巧
如果你已经顺利跑通了基础的 H2 VQE,恭喜你踏入了量子化学模拟的门槛。但 H2 毕竟只是玩具,想要处理更大的分子,下面这几个进阶技巧你就必须掌握了。
4.1 用更聪明的试探电路:UCCSD 的物理直觉
前面用的硬件高效拟设虽然简单,但它没有利用分子体系的物理结构,所以在大分子上往往需要很多层才能达到化学精度。这时候就该 UCCSD 登场了。
UCCSD 的全称是 Unitary Coupled Cluster Singles and Doubles(酉耦合簇单双激发)。它的核心思想是:基态波函数可以由 HF 参考态通过一系列指数化的单粒子、双粒子激发算符生成。物理直觉是:基态不仅仅是一个 Slater 行列式,而是很多行列式的叠加,其中单激发和双激发占了主要成分。
在量子电路里,UCCSD 实现起来比硬件高效电路复杂得多,因为它需要把指数算符分解成量子门序列。你可能需要在电路里塞进去几十个旋转门和 CNOT 门,参数数量也大大增加。但好处也很明显:它从一开始就朝着正确的化学方向搜索,所以通常需要的优化迭代次数更少。
实现 UCCSD 最省力的方式是用 qiskit-nature 自带的 UCCSD 类:
from qiskit_nature.second_q.circuit.library import UCCSD ansatz = UCCSD( problem.num_spatial_orbitals, problem.num_particles, mapper, initial_state=initial_state )它会自动根据活性空间的轨道数和电子数生成对应的参数化电路。你不需要手搓那一堆复杂的门序列——虽然如果你有兴趣,手搓一遍能极大加深你对量子化学的理解,但第一次跑通流程,交给库就好。
4.2 用并行化加速分子扫描:一次跑一条势能曲线
VQE 的一个典型应用场景是扫描势能曲线——比如把两个 H 原子的间距从 0.5 埃拉到 2.5 埃,在每个间距上都跑一次 VQE,得到能量曲线,从而判断分子是否稳定、键长和离解能是多少。
这个场景非常适合并行化,因为每个几何构型的 VQE 是相互独立的。你可以用 multiprocessing 或者直接把任务丢到云端并行跑:
from multiprocessing import Pool def run_vqe_at_distance(dist): driver = PySCFDriver( atom=f'H 0.0 0.0 0.0; H 0.0 0.0 {dist}', basis='sto3g' ) # 省略中间步骤... return dist, final_energy distances = np.linspace(0.4, 2.5, 10) with Pool(4) as pool: results = pool.map(run_vqe_at_distance, distances)值得注意的是,相邻几何构型的 VQE 优化结果是高度相关的。你可以用**热启动(warm start)**技巧:先把上一个距离优化好的参数作为下一个距离的初始参数,这样收敛速度会快很多。这就像你从一楼爬到二楼,再上三楼,比每次从一楼重新爬要省力得多。
4.3 选择正确的工具:Qiskit vs. PennyLane
如果你准备认真做 VQE,除了 Qiskit,你还应该了解一下 PennyLane。PennyLane 最大的优势是它把量子计算和经典自动微分框架(PyTorch、TensorFlow)无缝集成在一起,所以你在搭建 VQE 时可以直接用 PyTorch 的 autograd 来计算梯度,代码写起来非常优雅。
Qiskit 的优势则是生态完整,量子化学模块 qiskit-nature 非常成熟,内置的驱动和映射工具很省心。两个框架各有优劣,我的建议是:做量子化学模拟首选 Qiskit,做机器学习相关实验首选 PennyLane。当然,如果你已经熟悉其中一个也没有必要强行切换。
我自己经常混合使用:用 Qiskit 构建分子和 Hamiltonian,再用 PennyLane 做优化。它们之间通过 OpenQASM 或者其他中间表示来传递电路。不过这种混合方式调试成本较高,新手建议先专精一个框架。
5. 一些容易被忽略但至关重要的小细节
最后,我把自己 VQE 实操中的几个独门心得分享一下。这些细节你在教科书上很难找到,但它们常常决定了你的代码能不能收敛到化学精度。
5.1 关于测量代价的控制
VQE 的每个能量评估都要执行多次量子测量(shots),而 Hamiltonian 的项数往往很多。如果你不做任何优化,一个 H2 分子的评估就要跑 15 次电路,每次 1024 次射击,大分子更夸张。这个成本在模拟器上无所谓,但在真实硬件上是非常昂贵的。
一个叫**分组测量(commuting grouping)**的技术可以把能对易的泡利项合并到一起,一次电路测量同时得到多个项的值,从而把测量次数从 O(N^4) 降到 O(N^3)。Qiskit 的 Estimator 和 SparsePauliOp 提供了这些功能,默认就会做部分优化。你如果想要更细粒度地控制,也可以自己实现分组策略。
还有一个细节是shots 数量的动态调整。刚开始优化时参数变化幅度大,能量梯度也大,不需要太高的测量精度;在快要收敛的区域,能量曲线平坦,需要更精确的测量来区分细微差异。你可以设计一个回调函数,根据历史能量的变化幅度自动调整 shots 数。
5.2 初始参数和热启动的价值
我的经验是,初始参数选择对 VQE 的收敛速度和最终精度影响极大。全零初始参数虽然稳定,但如果你从 HF 态出发,可能会卡在局部极小值,尤其是体系有很强的关联效应时。
一种提升鲁棒性的做法是先用小规模模拟跑一遍,把优化好的参数作为真实硬件实验的初始参数。这被称为零噪声参数初始化或镜像初始化。另一种做法是用经典量子化学方法(例如 CCSD)算出的近似波函数来构造初始参数。好处是一开始就在好点附近,优化器只需要做精细调整。
5.3 VQE 的下一步:测量误差消除与自适应变分方案
最后,再聊聊 VQE 这个方向目前还在发展中的几个热点。一个是ADAPT-VQE(自适应变分量子本征求解器),它不再使用固定的试探电路,而是从一个空电路开始,每次迭代根据梯度信息选择一个最'重要'的激发算符加进去。这样可以用最少的参数达到给定精度,大大减少电路深度。我试过这个方案,在模拟器效果非常好,但代价是实现起来弧度复杂,而且每一步都要做矩阵计算评估梯度,计算瓶颈转移到了经典侧。
另一个方向是把 VQE 纳入更大规模的量子-经典工作流中,例如用于材料设计或催化剂筛选。这时候单个 VQE 的精度反而不是最关键的,关键是整个流程如何自动化、如何并行化、如何把统计误差控制在可接受范围内。从这个角度看,VQE 不仅仅是一个算法,更是一整套工程实践。
最后的最后,回到最本质的一件事:VQE 之所以能成立,是因为量子计算机擅长处理高维波函数的叠加和干涉,而经典计算机擅长参数优化。两者各司其职,互相配合,才让'量子计算化学'成为 NISQ 时代最有希望落地的应用之一。理解了这层逻辑,你就掌握了 VQE 的精髓——剩下的,不过是工程的打磨罢了。
我在实际调试中体会最深的一件事是:VQE 的报错信息往往非常不友好,但你只要能沉下心,一行行检查 Hamiltonian 的每一项、参数化电路的每一个门、优化器的每一步迭代,就一定能找到问题所在。不要怕代码烦琐,多调试几次你就会发现,VQE 其实就是一个被包装得极其华丽的最小化问题——剥开那层量子力学的神秘外衣,内核还是那个你熟悉的优化理论。把基础打牢,后面的路就顺了。