上个月有个做计算化学的朋友拿着他的 Qiskit 脚本过来,屏幕上是一张相位估计的概率直方图,两条主峰,一条落在 0.82 附近,一条在 0.18 附近。他说基态能量是算出来了,可是我要的分子动力学轨迹呢?这个问题问得挺好,因为它戳中了一个特别普遍的理解偏差——量子相位估计(QPE)从头到尾都不是一个能输出轨迹的算法,它输出的东西更像一把尺子,量的是能量刻度。而分子动力学之所以能跑起来,靠的恰恰是能量对坐标的导数。
所以这篇东西想聊的不是“QPE 是什么”这种教材内容,而是把一个真实工程问题拆开:在一个分子动力学工作流里,QPE 到底插在哪一环、用什么参数、Trotter 步数怎么定、评估比特数取多少才够化学精度、从相位反解出来的能量怎么变成力场参数喂给经典积分器。我自己在这条链路上来回改了三四版,踩过的坑比预期多,包括相位卷绕读反符号、本征态重叠度低导致主峰劈裂、受控酉算子把线路深度顶到三位数这类。下面按我实际搭建的顺序走一遍,代码骨架给到能直接跑通模拟器的程度,适合有基础量子线路概念、也懂一点分子模拟的读者。完全没接触过量子计算的也能跟,关键概念我都会用生活化的说法铺一遍。
1. QPE 在分子动力学里到底扮演什么角色:先把边界划清楚
很多人第一次把这两个词放一起搜,脑子里想的是“用量子计算机跑 MD 会更快”,然后去查文献,发现论文里全是基态能量曲线,跟轨迹图完全不搭。这不是文献写错了,是两个东西的分工本来就不一样。
1.1 经典 MD 的动力来源是什么
经典分子动力学的核心方程很简单,牛顿第二定律加一个势能面:质量乘以加速度等于负的势能梯度。整个模拟的质量高低,几乎全押在势能面准不准上。分子力场就是势能面的一个廉价近似,用弹簧模型(键伸缩)、余弦模型(二面角)、伦纳德-琼斯模型(范德华)拼出来,跑一微秒只要几小时。但力场在键断裂、过渡态、强关联体系上基本失效,因为那些地方的电子结构已经不是弹簧能描述的了。
从头算分子动力学用密度泛函或者后哈特里-福克方法,每一步都重新算电子结构,精度上去了,代价是每步几秒到几分钟,跑一万步就是几天。中间这一档——比力场准、比从头算便宜——正是量子计算想切进去的位置。不是替代经典 MD,而是给势能面提供一个更高精度的采样器。
1.2 QPE 输出的相位不是位置
量子相位估计的输入是一个酉算子 U 和它的一个本征态,输出是这个本征态对应的本征相位。如果你取 U 等于时间演化算子,那么本征相位就编码了本征能量。整个过程从头到尾没有任何“坐标”或“动量”的测量,所以它天然不是一个能产生轨迹的算法。
我见过不少人第一次跑 QPE 之后困惑:为什么我只能拿到一个数字?因为这个数字就是能量本征值,它是势能面上的一个点,不是这一点的运动状态。想要轨迹,你得先拿到足够多的势能面点,拟合出势能函数,再交给经典积分器去解运动方程。这个分工必须一开始就想明白,否则后面所有参数都会调错方向。
1.3 三条能真正落地的技术路线
我把可行的组合分成三条,实际选哪条取决于你的算力预算和精度诉求。
| 路线 | QPE 的作用 | 经典侧的工作 | 落地难度 |
|---|---|---|---|
| A 势能面扫描 | 逐点算电子能量 | 拟合势函数,跑经典 MD | 低,模拟器就能做 |
| B 量子-经典混合 | 每步算能量,参数移动法算力 | 每步调用量子线路 | 中,需要大量线路执行 |
| C 波包直接演化 | 不用于轨迹,用于读能谱 | 对比经典振动分析 | 中,但需要额外测量策略 |
路线 A 是我推荐所有人先跑的,因为它把量子部分和经典部分彻底解耦,量子侧只需要输出一串高精度能量点,出问题好定位。路线 B 更接近严格意义上的量子分子动力学,但每帧都要重新执行 QPE,线路次数乘以轨迹帧数,在模拟器上还能忍,真机上基本别想。路线 C 其实是时间演化算子的活儿,QPE 在里面只负责读出振动能级,用来验证演化结果对不对。
后面几节我按路线 A 展开,因为它把 QPE 的工程细节暴露得最完整,学会了之后再去改路线 B 或 C,只是换调用频率的问题。
2. 分子哈密顿量到 Pauli 串的落地转换
QPE 的受控酉算子必须建立在哈密顿量之上,而 Qiskit 只认识泡利串形式的哈密顿量。从化学的电子结构描述走到泡利串,中间要过两道映射,这两道映射里各有各的坑。
2.1 二次量子化之后得到的是费米子算符
做电子结构的人熟悉的是一堆积分,单电子积分、双电子积分,写成二次量子化形式就是一堆产生算符和湮灭算符的组合。Qiskit 生态里对应的对象是费米子算符,每一项由若干产生/湮灭算符串起来,前面挂一个系数。
这一步的关键是基组选择。最小基组下氢分子只有两个电子、两个空间轨道,展开成四个自旋轨道,非常小。加极化函数之后轨道数翻倍,费米子项数按四次方涨。我个人的建议是先用最小基组把整条流水线跑通,因为 QPE 的线路复杂度对系统比特数极敏感,四个比特的系统能做演示,八个比特就开始吃力,十二个比特在模拟器里要等很久。
2.2 Jordan-Wigner 映射把费米子变成比特
费米子有个硬性约束:一个轨道最多占一个电子,而且交换两个电子要出负号。量子比特没有这个约束,所以要靠映射把这套规则编码进去。约当-维格纳映射是最直观的一种:用比特的 0 和 1 表示轨道空和满,用 Z 算符串来传递交换产生的符号。
代价是那个 Z 串会随着轨道编号线性变长。四个轨道时最长三项,看起来无所谓;到二十个轨道时,一个局域跳跃项要挂十九个 Z 算符,线路深度会爆炸。这就是为什么大体系的量子化学模拟要换更省比特的映射方法,但对我们的演示规模来说,约当-维格纳完全够用。
映射完拿到的是一个稀疏泡利算符对象,形如每一项是“系数乘以若干个 I、X、Y、Z 的直积”。氢分子在最小基组下,去掉恒等项之后通常在十几个泡利串的量级。
2.3 必须先做一次对角化自检
这一步特别容易被跳过,但我建议任何人都别跳。拿到泡利串之后,先用稀疏矩阵把它展开成矩阵,调一次本征分解,把最小的几个本征值打印出来,跟电子结构后端给出的参考值对照。
我吃过一次亏:映射时自旋轨道顺序搞反了,哈密顿量看起来完全正常,QPE 也能跑出峰,但峰的位置对不上任何已知能级。如果当时花五分钟做一次对角化对比,能省掉两天排查。对角化只对四到十二个比特可行,而演示规模正好在这个范围内,所以这个自检几乎是免费的。
下面是一段可以直接用的骨架代码,系数我写成示意值并加了标注,真实数值必须你自己从电子结构计算结果生成,不要直接照抄:
import numpy as np from qiskit.quantum_info import SparsePauliOp # 注意:以下系数仅用于打通数据流,不具物理意义 # 真实场景请用电子结构后端输出的积分做映射,再构造 SparsePauliOp pauli_terms = [ ("IIII", -0.0988), ("ZIII", 0.1712), ("IZII", 0.1712), ("IIZI", -0.2227), ("IIIZ", -0.2227), ("ZZII", 0.1689), ("ZIZI", 0.1205), ("ZIIZ", 0.1659), ("IZZI", 0.1659), ("IZIZ", 0.1743), ("IIZZ", 0.1205), ("XXYY", 0.0452), ("YYXX", 0.0452), ("XYYX", 0.0452), ("YXXY", 0.0452), ] hamiltonian = SparsePauliOp.from_list(pauli_terms) # 自检:展开成矩阵,看最低本征值是否接近参考能量 mat = hamiltonian.to_matrix() eigs = np.linalg.eigvalsh(mat) print("最低三个本征值:", eigs[:3])跑完这段,如果最低本征值和你手上的参考基态能量差了 0.1 哈特里以上,别急着往下走,先回去查映射和轨道顺序。这个能量的绝对量级本身不重要,重要的是它跟参考值的一致性,因为后面所有相位解码都建立在它之上。
3. 受控酉算子:Trotter 化时间演化的构造细节
QPE 需要一个能被幂次化的酉算子。理论上最干净的选择是直接指数化哈密顿量,但那个矩阵是指数规模的,根本存不下。实际做法是把时间演化拆成小段,用小段的乘积近似整段,这就是 Trotter 化。
3.1 为什么不能自己手搓指数化
有人会想,反正哈密顿量能写成泡利串,每一项的指数化都有解析形式,那就一项一项写门不就行了。问题在于泡利串之间通常不对易,直接把各项的指数化乘起来并不等于整体的指数化,误差是一阶的。虽然可以靠增加分段数把误差压下去,但如果你自己手写门序列,很难保证幂次化的时候结构还正确。
Qiskit 里对应的对象是泡利演化门,构造时传入哈密顿量、演化时间和合成方法。它内部会调用指定的 Trotter 合成器,把整段时间切割成若干重复单元,每个单元按顺序展开成旋转门和纠缠门。这样做的好处是:结构标准化、误差可控、后面做幂次和受控都有现成接口。
3.2 步数怎么定才不浪费
Trotter 步数是这条链路上最需要实测的参数。步数少了,演化不准,相位偏移;步数多了,线路深度翻倍,模拟器还能扛,噪声模型下直接崩。
我的经验做法是做一个收敛扫描:固定演化时间,把重复次数取 1、2、4、8、16,分别跑相位估计,看读出的能量往哪个值收敛。通常到某个步数之后,能量变化小于你要的精度(比如千分之一个哈特里),就可以停。对一个十来项的哈密顿量,四到八次重复往往就把误差压到可接受范围了。
还有一个容易忽略的点:Trotter 误差和时间长度是耦合的。你把演化时间拉长以换取更高分辨率,同时也会放大 Trotter 误差,两个效应反方向拉扯,所以参数不能分开调。这一点我在第五节会展开讲怎么找平衡点。
3.3 受控化之后线路会膨胀
QPE 的核心是受控演化:评估寄存器上每个比特控制一次系统演化,第 k 个比特控制的是演化算子的一百次、两次、四次……倍的幂次。这里的“平方”是在算子层面做的,不是把线路复制一遍,但仍然意味着最深的那次受控演化要包含指数级的 Trotter 单元数。
实测下来,一个四比特系统、八次 Trotter 重复的演化门,一份拷贝的门数在几十到一百多;受控化之后大约翻倍,还要额外加上控制比特和系统比特之间的纠缠门。评估寄存器取八到十个比特,最深一路的受控演化对应一的幂次乘以二的九次方,五百多次 Trotter 单元,总门数很容易到几万。
这个量级在状态向量模拟器上还能跑,但已经能感觉到时间了。所以演示阶段一定要先把比特数和步数压到最小,能用四比特就不用六比特,能收敛到四次重复就不用八次。
from qiskit.circuit.library import PauliEvolutionGate from qiskit.synthesis import LieTrotter, SuzukiTrotter # 一阶 Trotter,重复 4 次 evol_gate = PauliEvolutionGate( hamiltonian, time=1.0, synthesis=LieTrotter(reps=4), ) # 二阶 Suzuki 在同样重复次数下误差更小,但门数更多 evol_gate_order2 = PauliEvolutionGate( hamiltonian, time=1.0, synthesis=SuzukiTrotter(order=2, reps=4), ) print("一阶门数:", evol_gate.definition.count_ops()) print("二阶门数:", evol_gate_order2.definition.count_ops())这两行打印能直接告诉你精度和门数的交换比。跑一次就知道该选哪个。
4. QPE 线路逐段拆解:从 Hadamard 墙到逆 QFT
看懂相位估计的线路图是一回事,把它拆成能改的代码是另一回事。我把这条线路分成四段,每段都有独立的调参空间。
4.1 态准备和重叠度决定一切
QPE 有一个隐含前提:输入的系统态必须是待求酉算子的本征态,或者至少在本征态基下有足够大的分量。如果输入的态和基态的
QPE 的相位读数来自输入态在各本征态上的投影权重,权重低意味着主峰低、旁峰高,此时读出的相位不稳。
实际操作中我们拿不到精确的基态,只能用哈特里-福克态做初猜。氢分子在平衡键长附近,哈特里-福克态和真实基态的重叠度通常很高,主峰占比能到八九成,够用。但键长拉长之后重叠度迅速掉下来,主峰开始劈成好几条,这时候读出来的“能量”就不一定是基态能量了。
我处理这个问题的方式是:在扫描势能面时,一旦发现主峰概率低于某个阈值(比如百分之六十),就在那个点上换一个更好的初猜态,比如从相邻键长解出来的态跑一个短暂的变分优化。这个技巧在过渡态附近特别有用。
4.2 受控幂次的递推实现
评估寄存器上第 j 个比特控制的是演化门的一的二次方再二次方……共二的 j 次方份。实现上有两种写法:一种是每层都用原门做幂次,另一种是先做一个平方递推——第一层用原门,第二层用原门的平方,第三层用平方的平方,依此类推。
递推写法的好处是每个门的内部结构在编译时可以复用,坏处是平方操作在门的层面上会展开成更复杂的结构,有些合成器不支持直接平方。我实测下来,用幂次接口在 Qiskit 里最省心,虽然它内部还是要算,但你不用操心底层展开的正确性。
受控化的写法要注意控制比特的值:只有控制比特为一才生效。有人习惯先做 X 翻转再控制,搞混了控制极性,结果读出来的相位整体取反,能量符号全错。这个坑我在第七节会再提一次。
4.3 逆 QFT 把相位信息汇聚到一个比特串
受控演化结束之后,相位信息散落在评估寄存器的相对相位里,肉眼看不出来。逆量子傅里叶变换的作用是把这些相对相位转换成计算基下的概率分布:某个比特串的概率高,说明对应那个二进制小数的相位分量大。
逆 QFT 的实现有个小细节:标准 QFT 末尾有一串交换门,把比特顺序倒过来。如果你用逆 QFT 但没做交换,读出来的比特串是反的,解码时要把字符串倒序再转整数。这个错误极其隐蔽,因为结果看起来仍然是个合法的相位值,只是对应的能量完全不对。
4.4 完整代码骨架
下面这段把前面几节拼起来,可以在模拟器上跑通,输出一张相位分布图:
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister from qiskit.circuit.library import QFT from qiskit_aer import AerSimulator def build_qpe(hamiltonian, n_eval=8, t=1.0, trotter_reps=4): """构造 QPE 线路:n_eval 个评估比特 + 系统比特""" n_sys = hamiltonian.num_qubits qr_eval = QuantumRegister(n_eval, "eval") qr_sys = QuantumRegister(n_sys, "sys") cr = ClassicalRegister(n_eval, "c") qc = QuantumCircuit(qr_eval, qr_sys, cr) # 1) 系统态准备(此处用哈特里-福克占据态示意) for q in [0, 1]: qc.x(qr_sys[q]) # 2) 评估寄存器打平等叠加 qc.h(qr_eval) # 3) 受控演化,第 j 个评估比特控制演化门的 2**j 次幂 evol = PauliEvolutionGate(hamiltonian, time=t, synthesis=LieTrotter(reps=trotter_reps)) for j in range(n_eval): powered = evol.power(2 ** j) qc.append(powered.control(1), [qr_eval[j]] + list(qr_sys)) # 4) 逆量子傅里叶变换 qc.append(QFT(n_eval, inverse=True, do_swaps=True).to_gate(), qr_eval) # 5) 测量 qc.measure(qr_eval, cr) return qc n_eval = 8 t = 1.0 qc = build_qpe(hamiltonian, n_eval=n_eval, t=t, trotter_reps=4) sim = AerSimulator() result = sim.run(qc, shots=20000).result() counts = result.get_counts() # 解码:counts 的键是比特串,最左字符是最高位 best_bitstring = max(counts, key=counts.get) phase = int(best_bitstring, 2) / (2 ** n_eval) print("主峰比特串:", best_bitstring, "概率:", counts[best_bitstring] / 20000) print("解出相位:", phase)这段骨架里我故意保留了幂次调用,虽然编译慢一点,但正确性有保证。如果你要跑大一点的评估比特数,把幂次的递推手写出来能省不少编译时间,代价是代码复杂度上去。
5. 精度、演化时间与相位卷绕的三角标定
参数是这条链路上最容易调错的部分,而且错了之后结果往往看起来很正常,只是数值不对。这几个参数之间存在硬性约束,不是随便填的。
5.1 评估比特数直接决定能量分辨率
相位被编码在评估寄存器的二进制小数里,n 个比特最多能分辨二的 n 次方分之一。映射到能量上,分辨率等于圆周率的两倍除以演化时间再乘以这个最小相位间隔。
换算一下:如果演化时间取 4(原子单位),n 取 10,那么能量分辨率的量级是千分之一点五哈特里左右。化学里常说的精度门槛是每摩尔一千卡,换算成哈特里大概是 0.0016。所以 10 个评估比特配 4 的演化时间,刚好卡在这个门槛上,属于最低配置。
想要留余量就往上加。但每加一个评估比特,最深那路受控演化的幂次翻倍,线路时间几乎翻倍。我的建议是先按目标精度反推比特数,再往上一档做验证,不要盲目堆。
5.2 演化时间拉太长会把相位绕回去
相位是模一的,也就是说算出来的本征相位超过一之后会绕回来,你无法从单个读数里区分它是本身就这么大,还是绕了一圈。落到能量上,就是要求能量乘以演化时间再除以两倍圆周率的绝对值小于一。
对氢分子这种能量绝对值在一个哈特里量级的体系,演化时间超过 5 左右就开始有卷绕风险。这就是一个两难:时间短了分辨率不够,长了又可能卷。
我用的解法是双时间点交叉验证。取两个不同长度的演化时间,比如 2 和 4,分别跑一次。如果两次解出来的能量一致,说明没有卷绕;如果差了一个整周期的量,说明其中一次绕了。这个技巧在经典测距里叫多波长相位展开,搬到相位估计上完全通用,而且几乎不增加成本——反正你也要做重复实验取统计。
5.3 一张参数对照表存着备用
调参时我把常见组合整理成了一张表,直接查比每次现算快:
| 评估比特 | 演化时间 | 能量分辨率(哈特里) | 卷绕风险 | 适用场景 |
|---|---|---|---|---|
| 6 | 2.0 | 约 0.052 | 低 | 仅做流程验证 |
| 8 | 3.0 | 约 0.008 | 低 | 定性趋势观察 |
| 10 | 4.0 | 约 0.0015 | 中 | 化学精度边界 |
| 12 | 4.0 | 约 0.0004 | 中 | 需要高精度对比 |
| 12 | 6.0 | 约 0.0003 | 高 | 需配合交叉验证 |
表里的卷绕风险栏是按能量绝对值一个哈特里估的,如果你的体系能量更大,把演化时间相应往下压。这个表不是物理定律,是我把几个约束代进去算出来的经验值,你自己体系的量级不同就要重算。
还有一个反直觉的点:评估比特数增加之后,主峰的概率会下降。因为相位被分到更细的格子里,同样的统计次数下每个格子的计数变少,噪声占比上升。所以比特数也不是越多越好,配合足够多的采样次数一起看才有意义。
6. 从相位回到势能面:接上经典 MD 的完整链路
拿到了能量点,剩下的是把散点变成能驱动积分的势函数。这一步完全在经典侧完成,但转换方式会影响整个模拟的质量。
6.1 键长扫描与势函数拟合
最直接的做法是扫描键长。取五到七个键长点,从平衡位置附近往外扩,每个点跑一次 QPE 拿电子能量,加上核间排斥能,得到总势能。然后把这一串点和一条解析势函数做最小二乘拟合。
双原子分子用莫尔斯势拟合最合适,它有三个参数:阱深、平衡键长、宽度参数。三个参数用五个以上的点拟合是超定的,可以顺便看残差判断拟合质量。如果残差明显偏大,说明点位不够或者某个点的 QPE 结果被噪声污染了。
多原子体系不能这么简单处理,通常要保持其他坐标固定做单坐标扫描,得到每条键的势能曲线,拼成一个近似的可分离势。这个近似不算严格,但对后续的动力学行为定性分析足够。
6.2 参数移动法算力的现实成本
严格意义上的量子分子动力学每步都要力,力就是能量对核坐标的导数。量子线路上算导数有成熟方法,参数移动法是最常用的:在参数上加和减一个小量,两次能量相减除以二倍的小量。
问题在于成本。一次力需要至少两次能量评估,而每个能量评估本身又是一次完整的 QPE。四个原子、三个自由度,就是六次参数移动、十二次 QPE,再乘以轨迹帧数。假设你只想跑一百帧,那也是上千次相位估计。在模拟器上还能接受,但如果有噪声,误差会在差分的减法里被放大——两个大数相减,有效位数掉得很快。
我的结论是参数移动法只适合做验证,比如挑三五个关键构型算一下力,跟有限差分的结果对比,确认势能面拟合没有系统偏差。真正跑轨迹还是走路线 A。
6.3 拟合出来的力场怎么用
拟合完的参数可以直接塞进任何经典 MD 引擎的自定义力场里。这一步没有量子计算的特殊性,就是常规的分子模拟流程。有两点经验值得提一下:
一是拟合点的密度要在平衡位置附近加密。势能面在平衡点附近的曲率决定了振动频率,而振动频率对轨迹的影响比远端的行为大得多。二是远端取样不要取太远,超过键解离点附近的势能曲线形状和莫尔斯势偏差很大,硬拟合会拉偏整体参数。
我把拟合出来的势函数代回经典积分器跑了几条短轨迹,再和全量子时间演化得到的自相关函数做对比,两者的主频能对上就说明这条链路是通的。这一步验证比单纯对比能量点更有说服力,因为它检验的是导数,而导数才是动力学真正用到的东西。
7. 踩坑实录:几个让我重跑半天的问题
这一节把前面提到的坑集中讲清楚,都是实际调试中真金白银花时间换来的。
7.1 相位符号和比特串顺序
第一个坑是符号。如果酉算子取的是负指数演化,本征相位等于负的能量乘以时间除以两倍圆周率。当能量是负的时候,相位在模一的意义下接近于一,而不是负的小数。我一开始直接按小数乘回去,得到的是接近正值的结果,跟参考能量差了一整个量级。
正确的处理是:读出的相位如果大于零点五,就认为它代表负相位,实际相位等于它减一。这个判断阈值要跟你体系的能量符号范围匹配,如果所有本征值都是正的,就不能这么简单切。
第二个坑是比特串顺序。测量结果里最左边的字符对应评估寄存器的最高位,直接转整数再除以二的 n 次方是对的。但如果你的逆 QFT 没做末尾的交换操作,字符串就是反的。我当时为了省一次交换,结果能量完全错位,排查了很久才发现是顺序问题。建议第一次跑的时候两种情况都打出来对比一下,确认哪个对。
7.2 主峰劈裂其实是好事
7.2 主峰劈裂说明输入态不是本征态
有一阵子我以为主峰劈裂是噪声导致的,后来才想明白,这在无噪声的状态向量模拟里也会出现,原因是输入的哈特里-福克态是多个本征态的叠加,相位估计分别读出每个分量的相位,自然就出来好几条峰。
这不是 bug,反而是一个诊断信号。峰的个数和高度直接告诉你输入态里混进了多少激发态分量。我的做法是把所有峰的相位都解出来,换算成能量,按高度排序,最高的那条对应权重最大的本征态。如果最高峰不是基态对应的值,说明初猜态选得不好,得换。
顺带一提,多峰结构其实可以用来读激发态能量。你不需要专门构造激发态,只要叠加里含这个分量,QPE 就会给你对应峰。这在研究振动能级时挺方便。
7.3 线路深度和门数失控
最耗时的一次调试是线路编译。评估比特数加到十之后,编译一份完整的相位估计线路要花很久,而且内存涨得厉害。原因是每一路受控演化都完整展开了 Trotter 单元,编译期没有做复用。
缓解办法有三个:一是把 Trotter 重复次数从八压到四,误差增加不多但门数减半;二是把受控化的实现从门级别降级到合成器级别,让合成器直接处理带控制比特的演化,避免先展开再控制带来的冗余;三是用转译优化等级拉高,让它合并相邻的旋转门。
我用第二条之后编译时间从几分钟降到几十秒。代价是不同版本的合成器对这的控制支持程度不一样,升级库的时候要重新验证一遍结果。
7.4 结果验证的三件套
每次改完参数我都跑这三项检查,缺一项都可能放过错误:
- 拿哈密顿量直接对角化的本征值做金标准,跟 QPE 结果逐点对,偏差超过精度带宽就查参数;
- 用两个不同演化时间交叉验证,排除相位卷绕;
- 把评估比特数加一档再跑,确认能量结果稳定,不随分辨率的提升而漂移。
三项都过了,我才认为这一组结果可以拿去做拟合。这个流程看起来繁琐,但比事后发现整条曲线整体偏移要划算得多。
噪声模型下还要加一条:换几个不同的采样次数看主峰位置的稳定性。如果主峰位置随着采样次数波动很大,说明信噪比不够,需要增加采样或者减小线路深度。我在带噪声的模拟里见过主峰位置在百分之零点五的范围内漂移,换算成能量大概千分之几哈特里,正好卡在化学精度边缘,这种情况下任何拟合结论都得打折扣。
我在实际使用中的体会是,QPE 这条链路的难点不在公式,而在参数之间的耦合关系——你压缩线路深度,精度掉;你拉长演化时间,卷绕风险来;你增加评估比特,采样成本涨。真正把这条链路跑顺的标志,不是某一次读出了正确能量,而是你能在给定算力预算下,快速估出一组可行参数并且知道误差大概在什么量级。这个判断力是靠反复跑出来的,别人给的参数表只能当起点。