编译电路

PennyLane提供多种工具用于编译电路。我们在此以松散的方式使用“编译”一词,指的是将一个电路转变为一个或多个不同电路的过程。一个电路可以是量子函数或操作符的序列。例如,这种转变可以将一种门类型替换为另一种,合并门,利用数学关系简化可观察量,或者用多个较小的电路替代一个大的电路。

编译功能主要设计为 转换;有关更多细节,请参见转换文档,以及如何编写您自己的自定义转换的信息。

除了量子电路变换,PennyLane 还支持通过 qjit() 装饰器和 Catalyst 进行实验性的即时编译。这更为通用,支持完整的混合编译——将您工作流中的经典和量子组件编译成可以在您使用的加速器附近运行的二进制文件。更多细节请参见 编译工作流

简化运算符

PennyLane提供了simplify()函数,用于简化单个算子、量子函数、QNode和录音。这函数有几个目的:

  • 将给定运算符的算术深度减少到最小。

  • 将相似的项在和与积中进行分组。

  • 解决泡利算子的乘积。

  • 通过求和其角度来合并相同的旋转门。

这里有一些简单的简化例程:

>>> qml.simplify(qml.RX(4*np.pi+0.1, 0 ))
RX(0.09999999999999964, wires=[0])
>>> qml.simplify(qml.adjoint(qml.RX(1.23, 0)))
RX(11.336370614359172, wires=[0])
>>> qml.simplify(qml.ops.Pow(qml.RX(1, 0), 3))
RX(3.0, wires=[0])
>>> qml.simplify(qml.sum(qml.Y(3), qml.Y(3)))
2.0 * Y(3)
>>> qml.simplify(qml.RX(1, 0) @ qml.RX(1, 0))
RX(2.0, wires=[0])
>>> qml.simplify(qml.prod(qml.X(0), qml.Z(0)))
-1j * Y(0)

现在让我们简化一个嵌套运算符:

>>> sum_op = qml.RX(1, 0) + qml.X(0)
>>> prod1 = qml.X(0) @ sum_op
>>> nested_op = qml.prod(prod1, qml.RX(1, 0))
>>> qml.simplify(nested_op)
(X(0) @ RX(2.0, wires=[0])) + RX(1.0, wires=[0])

这里发生了几个简化步骤。首先,去除了嵌套的乘积:

qml.prod(qml.X(0), qml.sum(qml.RX(1, 0), qml.X(0)), qml.RX(1, 0))

然后,求和的乘积被转化为积和的和:

qml.sum(qml.prod(qml.X(0), qml.RX(1, 0), qml.RX(1, 0)), qml.prod(qml.X(0), qml.X(0), qml.RX(1, 0)))

最后,在获得的结果中,像项被归类在一起,去除了所有恒等式:

qml.sum(qml.prod(qml.X(0), qml.RX(2, 0)), qml.RX(1, 0))

正如前面提到的,我们还可以简化QNode对象,例如,组合旋转门:

dev = qml.device("default.qubit", wires=2)

@qml.simplify
@qml.qnode(dev)
def circuit(x):
    (
        qml.RX(x[0], wires=0)
        @ qml.RY(x[1], wires=1)
        @ qml.RZ(x[2], wires=2)
        @ qml.RX(-1, wires=0)
        @ qml.RY(-2, wires=1)
        @ qml.RZ(2, wires=2)
    )
    return qml.probs([0, 1, 2])
>>> x = [1, 2, 3]
>>> print(qml.draw(circuit)(x))
0: ───────────┤ ╭Probs
1: ───────────┤ ├Probs
2: ──RZ(5.00)─┤ ╰Probs

电路优化的编译变换

PennyLane 包含多个变换,可以将量子函数转换为优化电路的新量子函数:

cancel_inverses

量子函数变换以移除任何应用于其(自我)逆或伴随的操作。

commute_controlled

量子变换以将可交换门移动到受控操作的控制和目标量子比特之后。

merge_amplitude_embedding

量子函数变换以组合作用于不同量子比特的幅度嵌入模板。

cancel_inverses

量子函数变换以移除应用在其(自)逆或伴随旁边的任何操作。

merge_rotations

