基于MindSpore Quantum的Shor算法
Shor算法简介
Shor算法在量子计算机上分解整数
Shor算法基本思路
Shor算法要解决的主要问题是:给定一个整数
因子分解涉及到数论里的一些知识,可以将因子分解问题归结为函数
对于
令
这也表明对于
因此,Shor算法的核心在于,将大数分解的问题转化为找周期的问题。由于量子计算可以利用叠加态进行并行计算,因此通过量子算法我们可以很快地找到函数周期查找算法
)。总的来说,我们需要在量子线路中实现
下面以
选择一个任意的数字,比如
求最大公约数,
找函数
的周期,使得通过量子电路图运算得到
求最大公约数,
求最大公约数,
分解得到的质数结果为3和5,分解完成。
Shor算法的量子电路如下图所示:
通过MindSpore Quantum实现Shor算法
首先,导入需要用到的模块。
[1]:
#pylint: disable=W0611
import numpy as np
from fractions import Fraction
import mindquantum as mq
from mindquantum.core.gates import X, Z, H, UnivMathGate, Measure
from mindquantum.core.circuit import Circuit, controlled, UN
from mindquantum.algorithm.library import qft
from mindquantum.simulator import Simulator
从Shor算法的基本思路我们可以看出,Shor算法最核心的部分就在于由量子计算机处理的周期查找算法,而周期查找算法中最困难的地方就是将态
构造Oracle
该Oracle的构造方法原本十分简单,只需3步:
将变换前所有可能的
进行穷举(从 到 共有 个数),并一一算出对应的 。对每一个
,我们都可以写出变换前的态 和变换后的态 的矩阵表示,将它们进行外乘即可得到每一个 对应的变换矩阵,然后将所有矩阵求和即得到算子 的矩阵表示,即用矩阵
生成自定义门。
举例:
[2]:
q = 4 # 比特数
N = 15
a = 2
x = []
f = []
for i in range(2**q):
x.append(i)
f.append(a**i % N)
print('x: ', x)
print('f(x): ', f)
x: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
f(x): [1, 2, 4, 8, 1, 2, 4, 8, 1, 2, 4, 8, 1, 2, 4, 8]
然后计算
但是,由于MindSpore Quantum当前版本的Simulator对自定义门的比特数做出了限制(不能大于5比特),而即使分解最小的非偶质因数整数15=3*5也要至少8个比特,因此构造此Oracle使用了妥协而复杂得多的办法,即寄存器1(4个比特)作为控制比特,在寄存器2(4个比特)作用每一个
每一个
下面是妥协后的Oracle构造方法:
[3]:
def U_operator(N, a, register1, register2):
Q = 2**len(register1)
x = []
f = []
for i in range(Q):
x.append(i)
f.append(a**i % N) # 计算f(x)
# 创建量子态|register2>的矩阵表示
vector = np.zeros((Q, Q))
for i in range(Q):
vector[i, i] = 1
T = []
for i in range(Q):
matrix = np.outer(vector[f[i]], vector[0]) # 计算映射Tx的矩阵
T.append(UnivMathGate(f'f({i})', matrix)) # 用变换矩阵构造Tx“门”
# 创建控制线路,得到算子U。对于每个Tx“门”,都受寄存器1中所有比特控制,其对应x的二进制中比特位是1的是正常控制节点,比特位是0的则要在控制节点两侧作用X门,翻转控制位
circuit = Circuit()
for i in range(Q):
bin_x = bin(x[i])[2:] # 将x转换为二进制
flip_control_qubit = list(range(len(register1))) # 初始化需要作用X门的比特的list
for j in range(len(bin_x)):
if bin_x[len(bin_x) - j - 1] == '1': # 获得x的二进制中是‘1’的比特
flip_control_qubit.remove(j) # 从list中删除不需要作用X门的控制比特
circuit.barrier() # 添加barrier
circuit += UN(X, flip_control_qubit) # 在控制节点前作用X门
circuit += T[x[i]].on(register2, list(register1)) # 给Tx“门”接上控制比特
circuit += UN(X, flip_control_qubit) # 在控制节点后作用X门
return circuit
现在,U_operator()
函数就可以对寄存器1中的量子态
举例:
[4]:
# pylint: disable=W0104
register1 = range(4)
register2 = range(4, 8)
circuit = Circuit(X.on(2)) # 创建线路,使输入态为|0100⟩|0000⟩,即x=8,|8⟩|0⟩
circuit += U_operator(15, 2, register1, register2) # 作用U算子
print(circuit.get_qs('mqvector', ket=True)) # 打印末态
circuit.svg() #打印线路
1¦00010100⟩
[4]:
寄存器1中结果为0100,寄存器2中结果为0001,先前我们已经算出了
接下来我们需要实现周期查找算法。
周期查找算法
在寄存器1中我们需要
个比特来记录自变量 的二进制数,寄存器2中同样需要 个比特来记录 的二进制数。此时寄存器1和寄存器2分别能记录 的整数,其中 。对寄存器1中的所有比特作用 Hadamard 门,此时寄存器1中的比特处于
中所有整数的均匀叠加态对寄存器1存储的态
做函数运算 ,并将结果存入寄存器2,此步骤由先前构造的U_operator完成。由于直接对叠加态 进行运算,此步骤只需一步完成,体现了量子计算的优势————并行计算。此时线路中存储的态是纠缠态,可以表示为对寄存器1做傅立叶逆变换,此变换使用一个
次单位根 ,会将任意给定态 的振幅平均分布在 个 态上。而如步骤3中显示的,寄存器1中 与 等态均与寄存器2中同一个态 相纠缠,因此会发生量子干涉,最终使得当单位矢量 越接近1(指向正实数轴)时,测量得到态 的概率越大。换句话说,我们测得的态 ,有很大概率使得 接近某一整数 。更详尽的数学描述可以参考链接:https://zh.wikipedia.org/wiki/秀爾演算法 中的“量子部分:周期查找子程序”。测量寄存器1,得到二进制串。将二进制数转化为十进制数
,此时 ,其中 是未知整数。通过连分数分解法计算 逼近的不可约分数(分母不大于 ),取其分母即得到周期 。但是,在分母小于 的不可约分数中可能存在比 更逼近 的分数,或是 与 存在公因数,则得到的 会是真正函数周期的因数,此时计算失败,重新计算。
举例:还是用构造Oracle
中我们把每一个
[5]:
# pylint: disable=W0104
circuit = Circuit() # 创建量子线路
register1 = range(4) # 设置前4个比特为寄存器1
register2 = range(4, 8) # 设置后4个比特为寄存器2
circuit += UN(H, register1) # 对寄存器1中的所有比特作用H门
# 对寄存器1做模乘运算,并将结果存入寄存器2,该操作由一个大的U门完成
circuit += U_operator(15, 2, register1, register2)
circuit.barrier() # 添加barrier
circuit += qft(register1[::-1]).hermitian() # 对寄存器1做傅立叶逆变换,须注意傅立叶变换作用的比特顺序,在这里需要反序
circuit.barrier() # 添加barrier
circuit += UN(Measure(), register1) # 测量寄存器1
circuit.svg() # 画出线路图
[5]:
从线路图我们可以很直观地看到,整个周期查找线路由四部分组成:产生叠加态
接下来运行该线路100次,观察测量结果。
[6]:
# pylint: disable=W0104
sim = Simulator('mqvector', circuit.n_qubits) # 创建量子线路模拟器
# 模拟线路100次,打印测量结果,随机种子seed设为100内的随机整数
result = sim.sampling(circuit, shots=100, seed=np.random.randint(100))
result.svg()
[6]:
从统计结果可以看出,最后寄存器1中只可能测出4个态,分别是
接下来构造的是通用的周期查找算法。
[7]:
def period_finder(N, a, q):
circuit = Circuit() # 创建量子线路
register1 = range(q) # 设置前q个比特为寄存器1
register2 = range(q, 2 * q) # 设置后q个比特为寄存器2
circuit += UN(H, register1) # 对寄存器1中的所有比特作用H门
# 对寄存器1做模乘运算,并将结果存入寄存器2,该操作由一个大的U门完成
circuit += U_operator(N, a, register1, register2)
circuit += qft(register1[::-1]).hermitian() # 对寄存器1做傅立叶逆变换,须注意傅立叶变换作用的比特顺序,在这里需要反序
circuit.barrier() # 添加barrier
circuit += UN(Measure(), register1) # 测量寄存器1
sim = Simulator('mqvector', circuit.n_qubits) # 创建量子线路模拟器
# 模拟线路,收集测量结果,随机种子seed设为100内的随机整数
result = sim.sampling(circuit, seed=np.random.randint(100))
# result.data是一个字典,key是测量结果,value是出现频数,我们只做了一次采样,因此只有一个key, value必定为1
result = list(result.data.keys())[0] # 将key取出
result = int(result, 2) # 将结果从二进制转化为十进制
# 通过连分数分解法计算result/2**q逼近的不可约分数,分母不能大于N
eigenphase = float(result / 2**q)
f = Fraction.from_float(eigenphase).limit_denominator(N)
r = f.denominator # 取f的分母,得到周期r
# r有可能是周期的因数,因此需要验证,当且仅当r是函数周期本身时返回r,否则返回None
if a**r % N == 1:
return r
return None
经典计算机部分
经典计算机部分负责将因数分解问题转化成寻找函数周期的问题,具体步骤如下:
随机取一个小于
的整数 ,用gcd算法验证 与 是否互质,若 与 存在公因数,则直接得到 的一个因数,输出结果。计算需要
个比特来存储 的二进制数。用周期查找算法得到函数
的周期 。判断
是否为偶数,若不是则回到第一步。计算
和 ,它们当中必有其一与 存在非1公因数。但是, 有可能可以整除 ,因此最后输出结果仍有可能是 本身。
[8]:
#pylint: disable=C0121,R1705
def shor(N):
while True:
a = np.random.randint(N - 2) + 2 # 获得区间[2,N-1]内的随机整数a
b = np.gcd(a, N) # 得到a与N的最大公因数b
if b != 1:
return b, int(N / b) # 如果b不等于1,则b是N的质因数,返回分解结果
# 获得足够表示N的二进制的比特数q
q = 0
while True:
Q = 2**q
if Q >= N:
break
q += 1
r = period_finder(N, a, q) # 使用周期查找算法得到r
# 判断r是否为偶数,若是则跳出循环,若不是则重新选择随机整数a
if r != None and r % 2 == 0:
break
# 计算a**(r/2)+1和a**(r/2)-1,并验证它们是否与N有公约数,若有则输出结果
c = np.gcd(a**(int(r / 2)) + 1, N)
d = np.gcd(a**(int(r / 2)) - 1, N)
if c != 1 and N % c == 0:
return c, int(N / c)
else:
return d, int(N / d)
由于经典计算机模拟量子算法需要大量的内存,以及先前提到的MindSpore Quantum中的模拟器暂时无法运行超过5比特的自定义门,因此我们暂时无法利用Shor算法计算
[9]:
N = 15
print("Factoring N = p * q =", N)
p, q = shor(N)
print("p =", p)
print("q =", q)
Factoring N = p * q = 15
p = 5
q = 3
从运行结果可以看到,我们成功的分解出15的两个质因数:3和5。
至此,我们成功的使用MindSpore Quantum实现了Shor算法。
[10]:
from mindquantum.utils.show_info import InfoTable
InfoTable('mindquantum', 'scipy', 'numpy')
[10]:
Software | Version |
---|---|
mindquantum | 0.9.11 |
scipy | 1.10.1 |
numpy | 1.24.4 |
System | Info |
Python | 3.8.17 |
OS | Linux x86_64 |
Memory | 16.62 GB |
CPU Max Thread | 16 |
Date | Tue Jan 2 16:56:02 2024 |