4. 仿真环境搭建

连续变量QKD的仿真工作几乎全部围绕“复数矩阵运算 + 随机数 + 数值优化”展开:高斯调制需要生成大量服从复高斯分布的随机变量,协方差矩阵需要反复做矩阵乘法与求逆,安全密钥率计算涉及对数、开方与最优化求解,最后还要把密钥率随距离、过噪声、调制方差的变化画成曲线。因此,一个可复现的实验环境至少应包含三层:数值计算层(NumPy/SciPy)、可视化层(Matplotlib)和量子光学建模层(QuTiP 或专用 CV-QKD 仿真库)。本节按“装什么、怎么装、装完怎么验证、如何固定随机性”的顺序展开。

一、环境分层与依赖关系

建议使用 conda 创建独立环境,避免与系统 Python 冲突。NumPy 提供 ndarray 与复数运算,SciPy 提供 linalg、optimize、special 等模块,Matplotlib 负责绘图,QuTiP 依赖 NumPy/SciPy 并额外需要 Cython 编译的稀疏矩阵后端。下图给出各层之间的依赖与调用关系,以及每层在 CV-QKD 仿真中的典型职责。

CV-QKD 仿真环境分层与依赖 应用层:协议仿真脚本 高斯调制 · 协方差矩阵 · 密钥率计算 · 参数扫描 量子建模层 QuTiP / 专用CV-QKD库 态、算符、信道作用 可视化层 Matplotlib 密钥率-距离曲线 数值计算层 NumPy(ndarray/复数/随机数) · SciPy(linalg/optimize/special) 矩阵乘法 · 求逆 · 对数与开方 · 最优化 Python 解释器 + conda 虚拟环境 版本锁定 · 依赖隔离 · 可复现 版本一致性 NumPy 与 SciPy 需匹配 QuTiP 对版本较敏感 随机性控制 np.random.default_rng 固定 seed 保证复现

二、安装与版本锁定

推荐使用 Python 3.10 或 3.11,这两个版本对 QuTiP 4.7 与 SciPy 1.11 的兼容性较好。创建环境并安装核心栈:

conda create -n cvqkd python=3.11 -y
conda activate cvqkd
conda install numpy=1.26 scipy=1.11 matplotlib=3.8 -y
pip install qutip==4.7.5
pip freeze > requirements.txt

若使用专用 CV-QKD 仿真库(例如某些课题组开源的 cvqkd 包),应优先通过 pip install 安装并检查其声明的 NumPy 版本上限。QuTiP 4.x 在 Windows 上需要 C 编译器支持,若安装失败,可改用 conda install -c conda-forge qutip 获取预编译版本。安装完成后务必执行 pip freeze 生成 requirements.txt,这是“可复现”的第一道保障:同一份依赖清单在任何机器上都能重建相同环境。

三、安装验证:从导入到数值自检

安装成功不等于环境可用。建议写一个自检脚本,逐层验证:NumPy 的复数矩阵乘法、SciPy 的求逆与最优化、Matplotlib 的无界面绘图后端、QuTiP 的算符构造。下面是一段可直接运行的验证代码,其中参数均为示例值:

import numpy as np, scipy, matplotlib, qutip
from scipy import linalg, optimize

print(np.__version__, scipy.__version__, matplotlib.__version__, qutip.__version__)

# 1) 复数协方差矩阵求逆(CV-QKD 中反复出现)
A = np.array([[2.0+0j, 0.5j], [-0.5j, 1.5+0j]])
Ainv = linalg.inv(A)
assert np.allclose(A @ Ainv, np.eye(2)), "矩阵求逆自检失败"

# 2) 最优化:示例目标函数 f(x)=x^2-4x+7,理论极小点 x=2
res = optimize.minimize(lambda x: x[0]**2 - 4*x[0] + 7, x0=[0.0])
assert abs(res.x[0] - 2.0) < 1e-6, "最优化自检失败"

# 3) QuTiP 算符构造
a = qutip.destroy(10)
print("光子数算符维度:", (a.dag()*a).shape)

若四行版本号正常打印、两个断言均通过、QuTiP 输出 (10, 10),说明数值层与建模层均可用。注意 Matplotlib 在无显示器的服务器上应设置 matplotlib.use("Agg"),否则绘图会因缺少显示后端而报错。

四、可复现性的三个关键点

第一,随机数必须显式播种。CV-QKD 仿真中高斯调制样本、信道过噪声采样都依赖随机数,推荐使用 rng = np.random.default_rng(20240501) 并在整个脚本中传递同一个 rng 对象,而不是调用全局 np.random.randn。第二,浮点比较要用容差,例如 np.allclose 而非 ==。第三,把仿真参数集中到一个字典或 YAML 文件中,与代码分离,便于记录每次实验的输入。

建议在项目根目录放置 requirements.txt、params.yaml 和 run.py 三个文件,分别锁定依赖、记录参数、驱动仿真。这样任何一次结果都能追溯到“哪份依赖 + 哪组参数 + 哪个随机种子”。

五、最小可运行示例:从随机样本到协方差矩阵

下面用示例参数演示一次最小闭环:假设调制方差 V_A = 4(散粒噪声单位),信道透射率 T = 0.9,过噪声 xi = 0.01,生成长度为 N = 100000 的复高斯调制样本,并计算接收端协方差矩阵的对角元,用于检查数值量级是否合理。

import numpy as np
rng = np.random.default_rng(20240501)
N, V_A, T, xi = 100000, 4.0, 0.9, 0.01

# 高斯调制:x,p 各自方差为 V_A/2,总方差 V_A
x = rng.normal(0, np.sqrt(V_A/2), N)
p = rng.normal(0, np.sqrt(V_A/2), N)

# 示例信道:衰减 + 加性过噪声(散粒噪声单位)
x_rec = np.sqrt(T)*x + rng.normal(0, np.sqrt((1-T)+xi), N)
p_rec = np.sqrt(T)*p + rng.normal(0, np.sqrt((1-T)+xi), N)

print("发送端方差:", np.var(x)+np.var(p))      # 应接近 4
print("接收端方差:", np.var(x_rec)+np.var(p_rec))  # 应接近 T*V_A + (1-T) + xi

按上述示例参数,发送端总方差应接近 4,接收端总方差应接近 0.9×4 + 0.1 + 0.01 = 3.71。若实测偏差超过几个百分点,通常说明方差定义(单象限还是双象限)或噪声单位用错,这是 CV-QKD 仿真中最常见的量纲错误。运行通过后,再把这段逻辑封装成函数,接入密钥率计算与 Matplotlib 绘图,就形成了可复现实验环境的骨架。