量子电路

../_images/qnode.png

在 PennyLane 中,量子计算涉及一个或多个量子电路的执行,表示为 量子节点 对象。量子节点用于声明量子电路,并将计算绑定到执行该计算的特定设备上。

QNodes 可以与任何支持的数值和机器学习库接口—NumPyPyTorchTensorFlowJAX—通过在创建 QNode 时提供可选的 interface 参数来指示。每个接口允许量子电路与特定于库的数据结构(例如,NumPy 和 JAX 数组或 PyTorch/TensorFlow 张量)以及 optimizers 无缝集成。

默认情况下,QNodes 使用 NumPy 接口。其他的 PennyLane 接口在接口部分有更详细的介绍。

量子函数

量子电路是作为一个特殊的Python函数构建的,一种量子电路函数,或简短的量子函数。例如:

import pennylane as qml

def my_quantum_function(x, y):
    qml.RZ(x, wires=0)
    qml.CNOT(wires=[0,1])
    qml.RY(y, wires=1)
    return qml.expval(qml.PauliZ(1))

注意

PennyLane 使用术语 wires 来指代量子子系统——对于大多数设备,这对应于一个量子比特。对于连续变量设备,电线对应于一个量子模态。

量子函数是Python函数的一个受限子集,遵循以下限制:

  • 量子函数接受经典输入,包含量子算子或称为模板的算子序列。

  • 该函数可以包含经典的流程控制结构,例如 for 循环或 if 语句。

  • 量子函数必须始终返回一个单一值或一个值元组,通过对量子比特寄存器应用一个 测量函数。最常见的例子是测量一个 量子比特可观测量连续值可观测量 的期望值。

注意

量子函数是在QNode内部的设备上评估的。

定义设备

要运行—并随后优化—量子电路,首先需要指定一个 计算设备

该设备是Device类的一个实例,可以表示模拟器或硬件设备。它们可以使用device加载器进行实例化。

dev = qml.device('default.qubit', wires=2, shots=1000)

PennyLane 提供了一些基本设备,例如 'default.qubit''default.mixed'lightning.qubit'default.gaussian''default.clifford''default.tensor' 模拟器;可以作为插件安装额外设备 (有关更多详细信息,请参见 可用插件)。请注意,设备的选择显著影响计算速度以及可以传递给设备加载器的可用选项。

注意

例如,请查看 'lightning.gpu' 插件, 这是一个快速的状态矢量模拟器,使用NVIDIA cuQuantum SDK进行GPU加速电路仿真。

注意

有关保存设备配置的详细信息,请访问 配置页面

设备选项

加载设备时,必须始终指定设备名称。进一步选项可以作为关键字参数传递,并可以根据设备有所不同。有关插件设备的可用设备选项,请参阅插件文档。

两个最重要的设备选项是 wiresshots 参数。

线

wires 参数可以是一个整数,它定义了你可以通过连续的整数标签 0, 1, 2, ... 地址的 线圈数量

dev = qml.device('default.qubit', wires=3)

或者,您可以通过传递一个包含子系统唯一标签的可迭代对象来使用自定义标签:

dev_unique_wires = qml.device('default.qubit', wires=['aux', 'q1', 'q2'])

在量子函数中,您现在可以使用自己的标签来引用线路:

def my_quantum_function(x, y):
    qml.RZ(x, wires='q1')
    qml.CNOT(wires=['aux' ,'q1'])
    qml.RY(y, wires='q2')
    return qml.expval(qml.PauliZ('q2'))

允许的线标签可以是任何可哈希的类型,这样可以唯一地区分两条线。

注意

一些设备,比如硬件芯片,可能有固定数量的导线。传递给设备的 wires 参数的标签可迭代对象必须与预期的导线数量匹配。

警告

为了支持任何可哈希类型的线标签,整数和 0-d 数组被视为不同的类型。 例如,在使用 qml.RX(1.1, qml.numpy.array(0)) 初始化的设备上运行 wires=[0] 将会失败,因为 qml.numpy.array(0) 在设备的线映射中不存在。

