XQuad Max-Cut 端到端示例深度解析:从 QUBO 建模到求解验证的完整流水线

发布时间:2026/10/12 2:04:11
XQuad Max-Cut 端到端示例深度解析:从 QUBO 建模到求解验证的完整流水线
【免费下载链接】xquadA rust implementation of the Quip Networks quantum virtual machine.项目地址https://gitcode.com/gh_mirrors/xq/xquad点击查看免费下载本文以 XQuad 仓库中的examples/maxcut示例为线索完整讲解如何用xqcp约束编程 DSL 将加权图最大割问题建模为 QUBO二次无约束二值优化模型经 XQVM 编码器生成模型、xqsa求解器采样、验证器独立校验、解码器还原二染色划分的六段式流水线。读完本文你将掌握 Max-Cut 的 QUBO 数学形式、runner.py中每个 DSL 调用对应的底层指令行为、命令行参数的完整含义以及如何把该示例改造成带固定终点的最小 s-t 割变体。问题定义与 QUBO 建模Max-Cut 的目标是给定一个带权图把所有节点染成两种颜色即二划分2-colour partition使得横跨两个染色集合的边的总权重最大。每个节点只能选择一侧0 或 1这是一个典型的二值组合优化问题。在 XQuad 中求解器只能最小化一个数字模型的能量因此必须把最大化割权重改写为最小化某个二次函数。示例采用如下形式输入num_nodesintedgesVec扁平化的(i, j, w)三元组共3*|E|个条目即每一条边依次存放起点、终点、权重三个整数模型n个二值变量每个节点一个x[v] ∈ {0, 1}表示节点v被分到哪一侧目标对每条边(i, j, w)累加线性项-w*(x_i x_j)与二次项2w*x_i*x_j。为什么这样写对单条边而言其贡献为H_edge -w·x_i - w·x_j 2w·x_i·x_j -w·(x_i x_j - 2·x_i·x_j)其中x_i x_j - 2·x_i·x_j正是二值变量的XOR当x_i ≠ x_j边跨越划分时等于 1当两者相等时等于 0。于是该边在跨越划分时贡献-w否则贡献 0。对所有边求和后整个哈密顿量H恰好是割权重的相反数——最小化H即最大化割权重。这正是仓库中二次模型工作示例的推导结论examples/maxcut/runner.py构建的正是这个模型。注意最大化问题改写为最小化时所有系数符号取反因此文档和输出中energy通常为负值其绝对值即为割权重。在--n 5 --seed 42的默认示例中输出energy: -354与cut_weight: 354严格互为相反数。DSL 方法runner.py 的核心建模代码examples/maxcut/runner.py的build_problem()是建模的核心它只用八个 DSL 方法就完成了整个问题定义。以下是其逐段拆解对应源码problem Problem(MaxCut) num_nodes problem.input(num_nodes, typeTypes.Int) edges_in problem.input(edges, typeTypes.Vec) problem.define_model(sizenum_nodes, domainDomain.BINARY) edge_count problem.stow(edge_count, edges_in.veclen() // 3) with problem.range(0, edge_count) as e: offset e * 3 i problem.stow(i, edges_in.get(offset)) j problem.stow(j, edges_in.get(offset 1)) w problem.stow(w, edges_in.get(offset 2)) problem.model.linear[i].add(-w) problem.model.linear[j].add(-w) problem.model.quadratic[i, j].add(w * 2) partition problem.output(partition, typeTypes.Vec) with problem.range(0, num_nodes) as node: partition.append(problem.sample.getline(node))各 DSL 方法的作用如下与文档中 [DSL methods used] 一节逐条对应DSL 调用作用底层对应problem.input(name, type)声明类型化的 calldata 输入返回符号化的InputRef编码器输出INPUT rN指令按调用顺序分配寄存器r0、r1…且必须在define_model()之前调用problem.define_model(size, domain)分配二值 XQMX 模型size个变量编码器输出BQMX指令Domain.BINARY对应{0,1}域此外还有 SPIN 的SQMX与 Integer 的XQMX见三域说明problem.stow(name, expr)把中间计算结果绑定到命名寄存器编译为STOW rN供后续指令复用problem.range(start, end)发出 RANGE 循环e是循环变量LoopVar编译为RANGE ... NEXT块edge_count循环遍历每一条边node循环遍历每个节点model.linear[i].add(w)在变量i上累加线性偏置编译为ADDLINE指令model.quadratic[i, j].add(w)累加变量i、j之间的二次耦合编译为ADDQUAD指令对角元quadratic[i,i]在二值域等价于线性项因为x² xproblem.output(name, type)声明类型化输出槽解码器为每个输出分配一个槽位编译为PUSH {slot} / OUTPUT rNproblem.sample.getline(node)从样本位串中读取一行即节点node的取值编译为GETLINE指令在解码器中把样本位串还原为划分向量值得注意的细节扁平化输入edges不是嵌套结构而是3*|E|个整数的扁平 Vecedge_count edges_in.veclen() // 3用veclen()计算边数每条边的三个字段通过edges_in.get(offset k)读取。寄存器分配是全局递增的从 xqcp 的寄存器分配器实现 可以看到_RegisterAllocator是一个简单的递增计数器起始 0超过r255会在编译期抛出RuntimeError。输入的num_nodes、edges先占用r0、r1之后每个stow()、循环变量、vec()与输出寄存器按调用顺序依次认领。这也解释了为什么编码器输出中的寄存器编号与runner.py的调用顺序一一对应。problem.model与problem.sample是隐式引用define_model()调用后可通过属性访问模型源码未定义模型就访问会抛出RuntimeError(No model defined — call define_model() first)。六段式流水线三程序架构与求解器Max-Cut 的端到端流程分为六步其中编码器、验证器、解码器是三个相互独立的 XQVM 程序仅通过 calldata 输入与输出槽通信三程序架构的完整说明见文档1. CP (xqcp) —— 构建随机加权完全图声明每个节点一个二值变量按边累加 QUBO 线性/二次项 2. Assemble —— problem.compile() 在内存中生成三段 .xqasm 文本encoder/verifier/decoder 经 xquad.asm 汇编为字节码 3. Encode —— 在所选 XQVMpython 或 rust上运行编码器产出 XQMX 模型 4. Sample —— 求解器SA/QPU/GPU对模型采样 5. Verify —— 验证器检查样本均为二值并独立重算能量本问题未声明约束故只做域检查 6. Decode —— 解码器从样本位串还原二染色划分注意problem.compile()返回的CompiledPrograms(encoder, verifier, decoder)是三段独立的.xqasm字符串编译过程不产生共享的中间表示编译文档。三个程序的 calldata 与输出槽契约是固定的程序Calldata按序输出编码器每个problem.input()一项按调用顺序槽 0模型验证器每个problem.input()一项再加模型、样本槽 0energy槽 1valid解码器样本、N每个problem.output()一个槽按声明顺序runner.py的run()函数源码正是按此契约驱动vm VM(backendbackend) vm.set_calldata([n, flat]) # 编码器输入 vm.set_output_slots(1) vm.run(programs.encoder) model vm.outputs()[0] # XQMX 模型 solver build_solver(solver_name, seedseed) sample solver.solve(model).sample # 求解发生在 VM 之外 vm VM(backendbackend) vm.set_calldata([n, flat, model, sample]) # 验证器输入 vm.set_output_slots(2) vm.run(programs.verifier) energy, valid vm.outputs() vm VM(backendbackend) vm.set_calldata([sample, n]) # 解码器输入 vm.set_output_slots(1) vm.run(programs.decoder)这里体现了三程序架构的核心设计XQVM 的指令集93 条没有任何调用求解器的指令——求解发生在宿主代码中、两次 VM 运行之间三程序文档。xquad.vm.VM是统一的解释器接口源码通过VMBackend.RUST/VMBackend.PYTHON在 Rust FFI 运行时与纯 Python 参考实现之间切换set_calldata()、set_output_slots()、run()、outputs()是四个关键方法。--interpreter参数选择的正是这个执行轴与--solver选择的求解轴完全正交一个正确的模型在两种解释器上总是产生相同的valid与energy。由于 Max-Cut不声明任何约束验证器中的域检查循环; Validity checks 只确认每个样本值属于{0, 1}而ENERGY重算是真实发生的验证器从模型与样本出发、用与求解器无关的精确整数算术重新计算能量而非直接采信求解器上报的数值三程序文档中的具体运行。运行示例命令行参数速查在仓库根目录下执行uv是项目采用的 Python 包管理工具仓库的uv.lock已锁定依赖uv run python examples/maxcut/runner.py --seed 42 uv run python examples/maxcut/runner.py --n 6 --seed 7 -o /tmp/mc.json第一条命令以默认参数运行并把 JSON 结果打印到 stdout第二条改为 6 个节点、随机种子 7并把结果写入/tmp/mc.json。完整参数如下对应文档 [Usage] 表格Flag默认值说明--n5完全图的节点数--solverdwave-cpu求解后端见下节选择求解器--interpreterpythonXQVM 后端python或rust--seed42随机种子同时决定边权重生成与求解器随机性-o/--outputstdout把 JSON 结果写入文件缺省打印到标准输出--n只改变问题规模不改变编译产物编码器的寄存器编号与指令数量取决于 Python 代码中input()、stow()、define_model()等调用的次数而非--n的值编译文档。边权重在[1, 100]区间内均匀随机生成runner.py 第 58 行。一个典型输出--n 5 --seed 42{ _seed: 42, _note: canonical CI golden, n: 5, partition: [0, 1, 0, 0, 1], cut_weight: 354, energy: -354, valid: 1 }字段说明_seed与_note是 runner 自己的簿记信息_note的值描述该次调用而非测试固化的承诺partition是解码出的 0/1 划分cut_weight由宿主代码用partition[i] ! partition[j]重新累加计算runner.py 第 84-86 行energy是验证器独立重算的哈密顿量本问题中恒为-cut_weightvalid: 1仅表示所有样本值都在{0, 1}内——这是本问题验证器的全部检查内容。选择求解器五种后端与安装方式求解器选择与安装 extras 对所有示例完全一致完整说明见示例使用指南与求解总览。--solver的合法取值由xqsa.SOLVERS注册表决定registry 源码共五种名称硬件安装方式dwave-cpuCPU默认本地模拟退火pip install xqsa基础包即含dwave-qpuD-Wave Leap 云账户真实量子退火硬件pip install xqsa[dwave]cuda-gpuNVIDIA CUDA GPU自定义模拟退火内核pip install xqsa[cuda]需 CUDA 12.x 驱动metal-gpuApple SiliconmacOSMetal GPUpip install xqsa[metal]quipQuip 网络远程矿工环境变量配置pip install xqsa[quip]其中dwave-cpu是xqsa.DEFAULT_SOLVERregistry 源码第 55 行也是 CI 冒烟测试使用的唯一求解器。它封装dwave-samplers的SimulatedAnnealingSampler无需任何凭据即可在任何机器上运行。build_solver(name, seed...)是后端无关的构造入口seed只对三个经典模拟退火后端dwave-cpu、cuda-gpu、metal-gpu生效dwave-qpu物理硬件无种子与quip矿工自身随机性不受调用方控制会忽略它求解总览。dwave-cpu的关键构造参数均可在solve(**kwargs)时按次覆盖num_reads100独立退火次数取最优、num_sweeps1000每次退火的扫描数、num_sweeps_per_beta1每个温度层级停留的扫描数、beta_rangeNone逆温区间None交给dwave-samplers自动选择、seedNoneNone表示非确定性。GPU 后端在此基础上还有strategymetal-gpu支持sa或gibbs与beta_schedule_type等参数详见本地求解器文档。重要非默认求解器无法复现规范输出不同求解器使用不同 RNG/硬件模型可能有多个能量相同的等价最优解。example-smoke始终使用dwave-cpu进行校验。另外注意当前所有xqsa求解器都只接受 binary 与 spin 域integer 域模型会被拒绝ValueError: Unsupported domain for solving: INTEGER因此示例统一使用Domain.BINARY。规范输出与冒烟测试不变式校验而非逐字节比对example-smoke是示例的 CI 守护者其目标不是逐字节对比两个解释器的输出而是验证不变式两个解释器都以--seed 42 --solver dwave-cpu跑出valid 1脚本源码。为什么不做精确输出比对模拟退火对 BQM 的构建顺序敏感Python 与 Rust 两条路径可能找到不同但同样有效的最优解。文档明确说明三程序文档编码器、验证器、解码器在每个解释器内部是确定性的相同字节码进、相同输出出但夹在中间的求解器只锚定种子与dwave-samplers版本——同一库的不同版本可能对同一种子返回不同的有效样本。在仓库中执行冒烟测试uv run --no-sync python scripts/example-smoke.py脚本会遍历examples/*/runner.py对每个示例在python与rust两种解释器下各跑一遍检查valid 1随后对 manifest 中标记hardware: true的示例子集Max-Cut、TSP、Knapsack 三个用于控制 GPU 时间与 D-Wave QPU 月度配额见 examples/manifest.yaml在可用硬件求解器上复跑。值得注意的是脚本会先强制要求xqffi.verifier.verify_source可导入脚本第 46-71 行xqcp在库模式下对验证器缺失会静默跳过但冒烟测试必须保证每个编译出的示例程序都真正经过了字节码验证器的检查。动手改造把 Max-Cut 变成带固定终端的最小 s-t 割把现有runner.py复制一份并修改是建模新问题最快的方式。文档以最小 s-t 割为例假设两个指定节点如节点0与节点n-1必须位于划分的两侧。这是两个小改动完整过程见示例使用指南with problem.range(0, edge_count) as e: offset e * 3 i problem.stow(i, edges_in.get(offset)) j problem.stow(j, edges_in.get(offset 1)) w problem.stow(w, edges_in.get(offset 2)) problem.model.linear[i].add(w) # 符号翻转最小化而非最大化 problem.model.linear[j].add(w) problem.model.quadratic[i, j].add(w * -2) # 把节点 0 钉在 0 侧、节点 n-1 钉在 1 侧。10_000 远大于所有边权重之和 #该图最多 C(n,2) * 100因此任何一侧都不会为了省权重而翻转固定节点。 problem.model.linear[0].add(10_000) problem.model.linear[num_nodes - 1].add(-10_000)其余全部保留随机加权图、define_model、partition输出循环与run()。注意main()必须删掉canonicalize_partition(partition)调用——该函数把节点 0 规范化为 0 侧是因为 Max-Cut 中一个割与其补割所有节点翻转是同一个割而 s-t 割变体的两个端点使两侧不再等价规范化会抹掉钉住端点这一新增事实。同一图--n 5 --seed 42上原版与变体的结果对比来自示例使用指南// 原版 maxcut最大化割权重 {cut_weight: 354, energy: -354, partition: [0, 1, 0, 0, 1], valid: 1} // s-t 割变体最小化跨越权重且端点固定 {cut_weight: 196, energy: -9804, partition: [0, 1, 1, 1, 1], valid: 1}这个对比同时是调试方法论的一课对于无约束问题仅凭valid无法发现建模错误它只确认样本是二值energy也不是安全指标——把两个端点的偏置符号写反linear[0] -10_000、linear[n-1] 10_000跑出来的cut_weight196与energy-9804与正确版本完全相同因为一个割与其补割永远代价相同反转钉住端点只是选中了这对补割的另一半。此时唯一的暴露点是partition[0]与partition[-1]正确版本为0和1写反版本为1和0。这给出了一条可迁移的验证习惯用编辑本应保证的具体属性去检查未经任何下游步骤规范化的输出而不是只盯valid与看起来合理的energy。小结examples/maxcut是 XQuad 工具链的浓缩展示一个runner.py文件贯穿了xqcp建模编译、xqasm汇编、XQVM编码/验证/解码三段程序、xqsa求解与宿主编排的全部层级没有预写好的.xqasm文件、没有 JSON 输入——CP 进解码后的划分出。其 QUBO 形式线性-w、二次2w是一个可以原样迁移到任意带权二划分问题的模板而验证器独立重算能量、冒烟测试校验不变式而非精确输出、通过补割对称性识别建模错误这三条实践对仓库中其余十三个示例图着色、背包、TSP、最大独立集等见示例画廊同样适用。赞分享【免费下载链接】xquadA rust implementation of the Quip Networks quantum virtual machine.项目地址https://gitcode.com/gh_mirrors/xq/xquad点击查看免费下载相关推荐XQuad Examples 全览14 个端到端 QUBO 建模与求解实例指南XQuad Examples 全览14 个端到端 QUBO 建模与求解实例指南 XQuad 是 Quip Network 量子虚拟机XQVM的 RustXQuad Bin Packing 端到端实战QUBO 建模、SLACK 松弛编码与三程序流水线详解XQuad Bin Packing 端到端实战QUBO 建模、SLACK 松弛编码与三程序流水线详解 Bin Packing装箱问题是组合优化中的经典 NXQuad Max-3-SAT 端到端实战三元子句的 QUBO 化、REDUCE 降阶与采样验证全流程解析XQuad Max 3 SAT 端到端实战三元子句的 QUBO 化、REDUCE 降阶与采样验证全流程解析 本文以 XQuad 仓库自带的 Max 3 SAT上一篇PaddleDetection PP-Vehicle 车辆违章任务二次开发实战基于 PP-LiteSeg 的车道线分割模型训练与部署全流程下一篇librealsense 深度引导 GrabCut 前景提取用 RealSense 深度数据替代人工交互的 OpenCV 实战创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考