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-Bit
specifiers (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 再说。