使用 ArcQML 求解 H₂ 基态能量

运行前请准备 ArcQML 源码仓库及对应示例数据。下文命令和 examples/ 路径均以该仓库为基准;本文展示关键代码片段,完整程序位于文中标注的示例路径。

本教程使用变分量子本征求解器(Variational Quantum Eigensolver,VQE)估计氢分子 H₂ 的基态能量,并展示 ArcQML 的电路、Pauli 可观测量、伴随自动微分和 Adam 优化器如何组成完整训练流程。

完整可运行代码位于 examples/rust/h2_vqe.rs

rust
use arcqml::prelude::*;

type AppResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;

问题定义

本例采用以下量子化学设置:

配置 数值
分子几何 H (0, 0, -0.35 Å),H (0, 0, 0.35 Å)
原子间距 0.70 Å
基组 STO-3G
活性电子数 2
活性轨道数 2
费米子映射 Jordan–Wigner
量子比特数 4
Hamiltonian 项数 15
固定粒子数子空间维数 6
Hartree–Fock 能量 -1.1173490350562805 Ha
精确基态能量 -1.1361894542078266 Ha

VQE 最小化参数化量子态的能量期望值:

E(θ)=ψ(θ)|H|ψ(θ)

根据变分原理,任意归一化试探态均满足 E(θ) ≥ E₀。优化的目标就是让试探态逐渐接近基态。

构造 Hamiltonian

Jordan–Wigner 变换后,H₂ Hamiltonian 是 15 个 Pauli 字符串的实系数线性组合。ArcQML 使用 SparsePauliOp 表示这种算符,并原生支持从 JSON 导入。

本例将完整数据保存在 examples/data/h2_hamiltonian.json,直接调用:

rust
let hamiltonian = SparsePauliOp::from_json(include_str!(
    "../data/h2_hamiltonian.json"
))?;

assert_eq!(hamiltonian.num_qubits(), 4);
assert_eq!(hamiltonian.len(), 15);

include_str! 在编译时把 JSON 嵌入可执行文件,运行时无需寻找数据文件。如果 Hamiltonian 来自用户文件,也可以用 std::fs::read_to_string 读取后传给同一个 API:

rust
let json = std::fs::read_to_string("examples/data/h2_hamiltonian.json")?;
let hamiltonian = SparsePauliOp::from_json(&json)?;

ArcQML 的 JSON 格式由顶层 num_qubitsterms 组成;每个 Pauli 操作显式记录 qubitpauli。例如常数项和 X(q0)X(q1)Y(q2)Y(q3) 项写作:

json
{
  "num_qubits": 4,
  "terms": [
    {
      "coefficient": -0.042078985845795724,
      "paulis": []
    },
    {
      "coefficient": -0.04475014386992153,
      "paulis": [
        { "qubit": 0, "pauli": "X" },
        { "qubit": 1, "pauli": "X" },
        { "qubit": 2, "pauli": "Y" },
        { "qubit": 3, "pauli": "Y" }
      ]
    }
  ]
}

paulis 数组表示单位算符。

准备 Hartree–Fock 初态

在 Jordan–Wigner 编码中,每个量子比特表示一个自旋轨道是否被占据。两个活性电子占据最低的两个自旋轨道,因此从 |0000⟩ 出发对 q0q1 施加 X 门:

rust
let mut circuit = Circuit::new(4)?;
circuit.x(0)?.x(1)?;

ArcQML 抽样字符串按 q3q2q1q0 显示,所以这个状态显示为 |0011⟩;内部状态索引采用 little-endian。

构造参数化 ansatz

本例使用 6 层硬件高效 ansatz。每层首先在每个量子比特上施加 RYRZ,随后使用环形 CNOT 纠缠:

rust
const NUM_QUBITS: usize = 4;
const LAYERS: usize = 6;

let mut parameter_index = 0usize;
for _ in 0..LAYERS {
    for qubit in 0..NUM_QUBITS {
        let ry = 0.04 * (0.37 * (parameter_index + 1) as f64).sin();
        parameter_index += 1;
        let rz = 0.04 * (0.37 * (parameter_index + 1) as f64).sin();
        parameter_index += 1;
        circuit.ry(ry, qubit)?;
        circuit.rz(rz, qubit)?;
    }
    for control in 0..NUM_QUBITS {
        circuit.cnot(control, (control + 1) % NUM_QUBITS)?;
    }
}

线路共有 6 × 4 × 2 = 48 个可训练参数。非零、非对称且确定性的初值可以打破参数对称性,同时保证重复运行从同一点开始。

这个硬件高效 ansatz 不严格保持粒子数。它适合演示通用 VQE 和 ArcQML 自动微分,但在化学计算中也可以改用保持粒子数的激发 ansatz。

执行 VQE 优化

StateVectorSimulator::run 返回能量期望值 Tensor。调用 backward() 后,ArcQML 使用整条量子线路的伴随反向传播计算所有参数梯度。

rust
let simulator = StateVectorSimulator::new(4)?;
let mut optimizer = Adam::new(0.05, 0.9, 0.999, 1e-8, 0.0)?;

for step in 1..=100 {
    let energy = simulator.run(&circuit, &hamiltonian)?;
    energy.backward()?;
    optimizer.step(circuit.parameters())?;
    optimizer.zero_grad(circuit.parameters());

    if step == 1 || step % 10 == 0 {
        let current = simulator.run(&circuit, &hamiltonian)?.value()?;
        println!("step {step:>3}: energy = {current:.12} Ha");
    }
}

每轮训练的顺序不能颠倒:

  1. run 计算当前参数对应的能量。
  2. backward 计算参数梯度。
  3. step 使用当前梯度更新线路参数。
  4. zero_grad 清除本轮梯度,为下一轮反向传播做准备。

线路参数刚创建时梯度为空,因此第一次迭代不需要预先调用 zero_grad。之后每轮都在参数更新后清空梯度,避免下一轮 backward 累加旧梯度。

模拟器始终保存 |0000⟩。Hartree–Fock 制备门已包含在线路中,而 run 不会修改模拟器自身,因此同一个模拟器可以在全部训练轮次中复用。

运行示例

在仓库根目录执行:

bash
cargo run --release -p arcqml --example h2_vqe

程序每 10 步输出一次能量以及相对精确基态能量的绝对误差,最后判断是否达到化学精度:

text
H2/STO-3G, Jordan-Wigner, 4 qubits
Hamiltonian terms : 15
Trainable params  : 48
Hartree-Fock      : -1.117349035056 Ha
Exact ground      : -1.136189454208 Ha
...
VQE energy        : <优化得到的能量> Ha
Absolute error    : <与精确值的差> Ha
Chemical accuracy: reached/not reached (threshold 1.6e-3 Ha)

具体迭代值由优化轨迹决定。由于本例初值固定,在相同 ArcQML 版本与数值环境下结果应当可复现。

如何理解结果

下一步

可以在此示例上继续尝试: