QFVM 输入模型与求解器替换¶
English · 简体中文
QFVM 应用从流场数据构造线性系统,并把它交给可替换的 QLSS。当前实现针对周期一维 Euler 方程,使用三个守恒量和 frozen-Roe Jacobian。它不覆盖原论文的全部网格、边界和物理模型。端到端工作流见教程:科学计算工作流。
数据与量子访问¶
流场的密度、动量和能量保存在 QRAM 数据中。几何表记录邻居、槽位与分量索引,不预存完整 Jacobian 矩阵。
量子矩阵元 oracle 查询相关单元,调用可逆 Roe 算术,并根据行列索引选择元素。位置 oracle 给出每列的结构位置,其接口是 CKS 使用的原地索引置换。RHS 制备使用残差数据结构提供的角表。
经典 Riemann 计算和局部流场更新由 RoeFlowData 管理。逻辑 QRAM patch 可以局部更新;当前原生后端物化仍可能重建被修改的 bank,不能把两者等同。
构造问题¶
from oracq import SpectralPromise
from oracq.applications.qfvm import roe_qfvm_inputs, roe_qfvm_problem
from oracq.algorithms.qlss.qlss import CKSConfig, CostaConfig, make_cks_qlss, make_costa_qlss
inputs = roe_qfvm_inputs()
problem = roe_qfvm_problem(
inputs,
spectrum=SpectralPromise(norm_upper=10.0, sigma_min_lower=0.5),
amax=4.0,
)
cks = make_cks_qlss(CKSConfig(order=2))
costa = make_costa_qlss(CostaConfig(steps=1))
cks.check(problem).require()
costa.check(problem).require()
amax、谱界和矩阵性质是调用者声明,应适用于实际量化后的矩阵。示例中的数值用于说明接口,并不代替某个真实流场的谱分析。
替换 QLSS¶
CKS 入口消费稀疏问题,Costa 入口请求 BE。当前实对称稀疏适配通过 T†ST 构造相应 BE,并记录 alpha。没有从任意 BE 反向恢复高效稀疏访问的通用适配。
QFVM 的非对称物理矩阵使用明确的 Hermitian 扩张,并在输出时选择物理坐标。这个扩张服务于线性求解;不能直接将它当作原生成元的等价 QODE 演化。
两条 QLSS 路线可以保持相同的问题输入与物理输出解释,但辅助位宽度和内部模块不同。更换实现后应重新生成上层布局。只有在 ABI 和 alpha 不变时,才适合对已有开放槽晚绑定。
输出与范数¶
SolveResult 返回态 oracle、矩阵范数探针、输入 alpha、谱声明和适配记录。它将求解成功概率与独立矩阵探针的条件概率分开,避免误用过滤成功率作为解向量范数。
解态方向、成功通道、数值误差以及 CFD 外循环仍需按具体案例验证。更完整的数学假设和原始论文对照见QFVM/QLSS 输入模型审查。
实现逐行讲解(含 QRAM 数据结构)¶
本节把 QFVM 的实现完整拆开讲:先列全部 QRAM bank 的契约(谁写、谁读、存什么),再逐字段讲几何表与残差树,然后逐段讲三段量子电路(矩阵元 oracle、稀疏位置 oracle、RHS 制备),最后给端到端装配的逐行清单。源码对应 src/oracq/applications/qfvm.py 与 src/oracq/applications/flow_data.py。
QR1. QRAM bank 总览¶
roe_qfvm_inputs 声明的是抽象槽位(开放模块),数据到绑定与执行期才出现。绑定后每个 bank 的完整契约:
bank |
地址宽 → 数据宽 |
经典侧谁写 |
量子侧谁查 |
内容 |
|---|---|---|---|---|
|
cell_width → fmt.width |
|
|
每单元守恒量的定点编码 |
|
width+4 → geometry_width |
|
位置 oracle 9 次;条目 oracle 9 次 |
七段打包的纯几何字(QR2) |
|
cell_width+2 → fmt.width |
|
—(经典范数树的数据面) |
每 (单元, 分量) 的量化残差值 |
|
cell_width+2 → 1 |
|
RHS 制备查 2 次(写入 + 反查询) |
残差符号位 |
|
cell_width+2 → angle_width |
|
|
平方范数树内部节点的旋转角字 |
|
fmt.width → angle_width |
|
—(预留数据面,见下) |
θ=2·acos(min(1, |
三个要点:
矩阵元从不预存。QRAM 里只有原始场量与几何;矩阵元素由可逆 Roe 算术在查询时现场算出(QR4)。
theta是预留的。它是"残差值定点字 → 旋转角字"的换算表,随qfvm_memories一并物化;当前 CKS/Costa 两条路线没有电路查询它,文档如实记录这一点,不把它说成已接入的 oracle。内存快照必须固定。同一段相干计算中的 P、P†、受控调用与范数探针必须读同一版本的数据;这写在
roe_qfvm_problem的data_assumptions属性里,语言不替硬件证明它。
QR2. 几何表 geometry_cells 逐字段¶
先定坐标布局:完整矩阵坐标是 half·2^width + 4·cell + var——最低 2 位是分量 var(0..3,3 为补齐分量),中间 cell_width 位是单元号,最高位 half 选择 Hermitian 扩张 D=[[0,M],[Mᵀ,0]] 的上/下块。QRAM 地址 = row + slot·2^width,其中 row 打包 (half, cell, var),slot 打包 (band, other_var)。
逐行读 geometry_cells:
cell, var, half = (row >> 2) % n, row % 4, row >> (cw + 2)
拆出行坐标:低 2 位 var、中段 cell、最高位 half(上块 = M,下块 = Mᵀ)。
band, other_var = divmod(slot, 3)
valid = int(slot < 9 and var < 3)
16 个 slot 值中只有前 9 个是结构槽位(band∈{西,中,东} × other_var∈{0,1,2});补齐分量(var=3)与无效槽位标 valid=0。
neighbor_cell = (cell + band - 1) % n
neighbor = other_var + 4 * neighbor_cell + ((1 - half) << (cw + 2))
对手坐标:本行经槽位 (band, other_var) 耦合到另一半空间的 (neighbor_cell, other_var)。上块行指下块列、下块行指上块列——这正是 D 的转置耦合结构;% n 实现周期边界。
reverse = (2 - band) * 3 + var if valid else 0
source = cell if half == 0 else neighbor_cell
rowvar, colvar, source_band = (
(var, other_var, band) if half == 0 else (other_var, var, 2 - band)
)
reverse 是同一矩阵元的对称槽位;source 是计算该元素时该用的源单元(下块行取邻居);最后一行把下块条目折算成上块的同一物理元素(行列互换、band 取 2−band)。
values = ((neighbor, w), (reverse, 4), (source, cw), (rowvar, 2),
(colvar, 2), (source_band, 2), (valid, 1))
七段按低位到高位打包成 geometry_width 位的一个字:对手坐标 | 对称槽位 | 源单元 | 行分量 | 列分量 | 源带 | 有效位。位置 oracle 与条目 oracle 查到的都是这个字,再按 _geometry_refs 的同一段宽切开。
QR3. RHS 的 QRAM 符号残差树¶
经典侧(RoeFlowData.update)维护 1-based 堆数组树(8n 项),叶存残差分量的平方:
node = 4 * self.n + address # 叶:堆 1..8n,叶子从 4n+1 起(address=4*cell+j)
self.tree[node] = value * value # 残差平方
while node > 1: # 沿祖先向上收集受影响节点
node //= 2
ancestors.add(node)
self.tree[node] = self.tree[2*node] + self.tree[2*node + 1] # 自底向上重算子树和
angle = 2 * math.acos(math.sqrt(self.tree[2*node] / self.tree[node]))
changes["rhs_angles"][node - 1] = round(angle * (1 << aw) / (2 * math.pi)) % (1 << aw)
角度公式来自 RY 半角约定:RY(θ)|0> = cos(θ/2)|0> + sin(θ/2)|1>,要让左右子树幅度比为 sqrt(S_left/S_node),就取 θ = 2·acos(sqrt(S_left/S_node))。角度量化成 angle_width 位角字写入 rhs_angles bank(地址 = node−1)。单点流场更新只触碰相邻界面与 O(log) 个树节点——这就是"局部更新"声明的来源。
量子侧幅度(qram_state_prep,按层制备):
for depth in range(width):
bit = width - depth - 1 # 本层决定的目标位(从最高位往下)
if depth:
b.xor(b["target"][bit + 1:], addr[:depth]) # 已确定前缀拷入地址
offset = (1 << depth) - 1
b.add_const(addr.reinterpret("uint"), offset) # 地址 = 前缀 + 树节点偏移
b.qram("angles", addr, angle) # 查该内部节点的角字
for k in range(angle_width):
with b.control(angle[k]):
b.ry(b["target"][bit], 2 * math.pi * (1 << k) / (1 << angle_width))
b.qram("angles", addr, angle) # 反查询复净 angle
b.add_const(addr.reinterpret("uint"), (-offset) % (1 << address_width))
逐行要点:第 depth 层的树节点编址是 (1<<depth)−1+前缀(与 qram_state_angles 的表编址一致);一个 angle_width 位角字不必编译成 2^angle_width 种旋转门——按位加权 2π·2^k/2^aw 用 angle_width 次受控 RY 合成任意角;每层一查一反查,总 QRAM 查询 2·width 次(对照逐叶制备的 O(2^n) 次查询)。
量子侧符号(rhs_qram_preparation):幅度树只产生非负振幅,符号用独立的 1 位库经相位反冲写入:
invoke(b, prep.operation, "prep", target=b["target"][:n], work=b["work"][: n + aw]) # 幅度
flag = b["work"][n + aw :]
invoke(b, sign.operation, "sign", address=b["target"][:n], data=flag) # 符号 XOR 进 flag
b.z(flag) # flag=1 的分支乘 −1
invoke(b, sign.operation, "sign", address=b["target"][:n], data=flag) # 反查询复净 flag
Load 自逆,所以"写入符号 → Z → 还原符号"的净效果只剩分支相位:负残差分量得到 π 相位,flag 与 work 全部复净(zero_input=True)。
QR4. 矩阵元 oracle roe_entry 逐段¶
寄存器:source(单元)、row/col(分量)、band(西/中/东)、value(定点输出)、status(算术状态)。资源是三个守恒量库。
for offset in (-1, 0, 1):
addr = g.local(cw)
b.xor(b["source"], addr) # addr = source
b.add_const(addr.reinterpret("uint"), offset % (1 << cw)) # addr += 偏移(周期回绕)
addresses.append(addr)
for name, op in fields:
word = g.local()
invoke(b, op, name, address=addr, data=word) # 查 rho/momentum/energy
state.append(word)
对 source 及其左右邻居各查三个守恒量库——元素计算的输入是 3 个单元 × 3 个场,共 9 次 QRAM 查询。
face = roe_face(fmt=fmt, gamma=gamma, entropy_delta=entropy_delta)
for i in range(2):
b.call(face, rho_l=..., m_l=..., e_l=..., rho_r=..., m_r=..., e_r=...,
row=b["row"], col=b["col"], left=left, right=right, status=flag)
roe_face 是 frozen_roe_face 纯函数经 compile_function 编译出的可逆定点电路(含 sqrt/div/mul/select);调用两次得到左、右界面的通量左右特征分量。status≠0(溢出/除零)时元素按文档约定 totalize 为零。
west = -faces[0][0] / dx
center = (faces[1][0] - faces[0][1]) / dx + g.choose(equal, mass, 0)
east = faces[1][1] / dx
value = g.choose3(b["band"], [west, center, east])
按有限体积通量差组装西/中/东三个候选;质量项只在 row == col(component_equal 布尔网络)时加到中心块对角;最后按 band 三选一。
QR5. 稀疏位置 oracle(CKS 原地置换)逐段¶
CKS 要求"给定列与稀疏序号,原地得到行索引"。qfvm_sparse_access 的 locator 用 9 次相干值转置构造完整置换:
for rank in range(9):
... # slot 置为 rank;查 geometry(column, slot) 得七段字;neighbor = data[:width]
padded = locator.local(...); locator.xor(locator["column"], padded)
locator.add_const(padded.reinterpret("uint"), rank)
with locator.control(locator["column"][:2], 3): # 补齐分量(var=3)
locator.xor(data[:n], neighbor)
locator.xor(padded, neighbor) # 对手坐标改成 column+rank(padding 对角)
for bit in range(n):
if (rank >> bit) & 1:
locator.x(left[bit]) # left 初值 = rank
for previous in range(rank):
locator.call(value_transposition(n), index=left, a=lefts[previous], b=neighbors[previous])
补齐分量没有真实的邻居结构:对手坐标被改写为 column+rank,使 9 个序号恰好映射到 9 个不同的补齐坐标(padding 对角)。left 从 rank 出发,对之前每个 (left_j, neighbor_j) 做值转置——若 rank 已被占用就被换到尚未使用的槽位,保证 9 个槽位映射构成完整置换。
setup = tuple(locator._frames[0])
for left, right in zip(lefts, neighbors, strict=True):
locator.call(value_transposition(n), index=locator["index"], a=left, b=right)
locator.emit(Adjoint(setup))
对 index 依次执行 9 次值转置(index 中等于 left_j 的字被换成 neighbor_j),再用 Adjoint(setup) 复净全部工作位。逆映射 = 同一电路的 adjoint,这正是 CKS 接口需要的原地语义;代价是稀疏度次可逆比较/换位,不是免费单位门。
条目 oracle同用几何表:对 9 个槽位各做"查询 → compare_words 匹配 row → XOR 累加出 selected 七段字 → 反比较 → 反查询"的 compute/uncompute 对;拆段后调用 QR4 的 roe_entry 得到 (value, status):
with b.control(valid):
with b.control(status, 0):
b.xor(value, b["data"]) # 有效且算术成功的元素 XOR 写入
with b.control(fuse(same, b["column"][:2]), 7): # 对角且列分量为补齐(3)
... # 写 padding_value
前后各包一层 Adjoint(forward) 保持可逆。最终 SparseAccess(location, entry, width, fmt.width, 9)。
QR6. 端到端装配逐行¶
from oracq import FixedFormat, SpectralPromise
from oracq.applications.flow_data import RoeFlowData
from oracq.applications.qfvm import (
bind_qfvm, qfvm_memories, roe_qfvm_inputs, roe_qfvm_problem,
)
from oracq.algorithms.qlss.qlss import CKSConfig, CostaConfig, make_cks_qlss, make_costa_qlss
# 定点格式:数值验证表明 (5,2) 是 roe_face 常数全部可精确表示的最小格式。
fmt = FixedFormat(5, 2)
# 1. 声明输入模型:六个抽象数据库 + 一个抽象 RHS 制备(全是开放槽,尚无数据)。
inputs = roe_qfvm_inputs(fmt=fmt, angle_width=6)
# 2. 经典侧流场:RoeFlowData 写 rho/momentum/energy/rhs_* 六个 bank 并维护范数树。
flow = RoeFlowData([(1, 0, 1), (1, 0.25, 1), (1.25, 0, 1), (1.25, -0.25, 1)],
fmt=fmt, angle_width=6)
# 3. 组装问题:Hermitian 扩张 + 补齐对角合并进谱声明;谱界是调用者的义务。
problem = roe_qfvm_problem(
inputs, spectrum=SpectralPromise(norm_upper=10.0, sigma_min_lower=0.5), amax=4.0,
)
# 4. 同一个问题交两条路线:CKS 消费 SparseSystem;Costa 触发稀疏→BE 适配。
cks = make_cks_qlss(CKSConfig(order=2))(problem)
costa = make_costa_qlss(CostaConfig(steps=1))(problem)
# 5. 闭合开放槽:五个数据库绑定 QRAM 实际库,RHS 槽绑定符号残差树制备。
cks_rir = bind_qfvm(cks.operation.program(), inputs)
# 6. 物化运行时内存表:流场快照六个 bank + geometry 表 + theta 表。
memories = qfvm_memories(inputs, flow, amax=4.0)
# 7. 执行时把 memories 按名交给后端(run_pysparq(cks_rir, memory=memories));
# 替换 Costa 只需换第 4 步的 protocol,其余各行不变。
数值验证¶
2026-09-16 的论文级数值验证(tests/verification/verify_qham_qfvm.py,真实后端无替身)从数值上确认了本章的数据与访问构造:
数值通量:编译后的 Roe 面电路与独立定点仿真逐位一致(32/32 分支),对照 float64 Roe 公式的方法误差 0.427(5 位定点固有量化);稀疏条目 oracle 在采样列上逐位一致,补齐对角精确给出 padding_value,结构域外为零。
单步更新:F*(L,R)=left·U_L+right·U_R 与矩阵求逆实现
riemann_flux相差 3.33e-16;矩阵恒等式 M·u−mass·u=−residual(隐式 FVM 符号约定)残差 2.22e-16;RoeFlowData局部更新只重算相邻界面与受影响树节点,且与全量重算逐 bank 一致。位置访问:全 32 列叠加下每列都是完整置换,9 个结构槽位映射与独立几何语义 0 失配,几何 QRAM 表逐点真值。
RHS 制备:残差态振幅与独立残差/范数计算一致(误差 1.11e-16),符号经 Z 反冲精确写入,角度树与符号 bank 逐点真值。
验证用 FixedFormat(5,2) 与 entropy_delta=0.5(全部常数精确可表示);更低精度格式下 2δ 可能截断为零导致熵修正分支除零、条目按文档行为归零(详见算法页数值验证)。QLSS 两条路线的数值求解精度属求解器一侧,不在本页验证范围。产物:out/verification/qham_qfvm.json。
QMem 直连并行路径¶
applications/qfvm_qmem.py 提供同一套 QFVM 数据访问的并行实现,数据面全部改经指针式 QRAM 访问:
三个守恒量库合并为一张
(场, 单元)状态表QMem(b, "state", shape=(3, n));周期邻居在 cell_width 位 scratch 上做模加(与旧路径相同),再经二维指针[field, addr]查询。几何表按
(槽位, 列)二维寻址(旧路径的fuse(column, slot)编址逐点一致)。残差态由
QVector平方范数树制备(取代qram_state_prep+ 符号库组合)。模块直接声明 QRAM 形式资源,不再经过抽象数据库槽位与
bind_qfvm。
可逆 Roe 算术、九槽位几何选择、补齐对角与原地位置置换的电路结构与本路径完全相同。等价性证据(tests/core/test_qfvm_qmem.py):几何/状态/周期邻居的叠加探针逐字一致、位置 oracle 全列置换逐振幅一致、残差态振幅一致、含编译 Roe 算术的物理层在单点上逐位一致。本页上文描述的槽位-绑定路径仍是 QLSS 集成的主路径;QMem 路径当前定位是数据访问层的等价重写与后续演进的基线。