4.1 量子软件开发环境
4.2 量子算法简介
4.3 Deutsch—Jozsa 算法
4.4 Grover算法
4.5 QAOA
4.6 VQE算法
4.7 Shor 分解算法
QPanda (Quantum Programming Architecture for NISQ Device Applications)是一个高效、便捷的量子计算开发工具库,为了让用户更容易的使用QPanda,更便捷的进行量子编程,它屏蔽了复杂的C++语法结构,甚至用户不需要了解所谓的面向对象,只需要学会如何把量子编程中用到的接口调用一遍就可以进行量子计算。
例:如何构造一个量子程序,并在量子虚拟机中运行它。
首先假设有一台量子计算机,它有2个量子比特:$$Q_1$$、$$Q_2$$,接着对其中一个量子比特($$Q_1$$)进行$$H$$门操作,构造了一个量子叠加态;并对$$Q_1$$和$$Q_2$$做$$CNOT$$门操作,$$Q_1$$为控制量子比特,$$Q_2$$为目标量子比特,最后对所有的量子比特进行测量操作。此时,将有50%的概率得到00或者11的测量结果。代码示例如下:
1.#include "QPanda.h"
2.using namespace QPanda;
3.int main()
4.{
5. auto qvm = CPUQVM(); /* 创建一个类型CPU计算的量子虚拟机*/
6. qvm.init(); /* 为该虚拟机进行初始化*/
7. auto prog = QProg(); /* 构造一个量子程序*/
8. auto q = qvm.qAllocMany(2); /* 申请两个量子比特*/
9. auto c = qvm.cAllocMany(2); /* 申请两个经典比特*/
10.
11. /* 向量子程序中插入H门、CNOT门和Measure*/
12. prog << H(q[0])
13. << CNOT(q[0],q[1])
14. << MeasureAll(q, c);
15.
16. /* 让量子程序在量子虚拟机中跑1000次*/
17. auto results = qvm.runWithConfiguration(prog, c, 1000);
18. for (auto result : results)
19. {
20. /* 循环输出结果*/
21. std::cout << "result.first" << ", " << "result.second" << std::endl;
22. }
23. return 0;
24.}
这是个简单却能体现出量子计算特点的例子,它即体现了量子态叠加,又体现量子比特纠缠。从上例可以看到,用户实质上只需要关注如何使用QPanda构建量子程序,其他的细节操作完全不需要用户操心。
再例如,在很多量子算法如QAOA算法中,都需要构造一组量子比特的叠加态,那么完全可以把这种操作抽象成一种生成量子线路的函数,输入是一组量子比特,输出是一个量子线路。代码示例如下:
1.#include "QPanda.h"
2.using namespace QPanda;
3.
4.QCircuit HadamardCircuit(QVec & qvec)
5.{
6. auto qcircuit = QCircuit(); /* 构造一个量子线路*/
7.
8. for (auto aiter : qvec) /* 对传入的量子比特做H门操作*/
9. {
10. qcircuit<<H(aiter); /* 把H门插入的量子线路中*/
11. }
12.
13. return qcircuit; /* 返回量子线路*/
14.}
15.
16.int main()
17.{
18. auto qvm = CPUQVM(); /* 创建一个类型未CPU计算的量子虚拟机*/
19. qvm.init(); /* 为该虚拟机进行初始化*/
20. auto prog = QProg(); /* 构造一个量子程序*/
21. auto q = qvm.qAllocMany(2); /* 申请两个量子比特*/
22. auto c = qvm.cAllocMany(2); /* 申请两个经典比特*/
23.
24. /* 调用HadamardCircuit函数*/
25. prog << HandmadeCircuit(q) << MeasureAll(q,c);
26.
27. /* 让量子程序在量子虚拟机中跑1000次*/
28. auto results = qvm.runWithConfiguration(prog, c, 1000);
29. for (auto result : results)
30. {
31. /* 循环输出结果*/
32. std::cout << "result.first" << ", " << "result.second" << std::endl;
33. }
34. return 0;
35.}
也可使用新的接口来一次性插入多个逻辑门:
1.#include "QPanda.h"
2.using namespace QPanda;
3.int main()
4.{
5. auto qvm = CPUQVM(); /* 创建一个CPU计算的量子虚拟机*/
6. qvm.init(); /* 为该虚拟机进行初始化*/
7. auto prog = QProg(); /* 构造一个量子程序*/
8. auto q = qvm.qAllocMany(2); /* 申请两个量子比特*/
9. auto c = qvm.cAllocMany(2); /* 申请两个经典比特*/
10. prog << H(q) << MeasureAll(q,c) /* 将2个H门插入到每个比特上*/
11.
12. /* 让量子程序在量子虚拟机中跑1000次*/
13. auto results = qvm.runWithConfiguration(prog, c, 1000);
14. for (auto result : results)
15. {
16. /* 循环输出结果*/
17. std::cout << "result.first" << ", " << "result.second" << std::endl;
18. }
19. return 0;
20.}
从上述例可以知道,用户只需要关注量子程序的构建,其他的部分,如量子虚拟机的构建、初始化、申请量子比特、执行量子程序和获取结果,都是一个固定的流程,只需要调用函数接口即可。
深入了解QPanda的使用,就必须要了解一下QPanda中与量子计算相关的数据类型:QGate(量子逻辑门),Measure(测量)、ClassicalProg(经典程序)、QCircuit(量子线路)、Qif(量子条件判断程序)、QWhile(量子循环程序)、QProg(量子程序);
QGate:量子逻辑门是量子计算的基本单位,任何一个量子程序都是由QGate组合而成,如果说量子程序或量子算法是一套拳法,那么QGate就是一个个被拆解出来的动作,几个QGate的固定组合就是一个招式,它们的最终目的就是把这套拳法打出来。目前QPanda的量子逻辑门大致分为单门、双门、三门、自定义门,其中每个类又分为带角度和不带角度两类。
常见的单门有:
不含角度的单门有:I、H、T、X、S、X、Y、Z、X1、Y1、Z1。
带有一个旋转角度的逻辑门:RX、RY、RZ、U1、P。
此外还支持U2、U3、U4。
双门不含角度的逻辑门:CNOT、CZ、iSWAP、SWAP、SqiSWAP。
双门含旋转角度的逻辑门:CR、CU、CP。
三量子逻辑门 :Toffoli。
QPanda 2把所有的量子逻辑门封装为API向用户提供使用,并可获得QGate类型的返回值。例如,如果想使用Hadamard门,就可以通过如下方式获得:
1.QGate h = H(qubit);
可以看到,H函数只接收一个qubit,qubit如何申请请参考
https://QPanda2.readthedocs.io/zh_CN/latest/TotalAmplitude.html#quantummachine
QPanda 2还支持对量子逻辑门做转置共轭操作:
1.auto gate = H(qubit);
2./* setDagger有一个布尔类型参数,用来设置当前逻辑门是否需要转置共轭操作*/
3.gate.setDagger(true);
4.
5./* dagger的作用是复制一份当前的量子逻辑门,并更新复制的量子逻辑门的dagger标记*/
6.QGate H_dagger = H(qubit).dagger();
setDagger有一个布尔类型参数,用来设置当前逻辑门是否需要转置共轭操作。
除了转置共轭操作,也可以为量子逻辑门添加控制比特:
1.auto gate = H(qubit)
2./* setControl的作用是给当前的量子逻辑门添加控制比特*/
3.gate.setControl(qvec);
4.
5./* control的作用是复制当前的量子逻辑门,并给复制的量子逻辑门添加控制比特*/
6.QGate H_control = H(qubit).control(qvec);
setControl、control都需要接收一个参数,参数类型为QVec,QVec是qubit的vector
再例如,想要使用RX门,可以通过如下方式获得:
1.QGate rx = RX(qubit,PI);
如上所示,RX门接收两个参数,第一个是目标量子比特,第二个偏转角度,也可以通过相同的方式使用RY,RZ门。
两比特量子逻辑门的使用和单比特量子逻辑门的用法相似,只不过是输入的参数不同,举个使用CNOT的例子:
1.QGate cnot = CNOT(control_qubit,target_qubit);
CNOT门接收两个参数,第一个是控制比特,第二个是目标比特。
此外我们还有一个可以自行填充矩阵和比特的QOracle门,具体使用方法请看下面的例子:
1.#include "QPanda.h"
2.USING_QPANDA
3.using namespace std;
4.int main()
5.{
6. auto qvm = new CPUQVM();
7. qvm->init();
8. auto q = qvm->qAllocMany(2);
9. auto c = qvm->cAllocMany(2);
10. auto prog = QProg();
11.
12. QStat source_matrix =
13. {
14. qcomplex_t(0.6477054522122977, 0.1195417767870219),
15. qcomplex_t(-0.16162176706189357, -0.4020495632468249),
16. qcomplex_t(-0.19991615329121998, -0.3764618308248643),
17. qcomplex_t(-0.2599957197928922, -0.35935248873007863),
18. qcomplex_t(-0.16162176706189363, -0.40204956324682495),
19. qcomplex_t(0.7303014482204584, -0.4215172444390785),
20. qcomplex_t(-0.15199187936216693, 0.09733585496768032),
21. qcomplex_t(-0.22248203136345918, -0.1383600597660744),
22. qcomplex_t(-0.19991615329122003 , -0.3764618308248644),
23. qcomplex_t(-0.15199187936216688, 0.09733585496768032),
24. qcomplex_t(0.6826630277354306, -0.37517063774206166),
25. qcomplex_t(-0.3078966462928956, -0.2900897445133085),
26. qcomplex_t(-0.2599957197928923, -0.3593524887300787),
27. qcomplex_t(-0.22248203136345912, -0.1383600597660744),
28. qcomplex_t(-0.30789664629289554, -0.2900897445133085),
29. qcomplex_t(0.6640994547408099, -0.338593803336005)
30. };
31.
32. prog << QOracle(q, source_matrix) << MeasureAll(q,c);
33. auto result = qvm->runWithConfiguration(prog, c, 1000);
34. for (auto res : result)
35. {
36. std::cout << res.first << " : " << res.second << std::endl;
37. }
38.
39. return 0;
40.}
运行结果如下:
1.00 : 433
2.01 : 182
3.10 : 180
4.11 : 205
Measure:它的的作用是对量子比特进行测量操作。
在量子程序中需要对某个量子比特做测量操作,并把测量结果存储到经典寄存器上,可以通过下面的方式获得一个测量对象:
1.auto measure = Measure(qubit, cbit);
可以看到Measure接两个参数, 第一个是测量比特,第二个是经典寄存器。
如果想测量所有的量子比特并将其存储到对应的经典寄存器上,可以如下操作:
1.# MeasureAll 的返回值类型是 QProg
2.auto measure_all = MeasureAll(qubits, cbits);
其中qubits的类型是 QVec , cbits的类型是 vector。
在得到含有量子测量的程序后,可以调用 directlyRun 或 runWithConfiguration 来得到量子程序的测量结果。
directlyRun 的功能是运行量子程序并返回运行的结果, 使用方法如下:
1.QProg prog;
2.prog << H(qubits[0])
3. << CNOT(qubits[0], qubits[1])
4. << CNOT(qubits[1], qubits[2])
5. << CNOT(qubits[2], qubits[3])
6. << Measure(qubits[0], cbits[0]); // 测量单个量子比特
7.
8.auto result = directlyRun(prog);
runWithConfiguration 的功能是末态目标量子比特序列在量子程序多次运行结果中出现的次数, 使用方法如下:
1.QProg prog;
2.prog << H(qubits[0])
3. << H(qubits[1])
4. << H(qubits[2])
5. << H(qubits[3])
6. << MeasureAll(qubits, cbits); // 测量所有的量子比特
7.
8.auto result = runWithConfiguration(prog, cbits, 1000);
其中第一个参数是量子程序,第二个参数是经典寄存器, 第三个参数是运行的次数。
getCircuitMatrix 的功能是获取线路中的矩阵,方法如下:
1.auto qvm = CPUQVM();
2.qvm.init();
3.auto qubits = qvm.qAllocMany(2);
4.auto cbits = qvm.cAllocMany(2);
5.QProg prog;
6.prog << H(qubits[0])
7. << H(qubits[1]);
8.auto matrix = getCircuitMatrix(prog);
9.
10.std::cout << matrix << std::endl;
1. (0.499999999999996, 0) (0.499999999999996, 0) (0.499999999999996, 0) (0.499999999999996, 0)
2. (0.499999999999996, 0) (-0.499999999999996, 0) (0.499999999999996, 0) (-0.499999999999996, 0)
3. (0.499999999999996, 0) (0.499999999999996, 0) (-0.499999999999996, 0) (-0.499999999999996, 0)
4. (0.499999999999996, 0) (-0.499999999999996, 0) (-0.499999999999996, 0) (0.499999999999996, -0)
实例:
1.#include <QPanda.h>
2.USING_QPANDA
3.
4.int main(void)
5.{
6. auto qvm = CPUQVM();
7. qvm.init();
8. auto qubits = qvm.qAllocMany(4);
9. auto cbits = qvm.cAllocMany(4);
10. QProg prog;
11. prog << H(qubits[0])
12. << H(qubits[1])
13. << H(qubits[2])
14. << H(qubits[3])
15. << MeasureAll(qubits, cbits);
16.
17. auto result = qvm.runWithConfiguration(prog, cbits, 1000);
18. for (auto &val: result)
19. {
20. std::cout << val.first << ", " << val.second << std::endl;
21. }
22.
23.
24. return 0;
25.}
运行结果:
1.0000, 47
2.0001, 59
3.0010, 74
4.0011, 66
5.0100, 48
6.0101, 62
7.0110, 71
8.0111, 61
9.1000, 70
10.1001, 57
11.1010, 68
12.1011, 63
13.1100, 65
14.1101, 73
15.1110, 55
16.1111, 61
ClassicalProg:经典程序也可以被插入到量子程序中,它使得量子程序也可以进行逻辑判断和简单的经典计算,使得量子程序更灵活。打个比方,就像是向一套拳法中加入了步法,使得这套拳法可以辗转腾挪。
QCircuit:量子线路是由多个量子逻辑门组成的,或者说量子线路是一个大型的量子逻辑门,也就是说QCircuit中可以插入QGate和ClassicalProg。如果映射到功夫中,QCircuit就是拳法中拆解出来的套路。此外QCircuit也可以设置dagger和control,请看下面的例子:
1./* setDagger的作用是根据输入参数更新当前量子线路的dagger标记*/
2.QCircuit cir;
3.cir.setDagger(true);
4.
5./* dagger的作用是复制一份当前的量子线路,并更新复制的量子线路的dagger标记*/
6.QCircuit cir1;
7.QCircuit cir_dagger = cir1.dagger();
8.
9./* setControl的作用是给当前的量子线路添加控制比特*/
10.QCircuit cir2;
11.cir1.setControl(qvec);
12.
13./* control的作用是复制当前的量子线路,并给复制的量子线路添加控制比特*/
14.QCircuit cir3;
15.QCircuit cir_control = cir.control(qvec);
16.
Qif:量子条件判断程序,顾名思义,它可以让量子程序进行逻辑判断,即针对不同的对手拳法的套路也是可变的。
QWhile:量子循环判断程序,即根据循环判断条件把一个量子程序或量子线路多次运行。
QProg:它是一个容器,可以容纳所有量子计算相关的数据类型,在构造量子程序时,用户可以把QGate、QCircuit、Qif、QWhile、QProg、ClassicalProg类型插入到QProg中。
QPanda的特点不仅仅体现着它的易用性,还包括它的高效,如图4.1.1,在同等硬件配置下,QPanda的量子虚拟机运行量子程序的速度,相对其他工具有着巨大优势。
QPanda的量子比特池,QPanda不仅可以使用qAllocMany() 来直接申请比特,还可以调用量子比特池来获取量子比特,请看示例:
1.#include "QPanda.h"
2.USING_QPANDA
3.using namespace std;
4.int main()
5.{
6. /*量子比特可以和虚拟机 脱离关系,获取对应池的单例 */
7. auto qpool = OriginQubitPool::get_instance();
8. auto cmem = OriginCMem::get_instance();
9.
10. /* 获取容器大小*/
11. cout << "set qubit pool capacity before: "<< qpool->get_capacity() << endl;
12. /* 设置最大容器*/
13. qpool->set_capacity(20);
14. cout << "set qubit pool capacity after: " << qpool->get_capacity() << endl;
15.
16. /* 通过比特池申请比特,由于是单例模式,要保证申请的比特数量不超过最大容量*/
17. auto qv = qpool->qAllocMany(6);
18. auto cv = cmem->cAllocMany(6);
19.
20. /* 获取被申请的量子比特*/
21. QVec used_qv;
22. auto used_qv_size = qpool->get_allocate_qubits(used_qv);
23. cout << "allocate qubits number: " << used_qv_size << endl;
24.
25. /* 构建虚拟机*/
26. auto qvm = new CPUQVM();
27. qvm->init();
28. auto prog = QProg();
29. /*直接使用物理地址作为量子比特信息入参 */
30. prog << H(0) << H(1)
31. << H(2)
32. << H(4)
33. << X(5)
34. << X1(2)
35. << CZ(2, 3)
36. << RX(3, PI / 4)
37. << CR(4, 5, PI / 2)
38. << SWAP(3, 5)
39. << CU(1, 3, PI / 2, PI / 3, PI / 4, PI / 5)
40. << U4(4, 2.1, 2.2, 2.3, 2.4)
41. << BARRIER({0, 1,2,3,4,5});
42.
43. /* 测量方法也可以使用比特物理地址*/
44. auto res_0 = qvm->probRunDict(prog, { 0,1,2,3,4,5 });
45.
46. /* 同样经典比特地址也可以作为经典比特信息入参*/
47. prog << Measure(0, 0)
48. << Measure(1, 1)
49. << Measure(2, 2)
50. << Measure(3, 3)
51. << Measure(4, 4)
52. << Measure(5, 5);
53.
54. /* 使用经典比特地址入参*/
55. vector<int> cbit_addrs = { 0,1,2,3,4,5 };
56. auto res_2 = qvm->runWithConfiguration(prog, cbit_addrs, 5000);
57. delete(qvm);
58. /* 同时我们还可以再次利用这里申请的qv,避免多次使用虚拟机多次申请比特的问题发生*/
59. auto qvm_noise = new NoiseQVM();
60. qvm_noise->init();
61. auto res_4 = qvm_noise->runWithConfiguration(prog, cbit_addrs, 5000);
62. delete(qvm_noise);
63.
64. return 0;
65.}
除此之外,QPanda还集成了量子程序优化器、量子程序调试工具,量子程序编译器。
1、 量子程序优化器:QPanda的优化器通过特有的算法对量子程序的优化,可最大限度的减少计算时间。
2、 量子程序调试工具:QPanda的调试工具解决的量子算法工程师长期以来的困扰,里程碑的实现类量子程序的调试功能。
3、 量子程序编译器:不仅把量子程序转换为多种量子汇编语言,更可以生成量子程序可执行文件。
QRunes是一种基于语句的量子编程式语言,它的出现是为了实现量子计算和经典计算的混合,从而让量子计算有更好的应用场景。QRunes根据量子计算的经典与量子混合(Quantum-Classical Hybrid)特性,在程序编译之后可以操作经典计算机与量子芯片来实现量子计算。
QRunes通过提供高级语言的形式来实现量子算法和程序的逻辑控制。其丰富的类型系统(Quatum Type, Classical Type)可以实现量子计算中数据对象的绑定和行为控制,可以满足开发人员的各类量子算法的实现需求。
QRunes由量子程序和量子线路组成的量子部分和经典函数组成的经典部分构成。量子部分定义了对量子比特的操作和行为控制。经典部分用于主程序的实现和控制量子程序的运行。
本源量子云平台是国内首家基于模拟器研发且能在传统计算机上模拟32位量子芯片进行量子计算和量子算法编程的系统,目前该系统主要服务于各大科研院所、高校及相关企业,旨在为专业人员提供基于量子模拟器的开发平台。
本源量子云平台提供了两种虚拟机供用户选择,其中32位量子虚拟机免费使用、64位需付费申请,虚拟机采用可视化编程模式图例+量子语言,用户可轻松拖动、放置图例进行量子算法模拟,并可将设计的运算转化为量子语言模式深入学习。量子云平台是连接用户和量子计算设备之间的桥梁,当前量子系统运作结构通常是经典计算向量子系统发起计算任务请求,待量子系统完成计算任务后再以经典信息的方式返回给用户,整个过程都需要量子云平台在中间协调。
本源量子计算云平台的工作结构可以划分为四个部分:后端系统、控制指令、量子云端、以及用户端;其中后端系统包括了量子虚拟机,以及不同组织机构开发的量子芯片;控制指令则是通过其他编程语言或底层语言构建的能被量子系统识别的指令;量子云即是可视化编程、数据中转、用户数据存储交流等云服务;用户端,包括问题的设计、算法规则构造、可视化结果等。
目前,本源量子计算系统包括了三种构造控制指令的方法,如图4.1.2所示,分别为可视化线路的设计、量子语言和量子软件开发套件QPanda,其中可视化编程和量子语言依托在量子云平台上,用户在进行量子程序设计的时候可以相互转化;对于功能完整的QPanda,则使用c++为宿主语言开发的SDK,用户可以使用c++直接开发量子程序。当然QPanda也开发了支持Python的库,也就是说可以使用Python来开发量子程序。使用QPadna编写的量子程序,可以很方便地转化为量子语言或者可视化的量子线路,在量子云平台上可以可视化的进行基础的算法设计、云平台的操作,通过拖动量子逻辑门来构建控制序列,添加测量指令,即可运行得出结果。通常用户会通过云平台构建简单的量子算法,之后待量子线路图转化为虚拟机或量子系统识别的指令,并将数据送入虚拟机或者量子系统,完成计算之后,回传结果,此时用户就能收到最终的计算结果。
量子算法是在现实的量子计算模型上运行的算法,最常用的模型是计算的量子电路模型。经典(或非量子)算法是一种有限的指令序列,或一步地解决问题的过程,或每一步指令都可以在经典计算机上执行。
量子算法是一个逐步的过程,每个步骤都可以在量子计算机上执行。虽然所有经典算法都可以在量子计算机上实现,但量子算法这个术语通常用于那些看起来是量子的算法,或者使用量子计算的一些基本特性,如量子叠加或量子纠缠。
使用经典计算机无法判定的问题,使用量子计算机仍然无法来确定。量子算法有趣的是,它们可能能够比经典算法更快地解决一些问题,因为量子算法所利用的量子叠加和量子纠缠可能不可以在经典计算机上有效地模拟。
最著名的算法是Shor分解算法和Grover的搜索非结构化数据库或无序列表的算法。Shor算法运行速度比最著名的经典因式分解算法(一般的数域筛选算法)快得多(几乎是指数级),对于同样的任务,Grover算法运行速度比最好的经典算法(线性搜索)要快得多。
量子计算机究竟什么时候能够真正实现?没有人能给出确切的答案,不过在这条路上探索的人们非常明白,建立一个容错的、具有足够多的逻辑比特的系统,是一个非常漫长的任务。
然而,一个具有50个比特的量子系统,或者一个50个比特能模拟的量子系统,已经难以被传统计算机所模拟,它具有非常巨大的计算潜力。为了解决这个问题,John Preskill教授提出了一个全新的概念:含噪声的中等规模的量子计算机(Noise Intermediate-Scale Quantum),它被定义为未经纠错的,具有50个到数百个量子比特的量子计算机,简称为NISQ量子计算机。在NISQ上设计的算法可能和以往假设的容错量子计算机上设计的算法完全不同,NISQ算法本身需要能容忍噪声所造成的影响。
量子霸权在最初提出的时候,代表超过50个量子比特的量子计算机在生成特定分布(distribution)上超过了传统计算机,但是研究表明,在这些问题上可以巧妙地选取模拟算法,使得经典计算机也可以产生相同的分布。取代“量子霸权”(Quantum Supremacy)这个称呼的,是“量子优势”(Quantum Advantage)。量子优势意味着量子计算机在处理某些领域问题上,超过了传统计算机的表现,相对于霸权而言,量子优势更注重量子算法,以及实际的领域应用。可以说,量子优势是NISQ量子计算机领域的皇冠,谁夺取了皇冠,谁就证明了量子计算机可以投入到现实应用中。相比为了制造出一个逻辑比特可能需要数万个物理比特的容错量子计算机而言,NISQ计算机被认为可以在短期的未来中被实现。因此,这个领域成为了量子计算研究的热门。
量子-经典混合算法是一类近期提出的,适用于NISQ量子计算机上的算法。它的特点是量子计算机只处理整个算法中的一个部分,经典计算机负责处理其他部分。绝大多数量子-经典混合算法中都会存在一个类似于机器学习中的参数优化过程,其中,量子计算机处理一个包含多个参数的量子线路,并且对这些参数进行随机的初始化,量子计算机执行的结果会进一步被计算成一个损失函数,这个损失函数被输入到经典计算机的优化器中,从而修改这些参数,之后再通过量子计算机进行计算,如此循环,直到达到优化终止条件。例如损失函数收敛,达到最大优化步数等。
第一个提出的量子-经典混合算法是变分本征求解器(Variational Quantum Eigensolver),即VQE算法,它可以被用于求解化学分子的基态,因此,这个算法可以被用于解决各类涉及化学计算的相关问题。对于经典计算机而言,要表示N个分子轨道的占据状态,需要用$$2^n$$维的线性空间去计算,因此,在计算具有超过50个轨道的分子时,就无法进行精确计算;而量子计算机的N个轨道正好需要N个量子比特完成模拟过程,所以这个问题可以在量子计算机上被有效的求解。现在,针对组合优化问题、机器学习问题,都有各种各样的量子-经典混合算法被提出,它们被认为是有希望在NISQ计算机上实现。
由于量子经典混合算法的框架类似于在经典计算机上执行的机器学习算法,因此,可以利用类似于机器学习框架的系统去进行编程。本源量子所开发的VQNet框架,XanaduAI公司开发的PennyLane框架,都是在原有机器学习框架上扩展支持量子计算的部分。VQNet是基于符号运算的机器学习框架,它设置了“含参量子线路”(Variational Quantum Circuit),可以通过变量生成一个量子线路。通过含参量子线路可以进一步构建成量子算符(Quantum Operator),量子算符相当于对变量的运算,这种运算等价于一个普通的算符,支持求值和偏微分操作,因此,量子算符就可以容纳到机器学习这个框架中。利用VQNet可以实现目前绝大多数的量子-经典混合算法,包括VQE,QAOA,QCL……
量子算法是量子计算落地实用的最大驱动力,好的量子算法设计将更快速推动量子计算的发展。
Deutsch-Jozsa 量子算法,简称 D-J 算法, David Deutsch 和 Richard Jozsa 早在 1992 年提出了该算法,这是第一个展示了量子计算和经典计算在解决具体问题时所具有明显差异性的算法。
D-J 算法是这样描述的:给定两个不同类型的函数,通过计算,判断该函数是属于哪一类型的函数,其可用来演示说明量子计算如何在计算能力上远超经典计算。
D-J 算法所阐述的问题是:考虑一个函数$$f(x)$$, 它将
考虑$n=1$的情况: $$ f:{0,1} \rightarrow{0,1} $$
这个函数称为常数函数。如果对任意
$$ f(x) =0 \ or \ f(x) = 1 $$ 而如果$$f(x) = 0$$的个数等于$$f(x) = 1$$的个数,则称这个函数为平衡函数:
考虑一下最简单的情况:当
如图4.3.2,在经典算法中,给定了输入之后,第一步是需要判断
通过图4.3.3所示的量子线路图来理解该算法是如何解决问题的。首先,对所有的比特都执行Hadamard 门操作,然后经过黑盒子$$U_f$$,再对工作比特添加 Hadamard 门,然后测量。
按照实施步骤,表达形式:
- 初始化
2.使用 Hadamard 门来构建叠加态
$$ \rightarrow \frac{1}{\sqrt{2^{n}}} \sum_{x}|x\rangle\left[\frac{|0\oplus f(x)\rangle-|1\oplus f(x)\rangle}{\sqrt{2}}\right] \= \frac{1}{\sqrt{2^{n}}} \sum_{x}(-1)^{f(x)}|x\rangle\left[\frac{|0\rangle-|1\rangle}{\sqrt{2}}\right] $$ 此处$$\oplus$$运算表示求和后整除$$2$$ 。
4.在工作位上添加 Hadamard 门 $$ \rightarrow \frac{1}{2^{n}} \sum_{z} \sum_{x}(-1)^{x \cdot z+f(x)}|z\rangle\left[\frac{|0\rangle-|1\rangle}{\sqrt{2}}\right] $$ 5.测量工作位,输出结果,一次性就可以判断出结果
访问本源量子云平台:https://qcloud.originqc.com.cn/ ,选择仿真开发训练云进入量子线路编程页面。
图4.3.4 本源量子云平台量子线路编程 首先新建项目“D-J算法”,点击保存。本源量子云平台提供包括“本源悟源1号”、“本源悟源2号”等在内的多种计算后端,本例选择“全振幅量子虚拟机”为计算后端。
图4.3.5 创建项目 点击设置按钮(“”),可以设置实验参数。在模拟类型上,可以选择两种:Monte-Carlo方法和概率方法。
图4.3.6 参数设置 Monte-Carlo方法:通过多次数值模拟,对测量结果进行仿真。
概率方法:直接计算出需要的比特概率分布,而不需要真正的去计算测量过程。
其中概率方法比Monte-Carlo方法运行会快一些;由于是多次随机,Monte-Carlo方法每次运行的结果可能会不一样。这里选择Monte-Carlo方法。
以两个量子比特的D-J算法为例,它需要使用2个量子比特和1个经典寄存器去保存一次测量的值,输入好之后,点击保存。将线路上方的逻辑门通过鼠标拖拽到线路上,即可构建量子线路。
根据D-J算法的内容,首先需要在第一条线上添加一个Hadamard门。拖拽$$H$$就可以在线路上插入一个Hadamard门。
图4.3.7 拖入H门插入逻辑门之后,双击这个逻辑门可以删除,也可以通过鼠标拖动这个门的位置;同样在其他地方点击,可以插入更多的量子逻辑门。根据D-J算法,将除了Oracle的部分添加上:
中间空出来的部分,就是Oracle可以插入的部分了。
有很多量子算法,Oracle是作为输入给出的,比如D-J算法和Grover算法等。所以,两比特D-J算法的量子程序就已经写好了。
这里,可以插入不同的Oracle试验一下;定义Oracle的输入输出是:
$$ |x\rangle|y\rangle \rightarrow|x\rangle|f(x)+y \bmod 2\rangle $$ 这里$$f(x)$$会根据Oracle不同而编码到量子线路中,例如一个$$CNOT$$门就可以做一个Oracle。
一个$$CNOT$$门的效果是$$|\mathrm{x}\rangle|\mathrm{y}\rangle \rightarrow|x\rangle|(x+y) \mathrm{mod} 2\rangle$$,对比上面的Oracle公式,相当于$$f(x)=x$$。由此,可以看出$$f(0)=0, f(1)=1$$,因此是一个平衡函数。
这个门能产生的效果是$$|x\rangle|y\rangle \rightarrow|x\rangle|(1+y) \bmod 2\rangle$$,对比上面的Oracle公式,相当于$$f(x)=1$$,这是一个常数函数,如图4.3.8。
图4.3.8 CNOT与 Pauli-X 组合门 这个相当于把两种情况组合起来了,效果是
依次来检验这三种Oracle,D-J算法是否能输出正确的结果。
第一种:$$CNOT$$
构建如图4.3.9所示的量子线路图,并设置如图4.3.10所示参数。设置重复实验次数为为默认次数100次;点击运行,就可以运行这个量子程序了。
图4.3.9 量子线路图 图4.3.10 设置实验参数 点击查看任务结果,如图4.3.11。
图4.3.11 查看任务结果 查看“概率统计图”,可以看到在第一个量子比特上得到100%的1的测量值,如图4.3.12;这个柱状图的横轴表示不同的测量值,纵轴表示这个测量值对应的概率。这里只测到了1,概率为100%,符合预期。
图4.3.12 任务结果—概率统计图 依次测试另外两种Oracle:
图4.3.13 量子线路图对于这种情况,可以看到测量值100%为0。
图4.3.14 任务结果—概率统计图第三种情况:
图4.3.15 量子线路图由此,可以得到100%的1,说明这个Oracle代表一个平衡函数。
图4.3.16 任务结果—概率统计图如上述,在本源量子云平台上,可以实现简单的两比特量子算法。
下面的代码可以在github的QPanda仓库中的 Applications/DJ_Algorithm/DJ_Algorithm.cpp中找到。
1.#include "Core/Utilities/Tools/Utils.h"
2.#include "QAlg/DJ_Algorithm/DJ_Algorithm.h"
3.#include "QPandaNamespace.h"
4.#include "Core/Core.h"
5.#include <vector>
6.
7.using namespace std;
8.USING_QPANDA
9.
10.DJ_Oracle generate_two_qubit_oracle(vector<bool> oracle_function) {
11. return [oracle_function](QVec qubit1, Qubit* qubit2) {
12. QCircuit prog;
13. if (oracle_function[0] == false &&
14. oracle_function[1] == true)
15. {
16. prog << CNOT(qubit1[0], qubit2);
17. }
18. else if (oracle_function[0] == true &&
19. oracle_function[1] == false)
20. {
21. prog << CNOT(qubit1[0], qubit2)
22. << X(qubit2);
23. }
24. else if (oracle_function[0] == true &&
25. oracle_function[1] == true)
26. {
27. prog << X(qubit2);
28. }
29. else
30. {
31. }
32. return prog;
33. };
34.}
35.
36.void two_qubit_deutsch_jozsa_algorithm(vector<bool> boolean_function)
37.{
38. auto qvm = CPUQVM();
39. qvm.init();
40. auto oracle = generate_two_qubit_oracle(boolean_function);
41.
42. auto prog = deutschJozsaAlgorithm(boolean_function, &qvm, oracle);
43. auto result = qvm.directlyRun(prog);
44. if (result["c0"] == false)
45. {
46. cout << "Constant function!" << endl;
47. }
48. else if (result["c0"] == true)
49. {
50. cout << "Balanced function!" << endl;
51. }
52.}
53.
54.int main()
55.{
56. while (1) {
57. bool fx0 = 0, fx1 = 0;
58. cout << "input the input function" << endl
59. << "The function has a boolean input" << endl
60. << "and has a boolean output" << endl
61. << "f(0)= (0/1)?";
62. cin >> fx0;
63. cout << "f(1)=(0/1)?";
64. cin >> fx1;
65. std::vector<bool> oracle_function({ fx0,fx1 });
66. cout << "Programming the circuit..." << endl;
67. two_qubit_deutsch_jozsa_algorithm(oracle_function);
68. }
69. return 0;
70.}
整个文件利用QPanda的组件实现了一个Deutsch-Jozsa算法,并且提供了一个2 Qubit的示例。各个模块的介绍:
1. Deutsch_Jozsa_algorithm函数
在实现这个函数的时候,并不是仅仅考虑了2 qubit的情况,而是对更一般的情况,即所有qubit的情况进行处理。
QPanda的核心逻辑在于对申请的量子比特进行量子线路构建,因此,这个函数会返回一个QProg类型,这个QProg类型会被放到量子机器中进行执行。
Deutsch_Jozsa_algorithm的参数有4个:
vector<Qubit*> qubit1:一组qubit,它对应了上面所述的工作比特;
Qubit* qubit2:一个qubit,它对应的是算法中的辅助比特;
vector cbit:一组cbit,它对应的是qubit 1的测量值;
DJ_Oracle oracle:输入的,待计算的Oracle
因此,这个算法的逻辑就可以表述为:通过输入的qubit1, qubit2构建量子线路;并且将oracle作用在qubit1和qubit2上;最后,对qubit1测量到cbit上。这个函数将量子算法的流程通过QPanda的组件编写出来,整个流程会被写入到一个QProg类型的对象中,最终,这个函数会返回这个对象。
1.auto prog = QProg();
在函数中,首先可以通过QProg函数去创建一个空的量子程序。
1.prog << X(qubit2);
2.prog << apply_QGate(qubit1, H) << H(qubit2);
将逻辑门插入到这个量子程序的后面,依次是X门作用在qubit2上(将qubit2初始化为|1⟩),对qubit1和qubit2执行Hadamard门。
1.prog << oracle(qubit1, qubit2);
制备完纠缠态之后,就可以进行oracle计算。这里同样将oracle插入到量子程序中。
1.prog << apply_QGate(qubit1, H) << MeasureAll(qubit1, cbit);
最后同样是一组Hadamard门,并且通过MeasureAll将qubit1整个测量到cbit上。
2. two_qubit_deutsch_jozsa_algorithm函数
这个函数对两个比特的情况提供了一个演示。在这种情况下,需要对不同情况构建oracle,并且输入到这个算法中。构建oracle的方式就是通过这个函数的参数——一个vector类型来代表一个函数。这个函数相当于一个真值表,例如:
1.std::vector<bool> fx = {0, 1};
这表示fx[0] = 0,fx[1] = 1这种情况。
通过真值表构建对应oracle的方法:即generate_two_qubit_oracle,它恰好返回一个oracle类型,感兴趣的读者可以自行对这个oracle中的不同情况进行验证。
这个函数中最重要的步骤就是初始化量子机器,申请量子比特和经典寄存器,取得量子程序,执行并且获得最终结果。
1.auto qvm = CPUQVM();
2.qvm.init();
这个语句定义一个CPU执行的模拟器。可以指定为不同类型,例如GPU、单线程CPU、或者云量子计算机。并调用init()函数来初始化qvm。
1.auto qvec = qvm.qAllocMany(2);
2.auto c = qvm.cAlloc();
通过qAlloc和cAlloc可以从量子机器中申请一定量的量子比特。Many表示很多,所以qAllocMany和cAllocMany可以一次性申请多个量子比特。
1.auto oracle = generate_two_qubit_oracle(boolean_function);
这个函数就是用于生成oracle的。
1.QProg prog;
2.prog << Deutsch_Jozsa_algorithm({ qvec[0] }, qvec[1], { c }, oracle);
这一段程序可以将上述量子程序打印出来。
1.qvm.directlyRun(prog);
2.if (c.eval() == false)
3.{
4. cout << "Constant function!" << endl;
5. }
6. else if (c.eval() == true)
7. {
8. cout << "Balanced function!" << endl;
9. }
10.}
函数directlyRun顾名思义是直接运行这个量子程序。QPanda提供很多运行量子程序的方式,而directlyRun表示这个量子程序在默认模式下运行1次。由于DJ算法是一个确定性算法,就只运行一次,运行结束后,可以从之前申请的经典寄存器中取得值:c.eval。这里false表示测量到|0⟩,true表示测量到|1⟩。通过测量的结果可以得到这个函数属于平衡函数还是常数函数。
什么是搜索算法呢?举一个简单的例子,在下班的高峰期,要从公司回到家里,开车走怎样的路线才能够耗时最短呢?最简单的想法,当然是把所有可能的路线一次一次的计算,根据路况计算每条路线所消耗的时间,最终可以得到用时最短的路线,即为最快路线,这样依次的将每一种路线计算出来,最终对比得到最短路线。搜索的速度与总路线数$$N$$相关,记为
那么怎么实现Grover搜索算法呢?
首先,先化简一下搜索模型,将所有数据存在数据库中,假设有$$n$$个量子比特,用来记录数据库中的每一个数据的索引,一共可以表示$$2^n$$个数据,记为$$N$$个;希望搜索得到的数据有$$M$$个,为了表示一个数据是否是搜索的结果,建立一个函数:
其中$$x_0$$为搜索目标的索引值,也即是说,当搜索到目标时,函数值$$f(x)$$值为$$1$$,如果搜索的结果不是目标时,$$f(x)$$值为$$0$$。假设有一个量子Oracle可以识别搜索问题的解,是别的结果通过Oracle的一个量子比特给出。可以将Oracle定义为:
$$ |x\rangle|q\rangle \stackrel{\text { Oracle }}{\longrightarrow}|x\rangle|q\oplus f(x)\rangle $$ 其中$$|q \rangle$$ 是一个结果寄存器, ⨁ 是二进制加法。通过Oracle可以实现,当搜索的索引为目标结果时,结果寄存器翻转;反之,结果寄存器值不变;从而可以通过判断结果寄存器的值,来确定搜索的对象是否为目标值。
Oracle对量子态的具体操作是什么样的呢?同D-J算法相似,先将初态制备在$$|0\rangle^{\otimes n}$$$$|1\rangle$$ 态上,$$|0\rangle^{\otimes n}$$ 为查询寄存器,$$|1\rangle$$ 为结果寄存器。 经过 Hardmard 门操作后,可以将查询寄存器的量子态,变为所有结果的叠加态;也即是说,经过了 Hardmard 门,就可以得到所有结果的索引,而结果寄存器则变为$$\frac{1}{\sqrt{2}}(|1\rangle-|0\rangle)$$ ,再使其通过Oracle,可以对每一个索引都进行一次检验,如果是目标结果,则将结果寄存器的量子态进行
$$ \frac{1}{\sqrt{2}}(|1\rangle-|0\rangle)\stackrel{\text { Oracle }}{\longrightarrow}-\frac{1}{\sqrt{2}}(|1\rangle-|0\rangle) $$ 而查询寄存器不变;但当检验的索引不是结果时,寄存器均不发生改变。因此,Oracle可以换一种表示方式:
$$ \left.\left.\right|\mathbf{x}\right\rangle\left(\frac{|0\rangle-|\mathbf{1}\rangle}{\sqrt{2}}\right) \stackrel{\text { Oracle }}{\longrightarrow}(-1)^{f(x)} |\mathbf{x}\rangle\left(\frac{(0)-|\mathbf{1}\rangle}{\sqrt{2}}\right) $$ 其中,$$|x\rangle $$是查询寄存器的等额叠加态中的一种情况。
如上述所知,Oracle的作用,是通过改变了解的相位,标记了搜索问题的解。
现在,将搜索问题的解通过相位标记区分出来,那么如何能够将量子态的末态变为已标记出的态呢?
将问题换一种思路进行考虑,当查询寄存器由初态经过 Hardmard 门后,会变为所有可能情况的等额叠加态;也就是说,它包含着所有搜索问题的解与非搜索问题的解。可以将这个态记为:
$$
|\psi\rangle=\frac{1}{\sqrt{N}} \sum_{x}|\mathrm{x}\rangle
$$
将所有非搜索问题的解定义为一个量子态
$$
\left.\left.\right| \alpha\right\rangle=\frac{1}{\sqrt{N-M}} \sum_{x_1}|\mathrm{x}\rangle
$$
显然,$$|\beta\rangle
初始态被搜索问题的解的集合和非搜索问题的解的集合重新定义,也即是说,初态属于
那么,Oracle作用在新的表示方法下的初态会产生怎样的影响呢?Oracle的作用是用负号标记搜索问题的解,所以,相当于将
对应在平面向量中,相当于将
第二种对称操作,是将量子态关于$$|\psi\rangle $$对称的操作,这个操作由三个部分构成:
- 将量子态经过一个 Hardmard 门;
- 对量子态进行一个相位变换,将$$|0\rangle^{\otimes n}$$态的系数保持不变,将其他的量子态的系数增加一个负号,相当于$$2|0\rangle\langle 0|-\mathrm{I} $$酉变换算子;
- 再经过一个 Hardmard 门。
这三步操作的数学表述为:
上述过程涉及到复杂的量子力学知识,这三部分的操作,只是为了实现将量子态关于$$ |\psi\rangle$$ 对称。如果想了解为什么这三步操作可以实现,可以阅读关于量子计算相关书籍进一步理解。
前面介绍的两种对称操作,合在一起称为一次Grover迭代。假设初态$$ |\psi\rangle$$与
$$ |\psi\rangle=\cos \frac{\theta}{2}|\alpha\rangle+\sin \frac{\theta}{2}|\beta\rangle $$ 很容易得到:
可以从几何图像上看到,每一次Grover迭代,可以使量子态逆时针旋转
因此,经过多次迭代操作,总可以使末态在$$|\beta\rangle$$态上概率很大,满足精确度的要求。经过严格的数学推导,可证明,迭代的次数
参考路线图4.4.2。
1.#include "Core/Core.h"
2.#include "Core/Utilities/Tools/Utils.h"
3.#include "QAlg/Grover/GroverAlgorithm.h"
4.#include "QAlg/Grover/QuantumWalkGroverAlg.h"
5.
6.USING_QPANDA
7.using namespace std;
8.
9.static size_t g_shot = 100000;
10.
11.static uint32_t quantum_grover_search(const std::vector<uint32_t>& search_space, const std::vector<uint32_t>& search_data,
12. std::vector<size_t>& search_result)
13.{
14. search_result.clear();
15. uint32_t search_times = 0;
16. uint32_t repeat_times = 0;
17. auto machine = CPUQVM();
18. machine.init();
19. machine.setConfigure({ 64,64 });
20.
21. CPUQVM _tmp_qvm;
22. _tmp_qvm.init();
23. auto x = _tmp_qvm.allocateCBit();
24.
25. std::vector<size_t> search_result_for_check;
26. for (size_t i = 0; i < search_space.size(); ++i)
27. {
28. if (search_space[i] == search_data[0]) {
29. search_result_for_check.emplace_back(i);
30. }
31. }
32.
33. cout << "Grover will search through " << search_space.size() << " data." << endl;
34. cout << "Start grover search algorithm:" << endl;
35.
36. QProg grover_Qprog;
37. QVec measure_qubits;
38. uint32_t qubit_size = 0;
39. vector<ClassicalCondition> c;
40. const double max_repeat = 3.1415926 * sqrt(((double)search_space.size()) / ((double)search_result_for_check.size())) / 4.0;
41. while (true)
42. {
43. measure_qubits.clear();
44. grover_Qprog = build_grover_prog(search_space, x == search_data[0], &machine, measure_qubits, ++repeat_times);
45. search_times += repeat_times;
46.
47. if (0 == qubit_size){
48. QVec _qv;
49. qubit_size = grover_Qprog.get_used_qubits(_qv);
50. printf("Number of used-qubits: %u.\n", qubit_size);
51. }
52.
53. if (0 == c.size()){
54. c = machine.allocateCBits(measure_qubits.size());
55. }
56.
57. grover_Qprog << MeasureAll(measure_qubits, c);
58. write_to_originir_file(grover_Qprog, &machine, "Grover_prog.ir");
59.
60. auto result = machine.runWithConfiguration(grover_Qprog, c, g_shot);
61.
62. std::map<string, double> normal_result;
63. for (const auto& _r : result){
64. normal_result.insert(std::make_pair(_r.first, (double)_r.second/(double)g_shot));
65. }
66. search_result = search_target_from_measure_result(normal_result, measure_qubits.size());
67. if ((search_result.size() > 0)
68. || ((search_result.size() == 0) && (max_repeat < repeat_times))){
69. break;
70. }
71. }
72. cout << "Draw grover_Qprog:" << grover_Qprog << endl;
73. return search_times;
74.}
75.
76.int main(int argc, char* argv[])
77.{
78. int search_type = 1;
79. std::vector<uint32_t> search_data = {21};
80. /* run search algorithm */
81. std::vector<size_t> search_result;
82. uint32_t search_cnt = 0;
83. try
84. {
85. std::vector<uint32_t> search_sapce;
86. search_sapce.push_back(8);
87. search_sapce.push_back(7);
88. search_sapce.push_back(6);
89. search_sapce.push_back(0);
90. search_sapce.push_back(6);
91. search_sapce.push_back(3);
92. search_sapce.push_back(6);
93. search_sapce.push_back(4);
94. search_sapce.push_back(6);
95. search_sapce.push_back(6);
96. search_sapce.push_back(6);
97. search_sapce.push_back(6);
98. search_sapce.push_back(6);
99. search_sapce.push_back(6);
100. search_sapce.push_back(7);
101. search_sapce.push_back(14);
102. search_sapce.push_back(9);
103. search_sapce.push_back(12);
104. search_sapce.push_back(4);
105. search_sapce.push_back(9);
106. search_sapce.push_back(9);
107. search_sapce.push_back(7);
108. search_sapce.push_back(21);
109. search_sapce.push_back(15);
110. search_sapce.push_back(3);
111. search_sapce.push_back(11);
112. search_sapce.push_back(3);
113. search_sapce.push_back(9);
114. search_sapce.push_back(7);
115. search_sapce.push_back(21);
116. search_sapce.push_back(15);
117. search_sapce.push_back(21);
118. search_sapce.push_back(21);
119. search_sapce.push_back(3);
120. search_sapce.push_back(9);
121. search_sapce.push_back(7);
122.
123. search_cnt = quantum_grover_search(search_sapce, search_data, search_result);
124.
125. }
126. catch (const std::exception& e)
127. {
128. cout << "Got a exception: " << e.what() << endl;
129. return -1;
130. }
131. catch (...)
132. {
133. cout << "Got an unknow exception: " << endl;
134. return -1;
135. }
136.
137. cout << "Search result:\n";
138. for (const auto &result_item : search_result)
139. {
140. cout << result_item << " ";
141. }
142. return 0;
143.}
QAOA算法(Quantum Approximate Optimization Algorithm),又名量子近似优化算法,是由Edward Farhi, Jeffrey Goldstone和Sam Gutmann开发的一个多项式时间算法,用于寻找“最优化问题的一种‘好’的解决方案” 。
Farhi, Goldstone和Gutmann都是现今量子计算领域有名的教授,其中Edward Farhi和Jeffrey Goldstone是麻省理工学院的名誉教授。他们在2000年一起发表了绝热量子算法,2014年在此基础上他们又共同发表了量子近似优化算法(QAOA)。
这个名字是什么意思呢?对于给定的NP-Hard问题,QAOA有望在多项式时间内给出一个优化后的近似解。理论上,当QAOA的算法层数增多,且线路中的参数值的选取足够好时,这个近似解会越来越趋近于最优解。QAOA算法很有意思的一个原因是它具有展示量子霸权的潜力。
最大切割问题(MAXCUT)是原始QAOA算法论文中描述的第一个应用。此问题类似于图形着色,对于给定节点和边的图形,并给每个边分配一个分值,接着将每个节点着色为黑色或白色,然后计算不同颜色节点边的分值之和,目的是找到一组得分最高的着色方式;更正式地表述是将图的节点划分为两组,使得连接相对组中的节点的边的数量最大化。例如图4.5.1的杠铃图:
有4种方式将节点分为两组:
图4.5.2中仅对连接不同集合中的节点时才会绘制边,带有剪刀符号的线条表示需要计算的切割边。对于杠铃图,有两个相等的权重分组对应于最大切割(如上图中间两个分组),将杠铃切成两半。可以将“组0”或“组1”中的节点表示为 0 或 1,组成一个长度为N的比特串 。上图的四个分组可以表示为
$$ {00,01,10,11} $$ 其中最左边的比特对应节点 A,最右边的比特对应节点 B。 用比特串来表示使得表示图的特定分组变得很容易,每个比特串具有相关联的切割权重。
对任意一个图,最大切割所使用的比特串长度是
有16个可能的分组 (
与每个分组相关联的比特串如上图所示,其中最右边的比特对应节点 A,最左边的比特对应节点 D。绘制出切割边,这样就可以很清楚的看到不同分组对应的边的切割情况。
假设令方形图切割边的值都为1,可以将方形图中的切割方案全部枚举出来,如表4.5.1所示
切割方案 | 切割值 | 切割方案 | 切割值 |
---|---|---|---|
I0000⟩ | 0 | I1000⟩ | 2 |
I0001⟩ | 2 | I1001⟩ | 2 |
I0010⟩ | 2 | I1010⟩ | 4 |
I0011⟩ | 2 | I1011⟩ | 2 |
I0100⟩ | 2 | I1100⟩ | 2 |
I0101⟩ | 4 | I1101⟩ | 2 |
I0110⟩ | 2 | I1110⟩ | 2 |
I0111⟩ | 2 | I1111⟩ | 0 |
通过查表很容易可以找到最优的切割方案。
从前面对杠铃图和方形图的示例介绍,知道可以通过枚举的方式找到最优的切割方案,并且枚举的数量跟节点个数有关,如果有N个节点,则枚举的数量是$$2^N$$。对于最大切割问题,在节点数较少的情况下,可以通过枚举的方式找到最优方案。但是,随着节点数的增加其计算时间复杂度也成指数级的增加。
当遇到1000个,10000个或者更多的节点时,还能通过枚举的方式来解决这个问题吗?
答案是不能的,对于10000个节点来说,就算把全球的计算资源都加起来也要计算很长的时间,那是否有其他的解决方案呢?答案是有的,QAOA其实就是最大切割的一种解决方案。
布尔可满足性问题(有时称为命题可满足性问题,缩写为SATISFIABILITY或SAT),就是确定是否存在满足给定布尔公式的解释的问题。
布尔表达式是由布尔变量和逻辑运算符(NOT , AND ,OR)所构成的表达式。其中NOT又称逻辑非$$(\neg)$$,AND又称逻辑与$$(\wedge)$$,OR又称逻辑或$$(\vee)$$。
例如:
布尔可满足性问题就是对于布尔表达式中的变量使用true或者false进行赋值,使得该布尔表达式的值为true,则该布尔表达式是可满足的。
例如:
当
当
当
那有没有不可满足的例子呢?
无论
已知的NP-完全问题有很多,但作为这些问题的“祖先”,历史上第一个被证明的NP-完全问题就是来自于布尔可满足性问题。
SAT问题是逻辑学的一个基本问题,也是当今计算机科学和人工智能研究的核心问题。工程技术、军事、工商管理、交通运输及自然科学研究中的许多重要问题,如程控电话的自动交换、大型数据库的维护、大规模集成电路的自动布线、软件自动开发、机器人动作规划等,都可转化成SAT问题。因此致力于寻找求解SAT问题的快速而有效的算法,不仅在理论研究上而且在许多应用领域都具有极其重要的意义。
SAT的问题被证明是NP困难的问题。目前解决该问题的方法主要有完备的方法和不完备的方法两大类。完备的方法优点是保证能正确地判断SAT问题的可满足性,但其计算效率很低,平均的计算时间为多项式阶,最差的情况计算时间为指数阶,不适用于求解大规模的SAT问题。不完备的方法的优点是求解的时间比完备的方法快得多,但在很少数的情况下不能正确地判断SAT问题的可满足性。
传统的方法有:枚举法、局部搜索法和贪婪算法等,但由于搜索空间大,问题一般难以求解。对于像SAT一类的NP难解问题,采用一些现代启发式方法如演化算法往往较为有效。
组合最优化是指通过对数学方法的研究去寻找处理离散事件的最优编排、分组、次序或筛选等问题的优化方法。实际上就是从有限个离散状态中选取最好的状态。
组合最优化的模型如下
其中,f(x)为目标函数,g(x)为约束条件,x为决策变量,D表示有限个点组成的集合。
从模型可看出组合最优化问题是一个规划问题(在一定条件下,求解目标函数的最大值或最小值,这类问题叫做数学规划,它是运筹学里的重要内容)。
组合最优化的特点就是定义域集合为有限点集。由直观可知,只要将定义域D中的有限个点逐一判别是否满足约束,并比较目标函数的大小,就可以得到该问题的最优解,这就是枚举法。对于某些优化问题可以通过枚举法得到最优解,这在问题规模较小时是十分有效的,考虑的点也是非常全面的。每一个组合最优化问题都可以通过枚举的方法求得最优解,然而枚举是以时间为代价的,有的枚举时间还可以接受,有的则不可能接受。
例如背包问题,旅行商问题,以及最大切割问题都是组合最优化问题。那么,有哪些方法解决这类问题呢?如表4.5.2所示:
最优化问题,它一般分为两大类:一类是具有连续型的变量,另一类是具有离散型的变量,后一类被称为组合最优化问题。在应用方面,可以将连续优化问题通过设定步长转换为离散优化问题,这样就可以使用组合优化问题的方法求解了。
很多实际应用问题都是NP-完全问题,这类问题很可能不存在多项式时间算法。一般而言NP-完全问题可采用以下三种方式处理。如果问题的输入规模较小,则可以利用搜索策略在指数时间内求解问题;如果输入规模较大,既可以利用随机算法在多项式时间内“高概率”地精确求解问题;也可以考虑在多项式时间内求得问题的一个“近似解”。
QAOA算法就是指这种能够在多项式时间内给出优化问题的近似优化解的算法。QAOA算法不仅可用于近似求解NP-完全问题,也可用于近似求解复杂度较高的NP困难问题。
通常在生活中,有些问题没有必要找到最完美的解,常常是找到一个可以满足期望的解就可以了。比如赛车比赛,赛道上其实有很多路线可以到达终点,车手只需要找到一种可以赢得比赛的路线就可以了。
泡利算符是一组三个2×2的幺正厄米复矩阵,一般都以希腊字母
每个泡利矩阵有两个特征值,+1和−1,其对应的归一化特征向量为:
通常,用
同理,也可以得到其它泡利矩阵相乘的表达式结果:
由此发现,顺序相乘的两个泡利矩阵跟未参与计算的泡利矩阵是
在QPanda中,实现了泡利算符类,定义了以下规则:
用大写字母$$X$$表示泡利$$x$$算符,又称为$$X$$门;用大写字母$$Y$$表示泡利$$y$$算符,又称为$$Y$$门;用大写字母$$Z$$表示泡利$$z$$算符,又称为$$Z$$门。
另外,定义形式如
表示在0号量子比特上作用了一个$$X$$门,其系数为2。
表示在0号和1号量子比特上作用了$$Z$$门,其系数为3。
表示在0号量子比特作用$$X$$门,在1号量子比特作用$$Y$$门,在2号和3号量子比特上作用了$$Z$$门,其系数为4。
表示的是单位矩阵,其系数为2。
最终表示的矩阵形式是作用在不同比特上的泡利门的张乘,这里提到的系数可以是实数也可以是复数。
在QPanda中,可以通过如下示例代码构建泡利运算符类。
使用C++构建方式:
1.#include "Operator/PauliOperator.h"
2.using namespace QPanda;
3.int main()
4.{
5. PauliOperator p1;
6. PauliOperator p2({ {"Z0 Z1", 2},{"X1 Y2", 3} });
7. PauliOperator p3("Z0 Z1", 2);
8. PauliOperator p4(2); // PauliOperator p4("", 2);
9. PauliOperator p5(p2);
10.
11. return 0;
12.}
Python构建方式:
1.from pyqpanda import *
2.if __name__=="__main__":
3.
4. p1 = PauliOperator()
5. p2 = PauliOperator({'Z0 Z1': 2, 'X1 Y2': 3})
6. p3 = PauliOperator('Z0 Z1', 2)
7. p4 = PauliOperator(2)
8. p5 = p2
构造一个空的泡利算符类P1,里面不包含任何泡利算符及单位矩阵;可以以字典序的形式构建多个表达式,例如P2;也可以构建单项,例如P3;还可以只构造一个单位矩阵,例如P4;也可以通过已经构造好的泡利运算符来构造它的一份副本例如P5。
泡利算符类支持常规的加、减、乘等运算操作,计算返回结果还是一个泡利算符类。例如:定义a和b两个泡利算符类,让泡利算符类之间进行加操作,减操作和乘操作。
C++示例:
1.#include "Operator/PauliOperator.h"
2.using namespace QPanda;
3.int main()
4.{
5. PauliOperator a("Z0 Z1", 2);
6. PauliOperator b("X5 Y6", 3);
7. auto plus = a + b;
8. auto minus = a - b;
9. auto muliply = a * b;
10.
11. return 0;
12.}
Python示例:
1.from pyqpanda import *
2.if __name__=="__main__":
3.
4. a = PauliOperator('Z0 Z1', 2)
5. b = PauliOperator('X5 X6', 3)
6. plus = a + b
7. minus = a - b
8. muliply = a * b
泡利算符类还支持打印功能,可以直接将泡利算符类打印输出到屏幕上。如示例代码示,将a+b,a-b和a*b的值打印输出到屏幕上来查看计算结果。
C++示例:
1.#include "Operator/PauliOperator.h"
2.using namespace QPanda;
3.int main()
4.{
5. PauliOperator a("Z0 Z1", 2);
6. PauliOperator b("X5 Y6", 3);
7. auto plus = a + b;
8. auto minus = a - b;
9. auto multiply = a * b;
10.
11. std::cout << "a + b = " << plus << std::endl;
12. std::cout << "a - b = " << minus << std::endl;
13. std::cout << "a * b = " << multiply << std::endl;
14.
15. return 0;
16.}
Python示例:
1.from pyqpanda import *
2.if __name__=="__main__":
3. a = PauliOperator('Z0 Z1', 2)
4. b = PauliOperator('X5 X6', 3)
5. plus = a + b
6. minus = a - b
7. multiply = a * b
8.
9. print("a + b = {}".format(plus))
10. print("a - b = {}".format(minus))
11. print("a * b = {}".format(multiply))
12.
上述示例的输出结果如下:
1.a + b =
2.{
3. "X5 X6" : 3.000000
4. "Z0 Z1" : 2.000000
5.}
6.a - b =
7.{
8. "X5 X6" : -3.000000
9. "Z0 Z1" : 2.000000
10.}
11.a * b =
12.{
13. "Z0 Z1 X5 X6" : 6.000000
14.}
还可以通过getMaxIndex接口返回泡利算符类需要操作的比特个数。如果是空的泡利算符类则返回0,否则返回最大下标索引值加1的结果。
C++示例:
1.#include "Operator/PauliOperator.h"
2.using namespace QPanda;
3.int main()
4.{
5. PauliOperator a("Z0 Z1", 2);
6. PauliOperator b("X5 Y6", 3);
7.
8. auto muliply = a * b;
9.
10. std::cout << "a * b = " << muliply << std::endl;
11. std::cout << "Index : " << muliply.getMaxIndex();
12.
13. return 0;
14.}
Python示例:
1.from pyqpanda import *
2.if __name__=="__main__":
3. a = PauliOperator('Z0 Z1', 2)
4. b = PauliOperator('X5 X6', 3)
5. muliply = a * b
6. print("a * b = {}".format(muliply))
7. print("Index : {}".format(muliply.getMaxIndex()))
在示例代码中,a*b的结果是Z0 Z1 X5 X6,则这个泡利算符类需要使用到的比特数,通过调用getMaxIndex接口得到7。
1.a * b = {
2."Z0 Z1 X5 X6" : 6.000000
3.}
4.Index : 7
另外一个跟泡利算符类下标有关的接口remapQubitIndex,它的功能是对泡利算符类中的索引从0比特开始分配映射,并返回新的泡利算符。
使用C++方式构建:
1.#include "Operator/PauliOperator.h"
2.using namespace QPanda;
3.int main()
4.{
5. PauliOperator a("Z0 Z1", 2);
6. PauliOperator b("X5 Y6", 3);
7.
8. auto muliply = a * b;
9.
10. std::map<size_t, size_t> index_map;
11. auto remap_pauli = muliply.remapQubitIndex(index_map);
12. std::cout << "remap_pauli : " << remap_pauli << std::endl;
13. std::cout << "Index : " << remap_pauli.getMaxIndex();
14.
15. return 0;
16.}
使用Python方式构建:
1.from pyqpanda import *
2.if __name__=="__main__":
3. a = PauliOperator('Z0 Z1', 2)
4. b = PauliOperator('X5 X6', 3)
5. muliply = a * b
6. index_map = {}
7. remap_pauli = muliply.remapQubitIndex(index_map)
8. print("remap_pauli = {}".format(remap_pauli))
9. print("Index : {}".format(remap_pauli.getMaxIndex()))
index_map里面是前后映射关系,以a*b为例,如果直接调用getMaxIndex接口返回的结果是7,说明这个泡利算符类需要操作7个量子比特,其实2号,3号和4号比特并未被使用;如果使用remapQubitIndex接口,即可用4个量子比特来进行计算操作。
1.remap_pauli = {
2."Z0 Z1 X2 X3" : 6.000000
3.}
4.Index : 4
泡利算符类提供了其它一些常用功能,例如:
1.isEmpyt() // 判空
2.dagger() // 返回共轭泡利算符
3.isAllPauliZorI() // 判断是否全为泡利“Z”或“I”
4.toString() // 返回字符串形式
5.data() // 返回泡利运算符内部维护的数据结构
对于一个物理系统,可以用哈密顿量来描述,其实哈密顿量在数学表示上就是一个矩阵,只不过这个矩阵有点特殊,它的本征值都是实数。
哈密顿量的本征向量描述对应的物理系统所处的各个本征态,本征值就是物理系统所处的本征态对应的能量。
对于一个问题,如果找到了它的哈密顿量,根据这个哈密顿量就可以求得它的本征态和本征能量,得到了本征态和本征能量就相当于解决了问题。
例如图4.5.5,对于这个由三个台阶和一个小球组成的系统,它可以存在三种不同的状态,将小球放在不同的台阶上,可以看到每个台阶的高度不同,小球的重力势能是不相等的,所以这个系统有三种不同的能量状态。
假设每个状态对应的能量分别是$$\mathrm{E1}$$,$$\mathrm{E2}$$和
用1,0,0来表示第一个状态,用0,1,0来表示第二个状态;用0,0,1来表示第三个状态。
那这个系统的哈密顿量可以表示成这样的矩阵形式:
$$ \mathrm{H}=\left[\begin{array}{ccc} \mathrm{E}{1} & 0 & 0 \ 0 & \mathrm{E}{2} & 0 \ 0 & 0 & \mathrm{E}_{3} \end{array}\right] $$ 对于对角矩阵,它对角线上的每个元素都是该矩阵的本征值。其中1,0,0;0,1,0和0,0,1就是该对角矩阵的本征向量。
对于布尔变量a来说,它有两个状态a和非a,如果a表示真的话,那非a就表示假,假设用数字1来表示真,用数字0来表示假。则如表4.5.3:
它有两个本征态$$|0\rangle$$ 和
1.逻辑表达式a∧b的哈密顿量
逻辑与也叫做逻辑乘,逻辑与对应的哈密顿量可以表示成两个逻辑变量的哈密顿量之间的乘积。
假设给出a与b的真值表,如表4.5.4。当逻辑变量a和逻辑变量b都为true时,逻辑表达式a与b值为1,其它情况值都为0。
哈密顿量的本征向量可以描述对应物理系统所处的各个本征态,哈密度量的本征值可以描述物理系统所处的本征态对应的能量。
对于逻辑表达式a与b这个简单系统来说,它有4个状态,第一个状态能量值为1,其它状态能量值为0;那么逻辑表达式a与b的哈密顿量,有4个本征态,本征态对应的能量分别为1,0,0,0。
$$ \mathrm{H}_{\mathrm{a} \land \mathrm{b}}=\left[\begin{array}{llll} 1 & 0 & 0 & 0 \ 0 & 0 & 0 & 0 \ 0 & 0 & 0 & 0 \ 0 & 0 & 0 & 0 \end{array}\right] $$ 逻辑与也叫做逻辑乘,逻辑与对应的哈密顿量可以表示成两个逻辑变量的投影算符之间的直积。如果用$$|0\rangle$$态来表示a状态和b状态,用$$|1\rangle$$态来表示非a状态和非b状态。令状态a和状态b的值为真。则逻辑表达式a与b的真值表如表4.5.5所示:
$$ \mathrm{H}{\mathrm{a} \land \mathrm{b}}=\frac{\mathrm{I}+\sigma{0}^{z}}{2} \cdot \frac{\mathrm{I}+\sigma_{1}^{z}}{2}=\left[\begin{array}{llll} 1 & 0 & 0 & 0 \ 0 & 0 & 0 & 0 \ 0 & 0 & 0 & 0 \ 0 & 0 & 0 & 0 \end{array}\right] $$
同理,如果用$$|1\rangle$$态来表示a状态和b状态,用$$|0\rangle$$态来表示非a状态和非b状态。则逻辑表达式a与b的真值表可以如表4.5.6所示:
$$|1 \rangle$$态对应的投影算符为$$\frac{I-\sigma^{2}}{2}$$,将变量a和b用投影算符来替换,逻辑与用矩阵乘来替换,则逻辑表达式a与b的哈密顿量推导形式为:
$$ \mathrm{H}{\mathrm{a} \land \mathrm{b}}=\frac{\mathrm{I}-\sigma{0}^{z}}{2} \cdot \frac{\mathrm{I}-\sigma_{1}^{z}}{2}=\left[\begin{array}{llll} 0 & 0 & 0 & 0 \ 0 & 0 & 0 & 0 \ 0 & 0 & 0 & 0 \ 0 & 0 & 0 & 1 \end{array}\right] $$
2.逻辑表达式的a∨b哈密顿量
所有的逻辑表达式都可以用逻辑与和逻辑非来表示。那么逻辑表达式a或b,可以表示为非a与非b的非。
假设给出逻辑表达式a或b的真值表,如表4.5.7。当逻辑变量a和b都为false的时候,逻辑表达式a或b值为0。其它情况值都为1。
根据状态和对应的能量,写出逻辑表达式a或b的哈密顿量,这个哈密顿量也有4个本征态,本征态对应的能量分别为1,1,1,0。
同样假设用$$|0 \rangle$$态来表示a状态和b状态,用$$|1 \rangle$$态来表示非a状态和非b状态,令状态a和状态b的值为真。则逻辑表达式a或b的真值表如表4.5.8所示:
$$|1 \rangle$$态对应的投影算符为
$$ \mathrm{H}{\mathrm{a} \lor \mathrm{~b}}=\mathrm{I}-\frac{\mathrm{I}-\sigma{0}^{2}}{2} \cdot \frac{\mathrm{I}-\sigma_{1}^{2}}{2}=\left[\begin{array}{llll} 1 & 0 & 0 & 0 \ 0 & 1 & 0 & 0 \ 0 & 0 & 1 & 0 \ 0 & 0 & 0 & 0 \end{array}\right] $$
从上述表达式不难发现,它与用状态及其能量写出来的哈密顿量形式一样。
同理,如果用$$|1 \rangle$$态来表示a状态和b状态,用$$|0 \rangle$$态来表示非a状态和非b状态。则逻辑表达式a或b的真值表如表4.5.9所示:
$$|0 \rangle$$态对应的投影算符为
$$ \mathrm{H}{\mathrm{a \lor b}}=\mathrm{I}-\frac{\mathrm{I}+\sigma{0}^{2}}{2} \cdot \frac{\mathrm{I}+\sigma_{1}^{2}}{2}=\left[\begin{array}{llll} 0 & 0 & 0 & 0 \ 0 & 1 & 0 & 0 \ 0 & 0 & 1 & 0 \ 0 & 0 & 0 & 1 \end{array}\right] $$
3.逻辑表达式的a+b哈密顿量
加法对应的哈密顿量可以表示为变量对应的投影算符进行加操作,所以a加b的哈密顿量等于变量a和变量b取值对应的投影算符之和。
假设a,b只能在0和1中进行取值,可以看到总共存在4种情况,当a取0,b取0的时候值为0,a取0,b取1的时候值为1,a取1,b取0的时候值为1,a取1,b取1的时候值为2,如表4.5.10所示:
根据状态及其对应能量,对于这种状态比较少的情况,直接将它对应的哈密顿量写出,这个哈密顿量也有4个本征态,本征态对应的能量分别为0,1,1,2。
同样,可以各用 1 个比特来表示a和b的取值范围,如果用
那么,逻辑表达式a+b的哈密顿量形式为:
$$ \mathrm{H}{\mathrm{a}+\mathrm{b}}=\frac{\mathrm{I}+\sigma{0}^{2}}{2}+\frac{\mathrm{I}+\sigma_{1}^{z}}{2}=\left[\begin{array}{llll} 2 & 0 & 0 & 0 \ 0 & 1 & 0 & 0 \ 0 & 0 & 1 & 0 \ 0 & 0 & 0 & 0 \end{array}\right] $$
同理,用
那么,逻辑表达式a+b的哈密顿量形式为:
$$ \mathrm{H}{\mathrm{a}+\mathrm{b}}=\frac{\mathrm{I}-\sigma{0}^{z}}{2}+\frac{\mathrm{I}-\sigma_{1}^{z}}{2}=\left[\begin{array}{llll} 0 & 0 & 0 & 0 \ 0 & 1 & 0 & 0 \ 0 & 0 & 1 & 0 \ 0 & 0 & 0 & 2 \end{array}\right] $$
4.最大切割问题的哈密顿量
对杠铃图AB这条边,如果顶点A和顶点B分配到相同组,例如“0组”或“1组”,则AB这条边将不会被切割,对总的切割贡献为0;相反,如果将顶点A和B分配到不同组,假设将A分配到“0组”,将B分配到“1组”,或对调分配,则AB这条边将会被切割,对总的切割贡献为这条边的权重(例如AB这条边权重为1,则贡献为1)。
那么,如果将对应于最大切割的比特串(或比特串组),视为是使用哈密顿量编码的代价函数的基态。这个哈密顿量的形式,可以通过构造经典函数返回值来决定,如果被切割边所对应的两个节点跨越不同组,则返回1(或边的权重),否则返回0。哈密顿量的形式为:
如果顶点
$$ \mathrm{C}{\mathrm{ij}}=\frac{1}{2}\left(1-\mathrm{Z}{\mathrm{i}} \mathrm{Z}{\mathrm{j}}\right) \text { 其中 }\left{\begin{array}{l} \mathrm{Z}{\mathrm{i}}, \mathrm{Z}{\mathrm{j}} \in 0 \text { 组, } \mathrm{Z}{\mathrm{i}}, \mathrm{Z}{\mathrm{i}}=1 \ \mathrm{Z}{\mathrm{i}}, \mathrm{Z}{\mathrm{j}} \in 1 \text { 组, } \mathrm{Z}{\mathrm{i}}, \mathrm{Z}_{\mathrm{j}}=-1 \end{array}\right. $$
对于更复杂的图来说,最大切割值等于每条边切割贡献的总和:
对于杠铃图来说,它有 4 个分组,如果将它看作一个系统的话,表示这个系统有 4 个状态,并且每个状态都是孤立存在的,用狄拉克符号表示它的状态就是
用哈密顿量表示成如下的矩阵形式:
它的第一个和第四个状态能量为0,第二个和第三个状态能量为1。
对于这个简单的系统,可以发现,它其实对应着一个异或表达式,异或表达式的真值表如表4.5.13所示:
当变量a和变量b取不同值时,异或的结果为1,取相同值时,异或的结果为0。
a异或b也可以表示成两个逻辑与运算之和。
前面介绍过逻辑与相当于矩阵之间直积,非操作相当于跟单位矩阵之间做减法操作,加操作相当于矩阵之间直和。则a异或b的哈密顿量可以用这样的公式来推导:
对于复杂的最大切割系统,将其简化成杠铃图这样的简单系统的组合,其切割值就是各个简单系统切割值的和。对应这个复杂系统的最大切割问题,其哈密顿量表示如下:
$$ H_{\text {Maxcut }}=H_{1}+H_{2}+\cdots+H_{n}=\sum_{i j} \frac{1}{2}\left(I-\sigma_{i}^{z} \sigma_{j}^{z}\right) $$ 5.在QPanda中构造最大切割问题对应的哈密顿量
对于杠铃图来说,它对应的哈密顿量形式为
去掉这个表达式中的单位矩阵和常系数,它描述的其实是:
$$ \sigma_{i}^{z} \sigma_{j}^{z} $$ 之前介绍过在QPanda中用泡利算符类来描述泡利矩阵之间的关系。用$${ ^{\prime \prime} \text { Z0 Z1}^{\prime \prime} \text{} ,1}$$来构造杠铃图对应的泡利算符类。其实Z0Z1对应的就是泡利算符之间的乘积关系,系数1表示切割的权重。
对方形图来说,它的哈密顿量是各个简单系统哈密顿量之和。在QPanda中可以通过map的形式构造多个表达式,各表达式之间对应的关系是加操作。
1.绝热量子计算
绝热量子计算(Adiabatic quantum computation)是量子计算的一种形式,它依赖于绝热定理进行计算。 首先,对于一个特定的问题,找到一个(可能复杂的)哈密顿量,其基态描述了该问题的解决方案;然后,准备一个具有简单哈密顿量的系统并初始化为基态;最后,简单的哈密顿量绝热地演化为期望的复杂哈密顿量。 根据绝热定理,在演化结束时系统将处于期望的复杂哈密顿量的基态,因此最后系统的状态描述了问题的解决方案, 绝热量子计算已经被证明在线路模型中与传统的量子计算是多项式等价的。
绝热量子计算是通过以下过程解决特定问题和其他组合搜索问题。通常这种问题就是寻找一种状态满足
$$ \mathrm{H}{B}=H{B_{1}}+H_{B_{2}}+\cdots+H_{B_{M}} $$
这里
这里
令
这就是算法的绝热演化哈密顿量。
根据绝热定理,$t=0$ 时,初始哈密顿量为简单哈密顿量$$\mathrm{H}{B}$$,系统的初态制备成$$\mathrm{H}{B}$$的基态;经过绝热演化后,$t=T$ 时,哈密顿量为问题哈密顿量$$\mathrm{H}{P}$$,系统的状态为$$\mathrm{H}{P}$$的基态。
在这里
从哈密顿量的基态开始 $$\mathrm{H}{B}$$ ,首先,经历一个绝热过程,最后以问题哈密顿量的基态结束 $$\mathrm{H}{P} ;$$ 然后测量最终状态
2.初始哈密顿量
QAOA定义的初始哈密顿量
该哈密顿量的基态为:
3.QAOA量子线路
以最大切割问题为例,QAOA线路是以$$H_B$$为生成元的酉变换跟以$$H_P$$为生成元的酉变换乘积的累积。
$$ \mathrm{U}(\vec{\beta}, \vec{\gamma})=\prod_{\mathrm{i}=1}^{\mathrm{m}} \mathrm{U}\left(\mathrm{H}{\mathrm{B}}, \beta{\mathrm{i}}\right) \mathrm{U}\left(\mathrm{H}{\mathrm{P}}, \gamma{\mathrm{i}}\right) $$
其中
$$ \mathrm{U}\left(\mathrm{H}{\mathrm{B}}, \beta{\mathrm{i}}\right)=\mathrm{e}^{-\mathrm{iH}{\mathrm{B}} \beta{\mathrm{i}}} $$
$$ \mathrm{U}\left(\mathrm{H}{\mathrm{P}}, \gamma{\mathrm{i}}\right)=\mathrm{e}^{-\mathrm{iH}{P} \gamma{\mathrm{i}}} $$
那么,$H_{B}$的基态
$$ \left|\phi_{1}\right\rangle=|\vec{\beta}, \vec{\gamma}\rangle=U(\vec{\beta}, \vec{\gamma})\left|\phi_{0}\right\rangle $$ 4.量子逻辑门
对于以
同样,以
$$ \mathrm{H}{\mathrm{P}}=\sum{\mathrm{ij}} \frac{1}{2}\left(\mathrm{I}-\sigma_{\mathrm{i}}^{z} \sigma_{\mathrm{j}}^{z}\right) $$
$$ \begin{aligned} &\mathrm{U}\left(\mathrm{H}{\mathrm{P}}, \gamma{\mathrm{i}}\right)=\mathrm{e}^{-\mathrm{iH}{\mathrm{p}} \gamma{\mathrm{i}}}\ &=\mathrm{e}^{-\mathrm{i} \sum_{\mathrm{jk}} \frac{1}{2}\left(\mathrm{I}-\sigma_{j}^{2} \otimes \sigma_{j}^{2}\right) \gamma_{\mathrm{i}}}\ &=\prod_{\mathrm{jk}} \mathrm{e}^{-\mathrm{i} \frac{\gamma_{\mathrm{i}} \mathrm{I}}{2}} \cdot \prod_{\mathrm{jk}} \mathrm{e} \mathrm{e}^{\mathrm{i} \frac{\mathrm{y}{\mathrm{i}}}{2} \sigma{\mathrm{j}}^{2} \otimes \sigma_{\mathrm{j}}^{z}}\ &=\left[\begin{array}{cccc} 1 & 0 & 0 & 0 \ 0 & 1 & 0 & 0 \ 0 & 0 & 0 & 1 \ 0 & 0 & 1 & 0 \end{array}\right]\left[\begin{array}{cccc} \mathrm{i}^{\mathrm{i} \frac{\gamma_{1}}{2}} & 0 & 0 & 0 \ 0 & \mathrm{e}^{-\mathrm{i} \frac{\gamma_{1}}{2}} & 0 & 0 \ 0 & 0 & \mathrm{e}^{\mathrm{i} \frac{\gamma_{1}}{2}} & 0 \ 0 & 0 & 0 & \mathrm{e}^{-\mathrm{i}{2}{ }^{\gamma{1}}} \end{array}\right]\left[\begin{array}{llll} 1 & 0 & 0 & 0 \ 0 & 1 & 0 & 0 \ 0 & 0 & 0 & 1 \ 0 & 0 & 1 & 0 \end{array}\right]\ &\equiv \mathrm{CNOT}(\mathrm{j}, \mathrm{k}) \mathrm{RZ}\left(\mathrm{k},-\gamma_{\mathrm{i}}\right) \mathrm{CNOT}(\mathrm{j}, \mathrm{k}) \end{aligned} $$
对于图4.5.9所示的方形环图,假设当步长等于1时,求解其最大切割问题需要使用到4个量子比特。对于A,B,C,D这4个节点分别用0号比特,1号比特,2号比特和3号比特进行映射。
首先对所有量子比特作用一个H门, 制备该系统的初始状态。
然后进行以
执行完以
$$ |\beta, \gamma\rangle=\mathrm{U}\left(\beta_{1}, \gamma_{1}\right)\left|\phi_{0}\right\rangle=\mathrm{U}\left(\mathrm{H}{\mathrm{B}}, \beta{1}\right) \mathrm{U}\left(\mathrm{H}{\mathrm{P}}, \gamma{1}\right)\left|\phi_{0}\right\rangle $$
如图4.5.10,红色部分就是步数为1时对应的线路,当步数增加,相当于红色线路重复执行,只不过线路对应的参数会不同。
QAOA的工作流程:
第1步:制备线路的初始状态;
第2步:初始化待优化的参数β和γ,主要是用来确定RZ门和RX门的旋转角度(通常参数初始化为0);
第3步:根据参数生成量子线路;
第4步:测量量子状态计算每一个子项的期望;
第5步:计算当前参数对应的总的期望值;
第6步:将当前参数及其对应的期望值传入到经典优化器进行优化得到一组新的参数;
第7步:重复执行3-6步,一直到满足预先设定好的结束条件。
计算期望的表达式如下: $$ \text{Cost}(\vec{\beta}, \vec{\gamma})=\left\langle\phi_{0}\left|\mathrm{U}^{\dagger}(\vec{\beta}, \vec{\gamma}) \mathrm{H}{\mathrm{p}} \mathrm{U}(\vec{\beta}, \vec{\gamma})\right| \phi{0}\right\rangle $$
对于n对象的MAX-CUT问题,需要n个量子位来对结果进行编码,其中测量结果(二进制串)表示问题的切割配置。
通过 VQNet 可以有效地实现 MAX-CUT 问题的 QAOA 算法。 VQNet中QAOA的流程图如图4.5.13所示:
给定一个MAX-CUT的问题如图4.5.14所示:
首先,输入MAX-CUT问题的图形信息,以用来构造相应的问题哈密顿量。
problem = {'Z0 Z4':0.73,'Z0 Z5':0.33,'Z0 Z6':0.5,'Z1 Z4':0.69,'Z1 Z5':0.36,
'Z2 Z5':0.88,'Z2 Z6':0.58,'Z3 Z5':0.67,'Z3 Z6':0.43}
然后,使用哈密顿量和待优化的变量参数beta和gamma,构建QAOA 的VQC。 QOP 的输入参数是问题哈密顿量、VQC 、 一组量子比特和量子运行环境。QOP 的输出是问题哈密顿量的期望。 在这个问题中,损失函数是问题哈密顿量的期望,因此需要最小化 QOP 的输出。通过使用梯度下降优化器 MomentumOptimizer 来优化vqc中的变量beta和gamma。
1.#include <string.h>
2.#include <iostream>
3.#include "Components/Operator/PauliOperator.h"
4.#include "Components/Optimizer/AbstractOptimizer.h"
5.#include "QAlg/QAOA/QAOA.h"
6.
7.USING_QPANDA
8.
9.double myFunc(const std::string& key, const PauliOperator& pauli)
10.{
11. double sum = 0;
12.
13. QHamiltonian hamiltonian = pauli.toHamiltonian();
14.
15. for_each(hamiltonian.begin(),
16. hamiltonian.end(),
17. [&](const QHamiltonianItem& item)
18. {
19. std::vector<size_t> index_vec;
20. for (auto iter = item.first.begin();
21. iter != item.first.end();
22. iter++)
23. {
24. index_vec.push_back(iter->first);
25. }
26.
27. // double value = item.second;
28. size_t i = index_vec.front();
29. size_t j = index_vec.back();
30. if (key[i] != key[j])
31. {
32. sum += item.second;
33. }
34. });
35.
36. return sum;
37.}
38.
39.auto getHamiltonian()
40.{
41. //return PauliOperator({
42. // {"Z0 Z4", 0.73},{"Z2 Z5", 0.88},
43. // {"Z0 Z5", 0.33},{"Z2 Z6", 0.58},
44. // {"Z0 Z6", 0.50},{"Z3 Z5", 0.67},
45. // {"Z1 Z4", 0.69},{"Z3 Z6", 0.43},
46. // {"Z1 Z5", 0.36}
47. //});
48.
49. return PauliOperator(PauliOperator::PauliMap{ {
50. {"Z0 Z6", 0.49},
51. {"Z6 Z1", 0.59},
52. {"Z1 Z7", 0.44},
53. {"Z7 Z2", 0.56},
54. {"Z2 Z8", 0.63},
55. {"Z8 Z13", 0.36},
56. {"Z13 Z19", 0.81},
57. {"Z19 Z14", 0.29},
58. {"Z14 Z9", 0.52},
59. {"Z9 Z4", 0.43},
60. {"Z13 Z18", 0.72},
61. {"Z18 Z12", 0.40},
62. {"Z12 Z7", 0.60},
63. {"Z12 Z17", 0.71},
64. {"Z17 Z11", 0.50},
65. {"Z11 Z6", 0.64},
66. {"Z11 Z16", 0.57},
67. {"Z16 Z10", 0.41},
68. {"Z10 Z5", 0.23},
69. {"Z10 Z15", 0.40},
70. {"Z5 Z0", 0.18}} });
71.}
72.
73.int main()
74.{
75. QAOA qaoa;
76. auto hamiltonian = getHamiltonian();
77. qaoa.setHamiltonian(hamiltonian);
78. qaoa.setStep(3);
79. qaoa.setShots(1000);
80. qaoa.getOptimizer()->setDisp(true);
81. qaoa.regiestUserDefinedFunc(std::bind(&myFunc,
82. std::placeholders::_1,
83. hamiltonian));
84.
85. return qaoa.exec();
86.}
87.
将图4.5.15的测量结果绘制出柱状,如图4.5.16所示,可以看到‘0001111’和‘1110000’这两个比特串测量得到的概率最大,也正是这个问题的解。
随着量子化学理论的不断完善,计算化学已经成了化学工作者解释实验现象、预测实验结果、指导实验设计的重要工具,在药物的合成、催化剂的制备等方面有着广泛的应用。但是,面对计算化学所涉及的巨大计算量,经典计算机在计算精度、计算尺寸等方面显得能力有限,这就在一定程度上限制了计算化学的发展。而费曼曾提出:可以创造一个与已知物理系统条件相同的系统,让它以相同的规律演化,进而获得我们自己想要的信息。费曼的这一猜想提示我们——既然化学所研究的体系是量子体系,我们何不在量子计算机上对其进行模拟呢?
就目前的量子计算机发展水平而言,可以通过变分量子特征值求解算法(Variational-Quantum-Eigensolver,简称VQE),在量子计算机上实现化学模拟。该算法作为用于寻找一个较大矩阵H的特征值的量子与经典混合算法,不仅能保证量子态的相干性,其计算结果还能达到化学精度。
1.定义
计算化学,顾名思义,就是利用数学方法通过计算机程序对化学体系进行模拟计算,以解释或解决化学问题。它主要包括两个分支——量子化学与分子模拟。
2.作用
早期由于计算能力较弱,化学研究主要以理论和实验交互为主。但随着科学技术的蓬勃发展、量子化学理论的不断完善,计算已经成为一种独立的科研手段,与理论和实验形成三足鼎立之势,如图4.6.2。
而如今,计算化学对于化学工作者来说,已经成了解释实验现象、预测实验结果、指导实验设计的重要工具,在材料科学、纳米科学、生命科学等领域得到了广泛的应用。
(1)原子间库仑衰变现象的成功预测
早在1997年,赛德鲍姆(Cederbaum)等人通过计算和分析HF分子和水分子的电离谱,成功预测了2003年才发现的原子间库仑衰变现象(Interatomic Coulombic Decay,简称ICD)。
图4.6.3 HF分子和水分子的电离谱^[52]^
原子间库仑衰变现象是指对于组成团簇的原子(或分子),如果它的电子层中存在激发态的电子,就可能会把能量传递到相邻原子的价层电子,使得后者电离变成阳离子。
图4.6.4 原子间库仑衰变现象^[53]^
如图4.6.4中所示,左侧原子中的一个激发态电子,从高能级跃迁至低能级时,所释放出的能量被右侧原子的一个价层电子吸收,由于原子核对价层电子的束缚能力本来就比较弱,所以吸收了这部分能量的价层电子很容易逸出,使右侧原子变成阳离子。
如今,人们正在尝试将这种现象应用于DNA损伤修复领域。
(2)α突触核蛋白聚集过程的动力学模拟
2007年,齐格列尼等美国科研工作者利用超级计算中心的运算能力,对含有23万多个原子的体系进行动力学模拟,并结合生物化学分析和超微结构分析,首次揭示了α突触核蛋白的聚集过程及其在细胞膜表面形成致病孔状结构的复杂过程,这一成果为帕金森症的治疗提供了新的线索。
图4.6.6 动力学模拟^[54]^
量子化学,作为计算化学的主要研究方向之一,简单来说就是应用量子力学的规律和方法来研究化学问题。
量子化学的研究范围包括:分子的结构、性质及其结构与性能之间的关系;分子与分子之间的相互作用;分子之间的相互碰撞和相互反应等问题。P.O.Lowdin提出量子化学可以分为三个领域,包括基础理论、计算方法和应用研究,三者之间相互影响,构成了三角关系,只有严密的理论、精细的算法、深入的应用相结合,才能构成完美的量子化学。但是,要想真正通过计算模拟、运用基础理论去解决或解释化学问题,仅仅依靠精细的算法还是无法实现,必须还要借助于计算机的计算能力。可以说,量子化学的发展与计算机的发展息息相关。
由于在对一个$$N$$电子体系计算模拟时,往往需要解薛定谔方程,这就涉及到了$$3N$$维波函数的求解。 $$ \hat{H} \Psi=E \Psi $$
由此可知,计算量会随着研究体系的电子数的增加而呈指数式递增。
就目前而言,面对量子化学计算中所涉及到的如此惊人的计算量,经典计算机在计算精度、计算范围等方面十分有限。想要突破这一瓶颈,就必须依靠量子计算机强大的计算能力。因此,在量子计算机上实现量子化学模拟刻不容缓。
量子计算最引人注目的可能性之一是对其它量子系统的模拟。量子系统的量子模拟包含了广泛的任务,其中最重要的任务包括:
-
量子系统时间演化的模拟。
-
基态属性的计算。
当考虑相互作用的费米子系统时这些应用特别有用,例如分子和强相关材料;费米子系统的基态性质的计算是凝聚态哈密顿量的相图映射出来的起点,它也为量子化学中电子结构问题的关键问题即化学反应速率的计算提供了途径。
由于在化学应用中的相关性,尺寸相对适中的分子系统,它们被认为是早期量子计算机的理想测试平台。具体来讲,基态问题要求如下:
对于某些物理哈密顿量
而量子化学模拟是指,将真实化学体系的哈密顿量(包括其形式和参数)映射到由自己构建的可操作哈密顿量上,然后通过调制参数和演化时间,找到能够反映真实体系的本征态。
众所周知,即使在量子计算机上,这个问题通常也是难以处理的,这意味着不能指望一种有效的量子算法来准备一般哈密顿量的基态。尽管存在这种限制,但对于特定的哈密顿量而言考虑到相互作用的物理限制,有可能有效地解决上述问题。目前,至少存在四种不同的方法来解决这个问题:
-
量子相位估计:假设可以近似准备状态$$| \varphi_g \rangle$$,该例使用哈密顿量的受控来找到其最小的特征值。
-
量子力学的绝热定理:通过哈密顿量的缓慢演化,量子系统绝热地从一个普通哈密顿量的基态演化为期望目标问题的哈密顿量基态。
-
耗散(非酉矩阵)量子操作:目标系统的基态是个固定点,而非在量子硬件上实现耗散映射。
-
变分量子特征值求解算法:假设基态可以用包含相对较少参数的参数化表示。
为了使量子化学模拟在近期硬件设备上实现,我们采用变分量子特征值求解算法(Variational-Quantum-Eigensolver,简称VQE)来寻找体系的基态。该量子算法不仅能保证量子态的相干性,其计算结果还能达到化学精度。
该算法是用于寻找一个较大矩阵H的特征值的量子与经典混合算法,如图4.6.10所示:
图4.6.10 算法示意图^[55]^
量子化学计算包
常用的量子化学包有Gussian16、PyQuante、pySCF、Psi4等。
其中,Psi4是一款开源的从头算量子化学计算包,它能够运用Hartree-Fock方法、密度泛函理论、耦合团簇理论、组态相互作用方法等对电子结构进行计算。后续章节将利用Psi4中的Hartree-Fock方法计算得到的二次哈密顿量构造量子线路,寻找H2的基态。
1.二次量子化哈密顿算符 $$ \hat H \Psi=E \Psi \qquad (1) $$
$$ \hat{\mathrm{H}}{\mathrm{el}}=-\frac{1}{2} \sum{i=1}^{N} \nabla_{i}^{2}-\sum_{i=1}^{N_{\mathrm{el}}} \sum_{A=1}^{N_{\mathrm{nu}}} \frac{Z_{A}}{r_{i A}}+\sum_{i=1, j>i}^{N_{\mathrm{el}}, N_{\mathrm{el}}} \frac{1}{r_{i j}} \qquad(2) $$
在之前的章节中,我们曾提到描述
如图4.6.12所示,由于一次量子化哈密顿算符量映射到量子比特上需要耗费的量子比特数要比二次量子化哈密顿算符多,VQE算法选择了更为经济的二次量子化哈密顿算符。
所谓二次量子化哈密顿算符就是将波场函数转换为场算符,这一转换需要借助创造算子 $$\mathrm{a}{\mathrm{p}}^{\dagger}$$ 和湮灭算子 $$\mathrm{a}{\mathrm{q}}$$ 来实现,它们满足反对易关系,即:
$$ \left{a_{p}^{\dagger}, a_{q}\right}=\delta_{p q} $$ 最终可以得到如下形式的二次量子化哈密顿算符:
$$
\hat{\mathrm{H}}{P}=\sum{p q} h_{p q} a_{p}^{\dagger} a_{q}+\frac{1}{2} \sum_{p q r s} h_{p q r s} a_{p}^{\dagger} a_{q}^{\dagger} a_{r} a_{s}
$$
该式中,
计算公式如下:
$$
\begin{aligned}
&h_{p q}=\int d r x_{p}(\mathrm{r})^{}\left(-\frac{1}{2} \nabla^{2}-\sum_{\alpha} \frac{Z_{\alpha}}{\left|r_{\alpha}-r\right|}\right) x_{q}(r) \
&h_{p q r s}=\int d r_{1} d r_{2} \frac{1}{\left|r_{1}-r_{2}\right|} \cdot x_{p}\left(r_{1}\right)^{} x_{q}\left(r_{2}\right)^{*} x_{r}\left(r_{1}\right) x_{s}\left(r_{2}\right)
\end{aligned}
$$
从
2.H2分子的簇算符
众所周知,每个氢原子都有一个电子,填充在1s轨道上,如图4.6.13所示:
而氢分子由两个氢原子组成,那么它就含有两个电子。以q0、q1量子比特表示a号氢原子自旋向下和自旋向上的1s轨道,以q2、q3分别表示b号氢原子自旋向下和自旋向上的1s轨道,如图4.6.14所示。
假设氢分子的两个电子分别处在q0和q1这两个轨道上面,如图4.6.15所示:
如果以量子态$$|1 \rangle$$表示自旋轨道上有一个电子,以量子态$$|0 \rangle$$表示自旋轨道为空轨道,那么此时氢分子的状态可以表示为$$|0011 \rangle$$。
当q0上的电子跃迁到q2上时,也就是电子先从q0上“湮灭”,然后在q1上“创造”,如图4.6.16所示:
此时,其簇算符表示为 $$ \mathrm{\hat{T}}{1}=t{20} a_{2}^{\dagger} a_{0} $$ 同理,当q0上的电子跃迁到q3上时,其哈密顿量可以表示为
$$ \mathrm{\hat{T}}{2}=\mathrm{t}{30} a_{3}^{\dagger} a_{0} $$ 当q1上的电子跃迁到q2上时,其哈密顿量可以表示为
$$ \mathrm{\hat{T}}{3}=\mathrm{t}{21} a_{2}^{\dagger} a_{1} $$ 当q1上的电子跃迁到q3上时,其哈密顿量可以表示为
$$ \mathrm{\hat{T}}{4}=\mathrm{t}{31} a_{3}^{\dagger} a_{1} $$ 当q0、q1上的电子同时跃迁到q2、q3上时,其哈密顿量可以表示为
$$
\mathrm{\hat{T}}{5}=\mathrm{t}{3210}a_{3}^{\dagger} a_{2}^{\dagger} a_{0} a_{1}
$$
$\mathrm{\hat{T}}{1}$、$\mathrm{\hat{T}}{2}$、$\mathrm{\hat{T}}{3}$、$\mathrm{\hat{T}}{4}$ 所描述的电子跃迁形式为单激发形式,而T5则是双激发形式。对于氢分子而言,由前四项单激发所构造成的簇算符称之为CCS,而由单激发和双激发所共同构造成的簇算符被称之为CCSD,也就是这五项之和,即:
$$ \mathrm{\hat{T}}{U}=t{20} a_{2}^{\dagger} a_{0}+t_{30} a_{3}^{\dagger} a_{0}+t_{21} a_{2}^{\dagger} a_{1}+t_{31} a_{3}^{\dagger} a_{1}+t_{3210} a_{3}^{\dagger} a_{2}^{\dagger} a_{0} a_{1} $$
3.费米子算符类
假设,用字符串$$X$$来表示湮没算符,用字符串$$X+$$来表示创建算符,其中$$x$$表示电子轨道的序号,表达为:
例如,
在QPanda中实现示例
通过如下示例代码构建费米子算符类。使用C++构建方式:
1.#include "Operator/FermionOperator.h"
2.int main()
3.{
4. using namespace QPanda;
5. FermionOperator p1;
6. FermionOperator p2({ {“1+ 0”, 2},{“3+ 2+ 1 0”, 3} });
7. FermionOperator p3(“1+ 0”, 2);
8. FermionOperator p4(2); // FermionOperator p4(“”, 2);
9. FermionOperator p5(p2);
10.
11. return 0;
12.}
Python构建方式:
1.from pyqpanda import *
2.if __name__=="__main__":
3. p1 = FermionOperator()
4. p2 = FermionOperator({'1+ 0': 2,'3+ 2+ 1 0': 3})
5. p3 = FermionOperator('1+ 0', 2)
6. p4 = FermionOperator(2)
7. p5 = p2
构造一个空的费米子算符类P1,里面不包含任何创建和湮没算符及单位矩阵;也可以通过前面所述的规则,以字典序的形式构建多个表达式,例如P2;或者只构建一个表达式例如P3;还可以只构造一个电子之间排斥能项,例如P4;也可以通过已经构造好的费米子算符来构造它的一份副本例如P5。
费米子算符类支持常规的加、减、乘等运算操作。计算返回的结果还是一个费米子算符类。
假设定义了a和b两个费米子算符类,可以让费米子算符类之间进行加操作,减操作和乘操作。使用C++示例:
1.#include "Operator/FermionOperator.h"
2.using namespace QPanda;
3.int main()
4.{
5. FermionOperator a(“1+ 0", 2);
6. FermionOperator b(“3+ 2", 3);
7. auto plus = a + b;
8. auto minus = a - b;
9. auto muliply = a * b;
10.
11. return 0;
12.}
Python示例:
1.from pyqpanda import *
2.if __name__=="__main__":
3. a = FermionOperator('1+ 0', 2)
4. b = FermionOperator('3+ 2', 3)
5. plus = a + b
6. minus = a - b
7. multiply = a * b
费米子算符类同样也支持打印功能,可以直接将费米子算符类打印输出到屏幕上。C++打印输出方式:
1. std::cout << "a + b = " << plus << std::endl;
2. std::cout << "a - b = " << minus << std::endl;
3. std::cout << "a * b = " << multiply << std::endl;
Python打印输出方式:
1. print("a + b = {}".format(plus))
2. print("a - b = {}".format(minus))
3. print("a * b = {}".format(multiply))
通过使用上述示例代码, a+b,a-b和a*b的计算结果如下:
1.a + b = {
2.1+ 0 : 2.000000
3.3+ 2 : 3.000000
4.}
5.a - b = {
6.1+ 0 : 2.000000
7.3+ 2 : -3.000000
8.}
9.a * b = {
10.1+ 0 3+ 2 : 6.000000
11.}
还可以通过normal_ordered接口对费米子算符进行整理。在这个转换中规定张量因子从高到低进行排序,并且创建算符出现在湮没算符之前。
整理规则如下:对于相同数字,交换湮没和创建算符,等价于单位1减去正规序,如果同时存在两个创建或湮没算符,则该项表达式等于0;对于不同的数字,整理成正规序,需要将正规序的系数变为相反数。
normal_ordered接口的使用方式如示例代码所示。C++示例:
1.#include "Operator/FermionOperator.h"
2.using namespace QPanda;
3.int main()
4.{
5. FermionOperator a("0 1 3+ 2+", 2);
6. auto muliply = a.normal_ordered() ;
7. std::cout << “before = " << a << std::endl;
8. std::cout << “after = " << b << std::endl;
9. return 0;
10.}
Python示例:
1.from pyqpanda import *
2.if __name__=="__main__":
3. a = FermionOperator('0 1 3+ 2+', 2)
4. b = a.normal_ordered()
5. print("before = {}".format(a))
6. print("after = {}".format(b))
对于表达式“0 1 3+ 2+”,整理成正规序“3+ 2+ 1 0”,相当于不同数字交换了5次,系数变为了相反数。
1.before = {
2.0 1 3+ 2+ : 2.000000
3.}
4.after = {
5.3+ 2+ 1 0 : -2.000000
6.}
费米子算符类还提供了其它一些常用功能,例如:isEmpyt接口,用来判断是否是个空的费米子算符;toString接口返回费米子算符的字符串形式;data接口返回费米子算符内部为维护的数据
1.isEmpyt() // 判空
2.toString() // 返回字符串形式
3.data() // 返回内部维护的数据
C++示例:
1.#include "Operator/FermionOperator.h"
2.using namespace QPanda;
3.int main()
4.{
5. FermionOperator a(" 1+0", 2);
6.
7. auto data = a.data();
8.
9. std::cout << “isEmpty = “ << a.isEmpty() << std::endl;
10. std::cout << “stringValue = “ << a.toString() << std::endl;
11.
12. return 0;
13.}
Python示例:
1.from pyqpanda import *
2.if __name__=="__main__":
3.
4. a = FermionOperator('1+ 0', 2)
5.
6. print("isEmpty = {}".format(a.isEmpty()))
7. print("strValue = {}".format(a.toString()))
8. print("data = {}".format(a.data()))
计算结果为:
1.isEmpty = False
2.strValue = {
3.1+ 0 : 2.000000
4.}
5.data = [(([(1, True), (0, False)], '1+ 0'), (2+0j))]
4.可变费米子算符类和可变泡利算符类
费米算符类是一个模板类,如果用complex来构造该模板参数T,得到的就是费米子算符类;如果用complex_var类来构造模板参数T,得到的就是可变费米子算符类;同样泡利算符类也是一个模板类,选择不同的模板参数类型,可以得到泡利算符类和可变泡利算符类,如图4.6.18。
可变费米子算符类和可变泡利算符类,跟费米子算符类和泡利算符类拥有相同的接口,但是在构造它们的时候所传的参数是个Var变量。Var类是VQNet框架中的符号计算系统,在表达式不变的情况下,通过改变Var的值,即可改变表达式的值。因此可以通过构造可变费米子算符类和可变泡利算符类并利用VQNet框架,来实现VQE算法。
C++示例:
1.#include “Variational/VarFermionOperator.h"
2.#include “Variational/VarPauliOperator.h"
3.using namespace QPanda;
4.using namespace Variational;
5.int main()
6.{
7. var a(2, true) ;
8. var b(3, true) ;
9. VarFermionOperator fermion_op("1+ 0", a);
10. VarPauliOperator pauli_op("Z1 Z0", b);
11. return 0;
12.}
Python示例:
1.from pyqpanda import *
2.if __name__=="__main__":
3.
4. a = var(2, True)
5. b = var(3, True)
6. fermion_op = VarFermionOperator('1+ 0', a)
7. pauli_op = VarPauliOperator('Z1 Z0', b)
费米子算符类构造分子的哈密顿量示例介绍
通过向get_ccsd接口传入轨道个数(也就是比特数)、电子数和每一个子项的系数,即可构造所需的费米子哈密顿量。
1.def get_ccsd(qn, en, para):
2. if n_electron>n_qubit:
3. assert False
4. if n_electron==n_qubit:
5. return FermionOperator()
6. if get_ccsd_n_term(qn, en) != len(para):
7. assert False
8.
9. cnt = 0
10.
11. fermion_op = FermionOperator()
12. for i in range(en):
13. for ex in range(en, qn):
14. fermion_op += FermionOperator(str(ex) + "+ " + str(i), para[cnt])
15. cnt += 1
16.
17. for i in range(n_electron):
18. for j in range(i+1,n_electron):
19. for ex1 in range(n_electron,n_qubit):
20. for ex2 in range(ex1+1,n_qubit):
21. fermion_op += FermionOperator(
22. str(ex2)+"+ "+str(ex1)+"+ "+str(j)+" "+str(i),
23. para[cnt]
24. )
25. cnt += 1
26.
27. return fermion_op
在图4.6.19中,红色框起来的部分代码构造的是单电子激发的哈密顿量,绿色框起来的部分构造的是双电子激发的哈密顿量。构造CCS只需要包含红色框起来的部分即可,而构造CCSD需要包含红色和绿色框起来的代码。
在二次量子化哈密顿量的介绍中,曾提到过费米子算符遵循反对易关系,
1)JW变换
$$
\begin{aligned}
&a_{j}^{\dagger}=I^{\otimes n-j-1} \otimes Q^{+} \otimes Z_{j-1}^{\rightarrow} \qquad (3) \
&a_{j}=I^{\otimes n-j-1} \otimes Q^{-} \otimes Z_{j-1}^{\rightarrow} \qquad (4)
\end{aligned}
$$
将创造算子和湮灭算子分别表示成式 (3) 、式 (4) 这种形式, 其中,
$$
Z_{j-1}^{\rightarrow}=\sigma_{j-1}^{z} \otimes \sigma_{j-2}^{z} \otimes \ldots \otimes \sigma_{1}^{z} \otimes \sigma_{0}^{z}
$$
为了能够同
$$ \begin{aligned} &Q^{+}=|1\rangle \langle0|=\frac{1}{2}\left(\sigma^{x}-i \sigma^{y}\right) \ &Q^{-}=|0\rangle \langle1|=\frac{1}{2}\left(\sigma^{x}+i \sigma^{y}\right) \end{aligned} $$ JW需要消耗的逻辑门个数与量子比特数成线性关系。
可以通过QPanda或者pyQPanda来构造JW变换,这里给出的是Python代码示例, get_fermion_jordan_wigner接口返回的是费米子算符中的一个子项JW变换。
1.def get_fermion_jordan_wigner(fermion_item):
2. pauli = PauliOperator("", 1)
3. for i in fermion_item:
4. op_qubit = i[0]
5. op_str = ""
6. for j in range(op_qubit):
7. op_str += "Z" + str(j) + " "
8. op_str1 = op_str + "X" + str(op_qubit)
9. op_str2 = op_str + "Y" + str(op_qubit)
10.
11. pauli_map = {}
12. pauli_map[op_str1] = 0.5
13.
14. if i[1]:
15. pauli_map[op_str2] = -0.5j
16. else:
17. pauli_map[op_str2] = 0.5j
18.
19. pauli *= PauliOperator(pauli_map)
20.
21. return pauli
JordanWignerTransform是对整个费米子算符进行$$JW$$变换。
1.def JordanWignerTransform(fermion_op):
2. data = fermion_op.data()
3. pauli = PauliOperator()
4. for i in data:
5. pauli += get_fermion_jordan_wigner(i[0][0])*i[1]
6. return pauli
JordanWignerTransformVar是对可变费米子算符进行$$JW$$变换。
1.def JordanWignerTransformVar(var_fermion_op):
2. data = var_fermion_op.data()
3. var_pauli = VarPauliOperator()
4. for i in data:
5. one_pauli = get_fermion_jordan_wigner(i[0][0])
6. for j in one_pauli.data():
7. var_pauli += VarPauliOperator(j[0][1], complex_var(
8. i[1].real()*j[1].real-i[1].imag()*j[1].imag,
9. i[1].real()*j[1].imag+i[1].imag()*j[1].real))
10.
11. return var_pauli
2)Parity变换:
$$
\begin{aligned}a_{j}^{\dagger} & \equiv \frac{1}{2} X_{j+1}^{\leftarrow} \otimes\left(\sigma_{j}^{x} \otimes \sigma_{j-1}^{z}-i \sigma_{j}^{y}\right) \a_{j} & \equiv \frac{1}{2} X_{j+1}^{\leftarrow} \otimes\left(\sigma_{j}^{x} \otimes \sigma_{j-1}^{z}+i \sigma_{j}^{y}\right)\end{aligned}
$$
$$ X_{j+1}^{\leftarrow}=\sigma_{n-1}^{x} \otimes \sigma_{n-2}^{x} \otimes \ldots \otimes \sigma_{j+2}^{x} \otimes \sigma_{j+1}^{x} $$ 通过比较JW变换和Parity变换,可以发现,由于JW变换中存在宇称算子,而Parity变换中存在更新算子,所以Parity变换所需要消耗的逻辑门数也是与量子比特数成线性关系。
B-K 变换
从B-K变换的表达式不难发现,它要比JW变换和Parity变换复杂的多,因为它既有更新算子$$X_U$$,又有宇称算子$$Z_P$$,只不过在这里,更新算子不再是$$(\mathrm{n}-\mathrm{j}-1)$$ 个泡利X的张量积,宇称算子也不再是j个泡利Z的张量积。最后一项$$Z_{P(j)}$$在j为偶数时,就等于宇称算子,在j为奇数时,就等于$$Z_{R(j)}$$。$$R(j)$$被称之为余子集,它等于
$$
R(j)=P(j) % F(j)
$$
假设$$j=3$$, 若$$U(3)=(q5,q6)$$, $$ P(3)=(q0,q2)$$,
$$ X_{U(3)}=\sigma_{6}^{x} \otimes \sigma_{5}^{x}, \quad Z_{P(3)}=\sigma_{2}^{z} \sigma_{0}^{z} $$ 而余子集:
$$ \mathrm{R}(3)=P(3) % F(3)=(\mathrm{q} 2) $$ 由此:
虽然,B-K变换相对于JW变换和Parity变换的在形式上很复杂,但实际上它所需要消耗的量子逻辑门数是与量子比特数的对数成线性关系,可以节省量子逻辑门资源,简化量子线路。
对于一个
所谓变分原理,是指对于任意一个试验态 (它是一个品优波函数),用某一体系 (如多电子体系) 的哈密顿量作用于它时,可以得到该体系在这一状态下的平均能量
$$
\mathrm{E}=\frac{\left\langle\psi^{}|\mathrm{\hat{H}}| \psi\right\rangle}{\left\langle\psi^{} \mid \psi\right\rangle} \geq \mathrm{E}{0}
$$
从该表达式中不难看出,如果所选择的试验态 $$|\psi\rangle$$ 正好就是体系的基态 $$\left|\psi{0}\right\rangle$$, 那么不等式中的等号成立,直接得到了体系的基态能量
通过对变分原理的介绍,可以发现:VQE算法在寻找体系基态能量时,实际上需要依次完成三步操作:
(1) 制备试验态
(2) 测量试验态
(3) 判断$$\mathrm{E}n- \mathrm{E}{n-1}$$ 是否小于所设定的阈值,是,就返回$$\mathrm{E}_n$$作为基态能量; 否,则用优化器优化生成一组新参数 重
如图4.6.20所示,这是一个循环迭代求解基态能量的过程。其中,步骤(1)和(2)是在量子处理器上完成的,步骤(3)是在经典处理器上完成的。在构造量子线路以制备试验态时, VQE算法利用了幺正耦合簇理论(UCC理论)和渐进近似定理;在试验态能量进行测量时,VQE采用的是量子期望估计方法;在对参数
1.初始化Hartree-Fock态
对于含有四个自旋轨道的氢分子来说,其双电子的Hartree-Fock态是用量子态在量子线路上,是用量子态$$|0011\rangle$$来表示的,即一个量子比特代表一个自旋分子轨道,$$|0\rangle$$表示空轨道,$$|1\rangle$$表示占据轨道。对于$$|0011\rangle$$的量子态,只要在q0和q1上分别加上一个$$NOT$$门,就可以在量子线路中将$$|0000\rangle$$初始化成$$|0011\rangle$$ ,如图4.6.21所示。 $$ H_{2}:|\varphi\rangle_{\text {Hartree-Fock }}=|0011\rangle $$
事实上,对于任意一个含有M个自旋分子轨道的N电子体系,它的Hartree-Fock态都可以这样简单的表示,如图4.6.22所示,只要在量子线路中给定M个量子比特,然后在前N个量子线路上加上NOT门即可得到所需要的N电子体系的Hartree-Fock态。
在QPanda或pyQPanda中用如下示例代码来初始化Hartree-Fock态:
1.def prepareInitialState(qlist, en):
2. circuit = QCircuit()
3. if len(qlist) < en:
4. return circuit
5.
6. for i in range(en):
7. circuit.insert(X(qlist[i]))
8.
9. return circuit
2、耦合簇法(Coupled Cluster, CC),
它是一种从Hartree-Fock分子轨道
拟设中的
$$ \mathrm{\hat{T}}=\mathrm{\hat{T}}{1}+\mathrm{\hat{T}}{2}+\mathrm{\hat{T}}{3}+\mathrm{\hat{T}}{4}+\mathrm{\hat{T}}{5}+\cdots+\mathrm{\hat{T}}{N} $$
其中
$$ \mathrm{\hat{T}}=\mathrm{\hat{T}}{1}+\mathrm{\hat{T}}{2} $$
其中:
$$ \begin{aligned} &\mathrm{\hat{T}}{1}=\sum{pq} t_{pq} a_{p}^{\dagger} a_{q} \ &\mathrm{\hat{T}}{2}=\sum{pq r s} t_{p q r s} a_{p}^{\dagger} a_{q}^{\dagger} a_{r} a_{s} \end{aligned} $$
这里的待定系数 $\mathrm{t}{\mathrm{pq}}$、 $\mathrm{t}{\mathrm{p q r s}}$ 就是需要通过优化器来寻找的参数
$$ \vec{\mathrm{t}}= {\mathrm{t}{\mathrm{pq}}, \mathrm{t}{\text {pqrs }} } $$
对于描述氢分子的
$$ \begin{aligned} &\mathrm{\hat{T}}=t_{20} a_{2}^{\dagger} a_{0}+t_{30} a_{3}^{\dagger} a_{0}+t_{21} a_{2}^{\dagger} a_{1}+t_{31} a_{3}^{\dagger} a_{1}+t_{3210} a_{3}^{\dagger} a_{2}^{\dagger} a_{1} a_{0} \ &\mathrm{\hat{T}}{1}=t{20} a_{2}^{\dagger} a_{0}+t_{30} a_{3}^{\dagger} a_{0}+t_{21} a_{2}^{\dagger} a_{1}+t_{31} a_{3}^{\dagger} a_{1} \ &\mathrm{\hat{T}}{2}=t{3210} a_{3}^{\dagger} a_{2}^{\dagger} a_{1} a_{0} \end{aligned} $$
当
3.么正耦合簇法(UCC)
但是,想要将
首先,定义一个等效的厄米算符
然后,以
若 UCC 中的簇算符
4.渐进近似定理
费米子哈密顿量通过 JW 变换变成泡利算符形式时,它是若干子项的和,表达式为:
$$
\mathrm{\hat{H}}(\vec{t})=\mathrm{\hat{H}}(\vec{h})=\sum h_{\alpha}^{i} \sigma_{\alpha}^{i}+\sum h_{\alpha \beta}^{i j} \sigma_{\alpha}^{i} \otimes \sigma_{\beta}^{j}+\ldots
$$
其中
但是,如果对这些子项进行求和,最后得到的泡利算符形式哈密顿量想要对角化以生成酉算符,是比较困难的。
那么,假设令 $\mathrm{\hat{H}}{k}\left(h{a \beta}^{i j}\right)=\frac{h_{a}^{i} \sigma_{\alpha}^{j}}{h_{a}^{\alpha} \sigma_{\alpha}} / \sigma_{\alpha}^{i} \otimes \sigma_{\beta}^{j} / \cdots$ ,是不是就可以将该哈密顿量分解成有限个酉算符
答案是不可以,这是因为一般情况下,子项
$$ \left[\mathrm{\hat{H}}{k}, \mathrm{\hat{H}}{l}\right] \neq 0 $$
这推导过程中:
$$ \exp \left[-i \sum \mathrm{\hat{H}}{k} \left( \ h{\alpha \beta . .}^{i j ..}\right)\right] \neq \prod \exp \left[-i\mathrm{\hat{H}}{k}\left(h{\alpha \beta . .}^{i j ..}\right)\right]. $$
为了能够以每个子项
其中,
那么,当
$$ \exp \left[-i \sum \mathrm{\hat{H}}{k}\left(h{\alpha \beta . .}^{i j ..}\right)\right]=\left{\prod\left[\exp \left[-\mathrm{i\mathrm{\hat{H}}}{k}\left(\theta{\alpha \beta . .}^{i j ..}\right)\right]\right}^{n}\right. $$
这就提供了一个分解 UCC 算符构造量子线路的新思路,如图 4.6.23 所示,可以通过三次演化逐步逼近最终得到试验态,这样的话每一次演化都是通过有限个西算符 $\mathrm{U}k$ 所构造的量子线 $\mathrm{U}{k}\left(\Pi U_{k}\right)$。此时,$\mathrm{U}k$ 的生成元为:$\mathrm{\hat{H}}{k}\left(\theta_{\alpha \beta ..}^{\mathfrak{ij} ..}\right)$,其中
5.哈密顿量模拟
假设 JW 变换后的泡利算符型哈密顿量
$$ \mathrm{\hat{H}}=\mathrm{\hat{H}}{1}+\mathrm{\hat{H}}{2}+\mathrm{\hat{H}}{3}+\mathrm{\hat{H}}{4}+\mathrm{\hat{H}}{5}=\sigma{z}^{0}+\sigma_{z}^{0} \otimes \sigma_{z}^{1}+\sigma_{z}^{0} \otimes \sigma_{z}^{1} \otimes \sigma_{z}^{2}+\sigma_{x}^{0} \otimes \sigma_{z}^{1}+\sigma_{y}^{0} \otimes \sigma_{z}^{1} $$
接下来将以它为例具体介绍如何构造量子线路来模拟哈密顿量。
根据渐进近似定理,可以逐项模拟。先对
$$ \mathrm{U}{1}\left(\mathrm{\hat{H}}{1}, \mathrm{\theta}{1}\right)=e^{-i \sigma{z}^{0} \theta_{1}}=\left[\begin{array}{cc} e^{-i \theta_{1}} & 0 \ 0 & e^{i \theta_{1}} \end{array}\right]=\mathrm{RZ}\left(0,2 \theta_{1}\right) $$
通过推导,不难得出在 q0上直接加
图4.6.24 模拟H1项(此时$RZ$门表达式为2$\theta_{1}$)
对于$$\mathrm{H}_2$$项进行模拟:
$$ \begin{aligned} &\mathrm{U}{2}\left(\mathrm{\hat{H}}{2}, \theta_{2}\right)=e^{-i \sigma_{z}^{0} \otimes \sigma_{z}^{1} \theta_{2}}=\left[\begin{array}{cccc} e^{-i \theta_{2}} & 0 & 0 & 0 \ 0 & e^{i \theta_{2}} & 0 & 0 \ 0 & 0 & e^{i \theta_{2}} & 0 \ 0 & 0 & 0 & e^{-i \theta_{2}} \end{array}\right] \ &=\left[\begin{array}{cccc} 1 & 0 & 0 & 0 \ 0 & 1 & 0 & 0 \ 0 & 0 & 0 & 1 \ 0 & 0 & 1 & 0 \end{array}\right]\left[\begin{array}{cccc} e^{i \theta_{2}} & 0 & 0 & 0 \ 0 & e^{-i \theta_{2}} & 0 & 0 \ 0 & 0 & e^{i \theta_{2}} & 0 \ 0 & 0 & 0 & e^{-i \theta_{2}} \end{array}\right]\left[\begin{array}{llll} 1 & 0 & 0 & 0 \ 0 & 1 & 0 & 0 \ 0 & 0 & 0 & 1 \ 0 & 0 & 1 & 0 \end{array}\right] \ &=\text{CNOT}(0,1) \ \text{RZ}\left(1,2 \theta_{2}\right) \ \text{CNOT}(0,1) \end{aligned} $$
通过推导可以发现,想要模拟 $\mathrm{\hat{H}}2$ 项,需以 q0为控制比特以 q1 为目标比特加上 $\mathrm{CNOT}$ 门,然后在 q1 上加上 $\mathrm{RZ}(2 \theta{2})$ 门,接着再以 q0比特为控制比特以 q1为目标比特加上
图4.6.25 模拟H2项(此时$RZ$门表达式为2$\theta_{2}$)
对于
$$ \mathrm{U}{3}\left(\mathrm{\hat{H}}{3}, \theta_{3}\right)=e^{-i \sigma_{z}^{0} \otimes \sigma_{z}^{1} \otimes \sigma_{z}^{2} \theta_{3}}=\text{CNOT}(0,2) \text{CNOT}(1,2) \mathrm{RZ}\left(2,2 \theta_{3}\right) \text{CNOT}(1,2) \text{CNOT}(0,2) $$
通过推导,可知模拟 $\mathrm{\hat{H}}3$ 项,需要先依次以 q0为控制比特 q2 为目标比特、q1 为控制比特 q2 为目标比特依次添加上两个 $\mathrm{CNOT}$ 门,然后在 q2上加上 $\mathrm{RZ}(2 \theta{3})$ 门,接着再依次以 q0 为控制比特 q2 为目标比特、q1 为控制比特 q2比特为目标比特依次添加上两个
图4.6.26 模拟H3项 (此时$RZ$门表达式为2$\theta_{3}$)
对于 $\mathrm{\hat{H}}4$ 项,因为 $\sigma{x}$ 不是对角阵,所以需要将它先对角化,然后再进行推导 $$ \begin{aligned} \mathrm{U}{4}\left(\mathrm{\hat{H}}{4}, \theta_{4}\right) &=e^{-{i} \sigma_{x}^{0} \otimes \sigma_{z}^{1} \theta_{4}}=e^{-i\left(H_{0} \sigma_{z}^{0} H_{0}\right) \otimes \sigma_{z}^{1} \theta_{4}} \ &=H_{0} e^{-i \sigma_{\sigma}^{0} \otimes \sigma_{z}^{1} \theta_{4}} H_{0}=H(0) \text{CNOT}(0,1) R Z\left(1,2 \theta_{4}\right) \text{CNOT}(0,1) H(0) \end{aligned} $$
通过推导可以发现模拟 $\mathrm{\hat{H}}4$ 项,需先在 q0上加上 Hadamard 门;再以 q0比特为控制比特,以 q1为目标比特加上 $\mathrm{CNOT}$;接着在 q1 上加上 $\mathrm{RZ}(2 \theta{4})$ 门;然后再以 q0比特为控制比特以 q1为目标比特加上
图4.6.27 模拟H4项(此时$RZ$门表达式为2$\theta_{4}$)
对于 $\mathrm{\hat{H}}5$ 项,因为 $\sigma{y}$ 也不是对角阵,所以需要对它进行对角化处理,类比
$$ \begin{aligned} &\mathrm{U}{5}\left(\mathrm{\hat{H}}{5}, \theta_{5}\right)=e^{-i \sigma_{y}^{0} \otimes \sigma_{z}^{1} \theta_{5}}=e^{-i\left(R X_{0}\left(\frac{\pi}{2}\right) \sigma_{g}^{0} R X_{0}\left(-\frac{\pi}{2}\right)\right) \otimes \sigma_{z}^{1} \theta_{5}} \ & =R X_{0}\left(\frac{\pi}{2}\right) e^{-i \sigma_{z}^{0} \otimes \sigma_{z}^{1} \theta_{4}} R X_{0}\left(-\frac{\pi}{2}\right) \ &=R X\left(0, \frac{\pi}{2}\right) \text{CNOT}(0,1) R Z\left(1,2 \theta_{4}\right) \text{CNOT}(0,1) R X\left(0,-\frac{\pi}{2}\right) \end{aligned} $$
因此要想模拟 $\mathrm{\hat{H}}5$ 项,需先在 q0上先加上 $\text{RX}\left(\frac{\pi}{2}\right)$ 门;再以 q0比特为控制比特以 q1为目标比特加上 $\mathrm{CNOT}$;接着在 q1上加上 $\mathrm{RZ}(2 \theta{5})$ 门;然后再以 q0 比特为控制比特,以 q1为目标比特加上 5项(此时$RZ$门表达式为2$\theta_{5}$)
那么,最终模拟
图4.6.29 模拟H的量子线路构造
可以通过QPanda来实现上述的哈密顿量模拟算法,哈密顿量模拟的Python示例代码:
1.def simulate_hamiltonian(qubit_list,pauli,t,slices=3):
2. '''
3. Simulate a general case of hamiltonian by Trotter-Suzuki
4. approximation. U=exp(-iHt)=(exp(-i H1 t/n)*exp(-i H2 t/n))^n
5. '''
6. circuit = QCircuit()
7.
8. for i in range(slices):
9. for op in pauli.data():
10. term = op[0][0]
11. circuit.insert(
12. simulate_one_term(
13. qubit_list,
14. term, op[1].real,
15. t/slices
16. )
17. )
18.
19. return circuit
simulate_hamiltonian接口需要传入的参数是哈密顿量相关联的一组量子比特,哈密顿量泡利算符,演化时间t和切片数。其中哈密顿量泡利算符、演化时间和切片数,分别对应特罗特公式中的$\mathrm{\hat{H}}$、$t$和$n$。
simulate_one_term接口是对哈密顿量的一个子项进行哈密顿量模拟,传入的参数分别为一组量子比特,哈密顿量子项,哈密顿量子项对应的系数以及演化时间。
1.def simulate_one_term(qubit_list, hamiltonian_term, coef, t):
2. '''
3. Simulate a single term of Hamilonian like "X0 Y1 Z2" with
4. coefficient and time. U=exp(-it*coef*H)
5. '''
6. circuit =QCircuit()
7.
8. if not hamiltonian_term:
9. return circuit
10.
11. transform=QCircuit()
12. tmp_qlist = []
13. for q, term in hamiltonian_term.items():
14. if term is 'X':
15. transform.insert(H(qubit_list[q]))
16. elif term is 'Y':
17. transform.insert(RX(qubit_list[q],pi/2))
18.
19. tmp_qlist.append(qubit_list[q])
20.
21. circuit.insert(transform)
22.
23. size = len(tmp_qlist)
24. if size == 1:
25. circuit.insert(RZ(tmp_qlist[0], 2*coef*t))
26. elif size > 1:
27. for i in range(size - 1):
28. circuit.insert(CNOT(tmp_qlist[i], tmp_qlist[size - 1]))
29. circuit.insert(RZ(tmp_qlist[size-1], 2*coef*t))
30. for i in range(size - 1):
31. circuit.insert(CNOT(tmp_qlist[i], tmp_qlist[size - 1]))
32.
33. circuit.insert(transform.dagger())
34.
35. return circuit
6.量子期望估计算法
VQE 算法在制备出试验态
在VQE算法中,每个子项期望的测量是在量子处理器上进行的,而经典处理器则负责对各个期望进行求和(如图4.6.30)。
假设某一个体系的哈密顿量为$\mathrm{\hat{H}}$,它最终可以展开成这种形式:
$$ \mathrm{\hat{H}}{P}=\mathrm{\hat{H}}{1}+\mathrm{\hat{H}}{2}+\mathrm{\hat{H}}{3}=I^{\otimes 2}+\sigma_{z}^{0} \otimes \sigma_{z}^{1}+\sigma_{x}^{0} \otimes \sigma_{y}^{1} $$
在该式中,所有子项系数
其中,$$a^2$$、$$b^2$$、$$c^2$$、$$d^2$$分别是指测量试验态时,坍塌到$$|00 \rangle$$、$$|01\rangle$$、$$|10\rangle$$、$$|11\rangle$$的概率$$P_s$$,将哈密顿量的各个子项
$$ \begin{aligned} &\mathrm{E}(1)=\left\langle\psi^{}\left|\mathrm{\hat{H}}_{1}\right| \psi\right\rangle \ &\mathrm{E}(2)=\left\langle\psi^{}\left|\mathrm{\hat{H}}{2}\right| \psi\right\rangle \ &\mathrm{E}(3)=\left\langle\psi^{*}\left|\mathrm{\hat{H}}{3}\right| \psi\right\rangle \end{aligned} $$
下面将以$$\mathrm{E}(1)$$、$$\mathrm{E}(2)$$、$$\mathrm{E}(3)$$为例,详细介绍VQE算法是如何构造线路来测量各项期望进而计算出平均能量$$E$$的。
对于期望$$\mathrm{E}(1)$$,系数
对于期望$$\mathrm{E}(2)$$,其哈密顿量为
由于测量操作是在$$\sigma_{z}$$上(以$$\sigma_{z}$$的特征向量为基向量所构成的子空间)进行的,所以只需要在0号量子比特和1号量子比特上加上测量门即可,然后将测量结果传递给经典处理器求和,如图4.6.31所示。
具体测量过程是这样的,因为两个
根据线性代数知识可知其特征值就是对角线上的元素
事实上,对于任何作为基底的量子态的特征值都有这样“奇负偶正"的规律。这样的话,一将测量门加到相应的量子线路,试验态就会以一定概率坍塌到不同的量子态
即,如果量子态中有奇数个 1 ,其概率
对于$$\mathrm{E}(3)$$项,其哈密顿量:
此时,不能直接测量。这是因为对于试验态中的每一个基底(如
所以在测量前,需要在
此时,试验态
之后再利用“奇负偶正"这一规律进行测量, 不难得到:
$$ E(3)=\left\langle\psi^{}\left|\sigma_{x}^{0} \otimes \sigma_{y}^{1}\right| \psi\right\rangle=\left\langle\psi^{'}\left|\sigma_{z}^{0} \otimes \sigma_{z}^{1}\right| \psi^{\prime}\right\rangle=A^{2}-B^{2}-C^{2}+D^{2} $$
在CPU上对这三个期望求和,就得到了平均能量
同样,也可以利用QPanda来实现量子期望估计算法,在VQE算法演示中, 需要向优化器注册一个计算损失值的函数。
这里定义损失值为计算体系哈密顿量在试验态下的期望值,定义损失函数 loss_func,传入的参数为待优化的参数列表,量子比特个数,电子个数,体系哈密顿量。
1.def loss_func(para_list, qubit_number, electron_number, Hamiltonian):
2. '''
3. <𝜓^∗|𝐻|𝜓>, Calculation system expectation of Hamiltonian in experimental state.
4. para_list: parameters to be optimized
5. qubit_number: qubit number
6. electron_number: electron number
7. Hamiltonian: System Hamiltonian
8. '''
9. fermion_cc =get_ccsd(qubit_number, electron_number, para_list)
10. pauli_cc = JordanWignerTransform(fermion_cc)
11. ucc = cc_to_ucc_hamiltonian(pauli_cc)
12. expectation=0
13. for component in Hamiltonian:
14. expectation+=get_expectation(qubit_number, electron_number, ucc, component)
15. expectation=float(expectation.real)
16.
17. return ("", expectation)
get_expectation接口用来计算体系哈密顿量的子项在试验态下的期望。这个接口需要传入的参数为量子比特数,电子个数,通过UCC模型构造的哈密顿量,体系哈密顿量的一个子项。
程序大体流程是首先创建一个虚拟机,从虚拟机申请量子比特,接着构建量子线路,先制备初态,再构造UCC哈密顿量的模拟线路,并在测量前对体系哈密顿量子项进行换基操作,最后进行测量,并根据测量结果通过奇负偶正规则,计算当前体系哈密顿量子项的期望。
1.def get_expectation(n_qubit, n_en, ucc, component):
2. '''
3. get expectation of one hamiltonian.
4. n_qubit: qubit number
5. n_en: electron number
6. ucc: unitary coupled cluster operator
7. component: paulioperator and coefficient,e.g. ('X0 Y1 Z2',0.2)
8. '''
9.
10. machine=init_quantum_machine(QMachineType.CPU)
11. q = machine.qAlloc_many(n_qubit)
12. prog=QProg()
13.
14. prog.insert(prepareInitialState(q, n_en))
15. prog.insert(simulate_hamiltonian(q, ucc, 1.0, 4))
16.
17. for i, j in component[0].items():
18. if j=='X':
19. prog.insert(H(q[i]))
20. elif j=='Y':
21. prog.insert(RX(q[i],pi/2))
22.
23. machine.directly_run(prog)
24. result=machine.get_prob_dict(q, select_max=-1)
25. machine.qFree_all(q)
26.
27. expectation=0
28. #奇负偶正
29. for i in result:
30. if parity_check(i, component[0]):
31. expectation-=result[i]
32. else:
33. expectation+=result[i]
34. return expectation*component[1]
其中,奇偶校验代码如下:
1.def parity_check(number, terms):
2. check=0
3. number=number[::-1]
4.
5. for i in terms:
6. if number[i]=='1':
7. check+=1
8.
9. return check%2
7、利用VQE算法寻找基态能量
以氢分子基态的寻找为例,介绍VQE算法寻找基态能量的整个流程。
首先,进行初始化,即在q0上和q1上分别加上
然后将Hartree-Fock态制备成试验态
现在,以第一个酉算子为例,介绍氢分子试验态的制备过程。类比之前哈密顿量模拟时的推导过程,可以得到以下推导:
根据推导结果,构造出的量子线路为:
再将剩下的哈密顿量子项也映射到量子比特,将费米子哈密顿量Hu模拟出来,然后利用这条线路对Hartree-fock态循环演化三次,就制备出了试验态$$|\psi\rangle$$。
下面,开始对试验态$$|\psi\rangle$$进行测量,而测量线路则是通过JW变换等方法 将
在测量期望时,所运用的方法是量子期望估计算法,即首先分别构造整个氢分子哈密顿量$$H_P$$的15个子项的测量线路,测得各个子项的期望
将各个子项期望测量线路展开,即可得到各个子项期望
接着,量子处理器将
最后,
使用梯度下降优化器进行演示
首先,开始准备工作,导入所需要的库:
1.from pyqpanda import *
2.from psi4_wrapper import *
3.import numpy as np
4.from functools import partial
5.from math import pi
6.import matplotlib.pyplot as plt
使用get_ccsd_n_term接口的作用是返回构造CCSD模型需要用到的参数个数:
1.def get_ccsd_n_term(qn, en):
2. '''
3. coupled cluster single and double model.
4. e.g. 4 qubits, 2 electrons
5. then 0 and 1 are occupied,just consider 0->2,0->3,1->2,1->3,01->23
6. '''
7.
8. if n_electron>n_qubit:
9. assert False
10.
11. return int((qn - en) * en + (qn - en)* (qn -en - 1) * en * (en - 1) / 4)
使用get_ccsd_var接口则是用来构造可变参数对应的CCSD模型哈密顿量,代码实
现跟get_ccsd接口一样,只不过这里用到的费米子算符类是可变费米子算符类获取CCSD模型的参数个数构造可变参数的CCSD模型的哈密顿量:
1.def get_ccsd_var(qn, en, para):
2. '''
3. get Coupled cluster single and double model with variational parameters.
4. e.g. 4 qubits, 2 electrons
5. then 0 and 1 are occupied,just consider 0->2,0->3,1->2,1->3,01->23.
6. returned FermionOperator like this:
7. { {"2+ 0":var[0]},{"3+ 0":var[1]},{"2+ 1":var[2]},{"3+ 1":var[3]},
8. {"3+ 2+ 1 0":var[4]} }
9.
10. '''
11. if en > qn:
12. assert False
13. if en == qn:
14. return VarFermionOperator()
15.
16. if get_ccsd_n_term(qn, en) != len(para):
17. assert False
18.
19. cnt = 0
20. var_fermion_op = VarFermionOperator()
21. for i in range(en):
22. for ex in range(en, qn):
23. var_fermion_op += VarFermionOperator(str(ex) + "+ " + str(i), para[cnt])
24. cnt += 1
25.
26. return var_fermion_op
27.
28. for i in range(en):
29. for j in range(i+1, en):
30. for ex1 in range(en, qn):
31. for ex2 in range(ex1+1, qn):
32. fermion_op += VarFermionOperator(
33. str(ex2)+"+ "+str(ex1)+"+ "+str(j)+" "+str(i),
34. para[cnt]
35. )
36. cnt += 1
37.
38. return fermion_op
get_fermion_jordan_wigner接口则是将费米子哈密顿量的子项转换成泡利哈密顿量:
1.def get_fermion_jordan_wigner(fermion_item):
2. pauli = PauliOperator("", 1)
3.
4. for i in fermion_item:
5. op_qubit = i[0]
6. op_str = ""
7. for j in range(op_qubit):
8. op_str += "Z" + str(j) + " "
9.
10. op_str1 = op_str + "X" + str(op_qubit)
11. op_str2 = op_str + "Y" + str(op_qubit)
12.
13. pauli_map = {}
14. pauli_map[op_str1] = 0.5
15.
16. if i[1]:
17. pauli_map[op_str2] = -0.5j
18. else:
19. pauli_map[op_str2] = 0.5j
20.
21. pauli *= PauliOperator(pauli_map)
22.
23. return pauli
JordanWignerTransformVar接口的作用是将可变费米子哈密顿量转换成可变泡利哈密顿量:
1.def JordanWignerTransformVar(var_fermion_op):
2. data = var_fermion_op.data()
3. var_pauli = VarPauliOperator()
4. for i in data:
5. one_pauli = get_fermion_jordan_wigner(i[0][0])
6. for j in one_pauli.data():
7. var_pauli += VarPauliOperator(j[0][1], complex_var(
8. i[1].real()*j[1].real-i[1].imag()*j[1].imag,
9. i[1].real()*j[1].imag+i[1].imag()*j[1].real))
10.
11. return var_pauli
cc_to_ucc_hamiltonian_var接口的作用是CC模型对应的簇算符转成UCC模型对应的簇算符:
1.def cc_to_ucc_hamiltonian_var(cc_op):
2. '''
3. generate Hamiltonian form of unitary coupled cluster based on coupled cluster,H=1j*(T-dagger(T)),
4. then exp(-jHt)=exp(T-dagger(T))
5. '''
6. pauli = VarPauliOperator()
7. for i in cc_op.data():
8. pauli += VarPauliOperator(i[0][1], complex_var(var(-2)*i[1].imag(), var(0)))
9.
10. return pauli
prepareInitialState制备初态
1.def prepareInitialState(qlist, en):
2. '''
3. prepare initial state.
4. qlist: qubit list
5. en: electron number
6. return a QCircuit
7. '''
8. circuit = QCircuit()
9. if len(qlist) < en:
10. return circuit
11.
12. for i in range(en):
13. circuit.insert(X(qlist[i]))
14.
15. return circuit;
simulate_one_term_var是构造哈密顿量子项的模拟线路:
1.def simulate_one_term_var(qubit_list, hamiltonian_term, coef, t):
2. '''
3. Simulate a single term of Hamilonian like "X0 Y1 Z2" with
4. coefficient and time. U=exp(-it*coef*H)
5. '''
6. vqc = VariationalQuantumCircuit()
7.
8. if len(hamiltonian_term) == 0:
9. return vqc
10.
11. tmp_qlist = []
12. for q, term in hamiltonian_term.items():
13. if term is 'X':
14. vqc.insert(H(qubit_list[q]))
15. elif term is 'Y':
16. vqc.insert(RX(qubit_list[q],pi/2))
17.
18. tmp_qlist.append(qubit_list[q])
19.
20. size = len(tmp_qlist)
21. if size == 1:
22. vqc.insert(VariationalQuantumGate_RZ(tmp_qlist[0], 2*coef*t))
23. elif size > 1:
24. for i in range(size - 1):
25. vqc.insert(CNOT(tmp_qlist[i], tmp_qlist[size - 1]))
26. vqc.insert(VariationalQuantumGate_RZ(tmp_qlist[size-1], 2*coef*t))
27. for i in range(size - 1):
28. vqc.insert(CNOT(tmp_qlist[i], tmp_qlist[size - 1]))
29.
30. # dagger
31. for q, term in hamiltonian_term.items():
32. if term is 'X':
33. vqc.insert(H(qubit_list[q]))
34. elif term is 'Y':
35. vqc.insert(RX(qubit_list[q],-pi/2))
36.
37. return vqc
simulate_hamiltonian_var接口作用是构造哈密顿量的模拟线路:
1.def simulate_hamiltonian_var(qubit_list,var_pauli,t,slices=3):
2. '''
3. Simulate a general case of hamiltonian by Trotter-Suzuki
4. approximation. U=exp(-iHt)=(exp(-i H1 t/n)*exp(-i H2 t/n))^n
5. '''
6. vqc = VariationalQuantumCircuit()
7.
8. for i in range(slices):
9. for j in var_pauli.data():
10. term = j[0][0]
11. vqc.insert(simulate_one_term_var(qubit_list, term, j[1].real(), t/slices))
12.
13. return vqc
梯度下降优化算法的主体接口GradientDescent,该接口接受的参数是体系哈密顿量,轨道个数,电子个数,迭代次数。
具体看一下这个接口的实现,首先初始化一组var类型的待优化参数,利用ccsd模型构造的可变费米子哈密顿量,通过JW变换将可变费米子哈密顿量转换为可变泡利哈密顿量,接着将CC转化成UCC,然后创建一个量子虚拟机,并向量子虚拟机申请指定数量的量子比特,再接着构建可变量子线路。首先制备初态,然后构造哈密顿量模拟线路。
接着通过VQNet的qop操作构造损失函数,然后创建一个基于动量的梯度下降优化器,迭代执行优化器,最后返回优化器优化的最低能量。
1.def GradientDescent(mol_pauli, n_qubit, n_en, iters):
2. n_para = get_ccsd_n_term(n_qubit, n_electron)
3.
4. para_vec = []
5. var_para = []
6. for i in range(n_para):
7. var_para.append(var(0.5, True))
8. para_vec.append(0.5)
9.
10. fermion_cc = get_ccsd_var(n_qubit, n_en, var_para)
11. pauli_cc = JordanWignerTransformVar(fermion_cc)
12. ucc = cc_to_ucc_hamiltonian_var(pauli_cc)
13.
14. machine=init_quantum_machine(QMachineType.CPU)
15. qlist = machine.qAlloc_many(n_qubit)
16.
17. vqc = VariationalQuantumCircuit()
18. vqc.insert(prepareInitialState(qlist, n_en))
19. vqc.insert(simulate_hamiltonian_var(qlist, ucc, 1.0, 3))
20.
21. loss = qop(vqc, mol_pauli, machine, qlist)
22. gd_optimizer = MomentumOptimizer.minimize(loss, 0.1, 0.9)
23. leaves = gd_optimizer.get_variables()
24.
25. min_energy=float('inf')
26. for i in range(iters):
27. gd_optimizer.run(leaves, 0)
28. loss_value = gd_optimizer.get_loss()
29.
30. print(loss_value)
31. if loss_value < min_energy:
32. min_energy = loss_value
33. for m,n in enumerate(var_para):
34. para_vec[m] = eval(n, True)[0][0]
35.
36. return min_energy
获取原子对应的电子数
1.def getAtomElectronNum(atom):
2. atom_electron_map = {
3. 'H':1, 'He':2, 'Li':3, 'Be':4, 'B':5, 'C':6, 'N':7, 'O':8, 'F':9, 'Ne':10,
4. 'Na':11, 'Mg':12, 'Al':13, 'Si':14, 'P':15, 'S':16, 'Cl':17, 'Ar':18
5. }
6.
7. if (not atom_electron_map.__contains__(atom)):
8. return 0
9.
10. return atom_electron_map[atom]
最后,这是该示例对应的主函数,首先构造一组不同距离下的氢分子模型,然后计算每个氢分子模型对应的基态能量,最后将计算的结果绘制成曲线图。
1.if __name__=="__main__":
2. distances = [x * 0.1 for x in range(2, 25)]
3. molecule = "H 0 0 0\nH 0 0 {0}"
4.
5. molecules = []
6. for d in distances:
7. molecules.append(molecule.format(d))
8.
9. chemistry_dict = {
10. "mol":"",
11. "multiplicity":1,
12. "charge":0,
13. "basis":"sto-3g",
14. }
15.
16. energies = []
17.
18. for d in distances:
19. mol = molecule.format(d)
20.
21. chemistry_dict["mol"] = molecule.format(d)
22. data = run_psi4(chemistry_dict)
23. #get molecule electron number
24. n_electron = 0
25. mol_splits = mol.split()
26. cnt = 0
27. while (cnt < len(mol_splits)):
28. n_electron += getAtomElectronNum(mol_splits[cnt])
29. cnt += 4
30.
31. fermion_op = parsePsi4DataToFermion(data[1])
32. pauli_op = JordanWignerTransform(fermion_op)
33.
34. n_qubit = pauli_op.getMaxIndex()
35.
36. energies.append(GradientDescent(pauli_op, n_qubit, n_electron, 30))
37.
38. plt.plot(distances , energies, 'r')
39. plt.xlabel('distance')
40. plt.ylabel('energy')
41. plt.title('VQE PLOT')
42. plt.show()
图4.6.40所示就是对应的输出结果,是氢分子在不同距离下对应的基态能量:
Shor算法,又叫质因数分解算法,是以数学家Peter·Shor命名的。1994年,Shor针对“给定一个整数$$N$$,找出它的质因数”这道题,发明了破解RSA加密的量子算法。在一个量子计算机上面,要分解整数$$N$$,Shor算法的运作需要多项式时间(时间是$$\log N$$的某个多项式这么长,$$\log N$$在这里的意义是输入的文件长度)。更精确的说,这个算法花费$$\mathrm{O}((\log N)^3)$$的时间,展示出质因数分解问题可以使用量子计算机以多项式时间解出,因此在复杂度类BQP里面。这比起传统已知最快的因数分解算法:普通数域筛选法,其花费次指数时间 -- 大约$$\mathrm{O}\left(e^{1.92(\log N)^{1 / 3}(\log \log N) ^{2 / 3}}\right)$$,还要快了一个指数的差异。
自古以来,加密和解密都伴随着人类的发展。中国军事谋略中也常听到知己知彼,百战不殆的说法;在军事上,信息的安全保密被认为是取得胜利的关键因素;在生活中,也经常可以看到使用智慧找到解开密码的方法(方法就是密码学里的密钥),从而解开那些千奇百怪的密文。
密码学,主要分为古典密码学和现代密码学。对于计算机时代,主要讨论现代加密方式,就是基于二进制编码信息的现代密码学。
1.对称加密(symmetric encryption)
采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单钥加密。通俗的说,就是将明文(原本的信息)通过某种方式打乱,使得加密后的信息与原文不相同,但是这种打乱方式有一定的规律,使用密钥进行加密。
所谓对称,就是采用这种加密方法的双方使用方式用同样的密钥进行加密和解密。密钥是控制加密及解密过程的指令;算法是一组规则,规定如何进行加密和解密。
例如:Alice要给Bob发一段信息,需要用密钥给信息加密,而Bob接收信息时候需要利用相同的密钥,才可以解密信息。如图4.7.1:
非对称加密(Asymmetric encryption)
非对称加密算法需要两个密钥来进行加密和解密,这两个密钥是公开密钥(public key,简称公钥)和私有密钥(private key,简称私钥)。公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。如图4.7.2所示:
RSA加密算法是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。该算法是著名的非对称加密算法,它是数论与计算机科学相结合产物。目前,很多加密方式都采用这个原理,而Shor算法所威胁的正是RSA的加密方式.
RSA是Internet上的标准加密算法。该方法是公知的,但非常难以破解。其核心是它使用两个密钥进行加密,公钥是公开的,客户端使用它来加密随机会话密钥;截获加密密钥的任何人都必须使用第二个密钥(私钥)对其进行解密;否则,得到的信息是没有任何含义。而会话密钥解密后,服务器使用它以更快的算法加密和解密更多消息。因此,只要保证私钥安全,通信就是安全的。
实际上,RSA算法,其核心的思想并不困难,它使用的是,两个质数相乘容易,但是反过来分解成两个质数相乘却非常困难的规则来构建。
例如图4.7.3,求这串数字相乘,对于经典计算机来说,非常的简单。但是如果将这串数字分解为两个质数相乘,就非常困难。
1.RSA算法规则
首先要使用概率算法来验证随机产生的大的整数是否是质数,这样的算法比较快而且可以消除掉大多数非质数。假如有一个数通过了这个测试的话,那么要使用一个精确的测试来保证它的确是一个质数。
首先,生成两个大的质数 $$ \mathrm{p} $$ 和 $$ \mathrm{q}
那么,解密钥则是:
2.GCD算法
两个整数的最大公约数等于其中较小的那个数和两数相除余数的最大公约数;最大公约数缩写为GCD;GCD算法是最大公约数算法的简称;例如 gcd $$ (\mathrm{N}{1}$$,$$ \mathrm{~N}{2}) $$ ,就是求 $$ \mathrm{N}{1}, \mathrm{~N}{2} $$ 的最大公因数算法,如果 $$ \text{gcd}(\mathrm{N}{1}$$,$$ \mathrm{~N}{2})=1 $$ ,则称 $$ \mathrm{N}{1}, \mathrm{~N}{2} $$ 互质。
例,求 $$ \text{gcd}(12$$,$$24) $$ ?
答: 数字 24 可以表示为几组不同正整数的乘积:
所以,24 的正因数为: 1,2,3,4,6,8,12,24 。 数字 12 可以表示为几组不同正整数的乘积:
所以, 12 的正因素为 1 , 2 , 3 , 4 , 6 , 12 。 两组数中共同的元素,就是它们的公因数:1,2,3,4,6,12;其中的最大公因数 是 12 ,即 $$ \text{gcd}(12$$ ,$$ 24)=12 $$ 。
3.Mod运算符
Mod 运算,是求模运算符(即求余运算),是在整数运算中求一个整数 $$ \mathrm{x} $$ 除以另一 个整数 $$ \mathrm{y} $$ 的余数的运算,且不考虑运算的商。例如
模运算满足条件:
例: 求 $$ 5^{3} \bmod 11 $$ ? 答:
那么,由此推导得出的公式为
4.RSA加密原理
RSA加密原理,就是发送方把信息进行RSA加密算法的运算,得到加密的信息进行传输,传输完成,接收方收到的加密信息需要进行解密算法的运算,才可以得出原始传输数据信息。讯息,也是明文。比如文本,有效数据之类的信息。
例:假设A=65,B=66,…,Z=90,…;怎样可以安全的把BY这个信息从上海带回合肥?
答: 由上述信息可知,运用 RSA算法,明文 BY 对应的字符串 T 是: 66,89 ;再构 造公钥和私么钥:
选取 $$ p=103$$,$$ q=97 $$ ;那么,得出公钥为 $$ (e$$,$$ n)=(1213$$,$$9991)
所以,最终荧回合肥的信息为 $$ 8151
再由题中的已知条件,就可以恢复明文为 BY。
5.Shor算法破解RSA加密问题
在一个量子计算机上面,要分解整数 $$ \mathrm{N} $$ , shor 算法的运作需要多项式时间 $$ ( $$ 时间是 $$ \log \mathrm{N} $$ 的某个多项式这么长, $$ \log \mathrm{N} $$ 在这里的意义是输入的档案长度 $$ )
参考图4.7.6的线路图,量子部分,主要帮助寻找到周期:
Shor算法可以分为经典部分和量子部分,通过两个部分的相互结合,从而到到分解的目的。经典部分,主要是在传统计算机上进行运行,目前不存在已知的算法可以对RSA带来威胁;但是量子部分是需要用量子系统来处理,量子计算对RSA提供了解决方案。
shor算法运算流程(图4.7.7):
- 随机选择任意数字$$ 1<a<N $$
- 计算$$ \text{gcd}(a$$,$$ N) $$。使用经典算法完成;
- 0如果 $$ \text{gcd}{(a}$$,$$ N) \neq 1 $$则返回到第一步;
- 当$$ \text{gcd}(a$$,$$ N)=1 $$ 时,构造函数$$ f(x)= $$ $$ a^{x} \bmod N $$ 。 寻找最小周期 $$ r
$$,使得$$ f(x+ $$ $$ r)=f(x) $$.(量子计算部分); - 如果得到找到的$$ \mathrm{r} $$是奇数,回到第1步。
- 如果$$ a^{\frac{r}{2}}=-1(\bmod N)
$$,同样回到第1步,从新开始选择$$ a $$。 - 如果$$ a^{\frac{r}{2}} \neq-1(\bmod N)
$$,则 $$ \text{gcd}(a^{\frac{r}{2}} \pm 1$$,$$ N) $$ 即为所求。分解完成。
量子算法效能比较:
经典算法和Shor算法就这个问题的对比情况(如图4.7.8);随着问题的增加,所需要的时间差异非常的大。
量子逻辑电路,分为经典不可逆逻辑电路和经典可逆逻辑电路。
1.经典不可逆逻辑电路
对于经典计算,可建立抽象的计算模型。如图4.7.9所示:
因为有信息擦出,从而导致,输出不可复原输入;这种不可复原输入的计算模型被称为不可逆计算。
例,假设这里有个黑盒子,给$$a$$ ,
但是,假设已知
2.经典可逆逻辑电路
对于经典计算,可建立抽象的计算模型。如图4.7.11所示:
Bennett已经证明了任何经典不可逆计算都可以转化为可逆计算的形式;可逆计算的优点,是可以通过逆计算恢复原始输入。
量子线路
在量子计算里,酉变换构成的线路是可逆的,如图4.7.12所示:
经典线路不可逆计算可以通过特殊的方式转换为量子线路;通过构建黑盒子$$ U_{a} $$ 来完成可逆计算,使用 $$ U_{a}^{-1} $$ 可以复原 $$ |0\rangle $$ 和 $$ |a\rangle $$ 。
量子可逆逻辑电路是构建量子计算机的基本单元,量子可逆逻辑电路综合就是根据电路功能,以较小的量子代价自动构造量子可逆逻辑电路。
4.量子加法器
经典加法器的模型,包括了三个输入和两个输出;其中输出与输入的对应关系是:
模型如图4.7.13所示:
其对应的真值表,如表4.7.1所示:
由上述可知,假设给定任意的输入 $$ a_{i}$$,
5.量子加法器假想模型
经典加法器的模型,实际上是一个不可逆的变换,因为它有三个输入两个输出,不可实现复原操作。所以量子加法器的模型需要去构建一个酉变换,也就是可逆操作;它可以通过一次计算,同时得到 $$ s_{i} $$ 和 $$ c_{i+1} $$ 。如图4.7.14:
相对于经典加法器, 它的三个输入没有发生变化,只是输出由之前的 $$ s_{i} $$ 和 $$ c_{i+1}
由此,可以发现其对应关系是没有发生变化的。
通过上述假想模型,给量子加法器提供了很好的思考方向;量子加法器里包含两个重要的模块,MAJ模块和UMA模块(如图4.7.15)。
两个模块是构建量子加法器的基本组件;是作为量子加法器最重要的核心单元之一。
假设给定MAG和UMA模块后,给定i=4,那么可以看到,它呈现一种递进关系,如图4.7.16:
给定一个初始辅助比特 $$ c_{0} $$ 和 0 ; 重要的是比如 $$ a_{0} $$ 的输出 $$ a_{0+1} $$ ,那么 $$ a_{0+1} $$ 就会作下一个模块的输入,依次递进;然后这个控制位,主要是用来判断是否有进位项;最后再通过一系列UMA模块的操作,从而将比特复原,给下一次反复使用。
MAJ单元
MAJ 单元包含三个输入: $$ a_{i}, b_{i}, c_{i}
那么, $$ c_{i+1} $$ 在这里被定义为三个输入两两相乘相加的结果,通过转换可以得到如下 等价形式:
7.量子逻辑门
在量子计算,特别是量子线路的计算模型里面,一个量子逻辑门是一个基本的、操作一个小数量量子位元的量子线路 。它是量子线路的基础,就像传统逻辑门跟一般数位线路之间的关系,与多数传统逻辑门不同,量子逻辑门是可逆的; 然而,传统的计算可以只使用可逆的门表示。
$$CNOT$$门,对应两个输入$$a$$,$$b$$;$$CNOT$$门具备这样的操作关系,如图4.7.18:
其中输入a为控制位,b为受控位;a不发生变化,如a为1时,b发生改变,得到结果为$$ a \oplus b $$ 。
$$Toffoli$$门,对应的是两个控制位分别是
基于这样基本的一个构造方式,给定三个输入;然后从上到下,逐个去实现,最后可以完整的推导出MAJ模块的实际构造情况,如图4.7.20所示:
输出结果与MAJ模块输出相同,如图4.7.21:
UMA单元
UMA单元同样需要
最后可以完整的推导出UMA模块的实际构造情况,输出结果如图4.7.25:
9.量子加法器电路
从上述的两个模块中,可以把完整的时序电路绘画出来,如图4.7.26:
量子加法器电路其实是可以优化的,可以采用更少的逻辑门来实现相同的结果。在上图中,如果要完成
10.快速傅里叶变换(FFT)
快速傅里叶变换是快速计算序列的离散傅里叶变换(DFT)或其逆变换的方法。如图4.7.27所示,傅里叶变换是一种积分变换,将信号从频域转换到时域的表示。
傅里叶变换可以将一个时域信号转换成在不同频率下对应的振幅及相位,其频谱就是时域信号在频域下的表现,而逆傅里叶变换可以将频谱再转换回时域的信号。
例:在图4.7.28的两个区域中,存在哪些联系和关系?
时域中的周期和频率中的周期成反比关系; 如果函数在时域中具有周期 $$ r $$ ,则变换函 数在频域中具有 $$ \frac{1}{r} $$ 的周期变化。
那么,快速傅里叶变换在数学上的表达形式为:
其中 $$ \mathrm{x}{\mathrm{j}} $$ 是输入, $$ \mathrm{y}{\mathrm{k}} $$ 是输出;由此可见,如果用量子计算中的一些相位门来表达傅里叶变换,以
11.量子傅里叶变换(QFT) $$ \left[\begin{array}{cc} 1 & 0 \ 0 & e^{i \theta} \end{array}\right]\left[\begin{array}{c} \alpha_{0} \ \alpha_{1} \end{array}\right] $$
量子傅里叶变换(quantum Fourier transform)是一种离散傅里叶变换,将原式分解成更为简单的多个幺正矩阵的积。
量子傅里叶变换实际上是作用在 $$ C^{2 n} $$ 空间上的离散傅立叶变换。离散傅立叶变换是作用在复 $$ \mathrm{N} $$ 维欧氏空间 $$ C^{N} $$ 上的一个酉变换,当输入为复向量 $$ \left(x_{0}, x_{1}, \cdots, x_{N-1}\right) $$ 时,其输出为复向量 $$ \left(y_{0}, y_{1}, \cdots, y_{N-1}\right) $$,其中:
由上式得出:
量子傅里叶变换,在量子力学的方式上,表达形式为:
其中 $$ \tilde{\alpha}_{\mathrm{k}} $$ 的定义形式为:
$$ \tilde{\alpha}{k}=\frac{1}{\sqrt{N}} \sum{j=0}^{N-1} e^{2 \pi i j k / N} \alpha_{j} $$
由此可见,量子傅里叶变换是可逆的,而且是一个酉变化。
例,假设输入一个 $$ |10\rangle $$ ,通过傅里叶变换之后,得出$$ |00\rangle
如果以线性算子的方式来理解量子傅里叶变换,那就是被定义为一个酉矩阵,表达形式是:
例,假设 $$ M=4
进行傅里叶变换,得出:
最终的得出状态被映射成 1 , 0 , 0 , 0 。假设得知最终状态,进行逆变换验证:
结果可以从输出态1,0,0,0又转换为输入态1,1,1,1;那如果用不同的输入重复计算的时候,其结果如图4.7.30所示:
量子傅里叶的量子计算的符号 $$ \begin{array}{c}j=j_{1} j_{2} \cdots j_{n}=j_{1} 2^{n-1}+j_{2} 2^{n-2}+\cdots+j_{n} \0 . j_{l} j_{l+1} \cdots j_{m}=\frac{j_{l}}{2}+\frac{j_{l+1}}{4}+\cdots+\frac{j_{m}}{2^{m-l+1}}\end{array} $$
例,假设令 $$ j=2 $$ ,使用二进制表达为 $$ 10$$ ,$$ j_{1}=1$$,$$ j_{2}=0 $$, 表达形式如下:
假设令 $$ j=0.5 $$ ,使用二进制表达为 $$ 0.10
通过证明可以迭代执行量子傅里叶变换为: $$ \begin{array}{c} \left|j_{1} \cdots j_{n}\right\rangle \ \frac{\left(|0\rangle+e^{2 \pi i 0 . j_{n}}|1\rangle\right)\left(|0\rangle+e^{2 \pi i 0 . j_{n-1} j_{n}}|1\rangle \cdots|0\rangle+e^{2 \pi i 0 . j_{1} j_{2} \cdots j_{n}}|1\rangle\right.}{2^{n / 2}} \end{array} $$ 如果,给定输入状态,以二进制表示的 $$ \mathrm{j}{1} $$ 到 $$ \mathrm{j}{\mathrm{n}} $$, 可以将状态变换。通过这个表达式, 可以转换为相位门的表达方式。$$CR$$ 量子门在控制位为$$|1\rangle$$时做控制相位变换操作, 受控运算符的矩阵形式为: $$ \hat{R}_{k}=\left(\begin{array}{cc} 1 & 0 \ 0 & e^{2 \pi i / 2^{k}} \end{array}\right) $$ 那么,通过一系列受控$$R$$门实现量子傅里叶变换,它的线路图如图4.7.31所示:
在第一个比特位上,总共会有$$n-1$$个控制位;状态也被置于叠加态。例如,6比特的量子云平台绘图形式如图4.7.32:
控制位从 $$ \frac{\pi}{2} $$ 开始,受控位为 $$ \frac{\pi}{2}
如果初始化都是0,则控制不工作。线路等价于对所有比特做H门操作(图4.7.33)。
PyQPanda演示(图4.7.34)
1.算法原理概述
从时间复杂度上比较:使用传统计算机,解决素数分解的最佳复杂度如图4.7.35所示:(n,表示素数乘积的位数)
Shor算法则可以将复杂度大幅降低,如图4.7.36所示:
由此可见,shor算法提供了超多项式执行加速;复杂度的降低,同时使RSA加密算法处在危险中。
Shor算法的思想,是将分解问题转化为寻找模指数电路的周期问题,构建模指数电路,通过逆QFT找到模指数电路的周期。
Shor 算法的核心电路主要包含傅里叶变换 (QFT),模指线路U$$ _{\mathrm{f}}
模指线路U$$_{\mathrm{f}} $$计算函数:
线路图总览(如图4.7.38):
n取决于N的比特位编码个数;比如分解15的时候,实际上会用4个比特位去表示。
2.问题转化
假设分解的数为 $$ \mathrm{N} $$ ,任取 $$ a \in[2, N-1]
如果 $$ a^{\frac{r}{2}} \neq-1 \bmod N, a^{\frac{r}{2}} \neq 1 \bmod N $$ 得到 $$ \mathrm{N} $$ 的两个因子 $$ p_{1} $$ 和 $$ p_{2} $$ $$ p_{1}=\text{gcd}\left(a^{\frac{r}{2}}+1, N\right) \text { 和 } p_{2}=\text{gcd}\left(a^{\frac{r}{2}}-1, N\right) $$ 在上述转化中,有个特殊的情况需要考虑;
如果 $$ N=p^{m} $$ ,则无法用该方法进行转化,所以在算法开始之前,还需做如下判定: 判断 $$ \sqrt[k]{N} \in Z $$ 是否为真,其中 $$ k \leq \log {2} N{\circ} $$
3.Shor算法电路框架
shor算法电路框架总共包括四个板块,分别是模指模块、常数模乘、常数模加、以及加法器的构造,如图4.7.39。
那么,构建量子加法器,它是作为模指底层的核心组件,通过加法器的构造来构建常数模加,它是将问题转换为常数模加,借用辅助比特完成操作;再由常数模加来构建常数模乘,将模指问题转换为可求解的模常数模块;再由常数模乘来完成最终的模指模块,该模块就是为问题解决的模块。
Shor算法的量子线路图,如图4.7.40所示:
1)模指模块
QFT和模指数电路如图4.7.41:
$$ \mathrm{N} $$ 对应的二进制长度为 $$ n $$ ,输入的 $$ x $$ 的位数 $$ m $$ 不固定,一般为 $$ 2 n $$ 位,即 $$ m=2 n
2)常数模乘
模指: $$ f(x)=a^{x} \bmod N
假设有电路$$U|y\rangle\rightarrow|Cy \ mod\ N)$$, 取$$C$$为 $$ a^{2^i}$$,$$ i=0$$,$$1$$,$$ \ldots$$,$$ 2 n-1
线路框架如图4.7.44
首先在 $$ |x\rangle $$ 上加 $$ Q F T $$ 构成叠加态,同时将 $$ 2^{2 n-1} $$ 个 $$ x $$ 输入电路,用 $$ Q F T^{-1} $$ 分析经过模 指电路后的态的周期性,从而得到 $$ f(x) $$ 的周期;
这里总共有 $$ 2 \mathrm{n} $$ 个控制 $$ \mathrm{U} $$ 块。每个输入量子比特都控制着下方的模 $$ \mathrm{N} $$ 乘法器 $$ \mathrm{CU}_{\mathrm{a}^{2^{i}}} $$ ,注意这里设其常数为 $$ a^{2^{i}} $$ 。
例:
使用同样的方法,用二进制表示 $$ y=\sum_{i=0}^{n-1} y_{i} \times 2^{i}
最终目标: $$ |C y \bmod N\rangle|y\rangle \rightarrow|C y \bmod N\rangle|0\rangle $$ 整个过程: $$ |y\rangle|0\rangle \rightarrow|C y \bmod N\rangle|0\rangle $$ 如图4.7.48:
图4.7.48 整个过程3)常数模加(图4.7.49)
图4.7.49 常数模加常数模加输入的比特:$$2N+2$$个量子比特;其中底部两个辅助比特,分别是:初始进位辅助比特;进位判断辅助比特。
内部结构分析(如图4.7.50):
图4.7.50 内部结构分析常数模加内部包含的三个模块,分别是绑定数据$$ \left(\mathrm{B}\left(2^{n}-N+C\right)\right) $$;进位器(Carrier);加法器(Adder)。
1、绑定数据,将 $$ \mathrm{N} $$ 个初始化为 0 的输入比特绑定为 $$ |a\rangle
2、进位器(Carrier)如图4.7.52:
图4.7.52 进位器(Carrier) 图4.7.53 翻转操作CNOT门 3、加法器(Adder)由MAJ和UMA模块组成。如图4.7.54:
图4.7.54 加法器(Adder) 常数模加的工作机制:
1、先进行数据绑定。
2、开始先用进位器来判断,是否有进位,如果有,执行第一个模块,带常数的加法器,反之,只是常数绑定的加法器。最后,为了不浪费量子比特,需要比特置零,方便反复使用。
3、绿色辅助比特加常数,判断是否大于N.如果大于N,问题就转化为绿色线(定义为a)$$a+c\ mod\ N$$ 的问题,就可以导出模加。
态的演化(如图4.7.55)
图4.7.55 态的演化 首先,给定 $$ Q=2^{t}, t=2 n $$ (量子比特数),$$ f(x)=\mathrm{a}^{x} \bmod N $$ , 周期为 $$ \mathrm{r} $$ 。
(1)初态为: $$ |\varphi\rangle=\frac{1}{\sqrt{Q}} \sum_{i=0}^{Q-1}|i\rangle|1\rangle
(2)之后,经过模指线路后:
$$ \begin{array}{l} |\varphi\rangle=\frac{1}{\sqrt{Q}}(|0\rangle|f(0)\rangle+|r\rangle|f(0)\rangle+\ldots+|m r\rangle|f(0)\rangle \ \quad+|1\rangle|f(1)\rangle+|1+r\rangle|f(1)\rangle+\ldots+|1+m r\rangle|f(1)\rangle \ \quad+|2\rangle|f(2)\rangle+|2+r\rangle|f(2)\rangle+\ldots+|2+m r\rangle|f(2)\rangle \ \quad \quad \ldots \ +|r-1\rangle|f(r-1)\rangle+|r-1+r\rangle|f(r-1)\rangle+\ldots+|r-1+m r\rangle|f(r-1)\rangle) \ \quad=\frac{1}{\sqrt{Q}} \sum_{i=0}^{r-1} \sum_{j=0}^{m}|i+j r\rangle|f(i)\rangle \end{array} $$ 假设定义$$ \mathrm{r} \times \mathrm{m} \approx \mathrm{Q}$$,M则可能是一个大的值;由上述演化可得出,经过模指线路,态呈现一种周期性的规律。
(3)上半部分做$$ Q F T^{-1}
得出共 $$ r \times Q $$ 个态。
(4)此时 $$ |k\rangle|f(i)\rangle $$ 的复振幅: $$ F_{k}=\frac{1}{Q} \sum_{j=0}^{m} w^{k(i+j r)}=\frac{1}{Q} w^{k i} \frac{1-w^{m k r}}{1-w^{k r}} $$
此时测量 $$ |\mathrm{k}\rangle $$ 态的概率为: $$ \begin{array}{c} P_{k}=\sum_{i=0}^{r-1}\left|F_{k}\right|^{2}=\frac{r}{Q^{2}} \times\left|\frac{1-w^{m k r}}{1-w^{k r}}\right|^{2} \ w=e^{\frac{-2 \pi i}{Q}} \ \left|\frac{1-w^{m k r}}{1-w^{k r}}\right|^{2}=\frac{1-\cos (m \theta)}{1-\cos (\theta)} \ \theta=\frac{k \times r}{Q} \times 2 \pi \ P_{k}=\frac{r}{Q^{2}} \times \frac{1-\cos (m \theta)}{1-\cos (\theta)} \ \quad \theta=2 \pi \times s \end{array} $$ $$ \mathrm{S} $$ 为整数时, $$ \mathrm{P}{\mathrm{k}} $$ 取最大值 $$ P{k \max }=\frac{r}{Q^{2}} \times m^{2} \approx \frac{1}{r}, m \times r \approx Q $$ 由此可知,可以通过测量概率找到 $$ \mathrm{r} $$ 的关系。
(5)最后测量的| $$ k\rangle
图4.7.56 $$ \mathrm{m}=50 $$ , $$ \frac{1-\cos (m \times \theta)}{1-\cos (\theta)} $$ 的演算
由上图可知,它已经有了相应的值,那这些值就是最终要选取的值。
确认周期
连分数的分解,采用层层分解的形式,如图4.7.57:
图4.7.57 层层分解 $$ \frac{k}{Q} $$ 是 $$ \frac{c}{r} $$ 的近似,将 $$ \frac{k}{Q} $$ 通过连分数方法发现 $$ \mathrm{r}
上述可知,由连分数的分解,最终得到 $$ \frac{r-1}{r}
如图4.7.58可知,K值是预计测量的值,通过计算,再使用连分数的逼近关系,可以得出的结果是$$r=30$$,满足我们的条件。
导入依赖的库
1.from pyqpanda import *
2.import matplotlib.pyplot as plt #绘图
3.import math as m #数学
绘制柱状图
绘制数据图所需,直接复制使用即可:
1.# 绘制柱状图
2.def plotBar(xdata, ydata):
3. fig, ax = plt.subplots()
4. fig.set_size_inches(6,6)
5. fig.set_dpi(100)
6.
7. rects = ax.bar(xdata, ydata, color='b')
8.
9. for rect in rects:
10. height = rect.get_height()
11. plt.text(rect.get_x() + rect.get_width() / 2, height, str(height), ha="center", va="bottom")
12.
13. plt.rcParams['font.sans-serif']=['Arial']
14. plt.title("Origin Q", loc='right', alpha = 0.5)
15. plt.ylabel('Times')
16. plt.xlabel('States')
17.
18. plt.show()
重新组织数据quick_measure的数据
1.def reorganizeData(measure_qubits, quick_meausre_result):
2. xdata = []
3. ydata = []
4.
5. for i in quick_meausre_result:
6. xdata.append(i)
7. ydata.append(quick_meausre_result[i])
8.
9. return xdata, ydata
用辗转相除法求最大公约数
1.def gcd(m,n):
2. if not n:
3. return m
4. else:
5. return gcd(n, m%n)
量子加法器MAJ模块
1.# a,b,c是单个量子比特, 其中a是辅助比特
2.#
3.# a ------o---x----- c xor a
4.# b --o---\---x----- c xor b
5.# c --x---x---o----- ((c xor a) and (c xor b)) xor c = R
6.#
7.def MAJ(a, b, c):
8. circ = QCircuit()
9. circ.insert(CNOT(c,b))
10. circ.insert(CNOT(c,a))
11. circ.insert(Toffoli(a, b, c))
12.
13. return circ
量子加法器UMA模块
1.# a,b,c是单个量子比特
2.#
3.# a --x---o---x----- ((a and b) xor c) xor a
4.# b --x---\---o----- (((a and b) xor c) xor a) xor b
5.# c --o---x--------- (a and b) xor c
6.#
7.# 以MAJ模块的输出作为输入的话,MAJ中辅助比特a保持不变,
8.# MAJ中的加项c比特保持不变,MAJ中的加项b比特保存的是b+c的结果
9.#
10.# c xor a --x---o---x----- a
11.# c xor b --x---\---o----- c xor b xor a
12.# R --o---x--------- c
13.def UMA(a, b, c):
14. circ = QCircuit()
15. circ.insert(Toffoli(a, b, c)).insert(CNOT(c, a)).insert(CNOT(a, b))
16.
17. return circ
量子加法器MAJ模块
1.# a 和 b 是一组量子比特表示特定的数,这里我们假设 a 和 b 的长度相同
2.# c 是一个辅助比特
3.def MAJ2(a, b, c):
4. if ((len(a) == 0) or (len(a) != (len(b)))):
5. raise RuntimeError('a and b must be equal, but not equal to 0!')
6.
7. nbit = len(a)
8. circ = QCircuit()
9. circ.insert(MAJ(c, a[0], b[0]))
10.
11. for i in range(1, nbit):
12. circ.insert(MAJ(b[i-1], a[i], b[i]))
13.
14. return circ
量子加法器 由MAJ和UMA模块组成,不考虑进位项
1.# a 和 b 是一组量子比特表示特定的数,这里我们假设 a 和 b 的长度相同
2.# c 是一个辅助比特
3.# 注意:a 中保存的是 a+b 的结果, b 保持不变
4.def Adder(a, b, c):
5. if ((len(a) == 0) or (len(a) != (len(b)))):
6. raise RuntimeError('a and b must be equal, but not equal to 0!')
7.
8. nbit = len(a)
9. circ = QCircuit()
10. circ.insert(MAJ(c, a[0], b[0]))
11.
12. for i in range(1, nbit):
13. circ.insert(MAJ(b[i-1], a[i], b[i]))
14.
15. for i in range(nbit-1, 0, -1):
16. circ.insert(UMA(b[i-1], a[i], b[i]))
17.
18. circ.insert(UMA(c, a[0], b[0]))
19.
20. return circ
判断是否有进位
1.# a 和 b 是一组量子比特表示特定的数,这里我们假设 a 和 b 的长度相同
2.# c 是一个辅助比特
3.# carry 是一个用来保存进位项的辅助比特
4.# 注:经过该模块后 a, b, c 对应的比特都保持不变,只有进位比特 carry 有可能会被改变
5.def isCarry(a, b, c, carry):
6. if ((len(a) == 0) or (len(a) != (len(b)))):
7. raise RuntimeError('a and b must be equal, but not equal to 0!')
8.
9. circ = QCircuit()
10.
11. circ.insert(MAJ2(a, b, c))
12. circ.insert(CNOT(b[-1], carry))
13. circ.insert(MAJ2(a, b, c).dagger())
14.
15. return circ
用量子比特来绑定经典数据
1.# 这里假定所有的比特都初始化为0态
2.def bindData(qlist, data):
3. check_value = 1 << len(qlist)
4. if (data >= check_value):
5. raise RuntimeError('data >= check_value')
6.
7. circ = QCircuit()
8. i = 0
9. while (data >= 1):
10. if (data % 2) == 1:
11. circ.insert(X(qlist[i]))
12.
13. data = data >> 1
14. i = i+1
15.
16. return circ
常数模加
1.# qa 是一组绑定经典数据的比特,并返回计算的结果
2.# C 表示待加的常数
3.# M 表示模数
4.# qb 表示一组辅助比特
5.# qs1 表示两个辅助比特,其中 qs1[0] 表示进位辅助比特, qs1[1] 表示MAJ模块用到的辅助比特
6.# 注:该模块会将所有使用到的辅助比特进行还原
7.def constModAdd(qa, C, M, qb, qs1):
8. circ = QCircuit()
9.
10. q_num = len(qa)
11.
12. tmp_value = (1 << q_num) - M + C
13.
14. circ.insert(bindData(qb, tmp_value))
15. circ.insert(isCarry(qa, qb, qs1[1], qs1[0]))
16. circ.insert(bindData(qb, tmp_value))
17.
18. tmp_circ = QCircuit()
19. tmp_circ.insert(bindData(qb, tmp_value))
20. tmp_circ.insert(Adder(qa, qb, qs1[1]))
21. tmp_circ.insert(bindData(qb, tmp_value))
22. tmp_circ = tmp_circ.control([qs1[0]])
23. circ.insert(tmp_circ)
24.
25. circ.insert(X(qs1[0]))
26.
27. tmp2_circ = QCircuit()
28. tmp2_circ.insert(bindData(qb, C))
29. tmp2_circ.insert(Adder(qa, qb, qs1[1]))
30. tmp2_circ.insert(bindData(qb, C))
31. tmp2_circ = tmp2_circ.control([qs1[0]])
32. circ.insert(tmp2_circ)
33.
34. circ.insert(X(qs1[0]))
35.
36. tmp_value = (1 << q_num) - C
37. circ.insert(bindData(qb, tmp_value))
38. circ.insert(isCarry(qa, qb, qs1[1], qs1[0]))
39. circ.insert(bindData(qb, tmp_value))
40. circ.insert(X(qs1[0]))
41.
42. return circ
辗转相除法求模逆
1.def modreverse(c, m):
2. if (c == 0):
3. raise RecursionError('c is zero!')
4.
5. if (c == 1):
6. return 1
7.
8. m1 = m
9. quotient = []
10. quo = m // c
11. remainder = m % c
12.
13. quotient.append(quo)
14.
15. while (remainder != 1):
16. m = c
17. c = remainder
18. quo = m // c
19. remainder = m % c
20. quotient.append(quo)
21.
22. if (len(quotient) == 1):
23. return m - quo
24.
25. if (len(quotient) == 2):
26. return 1 + quotient[0]*quotient[1]
27.
28. rev1 = 1
29. rev2 = quotient[-1]
30. reverse_list = quotient[0:-1]
31. reverse_list.reverse()
32. for i in reverse_list:
33. rev1 = rev1 + rev2 * i
34. temp = rev1
35. rev1 = rev2
36. rev2 = temp
37.
38. if ((len(quotient) % 2) == 0):
39. return rev2
40.
41. return m1 - rev2
常数模乘
1.# qa 是一组绑定经典数据的比特,并返回计算的结果
2.# const_num 表示待乘的常数
3.# M 表示模数
4.# qs1常数模乘使用的辅助比特
5.# qs2常数模加使用的辅助比特
6.# qs3 表示两个辅助比特,其中 qs1[0] 表示进位辅助比特, qs1[1] 表示MAJ模块用到的辅助比特
7.# 注:该模块会将所有使用到的辅助比特进行还原
8.def constModMul(qa, const_num, M, qs1, qs2, qs3):
9. circ = QCircuit()
10.
11. q_num = len(qa)
12.
13. for i in range(0, q_num):
14. tmp_circ = QCircuit()
15. tmp = const_num * pow(2, i) %M
16. tmp_circ.insert(constModAdd(qs1, tmp, M, qs2, qs3))
17. tmp_circ = tmp_circ.control([qa[i]])
18. circ.insert(tmp_circ)
19.
20. #state swap
21. for i in range(0, q_num):
22. circ.insert(CNOT(qa[i], qs1[i]))
23. circ.insert(CNOT(qs1[i], qa[i]))
24. circ.insert(CNOT(qa[i], qs1[i]))
25.
26. Crev = modreverse(const_num, M)
27.
28. tmp2_circ = QCircuit()
29. for i in range(0, q_num):
30. tmp = Crev* pow(2, i)
31. tmp = tmp % M
32. tmp_circ = QCircuit()
33. tmp_circ.insert(constModAdd(qs1, tmp, M, qs2, qs3))
34. tmp_circ = tmp_circ.control([qa[i]])
35. tmp2_circ.insert(tmp_circ)
36.
37. circ.insert(tmp2_circ.dagger())
38.
39. return circ
常数模指
1.# qa 是一组控制比特
2.# qb 保存计算结果
3.# base 表示指数基底
4.# M 表示模数
5.# qs1常数模乘使用的辅助比特
6.# qs2常数模加使用的辅助比特
7.# qs3 表示两个辅助比特,其中 qs1[0] 表示进位辅助比特, qs1[1] 表示MAJ模块用到的辅助比特
8.def constModExp(qa, qb, base, M, qs1, qs2, qs3):
9. circ = QCircuit()
10.
11. cqnum = len(qa)
12.
13. temp = base
14.
15. for i in range(0, cqnum):
16. circ.insert(constModMul(qb, temp, M, qs1, qs2, qs3).control([qa[i]]))
17. temp = temp * temp
18. temp = temp % M
19.
20. return circ
量子傅利叶变换
1.def qft(qlist):
2. circ = QCircuit()
3.
4. qnum = len(qlist)
5. for i in range(0, qnum):
6. circ.insert(H(qlist[qnum-1-i]))
7. for j in range(i + 1, qnum):
8. circ.insert(CR(qlist[qnum-1-j], qlist[qnum-1-i], m.pi/(1 << (j-i))))
9.
10. for i in range(0, qnum//2):
11. circ.insert(CNOT(qlist[i], qlist[qnum-1-i]))
12. circ.insert(CNOT(qlist[qnum-1-i], qlist[i]))
13. circ.insert(CNOT(qlist[i], qlist[qnum-1-i]))
14.
15. return circ
Shor算法主体代码
1.# base 表示指数基底
2.# M 表示待分解的数
3.def shorAlg(base, M):
4. if ((base < 2) or (base > M - 1)):
5. raise('Invalid base!')
6.
7. if (gcd(base, M) != 1):
8. raise('Invalid base! base and M must be mutually prime')
9.
10. binary_len = 0
11. while M >> binary_len != 0 :
12. binary_len = binary_len + 1
13.
14. machine = init_quantum_machine(QMachineType.CPU_SINGLE_THREAD)
15.
16. qa = machine.qAlloc_many(binary_len*2)
17. qb = machine.qAlloc_many(binary_len)
18.
19. qs1 = machine.qAlloc_many(binary_len) # 常数模乘使用的辅助比特
20. qs2 = machine.qAlloc_many(binary_len) # 常数模加使用的辅助比特
21. qs3 = machine.qAlloc_many(2) # 模加进位需要使用到的辅助比特
22.
23. prog = QProg()
24.
25. prog.insert(X(qb[0]))
26. prog.insert(single_gate_apply_to_all(H, qa)) #第一个QFT
27. prog.insert(constModExp(qa, qb, base, M, qs1, qs2, qs3))
28. prog.insert(qft(qa).dagger())
29.
30. directly_run(prog)
31. result = quick_measure(qa, 100)
32.
33. print(result)
34.
35. xdata, ydata = reorganizeData(qa, result)
36. plotBar(xdata, ydata)
37.
38. return result
主程序
1.if __name__=="__main__":
2. base = 7
3. N = 15
4. shorAlg(base, N)