镜头

参数 shots 是一个整数,用于定义电路应该被评估(或“采样”)多少次,以估计统计量。在某些支持的模拟器设备上,shots=None 精确计算测量统计数据 确切

请注意,当调用 QNode 时,此参数可以暂时被覆盖。例如,my_qnode(shots=3)将暂时使用三次实验评估my_qnode。这是每个 QNode 的一个特性,手动实现量子函数的shots关键字参数并不是必要的。

有时,为了在不同的shot数量下检索计算结果,而不需要多次评估QNode(“shot批处理”)是很有用的。通过传递一个整数列表,可以指定shot批次,从而允许通过单次QNode评估来对测量统计数据进行粗粒度处理。

考虑

>>> shots_list = [5, 10, 1000]
>>> dev = qml.device("default.qubit", wires=2, shots=shots_list)

当QNodes在该设备上执行时,将提交1015次实验的单次执行。然而,将返回三组测量统计;分别使用前5次实验、第二组10次实验和最后1000次实验。

例如:

@qml.qnode(dev)
def circuit(x):
    qml.RX(x, wires=0)
    qml.CNOT(wires=[0, 1])
    return qml.expval(qml.PauliZ(0) @ qml.PauliX(1)), qml.expval(qml.PauliZ(0))

执行这个,我们将得到形状 (3, 2) 的输出:

>>> results = circuit(0.5)
>>> results
((array(0.6), array(1.)),
 (array(-0.4), array(1.)),
 (array(0.048), array(0.902)))

我们可以索引这个元组并仅用5次尝试检索计算结果:

>>> results[0]
(array(0.6), array(1.))

创建量子节点

一起,一个量子函数和一个设备被用来创建一个量子节点QNode对象,它将量子函数封装并绑定到设备上。

可以通过以下方式显式创建一个 QNode:

circuit = qml.QNode(my_quantum_function, dev_unique_wires)

QNode可以用于计算量子电路的结果,仿佛它是一个标准的Python函数。它接受与原始量子函数相同的参数:

>>> circuit(np.pi/4, 0.7)
tensor(0.764, requires_grad=True)

要查看给定特定参数值的量子电路,我们可以使用 draw() 转换,

>>> print(qml.draw(circuit)(np.pi/4, 0.7))
aux: ───────────╭●─┤
 q1: ──RZ(0.79)─╰X─┤
 q2: ──RY(0.70)────┤  <Z>

或者 draw_mpl() 转换:

>>> import matplotlib.pyplot as plt
>>> qml.drawer.use_style("black_white")
>>> fig, ax = qml.draw_mpl(circuit)(np.pi/4, 0.7)
>>> plt.show()
../_images/draw_mpl.png

QNode 装饰器

一个更方便的——实际上是推荐的——创建 QNodes 的方式是提供的 qnode 装饰器。这个装饰器将包含 PennyLane 量子操作的 Python 函数转换为一个 QNode 电路,该电路将在量子设备上运行。

注意

该装饰器完全替换了基于Python的量子函数,与一个同名的QNode;因此,原始函数不再可访问。

例如:

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

@qml.qnode(dev)
def circuit(x):
    qml.RZ(x, wires=0)
    qml.CNOT(wires=[0,1])
    qml.RY(x, wires=1)
    return qml.expval(qml.PauliZ(1))

result = circuit(0.543)

QNodes中的参数广播

根据所使用的量子操作,QNode 可能支持同时在多个参数下执行:

>>> x = np.array([0.543, 1.234])
>>> result = circuit(x)
>>> result
tensor([0.85616242, 0.33046511], requires_grad=True)

请注意,我们正在将一个一维参数数组传递给上面定义的circuit() QNode,该QNode接受单个参数并返回单个期望值。由于输入现在是一个数组,因此输出也是一个数组,每个元素都是对应输入元素的期望值。

