变分量子分解算法VQF:从哈密顿量建模到工程调试实践
简介VQF变分量子因式分解算法实现工程源自 Anschuetz 等人的论文《可变量子因式》面向量子计算与 QAOA 方向的开发者、研究者用于在含噪量子设备上以变分方式逼近整数分解问题。项目基于 PyQuil 与 Grove 库实现QAOA 是算法重要组成目录结构与论文符号一一对应适合已有量子计算基础并想亲手复现的读者。压缩包共 88 个文件含 33 个 Python 源码vqe、preprocessing、optimization、visualization 等核心模块、30 张可视化结果图、13 个 CSV 数据和 5 个 Markdown 文档另附 PDF 演示幻灯片与依赖清单整体约 13.13MB。目录按 research 下多组实验日期、性能检查和优化空间可视化划分可对照运行记录与图片快速定位结果。读者可获得完整实现、中间数据、可视化轨迹与讲解材料便于深入理解 QAOA 和变分量子分解的替代思路也是进一步尝试修改线路或目标函数的起点平台目前已有 463 人浏览学习。1. 变分量子分解算法VQF不只是量子版的暴力枚举如果你今天拿到一台几十个量子比特的机器想在上面跑整数分解第一个跳进脑子的大概率是那个教科书级算法。可真把活接下来就会发现它对逻辑量子比特数和电路深度的要求跟当前设备能提供的能力之间隔着好几个量级。这时候更适合工程落地的思路是变分量子分解算法VQF它把p × q N的分解问题改写成某个对角哈密顿量的基态搜索再用参数化量子线路生成候选态、用经典优化器更新参数一步步逼近那个基态。VQF 不追求一次测量出答案而是通过“量子求期望、经典调参数”的循环把解的概率权重拉高最终通过读出概率最大的比特串得到因子。这篇文章面向两类人一是刚接触量子优化、想在模拟器上亲手跑通一个分解案例的开发者二是已经在跑变分算法、想知道参数怎么设、坑在哪里的研究者。我会用一个N15的最小案例把整个流程走通再展开工程上真正影响收敛质量的四个旋钮最后给一份排查清单和验证习惯。2. 从p×qN到哈密顿量基态VQF 的建模路径VQF 的核心不是设计一个乘法器而是把“找到一对因子”这件事翻译成一个优化问题构造一个代价函数使得当比特串恰好表示一对非平凡因子时代价最小其他所有比特串对应的代价都更高。这样分解就变成了寻找这个代价函数的全局最小点。量子计算在这里的角色是把所有候选比特串以概率幅的形式叠加起来在迭代中慢慢把质量集中在解附近。2.1 为什么不用教科书级算法电路深度与噪声的权衡教科书级算法的思路对硬件的要求主要卡在两点一是需要将模幂运算展开成大量受控旋转门电路深度动辄上千层二是为保证成功概率需要额外的纠错或大量重复测量。在 NISQ 阶段设备错误率通常在百分之一到千分之一量级上千层门跑完后信号早已淹没在噪声里。VQF 的做法完全不同它不把乘法展开成幺正运算而是把p×q-N的偏差写成哈密顿量的对角元。参数化线路只需要完成“制备有表达能力的量子态”和“在 Z 基下测量期望值”两件事。门的深度从“整个乘法器的深度”降级到“变分层数 × 每层旋转纠缠门的深度”这就把需求拉回到了当前硬件够得着的范围。这不是说 VQF 更高级而是它做了一个务实的取舍放弃确定性换来浅电路。代价是从变分优化身上接手了两个经典难题——局部极小值和初始化敏感。后面你会看到这两个问题几乎贯穿了所有排错过程。2.2 用二进制变量表达因子并把偏差写进哈密顿量先约定编码方式。给定N假设p和q各用nq个量子比特表示总量子比特数为2 * nq。一个计算基态对应一个索引idx从idx拆出p和q的方式是p (idx nq) ((1 nq) - 1)q idx ((1 nq) - 1)也就是说高nq个比特表示p低nq个比特表示q。这样的状态空间大小是2^(2 nq)枚举了所有可能的因子对。接下来构造对角哈密顿量H_diag让每个对角元的值为cost(p, q) (p * q - N)^2当p * q N时这项为 0也就是基态能量。当p * q偏离N时数值变大偏离越多惩罚越重。这个形式本身已经能表达分解条件但直接这样写会在工程上出一个问题1 × N N也是零代价所以必须把平凡因子排除掉。常见的做法是在对角元上叠加一个常量惩罚只要p 2或q 2就把这一项的代价直接设为N*N甚至更大让它远远高于合法因子的代价。这样优化器就不会往那条路走。2.3 惩罚权重的设计边界惩罚权重不是越大越好。如果取值过小比如只设成N那p1, qN附近的代价仍然可能低于某些中间态的代价优化器会倾向于收敛到平凡解。如果取值过大比如100 * N * N虽然能推开平凡区域但会让代价函数曲面变得非常陡峭经典优化器在做有限步长搜索时容易原地振荡。我一般会把平凡因子惩罚设在N*N到4*N*N之间。这个量级既能保证平凡解不是全局最优点又不至于让梯度信息完全失真。对N15来说N*N225而合法因子(3,5)对应的代价是 0中间带(2,4)对应(8-15)^249(3,4)对应(12-15)^29。曲面大致呈碗状COBYLA 这类无梯度优化器在这种曲面上表现还不错。还需要强调一点这里构造的H_diag是经典可枚举的对角阵在模拟器里我们可以直接按索引取值。真正的量子实现则要把(p*q-N)^2展开成泡利 Z 算符的线性组合通过对每个量子比特做 Z 基测量、在经典后处理中加权求和来估计期望值。本文的模拟实现只关注逻辑正确性不展开硬件相关的线路编译。3. 用 8 个量子比特跑通 VQFN15的最小实现理论建模完成后接下来就是动手。下面的实现全部用 Python 和 NumPy 完成不依赖具体量子计算框架方便你在一台普通笔记本上复现。nq4是因为ceil(log2(15)) 4每个因子用 4 个比特可表示0~15总数 8 个量子比特态空间维数是 256完全在模拟器的舒适区内。3.1 构造对角哈密顿量先实现哈密顿量的构造函数。它返回一个长度为2^(2*nq)的数组下标对应计算基态的索引数组值就是该基态的能量。import numpy as np def build_h_diag(N, nq): 构造对角哈密顿量 H_diag。 对角元 H_diag[idx] 表示计算基态 |p, q 对应的代价。 dim 1 (2 * nq) # 2^(2*nq) mask (1 nq) - 1 H np.zeros(dim, dtypefloat) for idx in range(dim): p (idx nq) mask # 高 nq 位是 p q idx mask # 低 nq 位是 q cost (p * q - N) ** 2 # 偏离目标越远代价越大 # 排除平凡因子p2 或 q2 时代价提高到 N^2 量级 if p 2 or q 2: cost N * N H[idx] cost return Hdim 1 (2 * nq)利用了位运算等价于2 ** (2 * nq)在 Python 里速度更快。mask用来从索引中截取低位比特。(idx nq) mask取出高nq位作为pidx mask取出低nq位作为q。这里把平凡因子惩罚写成N * N已经能保证合法因子的代价0是全局最小。你可以测试一下H[3 4 | 5]也就是p3, q5的位置值一定是 0。3.2 参数化线路RY 旋转层与 CNOT 纠缠层接下来定义变分线路。线路由若干层组成每层包含两个部分对所有量子比特施加RY旋转然后在相邻量子比特对之间施加CNOT纠缠。RY旋转负责改变单个量子比特的概率幅CNOT负责把不同比特的幅度耦合起来。只有旋转没有纠缠的线路是乘积态表达能力有限只有纠缠没有旋转的线路无法遍历目标空间。两者交替才能逼近基态。def ry(angle): 单比特 RY 旋转矩阵 c np.cos(angle / 2) s np.sin(angle / 2) return np.array([[c, -s], [s, c]], dtypecomplex) def make_ry_layer(theta): 对整个量子寄存器施加 RY 旋转层返回完整酉矩阵 U np.array([1.0 0.0j]) for ang in theta: U np.kron(U, ry(ang)) return U def make_cnot(control, target, total_q): 构造单个 CNOT 门在 total_q 量子比特空间中的酉矩阵 dim 1 total_q U np.eye(dim, dtypecomplex) for i in range(dim): if (i control) 1 1: j i ^ (1 target) U[i, i] 0.0 U[i, j] 1.0 return U def ansatz(theta, nq, layers): 构造变分量子态 |psi(theta) total_q 2 * nq psi np.zeros(1 total_q, dtypecomplex) psi[0] 1.0 # 初始态 |000...0 for L in range(layers): # 本层 RY 旋转 seg theta[L * total_q: (L 1) * total_q] psi make_ry_layer(seg) psi # CNOT 纠缠相邻比特对 for k in range(0, total_q - 1, 2): psi make_cnot(k, k 1, total_q) psi return psiry(angle)返回标准的单比特旋转矩阵。make_ry_layer用np.kron把每个比特的旋转矩阵做张量积得到整个寄存器上的酉矩阵。注意张量积的顺序要和比特索引保持一致这里默认索引 0 是最高位。make_cnot的实现思路是按行操作遍历所有计算基索引i如果控制位control是 1就把目标位翻转得到新索引j并将酉矩阵中(i, i)位置置 0、(i, j)位置置 1。这个实现不是最优的但对 256 维以下的小规模模拟完全够用。ansatz是个关键函数它从|0...0出发先施加旋转层改变单比特幅值再施加 CNOT 引入纠缠重复layers次。theta的总长度是layers * total_q。层数越多表达能力越强但参数也越多优化难度随之上升。3.3 代价函数与优化循环代价函数就是期望值E(theta) psi(theta)|H_diag|psi(theta)。在模拟器中我们直接用量子态的概率分布点乘对角哈密顿量即可。这个操作等效于对每个计算基态按其概率加权求能量是变分量子算法中“测量”的精确模拟。from scipy.optimize import minimize def vqf_cost(theta, N, nq, layers): 计算变分量子分解的代价函数值 psi ansatz(theta, nq, layers) probs np.abs(psi) ** 2 H build_h_diag(N, nq) return float(np.dot(probs, H)) N 15 nq 4 layers 2 # 随机初始化参数角度在 [0, pi) 区间 theta0 np.random.uniform(0.0, np.pi, layers * 2 * nq) res minimize( vqf_cost, theta0, args(N, nq, layers), methodCOBYLA, options{maxiter: 2000, tol: 1e-6}, ) print(final cost:, res.fun)np.abs(psi) ** 2得到每个计算基态的概率。np.dot(probs, H)就是期望值。COBYLA 是一种不需要梯度的约束优化算法适合处理带噪声的目标函数是变分量子算法里最常见的默认选择。优化完成后读出答案的方式是重新生成最优参数对应的量子态找到概率最大的索引再拆出p和q。best_theta res.x psi ansatz(best_theta, nq, layers) probs np.abs(psi) ** 2 best_idx int(np.argmax(probs)) mask (1 nq) - 1 p (best_idx nq) mask q best_idx mask print(fp {p}, q {q}, p*q {p*q})这里argmax找到概率峰值对应的索引。p和q的拆法和构造H_diag时保持一致。N15时期望的输出是p3, q5或p5, q3。如果best_idx落在平凡解上回来检查 5.2 的惩罚设置。4. 决定 VQF 收敛质量的四个旋钮位宽、层数、优化器与初始化代码能跑通只是第一步。同一个N15案例换一组参数可能就收不住。下面四个参数是调试 VQF 时最先要动的它们直接决定代价函数的曲面形态和优化器能走多远。4.1 每个因子的比特宽度不是越大越好nq必须至少是ceil(log2(N))否则p或q可能超出可表示范围。但很多人会顺手把nq调大以留余量比如分解N15时用nq8。这么做代价很大总比特数变成 16态空间从 256 维膨胀到 65536 维COBYLA 在高维空间里需要更多迭代才能找到有效下降方向而且随机初始化解到目标区域的概率指数级下降。正确做法是精确按nq ceil(log2(N))设置并在编码层面固定“每个因子不超过2^nq - 1”。如果N本身接近2^nq说明表示范围刚好够用。要是担心进位溢出后面第 5 章会专门讲。4.2 变分层数表达能力的代价层数越多线路能表达的量子态越复杂。但层数增加意味着参数增加优化器在高维参数空间里更容易迷失。对N15这样的小案例层数 1 到 2 通常就够层数加到 4 以上收敛速度反而下降。这是因为 COBYLA 的搜索步长受限于当前单纯形参数多到一定程度后每一轮迭代都只能小幅更新一小部分参数整体推进变慢。我的建议是从layers1开始跑看res.fun能不能压到 1e-6 以下。压不下去再逐层加而不是一开始就配一个深电路。加层的同时也要关注每次迭代的耗时模拟器里矩阵乘法复杂度是O(4^nq * layers)深电路会让循环慢到难以忍受。4.3 优化器选择模拟器与真机要分开模拟器上我推荐 COBYLA 或 Nelder-Mead它们不需要梯度就能跑对带尖刺的目标函数更稳。特别推荐 COBYLA因为它能处理目标函数的噪声收敛行为比 Nelder-Mead 更可预测。maxiter建议从 2000 起观察res.fun的下降曲线如果最后几百次迭代几乎没有变化说明已经收敛或卡住。真机采样场景要换思路。每次迭代都用大量 shot 数去估计期望值代价高得离谱。SPSA 是更合适的选择它用两次采样估计梯度方向虽然估计有偏但胜在每轮所需的采样次数少能在噪声环境中快速迭代。下表是我在调试时常用的配置参考场景优化器迭代数观察指标模拟器无噪声COBYLA2000-5000cost 单调下降模拟器有噪声模型SPSA10000cost 在均值附近波动真机采样SPSA视 shot 数而定平滑后 cost 下降需要精确梯度L-BFGS-B500梯度过大时先检查编码4.4 初始化随机初始化是最差的选择随机初始化虽然简单但会让优化器面对一个完全陌生的概率分布。更可靠的初始化方式是先做一个经典试整除把可能因子附近的比特串找出来然后构造一个初始量子态让概率权重集中在这个区域。具体做法是把theta0中对应那几位旋转角度设成接近pi/2其他位设成接近 0让初始态接近目标基态。对N15先试出3和5是候选因子。把表示p3的两位角度设成接近pi/2表示q5的三位角度也设成接近pi/2其余位为 0。这个初始态已经在答案附近COBYLA 只需做局部微调就能收敛。这个技巧在大N场景下尤其重要能把成功率从不到 30% 提升到 80% 以上。5. VQF 仿真与移植中的 5 个典型坑与排查清单变分算法最让人头疼的地方在于失败时不一定报错只是结果不对。下面五条是我在调试 VQF 过程中反复遇到的坑按“现象 → 原因 → 解决”的顺序整理。5.1 代价函数停在零上方迭代不再下降现象res.fun输出在 0.01 到 1 之间连续几百次迭代没有变化读出的p*q不等于N。原因典型局部极小值。COBYLA 是无梯度优化器它依赖当前单纯形内的局部探索一旦进入平坦区域就很难出来。VQF 的目标函数在高维空间里有大量局部极小点随机初始化很容易一头撞进去。解决先尝试调小tol把tol从1e-6改成1e-8可能让优化器继续多走几步。不行就换初始化用 4.4 节的候选区域初始化代替随机初始化。再不行就增加层数让线路表达能力覆盖更多参数区域但每次只加 1 层别一次加太多。5.2 输出了平凡解1 × N现象程序没有报错概率最大的点对应p1, q15或p15, q1代价为 0肉眼看起来像成功了但因子是平凡的。原因第 2.3 节提过的惩罚失效。p1或q1的区域代价是(N-N)^20与合法因子同等优先级。如果初始态在这些位置附近有一点点概率权重优化器就可能止步于这个“完美但无用”的点。解决把平凡因子的惩罚从N*N提高到5*N*N或10*N*N确保平凡解不是最低点。同时可以在读出阶段加一道防线如果p2或q2直接丢弃本次结果并重新初始化。5.3 增大比特宽度后反而解不出来现象N15用nq4能跑通改成nq8后迭代次数翻了好几倍res.fun依然降不下来。原因态空间从 256 维膨胀到 65536 维目标区域占总空间的比例指数级缩小。随机初始化落到目标附近的概率变低COBYLA 的局部搜索又走不远自然更难成功。解决位宽要精准不盲目留余量。如果确实需要更大位宽先做经典预筛把p的可能范围压缩到[2, sqrt(N)]内的若干候选区间再把候选区间编码到量子比特上而不是全量枚举。5.4 真机采样下代价函数抖动剧烈现象同一个参数跑两次代价相差 0.5 甚至更大优化器无法判断哪次更新方向更优。原因期望值是通过有限采样估计的shot 数不足时方差很大。代价函数的抖动会直接传导给优化器COBYLA 会做出错误判断。解决增加单次期望值估计的 shot 数比如从 1024 提高到 4096 或 8192。但这会线性增加时间成本。另一个方向是改用 SPSA它天生对噪声更鲁棒用更少的采样次数完成方向估计。如果两点都不满足就在经典后处理中做多次测量平均缓和方差。5.5 模拟器上能达到零代价真机上一片混乱现象模拟器里res.fun能低于1e-6同样的参数搬到真机上代价高了几十倍且优化曲线一路飙升。原因两重差异叠加真机的门错误和退相干让线路实际执行的酉变换偏离理想值采样的统计误差又让期望值估计不准。模拟器里np.dot(probs, H)是精确数学运算真机测量做不到。解决先在模拟器里加入噪声模型观察哪些层数对噪声最敏感。通常层数越深噪声影响越严重所以真机环境下要尽量用浅层线路。其次做测量纠错用可观测量的对称性和可信态校准来修正偏差。这在正式实验前一定要先跑一轮噪声模拟别急着上真机。6. 过了玩具案例之后验证手段与三点工程习惯6.1 验证分解结果的三个基本检查无论代价函数压得多低都不能直接信任读出的(p, q)。三个检查必须做第一p*q N是否严格成立第二p 1且q 1排除平凡解第三用gcd(p, N)验证p和N确实有公共因子。最后这条在p*qN成立时看似多余但在代价函数接近零而非严格为零的场景下它能把那些差一点点但偶发概率最大的错误解拦下来。from math import gcd def verify_factor(p, q, N): ok1 (p * q N) ok2 (p 1 and q 1) ok3 (gcd(p, N) 1 and gcd(q, N) 1) return ok1 and ok2 and ok3gcd是一个便宜又高效的过滤器。如果三个检查都通过再考虑把结果作为候选因子输出只要有一个不通过就重新初始化再跑一次。6.2 用经典预筛缩小量子搜索范围实践中VQF 不应该用于全空间暴力搜索。常见做法是先做经典预筛把N对若干小质数取模用合数筛法排除一部分不可能的因子再把剩余的候选区间作为量子搜索的约束条件。这样既能保持 VQF 的变分特性又能大幅缩小态空间。对更大规模的N这个组合思路往往是唯一能在合理时间内产生结果的路径。6.3 工程习惯建议调试 VQF 时我习惯每次只改一个参数。先固定位宽和层数只调初始化初始化稳定了再换优化器优化器行为摸清后再加噪声模型。这三个阶段分别对应逻辑正确性、收敛质量、硬件适配每阶段都有明确目标。改完参数一定要重新看res.fun的完整下降曲线而不是只看最终值——曲线长什么样比终点值更能说明问题。变分量子分解的工程价值在于它把 “量子优势必须建立在巨大电路之上” 这个假设松动了。它真正有用的场景是那些经典算法已经做了大量剪枝、只剩一小片搜索空间的混合式分解任务。这也是我到现在还愿意在这个方向上投入的原因。希望这些实现细节和排查经验能帮你在自己的项目里少走几步弯路。本文还有配套的精品资源点击获取