简介:针对量子计算中的变分量子分解(VQF)算法,这份压缩包提供了论文《可变量子因式分解》所提出算法的完整 Python 实现。资源面向量子计算研究者、QAOA 学习者以及希望了解量子因式分解编码的进阶开发者,代码符号与论文保持一致,便于对照阅读。包体内共 88 个文件,以 Python 脚本为主(33 个 py),包含 vqe、预处理、优化、可视化等核心模块;另有 30 张 png 图像、13 个 csv 数据、Markdown 与 txt 说明文档、PDF 演示文稿等,整体约 13.13 MB,目录按 research 阶段划分,适合逐步复现实验。已有 463 人学习下载。除源码外,还附带了 requirements、测试文件、运行冻结依赖以及演示幻灯片,能帮助读者快速搭建 pyquil/Grove 环境,并理解 QAOA 在其中如何用于优化。对于想复现论文实验或开展量子分解相关工作的人来说,这份资源具有直接的参考价值。
1. 变分量子分解:一台 NISQ 机器也能跑的 factoring-integers 方案
整数分解在量子计算里被反复讨论了三十年,每次聊到 factoring-integers,大家第一反应都是 Shor 算法,因为它的经典复杂度优势确实漂亮。可真要在一台量子计算机上跑出分解结果,Shor 需要带纠错的逻辑比特、极高的门保真度和海量的物理比特开销,NISQ 阶段的小芯片基本凑不齐。这个“理论上很强、工程上遥遥无期”的落差,让 quantum-computing 社区这几年把大量注意力转向了 optimization-algorithms:用浅电路估算成本函数期望,再交给经典优化器去调参数。VQF(变分量子分解)正是这类思路里专门面向整数分解的实现,仓库用 Python 写完,配套模拟器和可视化脚本。它把分解 N=a×b 改写成寻找 (N-ab)² 的最小值,用参数化量子线路制备候选位串,再用 COBYLA 这类优化器迭代逼近因子。适合刚接触变分算法的读者从代码入手,也适合已经在调 QAOA、想对比线路设计的人。
2. 数学结构先立住:成本函数、位宽选择与参数化线路
2.1 为什么选 VQF 而不是 Shor:NISQ 约束下的取舍
先说明白一个容易被忽略的前提:VQF 不是来替代 Shor 的,它是给 NISQ 设备留的一条现实路线。Shor 算法的量子部分依赖量子傅里叶变换,需要大量受控旋转门,级联误差积累非常快,没有容错保护根本跑不到多项式级的优势。VQF 的定位是“浅电路 + 经典优化器”,单轮电路深度可以控制在几十层以内,模拟器上跑通基本没有保真度压力,将来换到真实芯片也比较容易做误差缓解。
所以拆这个仓库时,我心里把它当成一个混合量子经典算法的样板,而不是一个“能分解任意大整数”的黑匣子。它的价值在于:成本函数怎么构造、位宽怎么选、线路怎么设计、读出结果怎么校验,每一步都能在代码里对应到具体实现,这是很多论文里含糊带过的地方。
2.2 成本函数与哈密顿量展开:(N-ab)² 如何变成 Pauli 片段
VQF 的第一步是把整数分解改成连续优化问题。定义
C(a,b) = (N - a·b)²
当 N = a·b 时目标为零。这里 a、b 不是直接作为连续变量处理,而是把它们的二进制位映射到量子比特的计算基态上。令 a 的位串为 a_i、b 的位串为 b_j,每个二进制位取值 0 或 1,那么 a = Σ a_i·2^i,b = Σ b_j·2^j。在量子线路里,这种取值可以用投影算子 (I - Z_i)/2 表示,其中 Z_i 是作用在第 i 个量子比特上的 Pauli Z。把这组投影算子代回 C(a,b),整个目标函数就重写成一个作用在计算基态上的哈密顿量:
H_cost = (N - AB)²
其中 A、B 是由 Pauli Z 组合成的算符。展开后你会得到大量形如 Z_i、Z_i Z_j、Z_i Z_j Z_k 的项,每一类前面带一个权重系数。量子态在计算基上投影到某个 |a⟩|b⟩ 时,期望值刚好等于经典目标函数值。
为了直观理解这个展开的规模,可以写一个很小的枚举代码,它不会用于最终求解,但能帮你看清项数的增长方式:
import itertools def inspect_cost_terms(N, n_A, n_B): """ 枚举所有位串组合,打印 (N - a*b)^2 的非零分布。 n_A/n_B 是 a、b 各自的比特数。 """ nonzero = 0 total = 0 for bits in itertools.product([0, 1], repeat=n_A + n_B): a = 1 + sum(bits[i] << i for i in range(n_A)) b = 1 + sum(bits[n_A + i] << i for i in range(n_B)) w = (N - a * b) ** 2 total += 1 if w != 0: nonzero += 1 return total, nonzero # N=21,a 和 b 各用 3 个比特,搜索空间是 2^6 = 64 个组合 total, nonzero = inspect_cost_terms(21, 3, 3) print(total, nonzero)这段代码的逻辑是把所有可能位串都过一遍,统计目标函数非零的位置有多少。n_A=n_B=3 时总组合只有 64,但当 n_A 和 n_B 各增加到 5,组合数就变成 2^10=1024;直接枚举不是求解手段,而是帮你理解为什么真实实现要用稀疏矩阵和项缓存,否则等位宽一上去内存立刻失控。
2.3 位宽与对称性:把搜索空间砍到平方根量级
很多第一次接触 VQF 的人会犯一个错误:把 a、b 的位宽设成和 N 一样宽,结果搜索空间指数膨胀,优化器在巨大的离散空间里完全找不到方向。正确做法是先利用对称性。因为 N = a·b 与 N = b·a 等价,只需要让 a ≤ b,所以 a、b 的位宽都可以取在平方根附近:
n_A = n_B = ceil(log₂(√N))
以 N=21 为例,√21≈4.58,log₂(4.58)≈2.19,向上取整得到 3,意味着 a、b 各用 3 个比特就能覆盖 1 到 7 的范围,而 3×7=21 正好在覆盖区间内。这个“位宽跟着根号走”的经验,比按 N 的比特位宽硬编码省掉大约一半的搜索空间。
另一个容易忽略的预处理是除掉 2 的幂。如果 N 是偶数,先把 2 的因子全部提出来,剩下的奇数部分再交给 VQF。原因很简单:成本函数 (N-ab)² 在 a=1、b=N 时也会等于零,这是一种毫无信息量的平凡解。VQF 的目标是找到非平凡的因子,而奇数目标能让平凡解只出现在搜索空间的边界上,降低优化器掉进边界陷阱的概率。仓库代码里把这个预处理放在构造函数里,拆包时看到这一步会觉得很不起眼,但少了它后面跑多少次都不稳定。
2.4 参数化线路与 QAOA 的关系:同类优化算法,不同问题哈密顿量
VQF 的量子线路本身并不复杂,常见实现是 hardware-efficient ansatz:先对全部量子比特加一层 RY 旋转,再做一层相邻比特间的 CZ 纠缠门,这样作为一个 block 反复堆叠 depth 次。每次迭代中,经典优化器输出一组角度 θ,线路用这些角度制备一个量子态,通过采样估算该量子态下 H_cost 的期望值,然后优化器更新 θ 继续下一轮。角度数量的计算公式是:
n_angles = (n_A + n_B) × 2 × depth
比如 n_A=n_B=3、depth=3 时会有 18 个待优化参数。这个规模对 COBYLA 这类无梯度优化器来说相当友好。
看到这里你会发现,VQF 和 QAOA 的本质骨架是一样的:都是参数化量子电路制备候选态,都是靠测量目标哈密顿量期望驱动参数搜索,区别只在 H_cost 怎么构造、线路的纠缠模式怎么设计。QAOA 的 mixer 和 cost 哈密顿量由组合优化问题的图结构决定,而 VQF 的 H_cost 来自乘法运算的二进制展开。因此你在 QAOA 上调参的经验,比如学习率、容差、shot 数量,在 VQF 上基本都能直接迁移,这一点对熟悉 optimization-algorithms 的读者来说会非常顺。
3. 拆 Python 实现:从参数类到求解器主循环
3.1 仓库结构:五类文件先对号入座
拆这个仓库时我先把文件按职责分成了五类,方便后面定位问题:
| 文件/目录 | 职责 |
|---|---|
| vqf_config.py | 参数定义,集中管理 N、位宽、深度、优化器 |
| hamiltonian.py | 成本哈密顿量的构造、项合并、稀疏矩阵导出 |
| circuit.py | 参数化线路的构建,后端接口抽象 |
| optimizer_wrapper.py | 封装 scipy 优化器,支持 COBYLA/SLSQP/Nelder-Mead |
| demo.py / tools/ | 命令行入口、可视化与批量验证脚本 |
这个划分比较干净:参数、模型、求解、调用四层分离。如果你之前拆过别的量子算法包,会发现大多数仓库的问题都出在层与层之间耦合太紧,VQF 这份代码在这点上做得不错,改后端、换优化器都不用动核心逻辑。
3.2 参数先行:VQFConfig 数据类说明
参数是这类算法最容易翻车的地方,所以仓库里把配置做成了独立数据类。拆包时我习惯直接把这类配置打印出来跑一遍,确认所有参数都有明确解释,避免后面改代码时还要回头翻构造函数。
from dataclasses import dataclass @dataclass class VQFConfig: N: int = 21 # 待分解的整数,必须是奇数 n_A: int = 3 # 因子 a 的比特数 n_B: int = 3 # 因子 b 的比特数 depth: int = 3 # ansatz 堆叠层数 shots: int = 8000 # 每次期望估计的采样次数 optimizer: str = "cobyla" max_iter: int = 150 init_strategy: str = "warm" # warm / random / zero backend: str = "statevector" # statevector / samplingN、n_A、n_B 三个参数直接决定搜索空间和线路规模,是最容易出现配错的一组。depth 控制表达能力,太小会欠拟合导致找不到解,太大则让优化收敛变慢,实际使用中 3 到 5 层是常见区间。shots 在高噪声模拟里尤其关键,过小时期望估计方差太大,优化器会把噪声当成梯度方向;过大则每次迭代耗时明显增加。init_strategy 我建议优先用 warm,后面避坑部分会说明原因。
3.3 求解器主循环:VQFactorizer 类怎么转起来
核心求解类的结构并不复杂,原理是先构造 H_cost 的分解项,再在每次迭代里构建线路、算期望、喂给优化器。仓库里的实现大致是这样的结构:
import numpy as np from scipy.optimize import minimize class VQFactorizer: def __init__(self, cfg: VQFConfig): self.cfg = cfg self.cost_ham = build_cost_hamiltonian(cfg.N, cfg.n_A, cfg.n_B) self._history = [] def _expectation(self, theta: np.ndarray) -> float: # 用当前角度构建线路,测量 H_cost 的期望 circuit = build_ansatz(theta, self.cfg) val = simulate_expectation(circuit, self.cost_ham, shots=self.cfg.shots) self._history.append(val) return val def solve(self): x0 = self._initial_angles(self.cfg.init_strategy) res = minimize( self._expectation, x0, method=self.cfg.optimizer, options={"maxiter": self.cfg.max_iter, "tol": 1e-3}, ) return self._read_factor(res.x)逻辑说明:_expectation 是每次优化的核心回调,它负责把当前角度转换成线路并得到期望值;代价函数的计算完全被封装在 simulate_expectation 里,这给后续替换真实量子后端留了接口。solve 则把初始角度、优化器、读数三件事串起来。参数上要特别注意 tol 的值,它直接决定优化器什么时候认为收敛,后面讲噪声时我会说明为什么这个默认值需要按场景调整。
3.4 读出与回代:位串到因子的最后一步
优化结束后,当前角度对应的量子态并不保证在某个计算基上,因此不能直接把优化器返回的参数当作因子。常见做法是:对最终态多次采样,统计出现频率最高的几个位串,把它们分别翻译成 (a, b),再代回 N 做整除验证。仓库里专门封装了一个 _read_factor 方法:
def _read_factor(self, theta, top_k=5): bitstring_counts = sample_bitstrings(theta, self.cfg, n_samples=self.cfg.shots) candidates = [] for bitstring, count in bitstring_counts.most_common(top_k): a = bits_to_int(bitstring[: self.cfg.n_A]) b = bits_to_int(bitstring[self.cfg.n_A :]) if a * b == self.cfg.N: return a, b candidates.append((a, b, count)) return None, None, candidates # 没有直接命中时返回候选列表读出这一步是整个 VQF 里最容易出现“结果看起来对但实际不对”的环节。位串到整数的转换方式、比特顺序、是否包含偏移量,任何一个不一致都会让验证失败。仓库里 bits_to_int 支持 lsb 和 msb 两种顺序,我强烈建议在项目最开始就固定一种并写测试,不要等到优化跑了几百轮才发现因子读反了。
4. 跑通与选型:代价曲面、优化器对比和噪声参数
4.1 最小复现:一条命令分解 N=21
仓库提供的 demo.py 可以直接跑通第一个例子,命令行参数设计得很直白:
python demo.py --N 21 --n_A 3 --n_B 3 --backend statevector --optimizer cobyla这条命令会依次做四件事:打印配置、构造 H_cost、跑优化、输出因子。如果你装好依赖后第一次跑就得到输出,说明整个链路已经通了。第一次跑我的建议是不要改任何参数,先用默认值确认环境没问题,然后再开始动 N、位宽和 depth,否则环境问题和参数问题混在一起,排查时很难定位。
4.2 预先看成本曲面:先确认优化目标不是玄学
在跑优化之前,先把目标函数的曲面画出来是个很好用的习惯。tools/plot_cost.py 会扫描全部位串组合并把对应的 (N-ab)² 值画成热力图。N=21、n_A=n_B=3 时,能看到最小值出现在 (3,7) 附近,而且这个曲面不是简单的碗状,而是有许多局部小平台。
这个曲面可视化有两个作用:一是检验位宽设置是否合理,如果最小值跑出了边界外,说明 n_A/n_B 给小了;二是让你对优化器的工作难度有预期。VQF 的优化过程本质上是在一个离散采样出来的曲面上找最低点,曲面不平滑、到处都是平台,这决定了无梯度优化器往往比依赖梯度的 SLSQP 更稳妥,因为梯度在这里经常会指向噪声而不是真正下降的方向。
4.3 优化器怎么选:无噪声模拟里的对照
仓库的 optimizer_wrapper.py 里封装了几种常见优化器,我在无噪声 statevector 后端下把 N=21 跑了几组,得到的大致印象如下表:
| 优化器 | 典型迭代次数 | 直接成功解比例 | 特征 |
|---|---|---|---|
| COBYLA | 60~120 | 高 | 默认首选,对平坦曲面容错性好 |
| SLSQP | 30~70 | 中 | 需要可用的梯度方向,噪声下容易抖 |
| Nelder-Mead | 60~180 | 中高 | 对初始角度敏感,重试效果明显 |
| SPSA | 40~80 | 高 | 每步只做两次采样,适合真实设备 |
这个结果只代表我在这个仓库默认参数下的观察,不同 seed 之间波动不小,不建议当成 benchmark 数据。但从趋势看,COBYLA 在绝大多数场景下是最省心的选择;如果你知道要跑真实硬件、采样代价高,SPSA 更合适,因为它每一步只需要两次目标函数估值。
4.4 含噪声时看什么:shots 与容差的关系
切到 sampling 后端后可以给模拟增加去极化噪声,控制参数是 noise_level。噪声对 VQF 的影响和经典优化里常见的“偏差与方差”问题很像。当 noise_level 从 0 增加到 1e-3 时,期望估计会出现一个系统性偏差,这时把 shots 从 8000 加到 80000,你会发现曲线变得更平滑,但收敛到的数值并没有更接近零,因为偏差并没有随采样量增加而减小。
这里有个反直觉的点:含噪声场景下,先把 noise_level 调低比拼命加 shots 更有效。仓库里用了一个很实用的经验公式来设置容差:
# 把优化器容差和采样噪声关联起来 expected_std = 1.0 / np.sqrt(cfg.shots) tol = 10 * expected_std意思是:当 shots=8000 时,单次期望估计的标准差大约在 0.01 量级,容差设成 0.1 以上才不会被噪声干扰。如果你把 tol 设成 1e-4,优化器会在它根本无法分辨的尺度上做无用功,最后停在半山腰却报告收敛成功,这是后面要重点排的坑之一。
5. 常见问题与排查:VQF 输出假解的五个实战坑
5.1 假象一:多次运行得到同一位串,但验算不通过
现象:同一个 N 连续跑好几次,输出位串几乎一模一样,但做整除验证全部失败。
原因:优化器陷入了一个与因子无关的局部极小点。这类局部极小点通常出现在初始化角度全为零或全在某个对称点附近时,线路退化成一个几乎没有表达能力的恒定状态,所有运行都从同一起点出发,自然收敛到同一个错误答案。
解决:把初始化策略从 zero 改成 warm。具体做法是先从经典角度估算一批合理的 a、b 候选值,选成本最低的一个,再把它对应的位串通过逆线路映射成初始角度。这样优化的起点本身已经带有正确方向,而不是让优化器在一片平坦区域里盲搜。
5.2 假象二:位序反了,验证必挂
现象:优化过程看起来完全正常,代价函数也确实在下降,但输出因子怎么验都不对。
原因:量子后端返回的 bitstring 排序与仓库里的 bits_to_int 预期相反。常见的是 lsb 与 msb 的寄存器顺序问题,采样结果里第一个比特到底是最高位还是最低位,每个后端约定不完全一样。
解决:在 _read_factor 里固定只用一种顺序,并在仓库测试里同时覆盖 lsb 和 msb 两种读法。我个人的习惯是全部转成 lsb 再进 bits_to_int,因为这种方式把低位放在数组开头,阅读逻辑更接近二进制展开式。
5.3 假象三:位宽加上去后,哈密顿量项目膨胀
现象:n_A、n_B 从 3 加到 4 或 5 后,程序开始阶段就明显卡顿,内存占用直线上升。
原因:直接展开 (N-ab)² 会同时出现一次项、二次项、三次项和四次项的组合,Z_i Z_j 这类双体项数量增长是 O(nₐ²n_b²),更高阶项的数目会更快膨胀。暴力展开在 n 较小时没问题,位宽过 10 以后就是一场灾难。
解决:真实实现不要用项枚举,而是构造稀疏矩阵。每个计算基 |a⟩|b⟩ 对应的成本值可以直接计算出来填进对角矩阵,对于 n_A+n_B ≤ 12 的场景,稀疏矩阵的构造时间和内存都在可控范围内。我在本机测试时把 n_A+n_B=12 的哈密顿量改用稀疏对角形式,构建时间从几十秒降到了不到一秒。
5.4 假象四:噪声下 COBYLA 提前宣布收敛
现象:在高 noise_level 下,优化器返回 success=True,但代价函数值离零很远,因子自然也读不出来。
原因:容差设置脱离了采样噪声水平。当 shots=8000、噪声标准差在 0.01 量级时,你把 tol 设成 1e-4,优化器根本分辨不出相邻两次迭代是真实下降还是随机波动,于是随便一个平坦区就被判定为收敛。
解决:优先按噪声水平设置容差,经验公式是 tol 取期望标准差的 10 倍左右;同时不要只看 optimize.success,要强制校验最终位串是否真正满足 a×b=N。仓库里我在优化器封装层加了一个 awareness 参数,专门把噪声水平传进容差设置,避免这类无声翻车。
5.5 假象五:输出了 a=1、b=N 的平凡分解
现象:优化很顺利,验证也通过了,但输出的是 1×N=N,完全没有分解价值。
原因:成本函数在平凡解处同样为零,如果位宽覆盖范围包含了 1,优化器有相当大概率被边界吸引。
解决:进入 VQF 之前先把 2 的幂因子提干净,保证待分解数是奇数;在读出阶段增加一个平凡解检查,如果 a=1 或 b=1,丢弃并换一个初始化策略重新跑。仓库里 check_factor 同时做了整除验证和平凡解检查,拆包后不要跳过这层过滤。
6. 验证与进阶:把小批量成功率的统计挂进工作流
最后分享一个我一直在用的实操技巧:不要把 VQF 当成一个“跑一次出结果”的黑匣子,而是给它挂上成功率的统计流程。因为变分算法天然有随机性,单次成功不能说明算法可行,单次失败也不能说明方案无效,只有多次运行后的成功率分布才是可靠的判断依据。
我一般把运行结果分成三个桶:成功、平凡、错误。成功是 a×b=N 且 a、b 都不是 1;平凡是输出了 1×N;错误是验证失败或优化未收敛。然后对每个 N 跑 10 到 20 次,统计三类结果的比例。这个流程直接在命令行完成:
python tools/benchmark.py --Ns 15,21,35,77 --trials 20 --backend statevector输出的 CSV 里每一行包含 N、trial_id、found_a、found_b、status、final_cost,我用一段简单的脚本聚合结果:
import pandas as pd df = pd.read_csv("benchmark_results.csv") summary = df.groupby("N")["status"].value_counts(normalize=True).unstack() print(summary[["success", "trivial", "error"]].fillna(0))从这个汇总表能很直观地看到:某个 N 如果 trivial 占比过高,说明初始化策略没有避开平凡边界;error 占比高则优先怀疑噪声容差或 depth 不够。我把这个统计流程加进去之后,VQF 的调试效率明显提升,不再需要对着单次输出的代价函数值猜问题。
还有一个值得养的进阶习惯:每次跑完都把代价下降曲线和最终位串写进日志,而不只是打印因子。代价曲线能告诉你优化器是真正收敛了还是提前停了,最终位串能帮你发现位序之类的系统性问题。上次我被一组假解带偏,就是只看收敛标志、没看输出位串;从那以后我每次跑完都强制走一遍“代价曲线 + 位串回代 + 成功率统计”三步,已经很少被这种无声的失败坑到了。希望这些拆包经验能帮你在 VQF 上少走几段弯路。
本文还有配套的精品资源,点击获取