量子变换,用于组合依次作用的相同类型的旋转门。

pattern_matching

应用模式匹配算法并返回最大匹配列表的函数。

remove_barrier

量子变换以去除障碍门。

single_qubit_fusion

量子函数变换将一组单量子比特操作融合为一个通用的单量子比特单位操作 (Rot).

undo_swaps

量子函数变换,通过从右到左运行电路,改变量子比特的位置,从而去除SWAP门。

decompose

将量子电路分解为用户指定的门集。

注意

大多数编译转换都支持即时编译,使用 jax.jit

编译(compile())变换允许您将量子函数变换的序列链在一起,形成自定义电路优化管道。

例如,考虑以下装饰的量子函数:

dev = qml.device('default.qubit', wires=[0, 1, 2])

@qml.compile
@qml.qnode(dev)
def circuit(x, y, z):
    qml.Hadamard(wires=0)
    qml.Hadamard(wires=1)
    qml.Hadamard(wires=2)
    qml.RZ(z, wires=2)
    qml.CNOT(wires=[2, 1])
    qml.RX(z, wires=0)
    qml.CNOT(wires=[1, 0])
    qml.RX(x, wires=0)
    qml.CNOT(wires=[1, 0])
    qml.RZ(-z, wires=2)
    qml.RX(y, wires=2)
    qml.Y(wires=2)
    qml.CZ(wires=[1, 2])
    return qml.expval(qml.Z(wires=0))

compile() 的默认行为应用了一系列三个变换:commute_controlled()cancel_inverses(),然后是 merge_rotations()

>>> print(qml.draw(circuit)(0.2, 0.3, 0.4))
0: ──H──RX(0.60)─────────────────┤  <Z>
1: ──H─╭X─────────────────────╭●─┤
2: ──H─╰●─────────RX(0.30)──Y─╰Z─┤

这个 compile() 转换是灵活的,接受一个自定义的量子函数转换管道(你甚至可以编写你自己的!)。例如,如果我们只想通过受控门推动单量子比特门并取消相邻的逆,我们可以这样做:

from pennylane.transforms import commute_controlled, cancel_inverses
from functools import partial

pipeline = [commute_controlled, cancel_inverses]

@partial(qml.compile, pipeline=pipeline)
@qml.qnode(dev)
def qfunc(x, y, z):
    qml.Hadamard(wires=0)
    qml.Hadamard(wires=1)
    qml.Hadamard(wires=2)
    qml.RZ(z, wires=2)
    qml.CNOT(wires=[2, 1])
    qml.RX(z, wires=0)
    qml.CNOT(wires=[1, 0])
    qml.RX(x, wires=0)
    qml.CNOT(wires=[1, 0])
    qml.RZ(-z, wires=2)
    qml.RX(y, wires=2)
    qml.Y(wires=2)
    qml.CZ(wires=[1, 2])
    return qml.expval(qml.Z(wires=0))
>>> print(qml.draw(qfunc)(0.2, 0.3, 0.4))
0: ──H──RX(0.40)──RX(0.20)────────────────────────────┤  <Z>
1: ──H─╭X──────────────────────────────────────────╭●─┤
2: ──H─╰●─────────RZ(0.40)──RZ(-0.40)──RX(0.30)──Y─╰Z─┤

注意

Barrier运算符可以用于防止代码块在编译时被合并。

有关compile()及可用的编译转换的更多详细信息,请访问 编译文档

自定义操作符分解

PennyLane 将设备未知的门分解为其他“低级”门。作为用户,您可能希望微调这个机制。例如,您可能希望您的电路使用不同的基本门。

例如,假设我们想要实现以下QNode:

def circuit(weights):
    qml.BasicEntanglerLayers(weights, wires=[0, 1, 2])
    return qml.expval(qml.Z(0))

original_dev = qml.device("default.qubit", wires=3)
original_qnode = qml.QNode(circuit, original_dev)
>>> weights = np.array([[0.4, 0.5, 0.6]])
>>> print(qml.draw(original_qnode, level="device")(weights))
0: ──RX(0.40)─╭●────╭X─┤  <Z>
1: ──RX(0.50)─╰X─╭●─│──┤
2: ──RX(0.60)────╰X─╰●─┤

