检查电路¶
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()
>>> 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.qubit 和 lightning.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
[]
