# 含参量子线路的等价性检查

## 概述

[1]:

# 引入相关库
from mindquantum.core.circuit import Circuit
import numpy as np
from mindquantum.core.gates import H, CNOT, RX, RZ
from mindquantum.core.circuit import dagger


## 第一步：准备量子线路

[2]:

# 每一组旋转层和纠缠层的组合就是一层ansatz线路
def build_ansatz(n_qubits, depth):
circ = Circuit()    # 初始化量子线路

for i in range(depth):
for j in range(n_qubits):
circ += RX(f'theta{i*n_qubits+j}').on(j)    # 每个量子比特上一个RX门
circ += CNOT.on(0, n_qubits-1)    # 最后一个量子比特和第一个量子比特上一个CNOT门
for j in range(n_qubits-1):
circ += CNOT.on(j+1, j)    # 相邻两个量子比特上一个CNOT门，CNOT门作用在第j+1位，且受第j位控制

for j in range(n_qubits):
circ += RX(f'theta{depth*n_qubits+j}').on(j)    # 每个量子比特上一个RX门

return circ

[3]:

# 作用在3个量子比特上的一层ansatz线路示例
build_ansatz(3, 1).svg()

[3]:

[4]:

# 初始线路共3层，作用在127个量子比特上
n_qubits = 127
depth = 3
circ1 = build_ansatz(n_qubits, depth)
circ1.summary()    # 总结初始线路

==============================================Circuit Summary==============================================
|Total number of gates  : 889.                                                                            |
|Parameter gates        : 508.                                                                            |
|with 508 parameters are:                                                                                 |
|theta0, theta1, theta2, theta3, theta4, theta5, theta6, theta7, theta8, theta9..                        .|
|Number qubit of circuit: 127                                                                             |
===========================================================================================================


[5]:

def compile_circuit(circ):
circ_compiled = Circuit()

for gate in circ:    # 遍历初始线路中的量子门
# H，CNOT，RZ门保持不变
if gate.name == 'H' or gate.name == 'CNOT' or gate.name == 'RZ':
circ_compiled += gate
elif gate.name == 'RX':    # RX门经过编译变成H*RZ*H
circ_compiled += H.on(gate.obj_qubits)
circ_compiled += RZ(gate.coeff).on(gate.obj_qubits)
circ_compiled += H.on(gate.obj_qubits)

return circ_compiled

[6]:

# 一层ansatz线路编译后生成的线路示例，可以看到，所有RX门都根据编译规则发生了变化
compile_circuit(build_ansatz(3, 1)).svg()

[6]:

[7]:

# 编译初始线路
circ2 = compile_circuit(circ1)
circ2.summary()    # 总结编译线路

==============================================Circuit Summary==============================================
|Total number of gates  : 1905.                                                                           |
|Parameter gates        : 508.                                                                            |
|with 508 parameters are:                                                                                 |
|theta0, theta1, theta2, theta3, theta4, theta5, theta6, theta7, theta8, theta9..                        .|
|Number qubit of circuit: 127                                                                             |
===========================================================================================================


[8]:

# 完整线路
circ1_inv = dagger(circ1)    # dagger()将量子线路左右逆转，得到初始线路的逆线路
circ_all = circ1_inv + circ2    # 完整线路=初始线路的逆线路+编译后的线路
circ_all.summary()    # 总结完整线路

========================================================Circuit Summary========================================================
|Total number of gates  : 2794.                                                                                               |
|Parameter gates        : 1016.                                                                                               |
|with 508 parameters are:                                                                                                     |
|theta507, theta506, theta505, theta504, theta503, theta502, theta501, theta500, theta499, theta498..                        .|
|Number qubit of circuit: 127                                                                                                 |
===============================================================================================================================


## 第二步：将完整的量子线路转换成ZX图

[9]:

# 顶点类
class Vertex:
def __init__(self, name, color, qubit, neighbor, phase=0.0):
self.name = name    # 量子门顶点的序号
self.color = color    # 量子门顶点的颜色
self.phase = phase   # 含参量子门的参数
self.qubit = qubit     # 作用在哪个量子比特上
self.neighbor = neighbor    # 顶点之间的邻居关系

[10]:

# 图类
class Graph:
def __init__(self):
self.vertices = {}    # 初始图，空
self.count = 0    # 顶点总数，只增不减，也用于给新顶点命名

# 新增边
def add_edge(self, from_vertex, to_vertex):    # 添加一条从起点到终点的边
self.vertices[from_vertex].neighbor.append(to_vertex)

