5.1 利用QPanda测试量子系统噪声
5.2 量子机器学习
5.3 使用单振幅和部分振幅量子虚拟机
5.4 将量子程序编译到不同的量子芯片上
近些年来,量子计算机芯片包含的量子比特数和量子比特质量不断提升,可以预计,不远的将来会出现包含数百个量子比特,且量子比特质量足够高的量子计算机芯片,称之为NISQ装置。如何用NISQ装置解决具体的实际问题是一个研究热点,比如VQE,QAOA都是有应用前景的NISQ量子算法。NISQ装置中量子比特数还不允许纠错编码,所以NISQ装置是一种噪声容忍装置,在NISQ装置中运行的量子算法都会受到噪声的影响。NISQ量子算法需要对量子噪声有一定的免疫性,需要在含噪声的环境中研究NISQ量子算法。QPanda中包含噪声量子虚拟机,可以模拟带噪声的量子线路,测试量子算法在噪声影响下的性能变化,从而辅助NISQ量子算法的开发。首先对量子噪声做一简单介绍。
量子计算机不是一个孤立系统,在运行的时候,不可避免会和环境产生耦合,从而影响量子计算机的运行过程,通常用噪声来描述环境对量子计算机的影响。 量子系统的噪声可以用一组算符和 $$\mathrm{K}=\left{\mathrm{K}{1}, \mathrm{~K}{2}, \ldots, \mathrm{K}{\mathrm{s}}\right}$$ 描述: $$\left{\mathrm{K}{1}, \mathrm{~K}{2}, \ldots, \mathrm{K}{\mathrm{s}}\right}$$ 也被称为Kraus算子,
在噪声影响下,量子态的演化方式为:
下面列举一些常见噪声的表示形式:
弛豫噪声:
$$ \mathrm{K}{1}=\left[\begin{array}{cc} 1 & 0 \ 0 & \sqrt{1-p} \end{array}\right], \mathrm{K}{2}=\left[\begin{array}{cc} 0 & \sqrt{p} \ 0 & 0 \end{array}\right] $$
比特翻转噪声:
$$ \mathrm{K}{1}=\left[\begin{array}{cc} \sqrt{1-p} & 0 \ 0 & \sqrt{1-p} \end{array}\right], \mathrm{K}{2}=\left[\begin{array}{cc} 0 & \sqrt{p} \ \sqrt{p} & 0 \end{array}\right] $$
退相位噪声:
$$ \mathrm{K}{1}=\left[\begin{array}{cc} \sqrt{1-p} & 0 \ 0 & \sqrt{1-p} \end{array}\right], \mathrm{K}{2}=\left[\begin{array}{cc} \sqrt{p} & 0 \ 0 & -\sqrt{p} \end{array}\right] $$
退极化噪声:
$$ \begin{aligned} &\mathrm{K}{1}=\sqrt{1-\frac{3}{4} p}\left[\begin{array}{ll} 1 & 0 \ 0 & 1 \end{array}\right], \mathrm{K}{2}=\frac{\sqrt{p}}{2}\left[\begin{array}{ll} 0 & 1 \ 1 & 0 \end{array}\right] \ &\mathrm{K}{3}=\frac{\sqrt{p}}{2}\left[\begin{array}{cc} 0 & -i \ -i & 0 \end{array}\right], \mathrm{K}{2}=\frac{\sqrt{p}}{2}\left[\begin{array}{cc} 1 & 0 \ 0 & -1 \end{array}\right] \end{aligned} $$
QPanda包含带噪声的量子虚拟机,噪声量子虚拟机可以模拟带噪声的量子线路,我们可以定义各种不同的量子噪声,用噪声量子虚拟机执行对应的含噪声量子线 路。下面介绍QPanda中量子噪声虚拟机的执行原理。 加入噪声后,量子线路中演化的量子态变成混态,所以需要用密度矩阵来描述量子态。与纯态相比,模拟混态演化需要更多的资源,不管是时间复杂度还是空间复杂度,都是纯态演化所需资源的平方量级。为节省资源,在QPanda中,用另外一种方式实现了噪声量子虚拟机,即借助蒙特卡洛方法, 演化过程中还是用态矢来 描述量子态。下面以实现一个噪声量子逻辑门的过程来说明QPandan噪声量子虚拟机的实现原理。
首先,假设输入态是一个纯态, 用
假设
$$ |\phi\rangle \rightarrow \mathrm{U} \frac{1}{\sqrt{\mathrm{P}{1}}} K{l}|\phi\rangle $$
当以上操作只执行一次时,得到的结果是错误的,但是,当执行以上步骤多次,然后对得到的执行结果做统计分析,就可以得到准确的输出态,假设执行了
当
即当
当执行噪声量子线路时,量子线路由一系列量子逻辑门构成, 每个量子逻辑门都执行上面的步骤,执行多次量子线路,即可得到输出量子态密度矩阵的近似表达式,量子线路执行次数越多,得到的结果越准确。
通过上述方法, QPanda中噪声量子虚拟机就可以用态矢来表示混态演化过程,通过多次运行量子线路得到噪声量子线路输出态的密度矩阵。
1.噪声QAOA测试
前面的章节已经介绍过QAOA,QAOA是一种经典-量子混合算法,提供了一种解决组合优化问题的方法。QAOA运行在NISQ装置上,无法避免噪声的干扰,所以研究噪声对QAOA性能的影响是一项很重要的工作。下面介绍噪声对QAOA性能影响的一些研究进展。
首先,如何构建含噪声的QAOA,前面章节可知QAOA可以用VQNet实现,目前VQNet可以和QPanda中噪声量子虚拟机结合,执行含噪声的QAOA。这里选取的问题还是最大分割问题。
2.理论模型
考虑一种特殊的噪声,可以表示为$$\mathrm{K}=\left{\mathrm{a}{0} \mathrm{I}, \mathrm{a}{1} \mathrm{~K}{1}, \ldots, \mathrm{a}{\mathrm{s}} \mathrm{K}{\mathrm{s}}\right}$$,即 $$\mathrm{K}$$ 的Kraus算子中一项是单位阵乘以常数$$a_0$$,这里$$\mathrm{a}{i}, i=0,1, \ldots, s$$都是非负实数,比如,比特翻转噪声,退相位噪声,退极化噪声都属于这种类型的噪声。
3.噪声对损失函数的影响
假设输入态是一个纯态
这里定义 $${1}-\mathrm{p}=\mathrm{a}{0}^{2}$$ , 不带噪声时,理想的输出态是 $$\rho{\text {uut }}^{\text {ideal }}=U\left|\phi_{\text {in }}\right\rangle\left\langle\phi_{\text {in }}\right| U^{\dagger}$$, 所以
可以看到,在
特别的,比例因子
下面开始介绍噪声对QAOA量子线路的影响。在QAOA中,QAOA步数$$n$$与QAOA量子线路包含的量子逻辑门数目线性相关,对一个步数为$$n$$的QAOA量子线路$$\mathrm{U}(\vec{\gamma}, \vec{\beta})$$, 如果没有噪声,经过
QAOA中,损失函数定义为目标哈密顿量
当 QAOA 量子线路包含噪声
当量子线路比较复杂时,
当
从上面的式子,可以发现,噪声并没有改变 QAOA 参数空间的整体形状,只是将参数空间磨平,比例因子为
近些年来,量子计算机芯片包含的量子比特数和量子比特质量不断提升,可以预计,不远的将来会出现包含数百个量子比特,且量子比特质量足够高的量子计算机芯片,称之为NISQ装置。如何用NISQ装置解决具体的实际问题是一个研究热点,比如VQE,QAOA都是有应用前景的NISQ量子算法。NISQ装置中量子比特数还不允许纠错编码,所以NISQ装置是一种噪声容忍装置,在NISQ装置中运行的量子算法都会受到噪声的影响。NISQ量子算法需要对量子噪声有一定的免疫性,需要在含噪声的环境中研究NISQ量子算法。QPanda中包含噪声量子虚拟机,可以模拟带噪声的量子线路,测试量子算法在噪声影响下的性能变化,从而辅助NISQ量子算法的开发。首先对量子噪声做一简单介绍。
4.噪声对损失函数梯度的影响
QAOA是一种经典-量子混合算法,其中参数优化过程是一个经典过程。我可以用典型的经典算法来优化QAOA量子线路的参数,比如Nelder-Mead算法,Powell算 法, Adagrad算法等。当使用梯度相关的优化算法时,需要计算损失函数的梯度。量子噪声对损失函数有影响,所以也会对损失函数的梯度产生影响。下面描述量 子噪声对损失函数的梯度 会有怎样的影响。
QAOA量子线路包含两种参数:
$$ \begin{aligned} &\frac{\partial f(\vec{\gamma}, \vec{\beta})}{\partial \gamma_{k}}=\sum_{i, j}-2 C_{i j} \frac{f(\vec{\gamma}, \vec{\beta}){k i j}^{+}-f(\vec{\gamma}, \vec{\beta}){k i j}^{-}}{2} \ &\frac{\partial f(\vec{\gamma}, \vec{\beta})}{\partial \beta_{k}}=\sum_{i=1}^{\ell}-2 \frac{f(\vec{\gamma}, \vec{\beta}){k i}^{+}-f(\vec{\gamma}, \vec{\beta}){k i}^{-}}{2} \end{aligned} $$
第一个式子中 $$f(\vec{\gamma}, \vec{\beta}){k i j}^{+}$$表示将 Q A O A 量子线路中与边 $$C{\mathrm{ij}}$$ 关联的参数
从上面两个公式,可以看出损失函数的梯度是一系列量子线路参数不同的损失函数的线性组合,从量子噪声对损失函数的影响公式中,可以推出量子噪声对损失函 数梯度的影响如下式所示:
随着噪声参数变大,损失函数对参数偏导的绝对值变小, 换句话说,损失函数梯度的绝对值随噪声参数和QAOA步数的增加而变小, 这从另外一个角度说明了噪声 会使QAOA参数空间变平。另一方面,可以看出噪声不会改变损失函数梯度的方向,所以噪声只会影响 QAOA参数优化过程的优化速度,并不会改变参数优化结果,理论上,当噪声参数
1)测试示例
用QPanda噪声虚拟机和VQNet构建QAOA,数模模拟几种噪声对QAOA的影响,来验证上一节提出的模型。
2)最大分割问题选取
选择最大分割问题作为测试问题,整个测试过程测试问题是固定的,最大分割问题对应的图如图5.1.1所示:
3)理想结果
首先演示理想情况下QAOA对这个问题的执行结果,QAOA步数为$$n=1 \sim 4$$,得到的损失函数优化过程如图5.1.2所示:
从图5.1.2可以看出,理想情况下,随着QAOA步数的增加,QAOA的性能不断提升,QAOA步数与优化后损失函数的关系如图5.1.3所示:
4)噪声对损失函数的影响
首先,测试噪声对QAOA损失函数的影响,选取3中不同的噪声:退相位噪声,比特翻转噪声和退极化噪声。在前面的章节已经介绍过,这三种噪声都包含一个参数
在接下来的测试中,对QAOA的损失函数简单做了变形,
$$ \mathrm{H}{\mathrm{p}}=-\sum{i, j} C_{i j} \frac{1-Z_{i} Z_{j}}{2} $$
为便于分析,丟弃 $$\mathrm{H}{\mathrm{p}}$$ 中的常数项,并将其变为原来的两倍,这种变化不会对 $$\mathrm{QAOA}$$ 的执行产生影响,变换后的 $$\mathrm{H}{\mathrm{p}}^{\prime}$$ 可表示为
$$ \mathrm{H}{\mathrm{p}}^{\prime}=\sum{i, j} C_{i j} Z_{i} Z_{j} $$
则损失函数为:
在测试过程中,固定QAOA量子线路的参数,测试损失函数与噪声参数的关系,具体测试结果如图5.1.4所示:
定义
用上面的公式对实验数据进行拟合,可以得到3种噪声对应的 3 个常数
图5.1.5
5)噪声对损失函数梯度的影响
现在测试噪声对损失函数梯度的影响,噪声对损失函数的影响公式为:
在这个测试中,固定$$QAOA$$量子线路的参数,测试损失函数对各参数的偏导与噪声参数的关系,
选取满足如下两个条件的数据: 一个条件还是
可以得到
将实验数据用上式拟合,拟合图像如图5.1.7所示,得到 3 个常数
6)噪声对QAOA的影响
接下来测试噪声对QAOA运行过程及运行结果的影响,在不同噪声及不同参数下运行QAOA,得到优化后的QAOA量子线路参数和损失函数。测试的噪声类型及噪声参数区间与之前的测试一致。测试数据如图5.1.8所示:
首先,分析噪声参数对
噪声参数与距离的测试数据如图5.1.9所示:
由此发现,当
对满足
可以得到:
用上面的公式对实验数据进行拟合,可以得到3种噪声对应的3个常数$$\alpha$$:
拟合图像如图5.1.10所示,其中“x”点是实验数据,红线是斜率分别等于得到的3个拟合参数的直线,可以看出,当$$\mathrm{np}$$比较小时,噪声损失函数与理想损失函数的比特因子$$\mathrm{y}$$与$$\mathrm{np}$$成线性关系。
另一方面,发现这一节中得到的拟合常数
总结
在这节中,介绍了噪声对NISQ量子算法性能影响的研究进展,讨论了一种特殊的噪声对$$QAOA$$的影响,提出了噪声对
从《2001:太空漫游》中HAL9000的惊鸿一瞥,到大卫·杰洛德在《When HARLIE Was One》中对何为人类的探讨,再到近年《心理测量者》等对交由AI管理的反乌托邦社会的存在的合理性的思考,半个余世纪以来,伴随着计算机科学的飞速发展,人工智能成为了最引人注目的方向之一。这或许是千百年来人类“造物”畅想和现实最接近的一次。诸多科幻作品中描绘的足以撼动现代文明根基的新一代智慧生命的诞生可能离21世纪还很遥远,但如Siri、Cortana这般具备自我学习功能的程序已经深入到普通人的生活之中。
而机器学习正是实现人工智能的重要途径,其关注于通过经验自动改进的计算机算法的研究。换言之,我们期冀通过某些算法使计算机“学会”自行分析某一类数据中的规律,并利用获得的规律对未知数据进行预测。常见的机器学习算法可以分为监督学习、无监督学习和强化学习三类。监督学习从给定目标的训练集中学习得到某个函数,并通过函数对未知数据进行预测,本节着重讨论的分类器便是一例。无监督学习与监督学习相比,训练集不进行人为的目标标注,常见的有聚类算法。强化学习同样没有目标标注,而会对输入有着具有延时性的某种奖惩反馈,算法会随着环境的变动而逐步调整其行为,监督学习与无监督学习的数据之间是独立的,而强化学习存在前后依赖关系。Q-Learning是典型的强化学习算法。
如前文所言,量子计算机有着不容小觑的并行计算能力,而对于机器学习,强大的算力正是其需要的。这样,两者便有了结合的前提。本书其他章节介绍的变分本征求解器(VQE)、量子近似优化算法(QAOA算法)等便是其中的代表。包括上述在内的诸多目前被广泛讨论的量子机器学习算法都属于量子-经典混合算法的范畴。令量子计算机处理其擅长的部分,其余工作交给经典计算机,是一个较为现实的方案。
接下来以监督学习中的分类问题为例进行讨论。
鸢尾花数据集是机器学习中常用的数据集,由三种各50组鸢尾花样本构成。每个样本包含4个特征,分别为萼片(sepals)及花瓣(petals)的长和宽。我们要做的是从中抽取一部分样本对量子分类器进行训练,学习完成后,将剩余的样本用于分类测试,并期望预测的成功率尽可能高。
图5.2.1介绍了一种算法结构。
1、将经典的鸢尾花数据
2、经测量操作线路输出向量
3、将分类预测
4、返回第一步,将剩余的鸢尾花数据
5、利用经典计算机对代价函数进行最小化,重复更新线路中的参数
这个算法的核心在于量子线路
如果采取第二种编码方式,希望得到量子态:
显而易见,这种编码方式能更好地节约量子比特。但与此同时,它的制备过程也更加复杂,如图5.2.3:
其中
事实上,对于包含
成功将经典数据编码到量子态后,需要考虑利用已有的量子比特或外加部分辅助比特设计量子线路。在设计线路时要尽快将量子比特纠缠起来。
图5.2.4是一种较为常用的线路设计。我们知道,所谓的各种量子门,本质上都是对态进行酉变换。(注意:当引入辅助比特时,线路对总的量子态依然进行酉变换 操作,但对于待测量部分的变换末必是酉的。)所以,将经典数据
希望每次测量后与真实分类结果
设
记
有
取一阶近似,
其中
为
其中
练过程的前两步即可。
1.将待分类的鸢尾花数据
从数据集中随机挑选120组数据进行训练,采取第一类编码方式,利用本节介绍的线路设计方窣,线路深度为4层。优化方法为Powell法。比特串与分类预测的对 应关系如下:
1.
2.
3.
最终将代价函数优化到0.045,利用剩余30组数据进行测试。结果如图5.2.6。
可见,对于新数据的预测成功率保持在较高的水准,训练过程不存在过拟合的情况。不过同时我们也能看到,杂色鸢尾与维吉尼亚鸢尾之间似乎比其与山鸢尾之间更不易区分。这是否与我们分类器的设计有关?该问题就留待读者朋友自行思考了。(提示:请关注比特串与分类预测的对应关系。)
本节以机器学习中常见的鸢尾花数据集分类问题为例,介绍了量子分类器的基本模型。但如同经典分类算法一样,实际操作中可能需要考虑核函数构造等更加复杂的问题,囿于篇幅所限,不在这里一一赘述。如果您日后打算更加深入地探究相关问题,请务必牢记,尽管与经典函数有着诸多不同,但量子线路的作用依然是为数据提供了某种映射操作,这是量子分类器能够生效的核心原因。当您希望针对某一问题进行改进时,不妨关注线路映射的空间。
不止于此,乃至更广阔的量子机器学习迄今依然是一个值得进入的研究领域,一方面,诸如量子分类器、量子生成对抗网络等在内的有价值的算法还在不断涌现,另一方面,仍有许多像“复杂数据集如何映射”的有趣问题尚待解决。这个领域欢迎更多志同道合的朋友共同探索新的进展。
本节用到的鸢尾花分类器的Python代码示例:
1.from pyqpanda import *
2.from pyqpanda.Algorithm.hamiltonian_simulation import *
3.from scipy.optimize import minimize
4.import sklearn.datasets as datasets
5.import numpy as np
6.import math
7.from functools import partial
8.
9.def initial_state(qubitlist, x):
10. qcir=QCircuit()
11. for qubit in qubitlist:
12. qcir.insert(H(qubit))
13. qcir.insert(RZ(qubitlist[0], -2 * math.pi * (x[0] - 4.3) / 3.6)) \
14. .insert(RZ(qubitlist[1], -2 * math.pi * (x[1] - 2) / 2.4)) \
15. .insert(RZ(qubitlist[2], -2 * math.pi * (x[2] - 1) / 5.9)) \
16. .insert(RZ(qubitlist[3], -2 * math.pi * (x[3] - 0.1) / 2.4))
17. return qcir
18.
19.def Uloc(qubitlist, theta_y, theta_z):
20. qcir=QCircuit()
21. for i in range(len(qubitlist)):
22. qcir.insert(RY(qubitlist[i], -theta_y[i])) \
23. .insert(RZ(qubitlist[i], -theta_z[i]))
24. return qcir
25.
26.def Uent(qubitlist):
27. qcir=QCircuit()
28. for i in range(len(qubitlist) - 1):
29. qcir.insert(CZ(qubitlist[i], qubitlist[i + 1]))
30. qcir.insert(CZ(qubitlist[0], qubitlist[len(qubitlist) - 1]))
31. return qcir
32.
33.def Meas(result, y):
34. freq_y = 0
35. if y == 0:
36. for i in result:
37. if i == '0001' or i == '0010' or i == '0100' or i == '1000':
38. freq_y+=result[i]
39. if y == 1:
40. for i in result:
41. if i == '1110' or i == '1101' or i == '1011' or i == '0111':
42. freq_y+=result[i]
43. if y == 2:
44. for i in result:
45. if i == '0011' or i == '0110' or i == '1100' or i == '1001':
46. freq_y+=result[i]
47. return freq_y
48.
49.def loss(result, y, label_class):
50. freq_total = 0
51. freq = []
52. for i in range(label_class):
53. freq.append(Meas(result, i))
54. freq_total += Meas(result, i)
55. freq_without_y = list(freq)
56. del freq_without_y[y]
57. loss_value = 1 / (1 + math.exp(-(math.sqrt(freq_total) * (max(freq_without_y) - freq[y]) / math.sqrt((freq_total - freq[y]) * freq[y]))))
58. return loss_value
59.
60.def cost(theta, x, y, qubit_num, depth, label_class):
61. cost_value=0
62. theta_y = np.zeros((depth, qubit_num))
63. theta_z = np.zeros((depth, qubit_num))
64. for i in range(depth):
65. for j in range(qubit_num):
66. theta_y[i][j] = theta[2 * i * qubit_num + 2 * j]
67. theta_z[i][j] = theta[2 * i * qubit_num + 2 * j + 1]
68. for i in range(len(y)):
69. init(QMachineType.CPU)
70. qubitlist = qAlloc_many(qubit_num)
71. prog = QProg()
72. prog.insert(initial_state(qubitlist, x[i])) \
73. .insert(Uloc(qubitlist, theta_y[0], theta_z[0]))
74. for j in range(1,depth):
75. prog.insert(Uent(qubitlist)) \
76. .insert(Uloc(qubitlist, theta_y[j], theta_z[j]))
77. directly_run(prog)
78. result = quick_measure([qubitlist[0],qubitlist[1],qubitlist[2],qubitlist[3]], 2000)
79. cost_value += loss(result, y[i], label_class)
80. finalize()
81. cost_value /= len(y)
82. print(cost_value)
83. return cost_value
84.
85.def binding(x, y, qubit_num, depth, label_class):
86. return partial(cost, x=x, y=y, qubit_num=qubit_num, depth=depth, label_class=label_class)
87.
88.def test(theta, x, qubit_num, depth, label_class):
89. y_predict = []
90. theta_y = np.zeros((depth, qubit_num))
91. theta_z = np.zeros((depth, qubit_num))
92. for i in range(depth):
93. for j in range(qubit_num):
94. theta_y[i][j] = theta[2 * i * qubit_num + 2 * j]
95. theta_z[i][j] = theta[2 * i * qubit_num + 2 * j + 1]
96. for i in range(len(x)):
97. init(QMachineType.CPU)
98. qubitlist = qAlloc_many(qubit_num)
99. prog = QProg()
100. prog.insert(initial_state(qubitlist,x[i])) \
101. .insert(Uloc(qubitlist,theta_y[0],theta_z[0]))
102. for j in range(1,depth):
103. prog.insert(Uent(qubitlist)) \
104. .insert(Uloc(qubitlist, theta_y[j], theta_z[j]))
105. directly_run(prog)
106. result = quick_measure([qubitlist[0],qubitlist[1],qubitlist[2],qubitlist[3]], 2000)
107. freq=[]
108. for j in range(label_class):
109. freq.append(Meas(result, j))
110. for j in range(label_class):
111. if freq[j] == max(freq):
112. y_predict.append(j)
113. break
114. finalize()
115. return y_predict
116.
117.def success_rate(predict_Y, test_Y):
118. success=0
119. fail=0
120. for i in range(len(predict_Y)):
121. if (predict_Y[i] == test_Y[i]):
122. success+=1
123. else:
124. fail+=1
125. return success,fail
126.
127.def data_shuffle_and_make_train_test(data, label, train_percentage = 0.8):
128. n_data = len(label)
129. permutation = np.random.permutation(range(n_data))
130. n_train = int(n_data * train_percentage)
131. n_test = n_data - n_train
132. train_permutation = permutation[0:n_train]
133. test_permutation = permutation[n_train:]
134. return data[train_permutation], label[train_permutation], data[test_permutation], label[test_permutation]
135.
136.qubit_num = 4
137.depth = 4
138.label_class=3
139.data, label = datasets.load_iris(return_X_y=True)
140.data, label, test_d , test_l = data_shuffle_and_make_train_test(data,label)
141.theta = np.random.random_sample(2 * qubit_num * depth)
142.result = minimize(binding(data, label, qubit_num, depth, label_class), theta, method = 'Powell')
143.
144.y_predict = []
145.theta = result.x
146.y_predict = test(theta, test_d, qubit_num, depth, label_class)
147.success, fail = success_rate(y_predict, test_l)
148.print(test_l)
149.print(np.array(y_predict))
150.print(success/(success+fail))
目前可以通过量子计算的相关理论,用经典计算机实现模拟量子虚拟机。 量子虚拟机的模拟主要有全振幅与单振幅两种解决方案,其主要区别在于,全振幅一次模拟计算就能算出量子态的所有振幅,单振幅一次模拟计算只能计算出$$2^N$$ 个振幅中的一个。
然而全振幅模拟量子计算时间较长,计算量随量子比特数指数增长,在现有硬件下,无法模拟超过49量子比特。通过单振幅量子虚拟机技术可以模拟超过49比特,同时模拟速度有较大提升,且算法的计算量不随量子比特数指数提升。
1.使用介绍
QPanda2中设计了 SingleAmplitudeQVM 类用于运行单振幅模拟量子计算,同时提供了相关接口,它的使用也很简单。
首先构建一个单振幅量子虚拟机:
1.auto machine = new SingleAmplitudeQVM();
然后必须使用 SingleAmplitudeQVM::init() 初始化量子虚拟机环境:
1.machine->init();
接着进行量子程序的构建、装载工作:
1.auto prog = QProg();
2.auto qlist = machine->qAllocMany(10);
3.auto clist = machine->cAllocMany(10);
4.
5.for_each(qlist.begin(), qlist.end(), [&](Qubit *val) { prog << H(val); });
6.prog << CZ(qlist[1], qlist[5]) << CZ(qlist[3], qlist[5]) << CZ(qlist[2], qlist[4]);
7....
8.machine->directlyRun(prog);
最后调用计算接口,设计多种返回值的接口用于满足不同的计算需求,具体见示例所述:
2.实例
以下示例展示了单振幅量子虚拟机接口的使用方式
1.#include "QPanda.h"
2.USING_QPANDA
3.
4.int main(void)
5.{
6. auto machine = new SingleAmplitudeQVM();
7. machine->init();
8.
9. auto prog = QProg();
10. auto qlist = machine->qAllocMany(10);
11. auto clist = machine->cAllocMany(10);
12.
13. for_each(qlist.begin(), qlist.end(), [&](Qubit *val) { prog << H(val); });
14. prog << CZ(qlist[1], qlist[5])
15. << CZ(qlist[3], qlist[5])
16. << CZ(qlist[2], qlist[4])
17. << CZ(qlist[3], qlist[7])
18. << CZ(qlist[0], qlist[4])
19. << RY(qlist[7], PI / 2)
20. << RX(qlist[8], PI / 2)
21. << RX(qlist[9], PI / 2)
22. << CR(qlist[0], qlist[1], PI)
23. << CR(qlist[2], qlist[3], PI)
24. << RY(qlist[4], PI / 2)
25. << RZ(qlist[5], PI / 4)
26. << RX(qlist[6], PI / 2)
27. << RZ(qlist[7], PI / 4)
28. << CR(qlist[8], qlist[9], PI)
29. << CR(qlist[1], qlist[2], PI)
30. << RY(qlist[3], PI / 2)
31. << RX(qlist[4], PI / 2)
32. << RX(qlist[5], PI / 2)
33. << CR(qlist[9], qlist[1], PI)
34. << RY(qlist[1], PI / 2)
35. << RY(qlist[2], PI / 2)
36. << RZ(qlist[3], PI / 4)
37. << CR(qlist[7], qlist[8], PI);
38.
39. machine->directlyRun(prog);
40. auto res = machine->getProbDict(qlist);
41. cout << res["0000000000"] << endl;
42. cout << res["0000000001"] << endl;
43. delete(machine);
44. return 0;
45.}
·getProbDict()接口表示输出计算后的量子态概率,输出结果用map容器保存,key为量子态对应的字符串,value为对应的概率,上述程序的计算结果如下:
1.0.00166709
2.0.00166709
若使用其他接口:
· getProbDict(qvec,std::string) ,使用示例
1.QVec qvec;
2.for_each(qlist.begin(), qlist.end(), [&](Qubit *val) { qvec.emplace_back(val); });
3.machine->directlyRun(prog);
4.auto res = machine->getProbDict(qlist);
5.for (auto val : res)
6.{
7.std::cout << val.first << " : " << val.second << std::endl;
8.}
结果输出如下:
1.0000000000 : 0.0016671
2.0000000001 : 0.0016671
3.0000000010 : 0.000286029
4.0000000011 : 0.000286029
5.0000000100 : 0.000286029
6.0000000101 : 0.000286029
7.......
· pMeasureBinindex(std::string) ,使用示例
1.auto res = machine->pMeasureBinindex("0000000001");
2.std::cout << res << std::endl;
通过二进制形式下标测量指定振幅,结果输出如下:
1.(0.0408301,-7.80626e-18)
· pMeasureDecindex(std::string) ,使用示例
1.auto res = PMeasure_bin_index("1");
2.std::cout << res << std::endl;
通过十进制形式下标测量指定振幅,结果输出如下:
1.0.00166709
目前用经典计算机模拟量子虚拟机的主流解决方案有全振幅与单振幅两种。除此之外,还有部分振幅量子虚拟机,该方案能在更低的硬件条件下,实现更高的模拟效率。 部分振幅算法的基本思想是将大比特的量子计算线路图拆分成若干个小比特线路图,具体数量视线路扶持情况而定。
1.使用介绍
QPanda2中设计了 PartialAmplitudeQVM 类用于运行部分振幅模拟量子计算,同时提供了相关接口,它的使用很简单。
首先构建一个部分振幅量子虚拟机:
1.auto machine = new PartialAmplitudeQVM();
然后必须使用 PartialAmplitudeQVM::init() 初始化量子虚拟机环境:
1.machine->init();
接着进行量子程序的构建、装载工作:
1.auto prog = QProg();
2.auto qlist = machine->qAllocMany(10);
3.auto clist = machine->cAllocMany(10);
4.
5.for_each(qlist.begin(), qlist.end(), [&](Qubit *val) { prog << H(val); });
6.prog << CZ(qlist[1], qlist[5]) << CZ(qlist[3], qlist[5]) << CZ(qlist[2], qlist[4]);
7....
8.machine->directlyRun(prog);
最后调用计算接口,设计多种返回值的接口用于满足不同的计算需求,具体见示例所述:
2.实例
以下示例展示了部分振幅量子虚拟机接口的使用方式:
1.#include "QPanda.h"
2.USING_QPANDA
3.
4.int main(void)
5.{
6. auto machine = new PartialAmplitudeQVM();
7. machine->init();
8.
9. auto prog = QProg();
10. auto qlist = machine->qAllocMany(10);
11. auto clist = machine->cAllocMany(10);
12.
13. auto prog = QProg();
14. for_each(qlist.begin(), qlist.end(), [&](Qubit *val) { prog << H(val); });
15. prog << CZ(qlist[1], qlist[5])
16. << CZ(qlist[3], qlist[7])
17. << CZ(qlist[0], qlist[4])
18. << RZ(qlist[7], PI / 4)
19. << RX(qlist[5], PI / 4)
20. << RX(qlist[4], PI / 4)
21. << RY(qlist[3], PI / 4)
22. << CZ(qlist[2], qlist[6])
23. << RZ(qlist[3], PI / 4)
24. << RZ(qlist[8], PI / 4)
25. << CZ(qlist[9], qlist[5])
26. << RY(qlist[2], PI / 4)
27. << RZ(qlist[9], PI / 4)
28. << CR(qlist[2], qlist[7], PI / 2);
29.
30. machine->directlyRun(prog);
31. auto res = machine->getProbDict(qlist);
32. cout << res["0000000000"] << endl;
33. cout << res["0000000001"] << endl;
34. delete(machine);
35. return 0;
36.}
上述程序的计算结果如下:
1.0.0016671
2.0.0016671
若使用其他接口:
· PMeasure_bin_index(std::string) ,使用示例:
1.auto res = machine->PMeasure("6");
2.for (auto val :res)
3.{
4. std::cout << val.first << " : " << val.second << std::endl;
5.}
结果输出如下:
1.0.00166709
· getProbDict(qvec,std::string) ,使用示例:
1.QVec qvec;
2.for_each(qlist.begin(), qlist.end(), [&](Qubit *val) { qvec.emplace_back(val); });
3.
4.auto res = machine->getProbDict(qlist);
5.for (auto val : res)
6.{
7. std::cout << val.first << " : " << val.second << std::endl;
8.}
结果输出如下:
1.0000000000 : 0.0016671
2.0000000001 : 0.0016671
3.0000000010 : 0.000286029
4.0000000011 : 0.000286029
5.0000000100 : 0.000286029
6.0000000101 : 0.000286029
· pmeasure_bin_indexstd::string) ,使用示例:
1.auto res = machine->pmeasure_bin_index("0000000001");
2. std::cout << res << std::endl;
通过二进制形式下标测量指定振幅,结果输出如下:
1.(0.0408301,-7.80626e-18)
· pmeasure_dec_index(std::string) ,使用示例:
1.auto res = machine->pmeasure_dec_index("6");
2.std::cout << res << std::endl;
通过十进制形式下标测量指定振幅,结果输出如下:
1.(-4.33681e-18,-0.0408301)
任何的量子程序,最终是要放到量子计算机中去运行的。和量子虚拟机相比,在量子计算机中运行有几个重要的区别:
1.支持的逻辑门
量子芯片中往往不一定允许执行所有的酉变换,对于单比特门来说,通常只支持一个子集。由于这个定理: 任何的两个不同方向的旋转 $${U}{\bar{x}}(\theta)=e^{i \vec{\sigma x} \theta}$$ 和 $$\mathrm{U}{\bar{y}}(\theta)=e^{i \vec{\sigma} \bar{y} \theta}$$ , 一定能通过依次施加不同角度的旋转操作,构成任何的单比特上的酉变换。因此,量子计算机中若支持两个方向的旋转操作, 即可实现任意的单门。
QPanda中提供了一个配置量子计算机中支持逻辑门的方式。通过配置,可以将任意的量子程序编译到支持的量子逻辑门中。
2.量子芯片拓扑结构
编写量子程序,或者设计量子算法时,往往在任意两个比特之间都有可能出现逻辑门的连接。然而,对于实际的量子芯片,有可能这种连接关系是受限的。例如量子比特在芯片上以网格式排列,只有最近邻(nearest neighbor)的量子比特之间才允许进行操作。那么,对于原有的量子程序,我们必须将所有不支持操作的量子逻辑门进行变换,或者对量子比特进行重新映射。
QPanda中提供了一个配置量子芯片拓扑结构的方式。
指令集
量子指令集是由各公司定义的量子计算机控制指令集,它适配于量子软件开发包,一般可以用于直接控制该公司的量子计算机。QPanda中通用的量子程序表示方式是QProg类,它提供的是一种对量子程序的抽象通用表述形式。QPanda提供一系列函数,将QProg类映射到不同的量子指令集上,包括QRunes,QASM,Quil……
配置支持的逻辑门
1.<QGate>
2. <SingleGate>
3. <Gate time = "2">rx</Gate>
4. <Gate time = "2">Ry</Gate>
5. <Gate time = "2">RZ</Gate>
6. <Gate time = "2">S</Gate>
7. <Gate time = "2">H</Gate>
8. <Gate time = "2">X1</Gate>
9. </SingleGate>
10. <DoubleGate>
11. <Gate time = "5">CNOT</Gate>
12. <Gate time = "5">CZ/Gate>
13. <Gate time = "5">ISWAP</Gate>
14. </DoubleGate>
15.</QGate>
在QPanda中,指令集的配置是通过QPandaConfig.xml中QGate元素来配置的。
QGate中的元素介绍:
SingleGate: 单量子逻辑门配置,是QGate的子元素
DoubleGate: 双量子逻辑门配置, 是QGate的子元素
Gate: 内容配置支持的量子逻辑门,属性time配置量子逻辑门的时钟周期,是DoubleGate和SingleGate的子元素。
示例:一个量子芯片支持单门RX、RY和双门CNOT,RX、RY的时钟周期均为2,CNOT的时钟周期为5。下面我们就配置这个量子芯片支持的逻辑门。
首先,打开QPandaConfig.xml文件,找到QGate元素,可以看到QGate元素的子元素SingleGate和DoubleGate,并删除SingleGate和DoubleGate元素中所有的内容。
1.<QGate>
2. <SingleGate>
3.</SingleGate>
4. <DoubleGate>
5. </DoubleGate>
6. </QGate>
然后,分别向SingleGate和DoubleGate 元素中添加Gate元素(Gate元素的内容为量子逻辑门,属性值time为逻辑门的时钟周期)。在SingleGate元素中添加两个Gate子元素,属性time均为2,内容分别为RX、RY。在DoubleGate元素中添加一个Gate子元素,属性time为5,内容为CNOT。
1.<QGate>
2. <SingleGate>
3. <Gate time = "2">RX</Gate>
4. <Gate time = "2">RY</Gate>
5.</SingleGate>
6. <DoubleGate>
7. <Gate time = "5">CNOT</Gate>
8. </DoubleGate>
9. </QGate>
配置量子芯片拓扑结构
1.<Metadata>
2. <QubitCount>8</QubitCount>
3. <QubitMatrix>
4. <Qubit QubitNum = "1">
5. <AdjacentQubit QubitNum = "2">1</AdjacentQubit>
6. <AdjacentQubit QubitNum = "8">1</AdjacentQubit>
7. </Qubit>
8. <Qubit QubitNum = "2">
9. <AdjacentQubit QubitNum = "1">1</AdjacentQubit>
10. <AdjacentQubit QubitNum = "3">1</AdjacentQubit>
11. <AdjacentQubit QubitNum = "7">1</AdjacentQubit>
12. </Qubit>
13. <Qubit QubitNum = "3">
14. <AdjacentQubit QubitNum = "2">1</AdjacentQubit>
15. <AdjacentQubit QubitNum = "4">1</AdjacentQubit>
16. <AdjacentQubit QubitNum = "6">1</AdjacentQubit>
17. </Qubit>
18. <Qubit QubitNum = "4">
19. <AdjacentQubit QubitNum = "3">1</AdjacentQubit>
20. <AdjacentQubit QubitNum = "5">1</AdjacentQubit>
21. </Qubit>
22. <Qubit QubitNum = "5">
23. <AdjacentQubit QubitNum = "4">1</AdjacentQubit>
24. <AdjacentQubit QubitNum = "6">1</AdjacentQubit>
25. </Qubit>
26. <Qubit QubitNum = "6">
27. <AdjacentQubit QubitNum = "3">1</AdjacentQubit>
28. <AdjacentQubit QubitNum = "5">1</AdjacentQubit>
29. <AdjacentQubit QubitNum = "7">1</AdjacentQubit>
30. </Qubit>
31. <Qubit QubitNum = "7">
32. <AdjacentQubit QubitNum = "2">1</AdjacentQubit>
33. <AdjacentQubit QubitNum = "6">1</AdjacentQubit>
34. <AdjacentQubit QubitNum = "8">1</AdjacentQubit>
35. </Qubit>
36. <Qubit QubitNum = "8">
37. <AdjacentQubit QubitNum = "7">1</AdjacentQubit>
38. <AdjacentQubit QubitNum = "1">1</AdjacentQubit>
39. </Qubit>
40. </QubitMatrix>
41.</Metadata>
在QPanda中,量子芯片拓扑结构是通过QPandaConfig.xml中Metadata元素来配置的。
Metadata中的元素介绍:
QubitCount:量子比特的个数,是metadata的子元素。
QubitMatrix:量子比特拓扑图,是metadata的子元素。
Qubit:量子比特配置,属性QubitNum表示量子比特序号,是QubitMatrix的子元素。
AdjacentQubit:内容是边的权重,属性QubitNum是指与其父元素Qubit连接的量子比特序号。
例如图5.4.1,将下面的量子拓扑结构配置到QPandaConfig.xml中,圆圈表示量子比特,圆圈里的值表示量子比特序号,线条表示是否连接,线条上数值的值表示权重。
首先,找到Metadata元素,并删除其QubitMatrix中的所有内容:
1.<Metadata>
2. <QubitCount>8</QubitCount>
3.<QubitMatrix>
4.</QubitMatrix>
5.</Metadata>
然后, 根据拓扑图向QPandaConfig.xml中添加配置。先配置量子比特的个数。例子中的量子比特个数为4个
1.<Metadata>
2. <QubitCount>4</QubitCount>
3.<QubitMatrix>
4.</QubitMatrix>
5.</Metadata>
再配置拓扑结构,添加第一个量子比特的拓扑配置,在QubitMatrix下添加Qubit子元素,其属性QubitNum的值为1,第一个量子比特与第二个量子比特和第三个量子比特连接,其权重都是1,所以Qubit会有两个AdjacentQubit子元素,第一个AdjacentQubit元素的属性QubitNum的值是2,第二个AdjacentQubit元素的属性QubitNum的值是3,其内容都是1,将第一个量子比特的拓扑配置添加到配置中
1.<Metadata>
2. <QubitCount>4</QubitCount>
3.<QubitMatrix>
4. <Qubit QubitNum = 1>
5. <AdjacentQubit QubitNum = 2>1</AdjacentQubit>
6. <AdjacentQubit QubitNum = 3>1</AdjacentQubit>
7.</Qubit>
8.</QubitMatrix>
9.</Metadata>
以此类推,将后面的几个量子比特拓扑结构添加到配置中
1.<Metadata>
2. <QubitCount>4</QubitCount>
3.<QubitMatrix>
4. <Qubit QubitNum = 1>
5. <AdjacentQubit QubitNum = 2>1</AdjacentQubit>
6. <AdjacentQubit QubitNum = 3>1</AdjacentQubit>
7.</Qubit>
8.<Qubit QubitNum = 2>
9. <AdjacentQubit QubitNum = 1>1</AdjacentQubit>
10. <AdjacentQubit QubitNum = 4>1</AdjacentQubit>
11.</Qubit>
12.<Qubit QubitNum = 3>
13. <AdjacentQubit QubitNum = 1>1</AdjacentQubit>
14. <AdjacentQubit QubitNum = 4>1</AdjacentQubit>
15.</Qubit>
16.<Qubit QubitNum = 4>
17. <AdjacentQubit QubitNum = 2>1</AdjacentQubit>
18. <AdjacentQubit QubitNum = 3>1</AdjacentQubit>
19.</Qubit>
20.</QubitMatrix>
21.</Metadata>
转换到不同的指令集
将量子程序编译到不同的量子芯片实际上就是可以将量子程序编译成的量子芯片对应的指令集,QPanda2当前支持将量子程序编译成QUIL指令集、IBM的QASM指令集、本源量子的QRunes指令集。
QUIL指令集是采用“指令+参数列表的设计方法”,一个简单的例子如下:
1.X 0
2.Y 1
3.CNOT 0 1
4.H 0
5.RX(-3.141593) 0
6.MEASURE 1 [0]
QASM具有C和汇编语言的元素,语句用分号分隔,忽略空格,同时语言是区分大小写的。注释以一对前斜线开始,并以新的行结束。一个简单的QASM指令集如下:
1.OPENQASM 2.0;
2.include "qelib1.inc";
3.qreg q[10];
4.creg c[10];
5.
6.x q[0];
7.h q[1];
8.tdg q[2];
9.sdg q[2];
10.cx q[0],q[2];
11.cx q[1],q[4];
12.u1(pi) q[0];
13.u2(pi,pi) q[1];
14.u3(pi,pi,pi) q[2];
15.cz q[2],q[5];
16.ccx q[3],q[4],q[6];
17.cu3(pi,pi,pi) q[0],q[1];
18.measure q[2] -> c[2];
19.measure q[0] -> c[0];
如何利用QPanda2将量子程序转化为想要的指令集呢?可以使用下面的方法,首先利用QPanda2构建一个量子程序:
1.QProg prog;
2.auto qubits = qvm->qAllocMany(4);
3.auto cbits = qvm->cAllocMany(4);
4.
5.prog << X(qubits[0])
6. << Y(qubits[1])
7. << H(qubits[2])
8. << RX(qubits[3], 3.14)
9. << Measure(qubits[0], cbits[0]);
然后调用QPanda2的工具接口,完成转换,比如转化为QUIL指令集可以调用下面的接口:
1.std::string instructions = convert_qprog_to_quil(prog, qvm);
得到转化后的QUIL指令集:
1.X 0
2.Y 1
3.H 2
4.RX(3.140000) 3
5.MEASURE 0 [0]
转化为QASM指令集与转化为QUIL指令集的方法类似,可以调用下面的接口:
1.std::string instructions = convert_qprog_to_qasm(prog, qvm);
得到转化后的QASM指令集:
1.openqasm 2.0;
2.qreg q[4];
3.creg c[4];
4.x q[0];
5.y q[1];
6.h q[2];
7.rx(3.140000) q[3];
8.measure q[0] -> c[0];