检查电路

PennyLane 提供功能来检查、可视化或分析量子电路。

这些工具大多数被实现为 变换。变换接收一个 QNode 实例并返回一个函数:

>>> @qml.qnode(dev, diff_method='parameter-shift')
... def my_qnode(x, a=True):
...     # ...
>>> new_func = my_transform(qnode)

该新函数接受与QNode相同的参数,并返回所需的结果,例如QNode属性的字典、绘制电路的matplotlib图形或表示其连接结构的DAG。

>>> new_func(0.1, a=False)

关于变换概念的更多信息可以在 Di Matteo et al. (2022)中找到。

提取电路的属性

specs() 转换器接受一个 QNode 并创建一个返回 QNode 详细信息的函数,包括深度、门的数量和所需的梯度执行次数。

例如:

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

@qml.qnode(dev, diff_method='parameter-shift')
def circuit(x, y):
    qml.RX(x[0], wires=0)
    qml.Toffoli(wires=(0, 1, 2))
    qml.CRY(x[1], wires=(0, 1))
    qml.Rot(x[2], x[3], y, wires=0)
    return qml.expval(qml.Z(0)), qml.expval(qml.X(1))

我们现在可以使用 specs() 转换来生成一个返回细节和资源信息的函数:

>>> x = np.array([0.05, 0.1, 0.2, 0.3], requires_grad=True)
>>> y = np.array(0.4, requires_grad=False)
>>> specs_func = qml.specs(circuit)
>>> specs_func(x, y)
{'resources': Resources(num_wires=3, num_gates=4, gate_types=defaultdict(<class 'int'>, {'RX': 1, 'Toffoli': 1, 'CRY': 1, 'Rot': 1}), depth=4, shots=0),
 'gate_sizes': defaultdict(int, {1: 2, 3: 1, 2: 1}),
 'gate_types': defaultdict(int, {'RX': 1, 'Toffoli': 1, 'CRY': 1, 'Rot': 1}),
 'num_operations': 4,
 'num_observables': 2,
 'num_diagonalizing_gates': 1,
 'num_used_wires': 3,
 'num_trainable_params': 4,
 'depth': 4,
 'num_device_wires': 4,
 'device_name': 'default.qubit',
 'gradient_options': {},
 'interface': 'auto',
 'diff_method': 'parameter-shift',
 'gradient_fn': 'pennylane.gradients.parameter_shift.param_shift',
 'num_gradient_executions': 10}

电路绘制

PennyLane 有两个内置的电路绘图工具, draw()draw_mpl()

例如:

dev = qml.device('default.qubit')

@qml.qnode(dev)
def circuit(x, z):
    qml.QFT(wires=(0,1,2,3))
    qml.IsingXX(1.234, wires=(0,2))
    qml.Toffoli(wires=(0,1,2))
    mcm = qml.measure(1)
    mcm_out = qml.measure(2)
    qml.CSWAP(wires=(0,2,3))
    qml.RX(x, wires=0)
    qml.cond(mcm, qml.RY)(np.pi / 4, wires=3)
    qml.CRZ(z, wires=(3,0))
    return qml.expval(qml.Z(0)), qml.probs(op=mcm_out)


fig, ax = qml.draw_mpl(circuit)(1.2345,1.2345)
fig.show()
../_images/main_example.png
>>> print(qml.draw(circuit)(1.2345,1.2345))
0: ─╭QFT─╭IsingXX(1.23)─╭●───────────╭●─────RX(1.23)─╭RZ(1.23)─┤  <Z>
1: ─├QFT─│──────────────├●──┤↗├──────│───────────────│─────────┤
2: ─├QFT─╰IsingXX(1.23)─╰X───║───┤↗├─├SWAP───────────│─────────┤
3: ─╰QFT─────────────────────║────║──╰SWAP──RY(0.79)─╰●────────┤
                             ╚════║═════════╝
                                  ╚════════════════════════════╡  Probs[MCM]

更多信息,包括各种微调选项,可以在绘图模块中找到。

使用中间电路快照进行调试

在调试运行在模拟器上的量子电路时,我们可能想要检查门之间的当前量子状态。