# 新增顶点
def add_vertex(self, color, qubit, neighbor, phase=0.0):
name = self.count
self.count += 1
# 添加从新顶点到相邻顶点的边
self.vertices[name] = Vertex(name, color, qubit, neighbor, phase)
for v in neighbor:    # 再添加从相邻顶点到新顶点的边

# 打印图信息
def print(self):
print("==================graph message==================")
for v in self.vertices.values():
print(v.name, '\t', v.neighbor, '\t', v.color, '\t', v.phase)
print('\n')

# 删除自身的环
# 省略了ZX图的无环规则（本文中，所有“无环”均指不存在单条边构成的环）
def clear(self):
for v in self.vertices.values():
while v.name in v.neighbor:
# 将本顶点从自己的邻居中删除
self.vertices[v.name].neighbor.remove(v.name)

# 删除顶点
def delete_vertex(self, name):
for v in self.vertices.values():
while name in v.neighbor:    # 删除终点是该顶点的边
self.vertices[v.name].neighbor.remove(name)
self.vertices.pop(name)    # 删除起点是该顶点的边

# 两个电路是否等价
def equiv(self):
if not self.vertices:    # 等价的两个电路，经过ZX演算化简后，图中无顶点
print("Equivalent!")
else:
print("Not sure!")


[11]:

def draw_graph(circ):
g = Graph()    # 初始化一个空图
last_name = [-1] * circ.n_qubits    # last_name保存每个量子比特上当前的最后一个顶点
for gate in circ:    # 遍历线路中的所有量子门
if gate.name == 'H':    # H门绘制成黄色顶点
if last_name[gate.obj_qubits[0]] != -1:    # 当前量子比特上已有顶点
[last_name[gate.obj_qubits[0]]])
else:    # 当前量子比特上暂无顶点
last_name[gate.obj_qubits[0]] = g.count-1    # 更新当前量子比特上最后一个顶点为新增顶点
if gate.name == 'RX':    # RX门绘制成红色顶点
if last_name[gate.obj_qubits[0]] != -1:
[last_name[gate.obj_qubits[0]]], gate.coeff)
else:
last_name[gate.obj_qubits[0]] = g.count-1
if gate.name == 'RZ':    # RZ门绘制成绿色顶点
if last_name[gate.obj_qubits[0]] != -1:
[last_name[gate.obj_qubits[0]]], gate.coeff)
else:
last_name[gate.obj_qubits[0]] = g.count-1
if gate.name == 'CNOT':    # CNOT门要分别绘制控制位顶点和受控位顶点
# 绘制控制位顶点，绿色
if last_name[gate.obj_qubits[1]] != -1:
[last_name[gate.obj_qubits[1]]])
else:
last_name[gate.obj_qubits[1]] = g.count-1
# 绘制受控位顶点，红色
if last_name[gate.obj_qubits[0]] != -1:
[last_name[gate.obj_qubits[0]], g.count-1])
else:
last_name[gate.obj_qubits[0]] = g.count-1
return g


[12]:

g = draw_graph(circ_all)


## 第三步：化简ZX图

ZX演算由ZX图和化简规则组成，根据化简规则，对ZX图中的顶点和邻居关系进行化简。

[13]:

def rule_1(g: Graph):
for v1 in list(g.vertices.keys()):    # ZX演算过程中，图中的顶点会发生增减，用list()获取最初的所有顶点
if v1 not in g.vertices.keys():    # 判断当前顶点在化简过程中有没有被删除
continue    # 已被删除，略过
v1 = g.vertices[v1]
# 顶点参数为0
if v1.phase == 0 or list(v1.phase.values()) == [0.0]*len(list(v1.phase.values())):
flag = True    # 用于判断当前顶点是否与其他量子比特上的顶点相关，如果相关，暂时不能删除
for v2 in v1.neighbor:
v2 = g.vertices[v2]
if v2.qubit != v1.qubit:    # 与其他量子比特上的顶点相关
flag = False
break
if flag:    # 与其他量子比特上的顶点无关
for v2 in v1.neighbor:
v2 = g.vertices[v2]
v2.neighbor.extend(v1.neighbor)    # 将前一个顶点与后一个顶点相连，略过当前顶点
g.clear()    # 清除化简过程中可能产生的环
g.delete_vertex(v1.name)    # 删除该顶点


[14]:

def rule_2(g: Graph):
for v1 in list(g.vertices.keys()):
if v1 not in g.vertices.keys():
continue
v1 = g.vertices[v1]
if v1.color == 'red' or v1.color == 'green':    # 红色或绿色顶点
for v2 in v1.neighbor:    # 相邻
v2 = g.vertices[v2]
if v2.color == v1.color:    # 相同颜色
v2.phase = v2.phase + v1.phase    # 参数相加
v2.neighbor.extend(v1.neighbor)    # 合并两个顶点
g.clear()
for v3 in v1.neighbor:    # 更新合并顶点后的邻居关系
v3 = g.vertices[v3]
v3.neighbor.append(v2.name)
g.clear()
g.delete_vertex(v1.name)    # 删除已被合并的顶点


[15]:

def rule_3(g: Graph):
for v1 in list(g.vertices.keys()):
if v1 not in g.vertices.keys():
continue
v1 = g.vertices[v1]
if v1.color == 'green':
flag = True    # 用于判断是否所有邻居都是黄色
for v2 in v1.neighbor:
v2 = g.vertices[v2]
if v2.color != 'yellow':    # 不满足所有邻居都是黄色
flag = False
break
if flag:    # 所有邻居都是黄色
v1.color = 'red'    # 变成红色
v1_neighbor = list(v1.neighbor)
for v2 in v1_neighbor:    # 删除这些黄色顶点
v2 = g.vertices[v2]
v1.neighbor.extend(v2.neighbor)
g.clear()
for v3 in v2.neighbor:
v3 = g.vertices[v3]
v3.neighbor.append(v1.name)
g.clear()
g.delete_vertex(v2.name)


[16]:

def rule_4(g: Graph):
for v1 in list(g.vertices.keys()):
if v1 not in g.vertices.keys():
continue
v1 = g.vertices[v1]
if v1.color == 'green':
for v2 in v1.neighbor:
v2 = g.vertices[v2]
# 红绿顶点，且两顶点间有两条边
if v2.color == 'red' and v2.neighbor.count(v1.name) == 2:
while v2.name in g.vertices[v1.name].neighbor:    # 删除相连的边
v1.neighbor.remove(v2.name)
while v1.name in g.vertices[v2.name].neighbor:
v2.neighbor.remove(v1.name)


[17]:

def simplify(g: Graph):
temp = []    # 用于对比本轮循环是否有顶点被删除
while temp != list(g.vertices.keys()):    # 如果本轮循环没有删除任何顶点，则认为化简结束，退出循环
temp = list(g.vertices.keys())
rule_3(g)
rule_2(g)
rule_4(g)
rule_1(g)


[18]:

test_circ1 = build_ansatz(3, 1)
test_circ1_inv = dagger(test_circ1)
test_circ2 = compile_circuit(test_circ1)

test_circ_all = test_circ1_inv + test_circ2

test_circ_all.svg()

[18]:

[19]:

# 将测试线路绘制成ZX图
test_g = draw_graph(test_circ_all)
test_g.print()

==================graph message==================
0        [4]     red     -theta5
1        [3]     red     -theta4
2        [5]     red     -theta3
3        [1, 4, 6]       green   0.0
4        [0, 3, 7]       red     0.0
5        [2, 6, 8]       green   0.0
6        [3, 5, 10]      red     0.0
7        [4, 8, 9]       green   0.0
8        [5, 7, 11]      red     0.0
9        [7, 18]         red     -theta2
10       [6, 15]         red     -theta1
11       [8, 12]         red     -theta0
12       [11, 13]        yellow          0.0
13       [12, 14]        green   theta0
14       [13, 22]        yellow          0.0
15       [10, 16]        yellow          0.0
16       [15, 17]        green   theta1
17       [16, 24]        yellow          0.0
18       [9, 19]         yellow          0.0
19       [18, 20]        green   theta2
20       [19, 21]        yellow          0.0
21       [20, 22, 26]    green   0.0
22       [14, 21, 23]    red     0.0
23       [22, 24, 27]    green   0.0
24       [17, 23, 25]    red     0.0
25       [24, 26, 30]    green   0.0
26       [21, 25, 33]    red     0.0
27       [23, 28]        yellow          0.0
28       [27, 29]        green   theta3
29       [28]    yellow          0.0
30       [25, 31]        yellow          0.0
31       [30, 32]        green   theta4
32       [31]    yellow          0.0
33       [26, 34]        yellow          0.0
34       [33, 35]        green   theta5
35       [34]    yellow          0.0