这称为参数广播(例如,对于沿着一个轴执行的NumPy函数)或参数批处理(例如,在机器学习中将函数应用于一组参数)。

除了更灵活的执行语法外,在参数设置的每次单独执行QNode的情况下,广播可以带来性能提升。是否会出现这种情况取决于许多细节,但特别是在(最多)中等大小的电路(\(\lesssim 20\) 条)和适量的参数(\(\lesssim 200\))在经典模拟器上执行时,可以预期会从广播中受益。有关用法的详细信息,请参阅QNode文档。

许多标准量子算子支持广播;请查看对应的属性 supports_broadcasting的列表。 Operator文档包含实现细节和使自定义算子与广播兼容的指南。 广播可以与任何设备一起使用,但通常只有在类似于 "default.qubit" 的设备上会带来性能提升,这些设备表明它们支持此功能:

>>> cap = dev.capabilities()
>>> cap["supports_broadcasting"]
True

其他设备将参数分开并顺序执行QNode。

从其他框架导入电路

PennyLane支持创建从其他框架导入的自定义PennyLane模板。通过将现有的量子代码加载为PennyLane模板,您可以添加执行解析微分的能力,并与机器学习库如PyTorch和TensorFlow进行接口。目前,可以使用以下函数加载来自Qiskit的QuantumCircuit对象、OpenQASM文件、pyQuil programs和Quil文件:

from_qiskit

将Qiskit QuantumCircuit 转换为PennyLane 量子函数

from_qasm

使用PennyLane-Qiskit插件中的转换器从QASM字符串加载量子电路。

from_pyquil

通过使用PennyLane-Rigetti插件中的转换器加载pyQuil程序对象。

from_quil

使用PennyLane-Rigetti插件中的转换器,从Quil字符串加载量子电路。

from_quil_file

使用PennyLane-Rigetti插件中的转换器从Quil文件加载量子电路。

注意

要使用这些转换函数,需要安装最新版本的 PennyLane-Qiskit 和 PennyLane-Rigetti 插件。

量子电路的对象可以在QNode外部或直接内部加载。还支持包含未绑定参数的电路。参数绑定可以通过传递包含参数值对的字典来实现。

一旦从这样的量子电路创建了一个PennyLane模板,它可以像PennyLane中的其他 模板 一样使用。一个重要的事项是,自定义模板必须始终在 QNode 中执行(类似于预定义的模板)。

注意

与外部框架特定的某些指令在加载外部量子电路时可能会被忽略。对于被忽略的指令,将会发出警告消息。

以下是加载和调用一个参数化的 Qiskit QuantumCircuit 对象的示例,同时使用 QNode 装饰器:

from qiskit import QuantumCircuit
from qiskit.circuit import Parameter
import numpy as np

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

theta = Parameter('θ')

qc = QuantumCircuit(2)
qc.rz(theta, [0])
qc.rx(theta, [0])
qc.cx(0, 1)

@qml.qnode(dev)
def quantum_circuit_with_loaded_subcircuit(x):
    qml.from_qiskit(qc)({theta: x})
    return qml.expval(qml.PauliZ(0))

angle = np.pi/2
result = quantum_circuit_with_loaded_subcircuit(angle)

此外,加载的模板可以与任何支持的设备一起使用,任意次数。 例如,在以下示例中,从 QASM 字符串加载模板,然后在forest.qpu设备上多次使用,由PennyLane-Rigetti提供:

import pennylane as qml

dev = qml.device('forest.qpu', wires=2)

hadamard_qasm = 'OPENQASM 2.0;' \
                'include "qelib1.inc";' \
                'qreg q[1];' \
                'h q[0];'

apply_hadamard = qml.from_qasm(hadamard_qasm)

@qml.qnode(dev)
def circuit_with_hadamards():
    apply_hadamard(wires=[0])
    apply_hadamard(wires=[1])
    qml.Hadamard(wires=[1])
    return qml.expval(qml.PauliX(0)), qml.expval(qml.PauliX(1))

result = circuit_with_hadamards()