1. 课程导入与场景锚定

在开始学习 Qiskit 的模块封装与复用之前,先看一个几乎所有量子计算学习者都会遇到的真实场景。假设你要比较三种不同的纠缠态制备方案:Bell 态、GHZ 三比特态,以及带参数化旋转的纠缠态。每种方案都需要先准备初态、施加 Hadamard 门、再串联若干 CX 门,最后做测量。如果你把这三段电路分别从头写一遍,代码里会出现大量重复的 qc.h(0)、qc.cx(0,1)、qc.measure(...)。更麻烦的是,当你想把测量方式从“全测量”改成“只测部分比特”,或者把初态准备从 |000⟩ 换成任意角度旋转态时,你必须逐个修改每一段电路——漏改一处,三种方案的对比就失去了公平性。

这就是本节要锚定的核心痛点:量子电路中的重复结构没有被抽象成可命名、可参数化、可独立测试的单元。重复本身不是最大的问题,最大的问题是重复带来的“修改扩散”——一处逻辑变更需要同步到多处,而量子电路对门顺序、比特索引、参数取值极其敏感,任何一处不同步都会导致结果不可比甚至错误。

封装与复用要解决的三类问题

第一类是结构重复。初态制备、纠缠层、测量层、错误缓解用的对称化结构,在大量电路里反复出现。它们应该被写成一次、调用多次。第二类是参数重复。变分电路中的旋转角度、纠缠强度、层数,往往以参数形式在多个位置出现,需要统一入口而不是散落在各处。第三类是验证重复。一个子电路是否正确,应该能单独拿出来做态矢量检查或模拟器验证,而不是只能在整个大电路跑完后靠最终分布“猜”它有没有问题。

Qiskit 提供了多个层次的封装手段,本课程会沿着“从轻到重”的路线逐步展开。最轻的是把一段门序列写进 Python 函数,返回 QuantumCircuit;进一步是用 QuantumCircuit.compose() 把子电路拼接进主电路,并控制比特映射与参数绑定;再进一步是把子电路转成可复用的指令(to_instruction / to_gate),让它像原生门一样出现在电路图上;最终是借助 QuantumCircuit 的元数据、参数对象 Parameter 与 ParameterVector,构建带接口约定的“电路模块库”。这条路线不是要求你每步都用最重的手段,而是让你知道在不同复用粒度下该选哪一层。

从重复搭建到模块库:四层封装路线 重复搭建的代价 结构重复 参数散落 修改扩散 无法单独验证 对比不公平 一处变更 → 多处同步 第 1 层:函数封装 def prepare_bell(qc, a, b): qc.h(a); qc.cx(a,b) 交付物:可调用的电路构造函数,输入比特索引 第 2 层:compose 拼接 main.compose(sub, qubits=[0,1], clbits=[0,1]) 交付物:带比特映射与参数绑定的组合电路 第 3 层:指令化 gate = sub.to_gate(label="Bell"); qc.append(gate, [0,1]) 交付物:像原生门一样出现在电路图上的黑盒模块 第 4 层:参数化模块库 ParameterVector("θ", n) + 接口约定 + 单元测试 交付物:可复用、可验证、可组合的电路模块库 最终交付物:一个可导入的 circuit_lib.py —— 含 Bell/GHZ/参数化纠缠层 + 接口说明 + 自检用例

课程整体路线与最终交付物

本课程共 30 章,路线可以概括为“先建心智模型,再练单点技术,最后组装成库”。前段聚焦封装的基本单位:函数、子电路、指令、参数;中段处理复用中的工程细节:比特映射、经典寄存器对齐、参数绑定顺序、嵌套模块的展开与去展开;后段进入实战:把常见量子算法中的重复结构(纠缠层、Ansatz 层、测量基旋转层、错误缓解的对称化层)抽成模块,并建立一套最小可用的自检流程。

课程的最终交付物是一个可导入的 circuit_lib.py 模块库。它至少包含三个模块:prepare_bell(qc, a, b)、prepare_ghz(qc, qubits)、entangling_layer(qc, qubits, theta_vec),每个模块都带明确的接口约定(输入比特列表、参数向量长度、是否含测量),并配有一组自检用例。自检用例不依赖真实硬件,而是用 Statevector 模拟器检查输出态是否符合预期,例如 Bell 模块应得到 (|00⟩+|11⟩)/√2,GHZ 三比特模块应得到 (|000⟩+|111⟩)/√2。

下面给出一个可直接运行的最小示例,它同时演示了第 1 层函数封装与第 2 层 compose 拼接。示例参数为教学假设,运行结果以模拟器输出为准:

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

def prepare_bell(qc, a, b):
    qc.h(a)
    qc.cx(a, b)

main = QuantumCircuit(3)
prepare_bell(main, 0, 1)          # 在比特 0、1 上制备 Bell 态
main.x(2)                          # 比特 2 翻到 |1>
print(Statevector(main).probabilities_dict())
# 预期非零项:|000> 与 |110> 各约 0.5(示例假设,实际以运行输出为准)

这段代码的价值不在于它多复杂,而在于它把“Bell 态制备”变成了一个有名字、有输入参数、可被反复调用的单元。当你后面要比较三种纠缠方案时,主电路只需要换一行 prepare_bell 调用,而不必重写门序列。这就是封装带来的第一层收益:变更点收敛到一处。

学习建议:本课程每一章都会围绕一个可运行的小例子展开。建议你在本地建一个 circuit_lib.py 文件,从第 1 章开始就把示例代码放进去,随着章节推进不断补充模块。到课程结束时,这个文件就是你的最终交付物,而不是一份只读过的讲义。

需要提前说明一个贯穿全课程的约定:本课程中所有比特索引均从 0 开始,qc.h(0) 表示对第 0 号比特施加 Hadamard 门;当子电路通过 compose 拼接到主电路时,qubits 参数给出的是“子电路第 i 个比特映射到主电路哪个比特”的列表,顺序敏感。这个约定在后续讲比特映射和嵌套模块时会反复用到,先在这里固定下来,避免后面出现参照系混乱。

最后回到场景锚定。你之所以要学封装与复用,不是为了把代码写得更“优雅”,而是为了让量子电路的可比性、可验证性和可维护性成立。三种纠缠方案的对比,只有在初态、测量层、参数入口完全一致时才有意义;一个子电路是否正确,只有能单独拿出来验证时才能快速定位问题。这两件事,正是本课程要帮你建立的能力。