Snapshot 是一个类似于门的算子,但它在电路中的位置保存设备状态,而不是操纵量子状态。

当前支持的设备包括:

  • default.qubit: 每个快照保存量子状态向量

  • default.mixed: 每个快照保存密度矩阵

  • default.gaussian: 每个快照保存协方差矩阵和均值向量

一个 Snapshot 可以像其他操作一样在 QNode 中使用:

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

@qml.qnode(dev, interface=None)
def circuit():
    qml.Snapshot(measurement=qml.expval(qml.Z(0)))
    qml.Hadamard(wires=0)
    qml.Snapshot("very_important_state")
    qml.CNOT(wires=[0, 1])
    qml.Snapshot()
    return qml.expval(qml.X(0))

在正常执行期间,快照被忽略:

>>> circuit()
0.0

然而,当使用 snapshots() 变换时,中间设备状态将被存储并与结果一起返回。

>>> qml.snapshots(circuit)()
{0: 1.0,
'very_important_state': array([0.707+0.j, 0.+0.j, 0.707+0.j, 0.+0.j]),
2: array([0.707+0.j, 0.+0.j, 0.+0.j, 0.707+0.j]),
'execution_results': 0.0}

所有快照都用连续的整数编号,如果没有提供标签,则快照的编号作为输出字典中的键。

在模拟器上进行交互式调试

PennyLane 允许通过 breakpoint() 以编程方式使用量子断点来进行更互动的量子电路调试。此功能当前支持 default.qubitlightning.qubit 设备。

考虑以下包含断点的量子电路的python脚本。

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

@qml.qnode(dev)
def circuit(x):
    qml.breakpoint()

    qml.RX(x, wires=0)
    qml.Hadamard(wires=1)

    qml.breakpoint()

    qml.CNOT(wires=[0, 1])
    return qml.expval(qml.Z(0))

circuit(1.23)

运行上面的电路会启动一个交互式 [pldb] 提示符。在这里我们可以逐步执行电路:

> /Users/your/path/to/script.py(8)circuit()
-> qml.RX(x, wires=0)
[pldb] list
  3
  4         @qml.qnode(dev)
  5         def circuit(x):
  6             qml.breakpoint()
  7
  8  ->         qml.RX(x, wires=0)
  9             qml.Hadamard(wires=1)
 10
 11             qml.breakpoint()
 12
 13             qml.CNOT(wires=[0, 1])
[pldb] next
> /Users/your/path/to/script.py(9)circuit()
-> qml.Hadamard(wires=1)

我们可以通过进行不会改变执行中电路状态的测量来提取信息:

[pldb] qml.debug_state()
array([0.81677345+0.j        , 0.        +0.j        ,
       1.        -0.57695852j, 0.        +0.j        ])
[pldb] continue
> /Users/your/path/to/script.py(13)circuit()
-> qml.CNOT(wires=[0, 1])
[pldb] next
> /Users/your/path/to/script.py(14)circuit()
-> return qml.expval(qml.Z(0))
[pldb] list
  8             qml.RX(x, wires=0)
  9             qml.Hadamard(wires=1)
 10
 11             qml.breakpoint()
 12
 13             qml.CNOT(wires=[0, 1])
 14  ->         return qml.expval(qml.Z(0))
 15
 16         circuit(1.23)
[EOF]

我们还可以可视化电路,并将操作动态排队到电路中:

[pldb] print(qml.debug_tape().draw())
0: ──RX─╭●─┤
1: ──H──╰X─┤
[pldb] qml.RZ(-4.56, 1)
RZ(-4.56, wires=[1])
[pldb] print(qml.debug_tape().draw())
0: ──RX─╭●─────┤
1: ──H──╰X──RZ─┤

查看更多信息和详细示例,请参见 qml.debugging

图形表示

PennyLane 利用几种方法将量子电路表示为有向无环图 (DAG)。

操作之间因果关系的有向无环图

有向无环图 (DAG) 可以用来表示电路中哪个操作符与另一个操作符有因果关系。有两种选项来构建这样的有向无环图:

CircuitGraph 接受一个门或通道及厄米可观测量的列表,以及一组线标签,并构建一个有向无环图(DAG),在这个图中,Operator 实例是节点,每个有向边对应于一个“节点”后续作用的线(或一组线)。

例如,这可以用于计算电路的有效深度,或检查两个门是否在因果上相互影响。

import pennylane as qml
from pennylane import CircuitGraph
from pennylane.workflow import construct_tape

dev = qml.device('lightning.qubit', wires=(0,1,2,3))

@qml.qnode(dev)
def circuit():
    qml.Hadamard(0)
    qml.CNOT([1, 2])
    qml.CNOT([2, 3])
    qml.CNOT([3, 1])
    return qml.expval(qml.Z(0))


circuit()
tape = construct_tape(circuit)()
ops = tape.operations
obs = tape.observables
g = CircuitGraph(ops, obs, tape.wires)

在内部,CircuitGraph 类构造了一个 rustworkx 图对象。

>>> type(g.graph)
rustworkx.PyDiGraph

Hadamard与第一个CNOT之间没有边,但在连续的CNOT门之间有:

>>> g.has_path(ops[0], ops[1])
False
>>> g.has_path(ops[1], ops[3])
True

哈达玛操作连接到可观测量,而CNOT算子则没有。可观测量不遵循哈达玛。

>>> g.has_path(ops[0], obs[0])
True
>>> g.has_path(ops[1], obs[0])
False
>>> g.has_path(obs[0], ops[0])
False

构造电路的“因果”有向无环图的另一种方法是使用 tape_to_graph() 函数,该函数由 qcut 模块使用。此 函数接受一个量子磁带,并从 networkx python 包创建一个 MultiDiGraph 实例。

使用上述示例,我们得到:

>>> g2 = qml.qcut.tape_to_graph(tape)
>>> type(g2)
<class 'networkx.classes.multidigraph.MultiDiGraph'>
>>> for k, v in g2.adjacency():
...    print(k, v)
H(0) {expval(Z(0)): {0: {'wire': 0}}}
CNOT(wires=[1, 2]) {CNOT(wires=[2, 3]): {0: {'wire': 2}}, CNOT(wires=[3, 1]): {0: {'wire': 1}}}
CNOT(wires=[2, 3]) {CNOT(wires=[3, 1]): {0: {'wire': 3}}}
CNOT(wires=[3, 1]) {}
expval(Z(0)) {}

非交换操作的有向无环图

变换 commutation_dag() 可用于生成 CommutationDAG 类的实例。 在一个对易DAG中,每个节点代表一个量子操作,边表示两个操作之间的非对易关系。

这个转换考虑到并非所有操作都可以通过成对交换移到彼此旁边:

>>> def circuit(x, y, z):
...     qml.RX(x, wires=0)
...     qml.RX(y, wires=0)
...     qml.CNOT(wires=[1, 2])
...     qml.RY(y, wires=1)
...     qml.Hadamard(wires=2)
...     qml.CRZ(z, wires=[2, 0])
...     qml.RY(-y, wires=1)
...     return qml.expval(qml.Z(0))
>>> dag_fn = qml.commutation_dag(circuit)
>>> dag = dag_fn(np.pi / 4, np.pi / 3, np.pi / 2)

可以通过 get_nodes() 方法访问交换DAG中的节点,它返回一个形式为 (ID, CommutationDAGNode) 的列表:

>>> nodes = dag.get_nodes()
>>> nodes
NodeDataView({0: <pennylane.transforms.commutation_dag.CommutationDAGNode object at 0x7f461c4bb580>, ...}, data='node')

可以通过 get_node() 方法访问换位有向无环图中的特定节点:

>>> second_node = dag.get_node(2)
>>> second_node
<pennylane.transforms.commutation_dag.CommutationDAGNode object at 0x136f8c4c0>
>>> second_node.op
CNOT(wires=[1, 2])
>>> second_node.successors
[3, 4, 5, 6]
>>> second_node.predecessors
[]

傅里叶表示

参数化量子电路通常计算可以用低阶傅里叶级数表示的参数中的函数。

这个 qml.fourier 模块包含计算和可视化这种傅里叶级数性质的功能。

../_images/fourier_vis_radial_box.png