Qiskit 电路 append 函数源码解析
广播机制引发的问题
添加单个控制位的门
之所以要研究 append 函数,是因为在使用该函数构造电路时,遇到了报错。下面是简化的应用场景。
假设构造一个空的电路,后面用 append 函数添加一个 CRY 门,代码如下:
1 | circuit = QuantumCircuit( |
名为 control 的量子计算器作为控制位,名为 target 的量子计算器作为目标位。由于这里使用了两个控制位,而一个 CRY 门只需要一个控制位,因此 Qiskit 使用了广播机制,认为 control 中的两个比特各自是一个 CRY 门的控制位。因此构造出的电路如下,两者都作为控制位,出现了两个 CRY 门。
1 | control_0: ─────■──────────────── |
添加多个控制位的门
如果要添加多个控制位的门,比如 UCRY 门,代码如下:
1 | circuit = QuantumCircuit( |
预期的结果应该是,control 中两个比特作为控制位,target 作为目标位,构造一个 2 控制位的 UCRY 门。但实际上,会得到如下错误。
1 | Traceback (most recent call last): |
错误来自于 Gate 类的 broadcast_arguments 函数。
源码解析
接下来,一步一步调试下 append 函数的源码来看看上面两个例子。
参数分析
1 | def append( |
关于参数的解释详见官方文档。我们只需要关注 instruction 和 qargs 参数,其余参数在上面的例子中没有用到。
instruction 可以传入 Operation 或 CircuitInstruction 类型,例子中传入的是 CRYGate 和 UCRYGate 类型,通过下面的继承关系图可以看出,两者都是 Operation 类型。
1 | classDiagram |
qargs 需要传入的是 QubitSpecifier 的 Sequence(列表、元祖、NumPy 数组等)。查看源码可知 QubitSpecifier 是可表示量子比特的类型的集合。
1 | QubitSpecifier = Union[ |
例子中传入的 qregs 是包含 QuantumRegister 类型的列表。
分步骤解释
类型检查
进入 append 函数,会首先进行一系列的类型检查,我们只需要知道经过类型检查后,传入的门会在函数中以 operation 变量的形式存在。简化后的关键代码可以直接理解为:
1 | if isinstance(instruction, Instruction): |
扩展 qargs
这一步主要是调用 self._qbit_argument_conversion 将 qargs 扩展成 Qubit 类型。
1 | expanded_qargs = [self._qbit_argument_conversion(qarg) for qarg in qargs] |
这个函数的内部实现就不说了,这里简单举个例子进行理解。假设我们传入的 qarg 是一个 QuantumRegister(2, "q"),QuantumRegister 类型本身是可迭代的,其 __iter__ 返回其中的比特。因此 self._qbit_argument_conversion(qarg) 实际上会返回 [Qubit(QuantumRegister(2, "q"), 0), Qubit(QuantumRegister(2, "q"), 1)]。
因此,得到的 expanded_qargs 的类型是 list[list[Qubit]]。具体到我们的例子,就是 control 和 target 中的量子比特。

广播
这一步,会调用 operation.broadcast_arguments 进行广播,这个 operation 就是例子传入的 CRYGate 或 UCRYGate。
广播机制简介
在调用 append 时,Qiskit 会使用广播机制。 官方文档中有如下描述:
The qargs and cargs will be expanded and broadcast according to the rules of the given
Instruction, and any non-Bitspecifiers (such as integer indices) will be resolved into the relevant instances.
翻译过来就是:传入的 qargs 和 cargs 会根据 Instruction 的规则进行扩展和广播,而非 Bit 类型的参数(如整数索引)则会被解析为相应的实例。这个解释真的非常抽象😅😅,不妨来看看 Gate 类中的 broadcast_arguments 函数。broadcast_arguments 函数来自于 Instruction 类,在其中只做了合法性检查,Gate 继承自 Instruction,重写了 broadcast_arguments 函数,在合法性检查的基础上实现了广播机制。官方文档给了广播机制的一个形象例子: 等价于 加上 的效果,即 和 作为控制位(逗号左边集合), 作为目标位(逗号右边集合),各作用一次 门。
报错原因分析
operation.broadcast_arguments 一上来就做了一步检查:
1 | if len(qargs) != self.num_qubits: |
如果我们插入的是 CRYGate,那么 len(qargs) 和 self.num_qubits 都是 2,不会报错。如果我们插入的是 UCRYGate,那么 len(qargs) 仍是 2,而 self.num_qubits 是取决于 UCRYGate 的控制位数,例子中是 3,因此就会报错。
总结
通过广播机制,Qiskit 成功生成了两个门 和 。但是按照同样的逻辑,构造 和 显然是不合理的,因为 UCRYGate 至少需要两个控制位。不过就是说这个报错信息是不是太抽象了:“qiskit.circuit.exceptions.CircuitError: ‘The amount of qubit(2)/clbit(0) arguments does not match the gate expectation (3).’”这谁看得懂😅。
由于广播机制的存在,导致 UCRYGate 等多控制位比特们不能简单地用 append 合并,目前仅探索出如下丑陋的解决方案:
1 | circuit = QuantumCircuit( |
是不是有点太丑陋了😅😅。或者你可以选择通过传入比特索引的方式进行 append 操作,但这就失去使用量子寄存器管理一组比特的意义了。
不知道这算是个 Bug 还是 Qiskit 官方处于什么原因不让我们 append 多控制位的门呢?先提个 issue 再说。


