4. 实验控制软件栈

量子实验控制软件栈并不是“选一个语言写脚本”这么简单。它同时承担三类职责:把实验意图翻译成仪器可执行的时序与波形;在毫秒到微秒尺度上协调多台设备;把原始采集数据连同参数、版本、时间戳一起落盘,使结果可复现。LabVIEW、Python、QCoDeS、Qiskit Experiment、ARTIQ 分别处在不同抽象层,理解它们的定位比记住名字更重要。

一、先分清“抽象层”,再谈选型

可以把控制栈分成四层:硬件接口层负责与示波器、AWG、锁相、DAC/ADC 通信;设备抽象层把仪器封装成带参数的 Python 对象;实验编排层描述扫描、循环、条件与数据流;实时执行层在 FPGA 上以纳秒级确定性触发脉冲。LabVIEW 传统上覆盖接口层与部分编排层,图形化数据流对仪器控制很直观,但版本管理、代码复用和与 Python 生态的互操作较弱。纯 Python 脚本灵活,却容易把仪器地址、量程、延时硬编码,导致换一台设备就不可复现。

QCoDeS 的定位是设备抽象与数据管理:用 Instrument 子类描述参数,用 Parameter 声明可读可写量,用 Measurement 组织扫描并自动写入数据库。它不负责实时脉冲,也不规定你用什么语言写实验逻辑。Qiskit Experiment 面向超导量子比特的校准与表征,提供 Rabi、Ramsey、T1、读出保真度等标准实验模板,输出结构化结果,适合把“测什么”标准化。ARTIQ 则面向实时时序:用 Python 子集写内核,编译到 FPGA,实现多通道 ns 级同步,适合离子阱、中性原子和需要严格触发对齐的超导实验。

量子实验控制软件栈分层与框架定位 实验编排层 扫描 / 循环 / 条件分支 / 数据流组织 QCoDeS Measurement 扫描 + 数据库落盘 Qiskit Experiment 校准模板 / 结构化结果 Python 脚本 / Notebook 灵活但需自管版本 实时执行层 ns 级确定性时序 / 多通道同步触发 ARTIQ 内核 Python 子集 → FPGA 编译 FPGA 时序引擎 TTL / DAC / 触发对齐 设备抽象层 仪器封装为参数对象 / 统一读写接口 QCoDeS Instrument Parameter / 量程 / 延时 LabVIEW VI 图形化仪器驱动 硬件接口层 VISA / TCP / USB / 串口 / 驱动 示波器 / 锁相 读回与触发 AWG / DAC 波形输出 ADC / 数字化仪 原始采样 集成要点:上层下发参数 → 设备抽象层翻译为仪器命令 → 实时层保证时序 → 数据回流至数据库,参数、版本、时间戳一并记录。 选型判断:是否需要 ns 级同步?是 → ARTIQ;否 → QCoDeS + Python。是否要做标准校准?是 → Qiskit Experiment 模板。

二、QCoDeS:把“仪器”变成可复现的参数对象

QCoDeS 的核心是 Instrument 与 Parameter。以一台可编程直流源为例,先声明参数,再在实验中通过 dac.ch1.voltage(0.5) 读写。关键价值在于:每次设置都会记录到数据库,扫描时自动生成 run_id,事后可用 qcodes.dataset.load_by_id 还原完整参数轨迹。下面是一个最小可运行示例(参数为教学示例,非实测值):

import qcodes as qc
from qcodes.instrument import Instrument, Parameter

class DummyDAC(Instrument):
    def __init__(self, name, address):
        super().__init__(name)
        self.add_parameter('voltage',
            get_cmd=lambda: self._v,
            set_cmd=self._set_v,
            unit='V', vals=qc.validators.Numbers(-2, 2))
        self._v = 0.0
    def _set_v(self, val):
        self._v = val

dac = DummyDAC('dac', 'TCPIP::192.168.1.10::INSTR')
qc.new_experiment('rabi_cal', 'sample_A')
with qc.do1d(dac.voltage, 0.0, 1.0, 21, 0.01) as scan:
    for v in scan:
        pass  # 此处接入采集,数据自动写入数据库

注意 vals 校验器:它把量程约束写进代码,避免误设 5 V 烧毁器件。数据库默认落在 ~/experiments.db,包含运行时间、参数快照和结果数组,这是复现的第一道保障。

三、Qiskit Experiment:把校准流程标准化

超导比特每次降温后都需要重新校准。Qiskit Experiment 提供 Rabi、Ramsey、T1、ReadoutFidelity 等实验类,输入是后端与物理量子比特编号,输出是带拟合的 ExperimentResult。例如 Rabi 实验扫描驱动脉冲时长,拟合出 π 脉冲时间;Ramsey 扫描等待时间,从振荡衰减提取 T2*。它的优势是结果结构统一,便于批量比对;局限是它假设你使用 Qiskit 的脉冲与后端抽象,若实验是自建 FPGA 时序,需要把 Qiskit 的脉冲参数导出为 AWG 波形或 ARTIQ 内核参数。

四、ARTIQ:确定性时序的边界

ARTIQ 用 Python 子集写内核函数,编译后在 FPGA 上执行。内核里不能做动态内存分配、不能调用任意 Python 库,所有时间用 ns 整数表示。典型片段:

@kernel
def run(self):
    self.ttl0.pulse(100*ns)
    delay(200*ns)
    self.dac0.set(0.3)
    delay(1*us)

这种限制换来的是通道间抖动小于 1 ns。若实验只需毫秒级扫描,用 QCoDeS 加普通 Python 循环即可;若需要多台设备在 10 ns 内同步,则必须把关键时序下沉到 ARTIQ 内核,QCoDeS 只负责外层参数扫描与数据记录。

五、集成策略与选型判断

实际项目常见组合是:QCoDeS 做设备抽象与数据管理,Python 写实验逻辑,ARTIQ 做实时脉冲,Qiskit Experiment 做标准校准。集成时注意三点:一是统一时间基准,所有设备触发源锁定到同一 10 MHz 参考;二是参数命名一致,QCoDeS 参数名与 ARTIQ 内核变量名建立映射表;三是版本冻结,把 requirements.txt、FPGA 比特流哈希、仪器固件版本写入实验元数据。选型时先问“最严苛的时序要求是多少”,再问“是否需要标准校准模板”,最后问“团队是否已有 LabVIEW 资产”。答案会自然收敛到某一层组合,而不是盲目追新。

复现检查:一次实验结束后,至少能回答——用了哪台仪器、什么量程、什么触发配置、哪个版本的代码与比特流、原始数据存在哪个 run_id。缺任何一项,复现都会变成猜谜。