3. 实验环境配置与版本控制

量子电路实验的复现,第一步不是写量子门,而是把“运行环境”本身固定下来。同一段 Qiskit 代码,在 Qiskit 0.45 与 1.0 上可能因为 transpile 参数、默认优化级别或后端接口变化而得到不同的电路结构;同一份 requirements.txt,在不同操作系统上也可能因为二进制依赖解析出不同版本。因此,可复现实验环境的核心目标是:让任何人、在任何时间、任何机器上,都能重建出与原始实验一致的软件栈。这需要同时管理三层内容:Python 解释器版本、Qiskit 及其生态包的精确版本、以及依赖解析工具本身。

一、环境分层:解释器、包管理器与依赖锁

建议把环境拆成三个可独立控制的层次。最底层是 Python 解释器,量子计算生态对版本较敏感,例如 Qiskit 1.x 要求 Python 3.8 及以上,而某些旧版 qiskit-aer 对 3.11 的支持存在滞后,因此应在项目文档中明确写出 python=3.10 这类约束。中间层是依赖管理工具,负责把“我用了哪些包”解析成“具体安装哪些版本”。最上层是锁文件,记录解析后的精确版本与哈希,是复现的真正依据。

常见工具有三类组合:pip + venv + requirements.txt 最轻量,但 requirements.txt 默认只记录直接依赖,间接依赖会浮动;pipenv 通过 Pipfile 声明直接依赖、Pipfile.lock 锁定全部依赖树,适合中小项目;conda(或 mamba)擅长管理包含非 Python 二进制依赖的科学计算栈,配合 environment.yml 可同时锁定 Python 版本与包版本。对量子实验项目,推荐 conda 管理解释器 + pip 安装 Qiskit 生态 的混合方式,因为 Qiskit 更新快,pip 源通常比 conda 频道更及时。

量子实验环境的三层锁定结构 第 1 层 · 解释器 Python 3.10.x conda / mamba 创建 environment.yml 声明 python=3.10 第 2 层 · 依赖声明 Pipfile / requirements.in qiskit==1.0.2 等直接依赖 environment.yml channels + pip 子节 版本约束策略 == 精确锁定,避免 ^ 浮动 第 3 层 · 锁文件(复现依据) Pipfile.lock 含哈希,pipenv 专用 requirements.txt pip freeze 全量导出 conda list --explicit 含 build 号与平台 复现时:解释器版本 → 锁文件安装 → 校验 qiskit.__version__ 与后端版本一致

二、用 conda 固定解释器,用 pip 锁定 Qiskit

下面给出一个可直接参考的 environment.yml 示例。它声明 Python 3.10,并通过 pip 子节安装 Qiskit 生态。注意 qiskit 在 1.0 之后拆分为 qiskit 元包与 qiskit-aer、qiskit-ibm-runtime 等独立包,锁定时应分别写版本,而不是只锁元包。

name: qexp-repro
channels:
  - conda-forge
dependencies:
  - python=3.10.13
  - pip=23.3.1
  - pip:
      - qiskit==1.0.2
      - qiskit-aer==0.14.1
      - qiskit-ibm-runtime==0.21.1
      - numpy==1.26.4
      - matplotlib==3.8.3
      - pylatexenc==2.10

创建与激活:

conda env create -f environment.yml
conda activate qexp-repro
python -c "import qiskit, qiskit_aer; print(qiskit.__version__, qiskit_aer.__version__)"

预期输出形如 1.0.2 0.14.1。这一步是环境自检:如果版本与锁文件不符,说明解析过程被本地缓存或频道优先级干扰,应清理后重建,而不是“先跑起来再说”。

三、pipenv 路线与锁文件生成

若项目不涉及复杂二进制依赖,pipenv 更简洁。初始化后安装并锁定:

pipenv --python 3.10
pipenv install qiskit==1.0.2 qiskit-aer==0.14.1
pipenv lock
pipenv sync --dev

Pipfile 记录直接依赖,Pipfile.lock 记录完整依赖树与哈希。pipenv sync 严格按锁文件安装,是复现时应使用的命令;pipenv install 则可能重新解析并升级间接依赖,不适合复现场景。团队协作时,Pipfile.lock 必须提交到版本库,且代码评审时把它当作“实验参数”一样审查。

四、版本控制策略:把环境当代码管理

环境文件应纳入 Git,并与实验代码同仓管理。推荐目录结构:仓库根目录放 environment.yml、Pipfile、Pipfile.lock、requirements.txt(由 pip freeze 导出作为兜底),实验代码按 experiments/ 分目录,每个实验目录内放 README.md 记录该实验使用的环境文件版本(可用 Git commit 短哈希标注)。

关键实践有三点。第一,提交锁文件而非仅提交声明文件,因为声明文件允许浮动,锁文件才可逐字节复现。第二,为环境变更单独提交,例如“升级 qiskit-aer 至 0.14.1”应是一个独立 commit,便于用 git bisect 定位某次结果变化是否由依赖升级引起。第三,在实验输出中记录环境指纹,例如在结果 JSON 中写入 qiskit.__version__、qiskit_aer.__version__、Python 版本与 Git commit,这样即使环境文件丢失,也能从结果反查当时软件栈。

一个轻量的环境指纹采集函数,可在每次实验开始时调用,把返回值写入结果文件:
import sys, platform, subprocess
import qiskit, qiskit_aer

def env_fingerprint():
    try:
        commit = subprocess.check_output(
            ["git", "rev-parse", "--short", "HEAD"], text=True).strip()
    except Exception:
        commit = "unknown"
    return {
        "python": sys.version.split()[0],
        "platform": platform.platform(),
        "qiskit": qiskit.__version__,
        "qiskit_aer": qiskit_aer.__version__,
        "git_commit": commit,
    }

五、常见陷阱与判断依据

第一类陷阱是“本地能跑、别人装不上”,多因锁文件未提交或 requirements.txt 只写了直接依赖。判断方法:在干净容器中仅用锁文件安装,若失败或版本漂移,即说明锁定不完整。第二类陷阱是 conda 与 pip 混装导致同一包出现两份,可用 conda list 与 pip list 交叉比对同名包版本,若不一致应统一由一层管理。第三类陷阱是 Qiskit 后端默认值随版本变化,例如 transpile 的 optimization_level 默认值或 AerSimulator 的 method 默认行为,复现时应显式写出这些参数,而不是依赖默认值。

最后给出一个可执行的验收标准:在一台未安装过量子计算包的机器上,按 README 执行“创建环境 → 安装锁文件 → 运行自检脚本”,自检脚本打印的版本指纹与原始实验记录完全一致,且同一量子电路在 AerSimulator 上运行得到的计数分布(在固定 seed_simulator 下)逐位相同,方可认为环境复现成功。环境配置不是一次性任务,而是与实验代码一同演进、一同版本化的工程资产。