现在,让我们将PennyLane的默认分解的 CNOT 门换成 CZHadamard。 我们定义自定义分解如下,并将其传递给设备:

def custom_cnot(wires, **_):
    return [
        qml.Hadamard(wires=wires[1]),
        qml.CZ(wires=[wires[0], wires[1]]),
        qml.Hadamard(wires=wires[1])
    ]

custom_decomps = {qml.CNOT: custom_cnot}

decomp_dev = qml.device("default.qubit", wires=3, custom_decomps=custom_decomps)
decomp_qnode = qml.QNode(circuit, decomp_dev)

请注意,自定义分解函数应该接受关键字参数,即使不使用它。

现在,当我们在该设备上绘制或运行QNode时,门将根据我们的规格进行扩展:

>>> print(qml.draw(decomp_qnode, level="device")(weights))
0: ──RX(0.40)────╭●──H───────╭Z──H─┤  <Z>
1: ──RX(0.50)──H─╰Z──H─╭●────│─────┤
2: ──RX(0.60)──H───────╰Z──H─╰●────┤

注意

如果自定义分解仅在特定代码上下文中使用,则可以使用单独的上下文管理器 set_decomposition()

电路分解

在编译电路时,通常将电路分解为一组基础门是有益的。为此,我们可以使用 decompose() 函数,该函数使得电路可以分解为一组通过名称、类型或必须遵循的一组规则定义的门。

使用门集

下面的示例演示了如何使用预定义的一组门电路分解一个三线电路:

from pennylane.transforms import decompose
from functools import partial

dev = qml.device('default.qubit')
allowed_gates = {qml.Toffoli, qml.RX, qml.RZ}

@partial(decompose, gate_set=allowed_gates)
@qml.qnode(dev)
def circuit():
    qml.Hadamard(wires=[0])
    qml.Toffoli(wires=[0,1,2])
    return qml.expval(qml.Z(0))

由于我们的门集不包含Hadamard门,它将被分解为相应的旋转门算子。

>>> print(qml.draw(circuit)())
0: ──RZ(1.57)──RX(1.57)──RZ(1.57)─╭●─┤  <Z>
1: ───────────────────────────────├●─┤
2: ───────────────────────────────╰X─┤

使用门规则

下面的示例演示了如何使用一个规则将三线电路分解为单量子位或双量子位门:

@partial(decompose, gate_set=lambda op: len(op.wires)<=2)
@qml.qnode(dev)

def circuit():
    qml.Toffoli(wires=[0,1,2])
    return qml.expval(qml.Z(0))
>>> print(qml.draw(circuit)())
0: ───────────╭●───────────╭●────╭●──T──╭●─┤  <Z>
1: ────╭●─────│─────╭●─────│───T─╰X──T†─╰X─┤
2: ──H─╰X──T†─╰X──T─╰X──T†─╰X──T──H────────┤

分步分解

您可以使用max_expansion参数来控制应用于电路的分解阶段数量。默认情况下,函数将分解电路直到达到所需的门集。

下面的示例展示了用户如何可视化分解。 我们首先创建一个 QuantumPhaseEstimation 电路:

phase = 1
target_wires = [0]
unitary = qml.RX(phase, wires=0).matrix()
n_estimation_wires = 3
estimation_wires = range(1, n_estimation_wires + 1)

@qml.qnode(qml.device('default.qubit'))
def circuit():
    # Start in the |+> eigenstate of the unitary
    qml.Hadamard(wires=target_wires)
    qml.QuantumPhaseEstimation(
        unitary,
        target_wires=target_wires,
        estimation_wires=estimation_wires,
    )

从这里,我们可以逐步进行分解的各个阶段:

>>> print(qml.draw(decompose(circuit, max_expansion=0))())
0: ──H─╭QuantumPhaseEstimation─┤
1: ────├QuantumPhaseEstimation─┤
2: ────├QuantumPhaseEstimation─┤
3: ────╰QuantumPhaseEstimation─┤
>>> print(qml.draw(decompose(circuit, max_expansion=1))())
0: ──H─╭U(M0)⁴─╭U(M0)²─╭U(M0)¹───────┤
1: ──H─╰●──────│───────│───────╭QFT†─┤
2: ──H─────────╰●──────│───────├QFT†─┤
3: ──H─────────────────╰●──────╰QFT†─┤
>>> print(qml.draw(decompose(circuit, max_expansion=2))())
0: ──H──RZ(11.00)──RY(1.14)─╭X──RY(-1.14)──RZ(-9.42)─╭X──RZ(-1.57)──RZ(1.57)──RY(1.00)─╭X──RY(-1.00)
1: ──H──────────────────────╰●───────────────────────╰●────────────────────────────────│────────────
2: ──H─────────────────────────────────────────────────────────────────────────────────╰●───────────
3: ──H──────────────────────────────────────────────────────────────────────────────────────────────
───RZ(-6.28)─╭X──RZ(4.71)──RZ(1.57)──RY(0.50)─╭X──RY(-0.50)──RZ(-6.28)─╭X──RZ(4.71)─────────────────
─────────────│────────────────────────────────│────────────────────────│──╭SWAP†────────────────────
─────────────╰●───────────────────────────────│────────────────────────│──│─────────────╭(Rϕ(1.57))†
──────────────────────────────────────────────╰●───────────────────────╰●─╰SWAP†─────H†─╰●──────────
────────────────────────────────────┤
──────╭(Rϕ(0.79))†─╭(Rϕ(1.57))†──H†─┤
───H†─│────────────╰●───────────────┤
──────╰●────────────────────────────┤

电路切割

电路切割允许您用少于N根线的电路集替换一个具有N根线的电路(另见Peng et. al)。当然,这会带来一定的成本:较小的电路需要更多的设备执行才能被评估。

在 PennyLane 中,可以通过在所需的切割位置放置 WireCut 操作符,并通过用 cut_circuit() 变换装饰 QNode 来激活电路切割。

下面的示例展示了如何在一个两线设备上运行三线电路:

dev = qml.device("default.qubit", wires=2)

@qml.cut_circuit
@qml.qnode(dev)
def circuit(x):
    qml.RX(x, wires=0)
    qml.RY(0.9, wires=1)
    qml.RX(0.3, wires=2)

    qml.CZ(wires=[0, 1])
    qml.RY(-0.4, wires=0)

    qml.WireCut(wires=1)

    qml.CZ(wires=[1, 2])

    return qml.expval(qml.pauli.string_to_pauli_word("ZZZ"))

电路不会被直接执行,而是根据 WireCut 位置被划分为更小的片段,每个片段将被多次执行。PennyLane 自动组合片段执行的结果,以恢复原始未切割电路的预期输出。

>>> x = np.array(0.531, requires_grad=True)
>>> circuit(0.531)
0.47165198882111165

电路切割支持也是可微分的:

>>> qml.grad(circuit)(x)
-0.276982865449393

注意

产生样本的模拟量子电路可以使用 cut_circuit_mc() 变换进行切割,这基于蒙特卡洛方法。

交换帕乌利算子的组

相互对易的保利字可以在量子计算机上同时测量。因此,找到相互对易的可测量量的组可以减少电路执行的次数,这是可测量量如何被“编译”的一个例子。

PennyLane包含不同的功能,旨在从高层次的变换作用于QNodes到低层次的功能作用于算子。

一个处理 QNodes 的变换示例是 split_non_commuting()。它将测量非交换观测量的 QNode 转换为一个内部使用 多个 电路执行的 QNode,使用按比特交换的组。该变换由设备使用,以使这些测量成为可能。

在更低的层面上,group_observables() 函数可以用来拆分可观测量和系数的列表:

>>> obs = [qml.Y(0), qml.X(0) @ qml.X(1), qml.Z(1)]
>>> coeffs = [1.43, 4.21, 0.97]
>>> groupings = qml.pauli.group_observables(obs, coeffs, 'anticommuting', 'lf')
>>> obs_groupings, coeffs_groupings = groupings
>>> obs_groupings
[[Z(1), X(0) @ X(1)], [Y(0)]]
>>> coeffs_groupings
[[0.97, 4.21], [1.43]]

此模块及更多操作Pauli可观测量的逻辑可以在pauli模块中找到。