广播机制引发的问题

添加单个控制位的门

之所以要研究 append 函数,是因为在使用该函数构造电路时,遇到了报错。下面是简化的应用场景。

假设构造一个空的电路,后面用 append 函数添加一个 CRY 门,代码如下:

1
2
3
4
circuit = QuantumCircuit(
QuantumRegister(2, "control"), QuantumRegister(1, "target")
)
circuit.append(CRYGate(np.pi / 4), circuit.qregs)

名为 control 的量子计算器作为控制位,名为 target 的量子计算器作为目标位。由于这里使用了两个控制位,而一个 CRY 门只需要一个控制位,因此 Qiskit 使用了广播机制,认为 control 中的两个比特各自是一个 CRY 门的控制位。因此构造出的电路如下,两者都作为控制位,出现了两个 CRY 门。

1
2
3
4
5
6
control_0: ─────■────────────────

control_1: ─────┼──────────■─────
┌────┴────┐┌────┴────┐
target: ┤ Ry(π/4) ├┤ Ry(π/4) ├
└─────────┘└─────────┘

添加多个控制位的门

如果要添加多个控制位的门,比如 UCRY 门,代码如下:

1
2
3
4
circuit = QuantumCircuit(
QuantumRegister(2, "control"), QuantumRegister(1, "target")
)
circuit.append(UCRYGate(np.ones(4).tolist()), circuit.qregs)

预期的结果应该是,control 中两个比特作为控制位,target 作为目标位,构造一个 2 控制位的 UCRY 门。但实际上,会得到如下错误。

1
2
3
4
5
6
7
8
Traceback (most recent call last):
File ".../test.py", line 100, in test_append_ucry
circuit.append(UCRYGate(np.ones(4).tolist()), circuit.qregs)
File ".../lib/python3.12/site-packages/qiskit/circuit/quantumcircuit.py", line 2523, in append
operation.broadcast_arguments(expanded_qargs, expanded_cargs)
File ".../lib/python3.12/site-packages/qiskit/circuit/gate.py", line 223, in broadcast_arguments
raise CircuitError(
qiskit.circuit.exceptions.CircuitError: 'The amount of qubit(2)/clbit(0) arguments does not match the gate expectation (3).'

错误来自于 Gate 类的 broadcast_arguments 函数。

源码解析

接下来,一步一步调试下 append 函数的源码来看看上面两个例子。

参数分析

1
2
3
4
5
6
7
8
def append(
self,
instruction: Operation | CircuitInstruction,
qargs: Sequence[QubitSpecifier] | None = None,
cargs: Sequence[ClbitSpecifier] | None = None,
*,
copy: bool = True,
) -> InstructionSet:

关于参数的解释详见官方文档。我们只需要关注 instructionqargs 参数,其余参数在上面的例子中没有用到。

instruction 可以传入 OperationCircuitInstruction 类型,例子中传入的是 CRYGateUCRYGate 类型,通过下面的继承关系图可以看出,两者都是 Operation 类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
classDiagram
direction LR
class Operation
class Instruction
class Gate
class ControlledGate
class CRYGate
class UCPauliRotGate
class UCRYGate

CRYGate --|> ControlledGate
ControlledGate --|> Gate
Gate --|> Instruction
Instruction --|> Operation

UCRYGate --|> UCPauliRotGate
UCPauliRotGate --|> Gate

qargs 需要传入的是 QubitSpecifierSequence(列表、元祖、NumPy 数组等)。查看源码可知 QubitSpecifier 是可表示量子比特的类型的集合。

1
2
3
4
5
6
7
QubitSpecifier = Union[
Qubit,
QuantumRegister,
int,
slice,
Sequence[Union[Qubit, int]],
]

例子中传入的 qregs 是包含 QuantumRegister 类型的列表。

分步骤解释

类型检查

进入 append 函数,会首先进行一系列的类型检查,我们只需要知道经过类型检查后,传入的门会在函数中以 operation 变量的形式存在。简化后的关键代码可以直接理解为:

1
2
if isinstance(instruction,  Instruction):
operation = instruction

扩展 qargs

这一步主要是调用 self._qbit_argument_conversionqargs 扩展成 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 中的量子比特。

202411011648692.png

广播

这一步,会调用 operation.broadcast_arguments 进行广播,这个 operation 就是例子传入的 CRYGateUCRYGate

广播机制简介

在调用 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.

翻译过来就是:传入的 qargscargs 会根据 Instruction 的规则进行扩展和广播,而非 Bit 类型的参数(如整数索引)则会被解析为相应的实例。这个解释真的非常抽象😅😅,不妨来看看 Gate 类中的 broadcast_arguments 函数。broadcast_arguments 函数来自于 Instruction,在其中只做了合法性检查,Gate 继承自 Instruction,重写了 broadcast_arguments 函数,在合法性检查的基础上实现了广播机制。官方文档给了广播机制的一个形象例子:CX((q0,q1),q2)\text{CX}((q_0,q_1),q_2) 等价于 CX((q0),q2)\text{CX}((q_0),q_2) 加上 CX((q1),q2)\text{CX}((q_1),q_2) 的效果,即 q0q_0q1q_1作为控制位(逗号左边集合),q2q_2 作为目标位(逗号右边集合),各作用一次 CX\text{CX} 门。

报错原因分析

operation.broadcast_arguments 一上来就做了一步检查:

1
2
3
4
5
if len(qargs) != self.num_qubits:
raise CircuitError(
f"The amount of qubit arguments {len(qargs)} does not match"
f" the instruction expectation ({self.num_qubits})."
)

如果我们插入的是 CRYGate,那么 len(qargs)self.num_qubits 都是 2,不会报错。如果我们插入的是 UCRYGate,那么 len(qargs) 仍是 2,而 self.num_qubits 是取决于 UCRYGate 的控制位数,例子中是 3,因此就会报错。

总结

通过广播机制,Qiskit 成功生成了两个门 CRY(control0,target)\text{CRY}(control_0,target)CRY(control1,target)\text{CRY}(control_1,target)。但是按照同样的逻辑,构造 UCRY((control0),target)\text{UCRY}((control_0),target)UCRY((control1),target)\text{UCRY}((control_1),target) 显然是不合理的,因为 UCRYGate 至少需要两个控制位。不过就是说这个报错信息是不是太抽象了:“qiskit.circuit.exceptions.CircuitError: ‘The amount of qubit(2)/clbit(0) arguments does not match the gate expectation (3).’”这谁看得懂😅。

由于广播机制的存在,导致 UCRYGate 等多控制位比特们不能简单地用 append 合并,目前仅探索出如下丑陋的解决方案:

1
2
3
4
5
6
circuit = QuantumCircuit(
QuantumRegister(1, "control1"),
QuantumRegister(1, "control2"),
QuantumRegister(1, "target"),
)
circuit.append(UCRYGate(np.ones(4).tolist()), circuit.qregs)

是不是有点太丑陋了😅😅。或者你可以选择通过传入比特索引的方式进行 append 操作,但这就失去使用量子寄存器管理一组比特的意义了。

不知道这算是个 Bug 还是 Qiskit 官方处于什么原因不让我们 append 多控制位的门呢?先提个 issue 再说。