[20]:

# 化简测试线路
print("化简之前：")
test_g.equiv()

simplify(test_g)

print("化简之后：")
test_g.equiv()

化简之前：
Not sure!

Equivalent!


[21]:

# 化简完整线路
print("化简之前：")
g.equiv()

simplify(g)    # 化简

print("化简之后：")
g.equiv()

化简之前：
Not sure!

Equivalent!


## 第四步：若ZX演算无法确定则实例化参数

[22]:

# 构造反例线路：ZX演算化简后无法确定、实际上不等价的两个线路
neq_circ1 = Circuit()
neq_circ1 += H.on(1)
neq_circ1 += RX(f'theta{0}').on(2)
neq_circ1 += CNOT.on(0, 1)
neq_circ1 += RZ(f'theta{1}').on(0)
neq_circ1 += CNOT.on(2, 1)
neq_circ1 += CNOT.on(0, 1)
neq_circ1 += RX(f'theta{2}').on(2)

neq_circ1.svg()

[22]:

[23]:

neq_circ2 = Circuit()
neq_circ2 += H.on(1)
neq_circ2 += RX(f'theta{0}').on(2)
neq_circ2 += CNOT.on(0, 1)
neq_circ2 += RZ(f'theta{1}').on(0)
neq_circ2 += CNOT.on(2, 1)
neq_circ2 += CNOT.on(0, 1)
neq_circ2 += RX({f'theta{0}': 1, f'theta{1}': 1, f'theta{2}': 1}).on(2)

neq_circ2.svg()

[23]:

[24]:

neq_circ1_inv = dagger(neq_circ1)
neq_circ_all = neq_circ1_inv + neq_circ2    # 构造完整反例线路
neq_circ_all.svg()

[24]:

[25]:

# 将反例线路绘制成ZX图并进行化简
neq_g = draw_graph(neq_circ_all)
print("化简之前：")
neq_g.equiv()

simplify(neq_g)

print("化简之后：")
neq_g.equiv()

化简之前：
Not sure!

Not sure!


[26]:

# 映射函数实例化参数
def map_para(n, r):
para = {}
for i in range(n):
para[f'theta{i}'] = (2*np.pi/((i+1)*r)-np.pi)
return para

# 随机实例化参数
def random_para(n):
para = {}
for i in range(n):
para[f'theta{i}'] = (np.random.uniform(np.pi, -np.pi))
return para

[27]:

# 实例化参数验证两个线路是否等价，验证r轮
def verify_by_para(circ1, circ2, r):
n = len(list(set(circ1.params_name+circ2.params_name)))    # 线路中一共n个参数
flag = True    # 记录前r-1轮验证是否有结果
for i in range(r-1):    # 前r-1轮指定参数
para = map_para(n, i+1)

# 直接比较两个实例化之后的线路的矩阵形式是否等价
if np.array_equal(circ1.matrix(para), circ2.matrix(para)):
continue
else:
print('Not equivalent!')    # 在任一情况下两个线路的矩阵不等价，即表示这两个线路不等价
flag = False    # 验证已有结果，结束
break

if flag:    # 前r-1轮没有结果，最后一轮随机参数
para = random_para(n)
if np.array_equal(circ1.matrix(para), circ2.matrix(para)):
print('Equivalent!')
else:
print('Not equivalent!')


[28]:

verify_by_para(neq_circ1, neq_circ2, 5)

Not equivalent!


## 最后：将以上过程合并成一个完整的功能

[29]:

def ZXcalculus(circ1, circ2):
circ1_inv = dagger(circ1)    # 将原始线路左右逆转
circ = circ1_inv + circ2    # 构造完整线路
g = draw_graph(circ)    # 将完整线路绘制成ZX图
print("化简之前：")
g.equiv()
simplify(g)    # 根据ZX演算规则进行化简
print("化简之后：")
if not g.vertices:    # 化简得到两个线路等价的结果
g.equiv()
else:    # 化简未能得到结果，需要实例化参数进行验证
g.equiv()
print("实例化参数验证：")
verify_by_para(circ1, circ2, 5)

[30]:

from mindquantum.utils.show_info import InfoTable

InfoTable('mindquantum', 'scipy', 'numpy')

[30]:

Software Version
mindquantum0.9.0
scipy1.10.1
numpy1.23.5
System Info
Python3.8.17
OSWindows AMD64
Memory8.39 GB